feat(mcp): support optional metadata in add_memory and save-memory tools

This commit is contained in:
Siddhesh Gawade 2026-10-14 21:33:06 +05:30
parent d42e71f8bb
commit 91c2f5a25f
5 changed files with 83 additions and 3 deletions

View file

@ -0,0 +1,57 @@
import { describe, expect, it, vi } from "vitest"
import { SupermemoryClient } from "./index"
describe("SupermemoryClient.createMemory", () => {
it("passes sm_source by default when no metadata is provided", async () => {
const client = new SupermemoryClient("test-token", "test-ns")
const addMock = vi.fn().mockResolvedValue({
id: "doc-123",
status: "queued",
})
;(client as unknown as { client: { add: typeof addMock } }).client = {
add: addMock,
}
const result = await client.createMemory("hello world")
expect(addMock).toHaveBeenCalledWith("test-ns", {
content: "hello world",
metadata: { sm_source: "supermemory-mcp" },
})
expect(result).toEqual({
id: "doc-123",
status: "queued",
namespace: "test-ns",
})
})
it("merges custom metadata with sm_source", async () => {
const client = new SupermemoryClient("test-token", "test-ns")
const addMock = vi.fn().mockResolvedValue({
id: "doc-456",
status: "queued",
})
;(client as unknown as { client: { add: typeof addMock } }).client = {
add: addMock,
}
const result = await client.createMemory("project fact", {
client: "claude",
project: "supermemory-mcp",
})
expect(addMock).toHaveBeenCalledWith("test-ns", {
content: "project fact",
metadata: {
sm_source: "supermemory-mcp",
client: "claude",
project: "supermemory-mcp",
},
})
expect(result).toEqual({
id: "doc-456",
status: "queued",
namespace: "test-ns",
})
})
})

View file

@ -191,11 +191,12 @@ export class SupermemoryClient {
async createMemory(
content: string,
metadata?: Record<string, unknown>,
): Promise<{ id: string; status: string; namespace: string }> {
try {
const result = await this.client.add(this.namespace, {
content,
metadata: { sm_source: MCP_SOURCE },
metadata: { sm_source: MCP_SOURCE, ...metadata },
})
return {
id: result.id,

View file

@ -23,6 +23,10 @@ export function register(deps: ToolDeps) {
.describe(
"Deprecated compatibility input. Use forget_memory to forget content; omit action to save.",
),
metadata: z
.record(z.string(), z.unknown())
.optional()
.describe("Optional key-value metadata to attach to the memory"),
namespace: optionalNamespaceSchema,
...legacyNamespaceInput,
})
@ -42,7 +46,7 @@ export function register(deps: ToolDeps) {
const namespace = await deps.resolveNamespace(namespaceArg(args))
const client = deps.getClient(namespace)
const result = await client.createMemory(args.content)
const result = await client.createMemory(args.content, args.metadata)
const message = `Memory saved (ID: ${result.id}, space: ${result.namespace})`
const structuredContent: AddMemoryOutput = withLegacyNamespace({
action: "save",

View file

@ -113,6 +113,20 @@ describe("containerTag compatibility", () => {
).not.toThrow()
})
it("passes optional metadata through to client.createMemory", async () => {
const { deps, tools, client } = fakeDeps()
addMemory.register(deps)
const tool = tools.get("add_memory")
if (!tool) throw new Error("add_memory not registered")
const metadata = { client: "claude", project: "secret-project" }
const result = await tool.cb({ content: "save this note", metadata }, {})
expect(client.createMemory).toHaveBeenCalledWith("save this note", metadata)
expect(result.structuredContent).toMatchObject({
action: "save",
success: true,
})
})
it("treats containerTag as the required namespace for set-active-tag", async () => {
const { deps, tools } = fakeDeps()
setActiveTag.register(deps)

View file

@ -13,6 +13,10 @@ export function register(deps: ToolDeps) {
description: "Save content to memory",
inputSchema: z.object({
content: z.string().min(1),
metadata: z
.record(z.string(), z.unknown())
.optional()
.describe("Optional key-value metadata to attach to the memory"),
namespace: namespaceSchema.optional(),
...legacyNamespaceInput,
viewId: z.string().uuid().optional(),
@ -28,7 +32,7 @@ export function register(deps: ToolDeps) {
return deps.errorResult(new Error("namespace is required"))
const viewId = args.viewId ?? crypto.randomUUID()
const client = deps.getClient(namespace)
const result = await client.createMemory(args.content)
const result = await client.createMemory(args.content, args.metadata)
const sc: ViewMessage = withLegacyView({
view: "save-success",
viewId,