mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-26 06:04:08 +00:00
feat(mcp): support optional metadata in add_memory and save-memory tools
This commit is contained in:
parent
d42e71f8bb
commit
91c2f5a25f
5 changed files with 83 additions and 3 deletions
57
apps/mcp/src/server/client/index.test.ts
Normal file
57
apps/mcp/src/server/client/index.test.ts
Normal 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",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue