mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-17 20:44:07 +00:00
Fixes cross-host graph rendering and moves widget uploads off the JSON/base64 tool transport. - render graph data from the launcher result without a second tool call - stream multipart uploads through one-time, short-lived upload sessions - remove temporary widget diagnostics and redundant unit tests Tested with Biome, TypeScript, 17 unit tests, a Wrangler deployment dry-run, and live graph rendering in ChatGPT and Claude. Authenticated E2E setup is currently blocked by the saved OAuth refresh session returning `invalid_grant: session not found`.
108 lines
2.6 KiB
TypeScript
108 lines
2.6 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, it } from "vitest"
|
|
import {
|
|
OAUTH_CREDENTIALS_AVAILABLE,
|
|
callTool,
|
|
connect,
|
|
textOf,
|
|
type Session,
|
|
} from "./helpers"
|
|
|
|
const EXPECTED_TOOLS = [
|
|
"add_memory",
|
|
"fetch-graph-data",
|
|
"getDocument",
|
|
"guided-save",
|
|
"listDocuments",
|
|
"listMemories",
|
|
"listSpaces",
|
|
"memory-graph",
|
|
"prepare-file-upload",
|
|
"save-memory",
|
|
"search_memory",
|
|
"select-space",
|
|
"set-active-tag",
|
|
"upload-file",
|
|
"whoAmI",
|
|
]
|
|
const describeWithAuth = describe.skipIf(!OAUTH_CREDENTIALS_AVAILABLE)
|
|
|
|
const READ_ONLY_TOOL_NAMES = [
|
|
"search_memory",
|
|
"listDocuments",
|
|
"listMemories",
|
|
"getDocument",
|
|
"listSpaces",
|
|
"whoAmI",
|
|
"memory-graph",
|
|
]
|
|
|
|
const READ_ONLY_ANNOTATIONS = {
|
|
readOnlyHint: true,
|
|
destructiveHint: false,
|
|
idempotentHint: true,
|
|
openWorldHint: false,
|
|
}
|
|
|
|
const MEMORY_TOOL_ANNOTATIONS = {
|
|
readOnlyHint: false,
|
|
destructiveHint: true,
|
|
idempotentHint: false,
|
|
openWorldHint: false,
|
|
}
|
|
|
|
describeWithAuth("MCP — discovery & identity", () => {
|
|
let s: Session
|
|
|
|
beforeAll(async () => {
|
|
s = await connect()
|
|
})
|
|
afterAll(async () => {
|
|
await s?.close()
|
|
})
|
|
|
|
it("handshakes and lists the expected tools", async () => {
|
|
const { tools } = await s.client.listTools()
|
|
const names = tools.map((t) => t.name).sort()
|
|
expect(names).toEqual([...EXPECTED_TOOLS].sort())
|
|
})
|
|
|
|
it("marks read-only tools as non-destructive", async () => {
|
|
const { tools } = await s.client.listTools()
|
|
for (const name of READ_ONLY_TOOL_NAMES) {
|
|
const tool = tools.find((t) => t.name === name)
|
|
expect(tool?.annotations).toMatchObject(READ_ONLY_ANNOTATIONS)
|
|
}
|
|
})
|
|
|
|
it("marks add_memory as mutating", async () => {
|
|
const { tools } = await s.client.listTools()
|
|
const memory = tools.find((t) => t.name === "add_memory")
|
|
expect(memory?.annotations).toMatchObject(MEMORY_TOOL_ANNOTATIONS)
|
|
})
|
|
|
|
it("lists profile and space resources", async () => {
|
|
const { resources } = await s.client.listResources()
|
|
const uris = resources.map((r) => r.uri)
|
|
expect(uris).toContain("supermemory://profile")
|
|
expect(uris).toContain("supermemory://spaces")
|
|
})
|
|
|
|
it("lists the context prompt", async () => {
|
|
const { prompts } = await s.client.listPrompts()
|
|
expect(prompts.map((p) => p.name)).toContain("context")
|
|
})
|
|
|
|
it("whoAmI resolves to the authenticated account", async () => {
|
|
const res = await callTool(s.client, "whoAmI")
|
|
expect(res.isError).toBeFalsy()
|
|
const parsed = JSON.parse(textOf(res))
|
|
expect(parsed.userId).toBeTruthy()
|
|
expect(parsed).toHaveProperty("activeSpace")
|
|
})
|
|
|
|
it("listSpaces returns content", async () => {
|
|
const res = await callTool(s.client, "listSpaces")
|
|
expect(res.isError).toBeFalsy()
|
|
expect(textOf(res).length).toBeGreaterThan(0)
|
|
})
|
|
})
|