import { randomUUID } from "node:crypto" import { describe, expect, it } from "vitest" import { API_KEY, callTool, connect, recallUntil, textOf } from "./helpers" type ToolLike = { name: string inputSchema?: { properties?: Record } } const propsOf = (tools: ToolLike[], name: string): Record => tools.find((t) => t.name === name)?.inputSchema?.properties ?? {} // Fixed tag (not a per-run UUID) so the test doesn't mint a new project each run. const SCOPE_TAG = "sm_e2e_root" // x-sm-project locks the connection to one project: strips containerTag from schemas and scopes every op — distinct from the per-call arg. describe.skipIf(!API_KEY)("MCP — x-sm-project root scoping", () => { it("strips containerTag from tool schemas when x-sm-project is set", async () => { const scoped = await connect({ containerTag: SCOPE_TAG }) const plain = await connect() try { const scopedTools = (await scoped.client.listTools()).tools const plainTools = (await plain.client.listTools()).tools expect(propsOf(plainTools, "memory")).toHaveProperty("containerTag") expect(propsOf(plainTools, "recall")).toHaveProperty("containerTag") expect(propsOf(scopedTools, "memory")).not.toHaveProperty("containerTag") expect(propsOf(scopedTools, "recall")).not.toHaveProperty("containerTag") } finally { await scoped.close() await plain.close() } }) it("scopes saves to the connection project and isolates them from default", async () => { const marker = `root-${randomUUID()}` const content = `e2e root scope. token=${marker}. The root flower is bluebell.` const rooted = await connect({ containerTag: SCOPE_TAG }) try { const save = await callTool(rooted.client, "memory", { content, action: "save", }) expect(save.isError).toBeFalsy() expect(textOf(save)).toContain(SCOPE_TAG) const found = await recallUntil( rooted.client, "root flower bluebell", marker, ) expect(found, "marker not found within its root scope").not.toBeNull() } finally { await callTool(rooted.client, "memory", { content, action: "forget", }).catch(() => {}) await rooted.close() } // A default connection searches sm_project_default only — must not see it. const plain = await connect() try { const leaked = await recallUntil( plain.client, "root flower bluebell", marker, { tries: 3, delayMs: 3000, }, ) expect(leaked, "rooted memory leaked into the default project").toBeNull() } finally { await plain.close() } }, 120_000) })