mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-11-15 09:11:45 +00:00
Merge ed3767deb7 into 5258cb74c8
This commit is contained in:
commit
572be9364d
9 changed files with 193 additions and 0 deletions
|
|
@ -50,6 +50,7 @@ Your assistant chooses these tools automatically. Use this table when you need t
|
|||
| `add_memory` | Save information or forget outdated information | `content` (required), `action` (`save` or `forget`), `containerTag` | Save or forget confirmation |
|
||||
| `listDocuments` | Browse stored source documents and their summaries | `page`, `limit`, `containerTag` | Document IDs, titles, types, status, dates, and summaries |
|
||||
| `getDocument` | Read the available content of one document | `documentId` (required) | Document metadata, summary, and available content |
|
||||
| `deleteDocument` | Permanently delete a document by ID | `documentId` (required) | Deletion confirmation |
|
||||
| `listMemories` | Browse recent extracted memory entries and their source document IDs | `page`, `limit`, `containerTag` | Memory IDs, text, versions, and source document IDs |
|
||||
| `listSpaces` | List accessible spaces and resolve a space name to its key | None | Formatted list plus structured `spaces` and `count` fields |
|
||||
| `whoAmI` | Inspect the authenticated account, permissions, scope, and active space | None | Account and access context |
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ The client discovers the OAuth authorization server through
|
|||
| `search_memory` | Search memories and optionally include profile context |
|
||||
| `listDocuments` | List document metadata and summaries in a space |
|
||||
| `getDocument` | Read one document's available content by ID |
|
||||
| `deleteDocument` | Permanently delete a document by ID |
|
||||
| `listMemories` | List extracted memory entries and their source document IDs |
|
||||
| `listSpaces` | List spaces visible to the authenticated account |
|
||||
| `whoAmI` | Return identity, access, and active-space context |
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
|
||||
const EXPECTED_TOOLS = [
|
||||
"add_memory",
|
||||
"deleteDocument",
|
||||
"fetch-graph-data",
|
||||
"getDocument",
|
||||
"guided-save",
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ const TOOL_SURFACES: Record<string, McpToolSurface> = {
|
|||
search_memory: "model_tool",
|
||||
listDocuments: "model_tool",
|
||||
getDocument: "model_tool",
|
||||
deleteDocument: "model_tool",
|
||||
listMemories: "model_tool",
|
||||
listSpaces: "model_tool",
|
||||
whoAmI: "model_tool",
|
||||
|
|
|
|||
|
|
@ -393,6 +393,14 @@ export class SupermemoryClient {
|
|||
}
|
||||
}
|
||||
|
||||
async deleteDocument(id: string): Promise<void> {
|
||||
try {
|
||||
await this.client.documents.delete(id)
|
||||
} catch (error) {
|
||||
this.handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async listMemoryEntries(
|
||||
page = 1,
|
||||
limit = 50,
|
||||
|
|
|
|||
111
apps/mcp/src/server/tools/delete-document.test.ts
Normal file
111
apps/mcp/src/server/tools/delete-document.test.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import { describe, expect, it, vi } from "vitest"
|
||||
import type { SupermemoryClient } from "../client"
|
||||
import { register } from "./delete-document"
|
||||
import { errorResult, type ToolDeps } from "./types"
|
||||
|
||||
function createMockDeps(overrides: Partial<ToolDeps> = {}): {
|
||||
deps: ToolDeps
|
||||
registeredTool: {
|
||||
name: string
|
||||
config: any
|
||||
handler: (args: any) => Promise<any>
|
||||
}
|
||||
mockClient: {
|
||||
getDocument: ReturnType<typeof vi.fn>
|
||||
deleteDocument: ReturnType<typeof vi.fn>
|
||||
}
|
||||
} {
|
||||
let registeredTool: any = null
|
||||
const registerTool = vi.fn((name, config, handler) => {
|
||||
registeredTool = { name, config, handler }
|
||||
})
|
||||
|
||||
const mockClient = {
|
||||
getDocument: vi.fn(),
|
||||
deleteDocument: vi.fn(),
|
||||
}
|
||||
|
||||
const deps: ToolDeps = {
|
||||
server: { registerTool } as any,
|
||||
actor: {
|
||||
userId: "user_123",
|
||||
organizationId: "org_123",
|
||||
bearerToken: "token_123",
|
||||
scopes: [],
|
||||
},
|
||||
getClient: vi.fn(() => mockClient as unknown as SupermemoryClient),
|
||||
getSession: vi.fn(),
|
||||
resolveContainerTag: vi.fn().mockResolvedValue("space_default"),
|
||||
getActiveContainerTag: vi.fn().mockResolvedValue("space_default"),
|
||||
setActiveContainerTag: vi.fn(),
|
||||
createUploadSession: vi.fn(),
|
||||
getClientInfo: vi.fn(),
|
||||
errorResult,
|
||||
...overrides,
|
||||
}
|
||||
|
||||
register(deps)
|
||||
|
||||
return { deps, registeredTool, mockClient }
|
||||
}
|
||||
|
||||
describe("deleteDocument tool", () => {
|
||||
it("registers with destructiveHint: true and correct schema", () => {
|
||||
const { registeredTool } = createMockDeps()
|
||||
|
||||
expect(registeredTool.name).toBe("deleteDocument")
|
||||
expect(registeredTool.config.annotations.destructiveHint).toBe(true)
|
||||
expect(registeredTool.config.annotations.readOnlyHint).toBe(false)
|
||||
expect(registeredTool.config.inputSchema).toBeDefined()
|
||||
})
|
||||
|
||||
it("successfully deletes a document belonging to the active space", async () => {
|
||||
const { registeredTool, mockClient } = createMockDeps()
|
||||
|
||||
mockClient.getDocument.mockResolvedValue({
|
||||
id: "doc_123",
|
||||
containerTags: ["space_default"],
|
||||
title: "My Document",
|
||||
})
|
||||
mockClient.deleteDocument.mockResolvedValue(undefined)
|
||||
|
||||
const result = await registeredTool.handler({ documentId: "doc_123" })
|
||||
|
||||
expect(mockClient.getDocument).toHaveBeenCalledWith("doc_123")
|
||||
expect(mockClient.deleteDocument).toHaveBeenCalledWith("doc_123")
|
||||
expect(result.structuredContent).toEqual({
|
||||
documentId: "doc_123",
|
||||
success: true,
|
||||
message: "Document doc_123 permanently deleted.",
|
||||
})
|
||||
expect(result.content[0].text).toContain("permanently deleted")
|
||||
})
|
||||
|
||||
it("rejects deletion of a document from a different space with 'Document not found'", async () => {
|
||||
const { registeredTool, mockClient } = createMockDeps()
|
||||
|
||||
mockClient.getDocument.mockResolvedValue({
|
||||
id: "doc_other",
|
||||
containerTags: ["other_space"],
|
||||
title: "Private Document",
|
||||
})
|
||||
|
||||
const result = await registeredTool.handler({ documentId: "doc_other" })
|
||||
|
||||
expect(mockClient.getDocument).toHaveBeenCalledWith("doc_other")
|
||||
expect(mockClient.deleteDocument).not.toHaveBeenCalled()
|
||||
expect(result.isError).toBe(true)
|
||||
expect(result.content[0].text).toContain("Document not found")
|
||||
})
|
||||
|
||||
it("propagates client errors via errorResult", async () => {
|
||||
const { registeredTool, mockClient } = createMockDeps()
|
||||
|
||||
mockClient.getDocument.mockRejectedValue(new Error("Network timeout"))
|
||||
|
||||
const result = await registeredTool.handler({ documentId: "doc_123" })
|
||||
|
||||
expect(result.isError).toBe(true)
|
||||
expect(result.content[0].text).toContain("Error: Network timeout")
|
||||
})
|
||||
})
|
||||
60
apps/mcp/src/server/tools/delete-document.ts
Normal file
60
apps/mcp/src/server/tools/delete-document.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { z } from "zod"
|
||||
import { MEMORY_TOOL_ANNOTATIONS } from "./annotations"
|
||||
import {
|
||||
deleteDocumentOutputSchema,
|
||||
type DeleteDocumentOutput,
|
||||
} from "./output-schemas"
|
||||
import { textContent, type ToolDeps } from "./types"
|
||||
|
||||
export function register(deps: ToolDeps) {
|
||||
const inputSchema = z.object({
|
||||
documentId: z
|
||||
.string()
|
||||
.min(1, "Document ID is required")
|
||||
.max(255, "Document ID exceeds maximum length")
|
||||
.describe("Document ID to permanently delete, as returned by listDocuments or getDocument"),
|
||||
})
|
||||
|
||||
deps.server.registerTool(
|
||||
"deleteDocument",
|
||||
{
|
||||
title: "Delete Document",
|
||||
description:
|
||||
"Permanently delete a document by its exact ID. Use listDocuments to discover document IDs before calling this tool.",
|
||||
inputSchema,
|
||||
outputSchema: deleteDocumentOutputSchema,
|
||||
annotations: MEMORY_TOOL_ANNOTATIONS,
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const effectiveTag = await deps.resolveContainerTag()
|
||||
const client = deps.getClient()
|
||||
const document = await client.getDocument(args.documentId)
|
||||
const docTags = document.containerTags
|
||||
if (
|
||||
Array.isArray(docTags) &&
|
||||
docTags.length > 0 &&
|
||||
!docTags.includes(effectiveTag)
|
||||
) {
|
||||
throw new Error("Document not found")
|
||||
}
|
||||
|
||||
await client.deleteDocument(args.documentId)
|
||||
|
||||
const message = `Document ${args.documentId} permanently deleted.`
|
||||
const structuredContent: DeleteDocumentOutput = {
|
||||
documentId: args.documentId,
|
||||
success: true,
|
||||
message,
|
||||
}
|
||||
|
||||
return {
|
||||
content: [textContent(message)],
|
||||
structuredContent,
|
||||
}
|
||||
} catch (error) {
|
||||
return deps.errorResult(error)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import * as addMemory from "./add-memory"
|
||||
import * as deleteDocument from "./delete-document"
|
||||
import * as fetchGraphData from "./fetch-graph-data"
|
||||
import * as getDocument from "./get-document"
|
||||
import * as guidedSave from "./guided-save"
|
||||
|
|
@ -19,6 +20,7 @@ export function registerAllTools(deps: ToolDeps) {
|
|||
searchMemory.register(deps)
|
||||
listDocuments.register(deps)
|
||||
getDocument.register(deps)
|
||||
deleteDocument.register(deps)
|
||||
listMemories.register(deps)
|
||||
listContainerTags.register(deps)
|
||||
whoAmI.register(deps)
|
||||
|
|
|
|||
|
|
@ -71,6 +71,14 @@ export const getDocumentOutputSchema = z.object({
|
|||
|
||||
export type GetDocumentOutput = z.infer<typeof getDocumentOutputSchema>
|
||||
|
||||
export const deleteDocumentOutputSchema = z.object({
|
||||
documentId: z.string(),
|
||||
success: z.boolean(),
|
||||
message: z.string(),
|
||||
})
|
||||
|
||||
export type DeleteDocumentOutput = z.infer<typeof deleteDocumentOutputSchema>
|
||||
|
||||
export const listDocumentsOutputSchema = z.object({
|
||||
documents: z.array(documentSummarySchema),
|
||||
pagination: paginationSchema,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue