supermemory/apps/mcp/e2e/memory.test.ts
Dhravya Shah 19e8f06cf1
MCP Revamp (#1120) (#1380)
Co-authored-by: Prasanna <106952318+Prasanna721@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: ved015 <vedant.04.mahajan@gmail.com>
Co-authored-by: ved015 <ved015@users.noreply.github.com>
Co-authored-by: Ishaan Gupta <ishaankone@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-07-30 17:08:51 -07:00

118 lines
3.8 KiB
TypeScript

import { randomUUID } from "node:crypto"
import { afterAll, beforeAll, describe, expect, it } from "vitest"
import {
OAUTH_CREDENTIALS_AVAILABLE,
callTool,
connect,
recallUntil,
type Session,
textOf,
} from "./helpers"
describe.skipIf(!OAUTH_CREDENTIALS_AVAILABLE)("MCP — memory behaviors", () => {
let s: Session
const created: Array<{ content: string; containerTag?: string }> = []
beforeAll(async () => {
s = await connect()
})
afterAll(async () => {
for (const { content, containerTag } of created) {
await callTool(s.client, "add_memory", {
content,
action: "forget",
...(containerTag ? { containerTag } : {}),
}).catch(() => {})
}
await s?.close()
})
it("save → recall round-trips the saved memory", async () => {
const marker = `rt-${randomUUID()}`
const content = `e2e round-trip. token=${marker}. The test fruit is dragonfruit.`
created.push({ content })
const save = await callTool(s.client, "add_memory", {
content,
action: "save",
})
expect(save.isError).toBeFalsy()
expect(textOf(save)).toMatch(/Memory saved/i)
const found = await recallUntil(s.client, "test fruit dragonfruit", marker)
expect(found, `recall never returned marker ${marker}`).not.toBeNull()
}, 120_000)
it("recall includeProfile=true returns profile + memories sections", async () => {
const res = await callTool(s.client, "search_memory", {
query: "dragonfruit",
includeProfile: true,
})
expect(res.isError).toBeFalsy()
const txt = textOf(res)
expect(txt).toMatch(/## (Profile|Recent context|Matching memories)/)
}, 30_000)
// Hybrid search returns nearest matches even for unrelated queries — assert it responds gracefully, not empty.
it("recall responds gracefully for an unmatched query", async () => {
const res = await callTool(s.client, "search_memory", {
query: `zzz-no-such-memory-${randomUUID()}`,
includeProfile: false,
})
expect(res.isError).toBeFalsy()
expect(textOf(res)).toMatch(
/## Matching memories|No matching memories found/i,
)
})
it("forget accepts a saved-memory request before extraction completes", async () => {
const marker = `fg-${randomUUID()}`
const content = `e2e forget target. token=${marker}. Secret animal is axolotl.`
created.push({ content })
await callTool(s.client, "add_memory", { content, action: "save" })
const found = await recallUntil(s.client, "secret animal axolotl", marker)
expect(found, "memory should exist before forget").not.toBeNull()
const forgotten = await callTool(s.client, "add_memory", {
content,
action: "forget",
})
expect(forgotten.isError).toBeFalsy()
expect(textOf(forgotten)).toMatch(/forgot|No matching memory found/i)
}, 120_000)
it("containerTag scopes memories (isolation)", async () => {
// Fixed tags (not per-run UUIDs) so the test doesn't mint a new project each run.
const tagA = "sm_e2e_scope_a"
const tagB = "sm_e2e_scope_b"
const marker = `sc-${randomUUID()}`
const content = `e2e scoping. token=${marker}. Project color is teal.`
created.push({ content, containerTag: tagA })
await callTool(s.client, "add_memory", {
content,
action: "save",
containerTag: tagA,
})
const inA = await recallUntil(s.client, "project color teal", marker, {
containerTag: tagA,
})
expect(inA, "marker should be found in its own container").not.toBeNull()
// Same query scoped to a different container must NOT see it.
const leaked = await recallUntil(s.client, "project color teal", marker, {
containerTag: tagB,
tries: 3,
delayMs: 3000,
})
expect(leaked, "marker leaked across containers").toBeNull()
}, 120_000)
it("returns an error result for a missing required argument", async () => {
const res = await callTool(s.client, "search_memory", {})
expect(res.isError).toBe(true)
expect(textOf(res).length).toBeGreaterThan(0)
})
})