From 6462dc315fef657518dfcec5162c4f3f9eedf5f1 Mon Sep 17 00:00:00 2001 From: Dhravya Date: Fri, 16 Oct 2026 06:02:28 +0000 Subject: [PATCH] fix(mcp): share the accessible default with prompts and resources, prefer writable spaces Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- apps/mcp/src/server/prompts/context.ts | 5 +++-- apps/mcp/src/server/resources/namespaces.ts | 5 +++-- apps/mcp/src/server/resources/profile.ts | 5 +++-- apps/mcp/src/server/server.ts | 20 ++++++++++++++++---- apps/mcp/src/server/space.test.ts | 17 +++++++++++++++++ apps/mcp/src/server/space.ts | 12 ++++++++++-- 6 files changed, 52 insertions(+), 12 deletions(-) diff --git a/apps/mcp/src/server/prompts/context.ts b/apps/mcp/src/server/prompts/context.ts index ad3ee9c1..5368b1d9 100644 --- a/apps/mcp/src/server/prompts/context.ts +++ b/apps/mcp/src/server/prompts/context.ts @@ -1,5 +1,5 @@ import type { McpServer } from "@modelcontextprotocol/server" -import { DEFAULT_NAMESPACE, type SupermemoryClient } from "../client" +import type { SupermemoryClient } from "../client" import { compactDescription, formatFactSection, @@ -15,6 +15,7 @@ export function registerContextPrompt( server: McpServer, getClient: (namespace?: string) => SupermemoryClient, resolveNamespace: () => Promise, + getDefaultNamespace: () => Promise, ) { server.registerPrompt( "context", @@ -24,7 +25,7 @@ export function registerContextPrompt( async () => { try { const selectedNamespace = await resolveNamespace() - const activeKey = selectedNamespace ?? DEFAULT_NAMESPACE + const activeKey = selectedNamespace ?? (await getDefaultNamespace()) const [profileResult, spaces] = await Promise.all([ getClient(activeKey).getProfile(), getClient().listNamespaces(), diff --git a/apps/mcp/src/server/resources/namespaces.ts b/apps/mcp/src/server/resources/namespaces.ts index 6b0e187a..917aab81 100644 --- a/apps/mcp/src/server/resources/namespaces.ts +++ b/apps/mcp/src/server/resources/namespaces.ts @@ -1,11 +1,12 @@ import type { McpServer } from "@modelcontextprotocol/server" -import { DEFAULT_NAMESPACE, type SupermemoryClient } from "../client" +import type { SupermemoryClient } from "../client" import { formatSpaceRow, sortSpaces } from "../space-presentation" export function registerNamespacesResource( server: McpServer, getClient: () => SupermemoryClient, resolveNamespace: () => Promise, + getDefaultNamespace: () => Promise, ) { server.registerResource("My Spaces", "supermemory://spaces", {}, async () => { const client = getClient() @@ -13,7 +14,7 @@ export function registerNamespacesResource( client.listNamespaces(), resolveNamespace(), ]) - const activeKey = selectedNamespace ?? DEFAULT_NAMESPACE + const activeKey = selectedNamespace ?? (await getDefaultNamespace()) const rows = sortSpaces(namespaces, activeKey).map((space) => formatSpaceRow(space, activeKey), ) diff --git a/apps/mcp/src/server/resources/profile.ts b/apps/mcp/src/server/resources/profile.ts index c5bc6388..26febec5 100644 --- a/apps/mcp/src/server/resources/profile.ts +++ b/apps/mcp/src/server/resources/profile.ts @@ -1,5 +1,5 @@ import type { McpServer } from "@modelcontextprotocol/server" -import { DEFAULT_NAMESPACE, type SupermemoryClient } from "../client" +import type { SupermemoryClient } from "../client" import { compactDescription, formatFactSection, @@ -12,6 +12,7 @@ export function registerProfileResource( server: McpServer, getClient: (namespace?: string) => SupermemoryClient, resolveNamespace: () => Promise, + getDefaultNamespace: () => Promise, ) { server.registerResource( "Active Space Profile", @@ -19,7 +20,7 @@ export function registerProfileResource( {}, async () => { const selectedNamespace = await resolveNamespace() - const activeKey = selectedNamespace ?? DEFAULT_NAMESPACE + const activeKey = selectedNamespace ?? (await getDefaultNamespace()) const [profileResult, spaces] = await Promise.all([ getClient(activeKey).getProfile(), getClient().listNamespaces(), diff --git a/apps/mcp/src/server/server.ts b/apps/mcp/src/server/server.ts index efa548b8..68327d80 100644 --- a/apps/mcp/src/server/server.ts +++ b/apps/mcp/src/server/server.ts @@ -98,9 +98,10 @@ export function createSupermemoryServer( spaceState.setActiveNamespace(namespace) const resolveSelectedNamespace = (explicit?: string) => resolveSpaceNamespace(explicit, getActiveNamespace) + const getDefaultNamespace = () => + defaultNamespaceFor(actor.bearerToken, apiUrl) const resolveNamespace = async (explicit?: string) => - (await resolveSelectedNamespace(explicit)) ?? - (await defaultNamespaceFor(actor.bearerToken, apiUrl)) + (await resolveSelectedNamespace(explicit)) ?? (await getDefaultNamespace()) const createUploadSession = async (namespace: string) => { const uploadId = crypto.randomUUID() const uploadToken = [crypto.randomUUID(), crypto.randomUUID()] @@ -139,14 +140,25 @@ export function createSupermemoryServer( } catch {} } - registerProfileResource(server, getClient, resolveSelectedNamespace) + registerProfileResource( + server, + getClient, + resolveSelectedNamespace, + getDefaultNamespace, + ) registerNamespacesResource( server, () => getClient(), resolveSelectedNamespace, + getDefaultNamespace, ) registerWidgetResource(server, mcpOrigin) - registerContextPrompt(server, getClient, resolveSelectedNamespace) + registerContextPrompt( + server, + getClient, + resolveSelectedNamespace, + getDefaultNamespace, + ) return server } diff --git a/apps/mcp/src/server/space.test.ts b/apps/mcp/src/server/space.test.ts index 6ca1cd9e..40def30e 100644 --- a/apps/mcp/src/server/space.test.ts +++ b/apps/mcp/src/server/space.test.ts @@ -80,6 +80,23 @@ describe("space application state", () => { scope: { type: "scoped", tags: ["notes"] }, }), ).toBe("notes") + expect( + accessibleDefaultNamespace({ + user, + accessType: "restricted", + namespaces: [ + { namespace: "readonly", permission: "read" }, + { namespace: "team", permission: "write" }, + ], + }), + ).toBe("team") + expect( + accessibleDefaultNamespace({ + user, + accessType: "restricted", + namespaces: [{ namespace: "readonly", permission: "read" }], + }), + ).toBe("readonly") expect( accessibleDefaultNamespace({ user, diff --git a/apps/mcp/src/server/space.ts b/apps/mcp/src/server/space.ts index 416a1a4e..89684ea0 100644 --- a/apps/mcp/src/server/space.ts +++ b/apps/mcp/src/server/space.ts @@ -15,7 +15,7 @@ export async function resolveNamespace( return explicit ?? (await getActiveNamespace()) } -// Mirrors the API's hard allowlist: scoped keys and restricted members can only reach their listed spaces. +// Mirrors the API's hard allowlist (scoped keys, restricted members), preferring a space writes can reach. export function accessibleDefaultNamespace(session: SessionInfo): string { const keyTags = session.scope?.type === "scoped" @@ -32,5 +32,13 @@ export function accessibleDefaultNamespace(session: SessionInfo): string { if (!allowed?.[0] || allowed.includes(DEFAULT_NAMESPACE)) { return DEFAULT_NAMESPACE } - return allowed[0] + const writable = + session.scope?.permission === "read" + ? undefined + : allowed.find( + (tag) => + session.namespaces?.find((access) => access.namespace === tag) + ?.permission !== "read", + ) + return writable ?? allowed[0] }