mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-26 06:04:08 +00:00
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>
This commit is contained in:
parent
5381882fb6
commit
d40b2f5a07
6 changed files with 52 additions and 12 deletions
|
|
@ -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<string | undefined>,
|
||||
getDefaultNamespace: () => Promise<string>,
|
||||
) {
|
||||
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(),
|
||||
|
|
|
|||
|
|
@ -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<string | undefined>,
|
||||
getDefaultNamespace: () => Promise<string>,
|
||||
) {
|
||||
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),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<string | undefined>,
|
||||
getDefaultNamespace: () => Promise<string>,
|
||||
) {
|
||||
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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue