This commit is contained in:
Aniketh Deb 2026-09-09 16:07:28 +00:00 • committed by GitHub
commit 572be9364d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 193 additions and 0 deletions

View file

@ -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 |

View file

@ -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 |

View file

@ -9,6 +9,7 @@ import {
const EXPECTED_TOOLS = [
"add_memory",
"deleteDocument",
"fetch-graph-data",
"getDocument",
"guided-save",

View file

@ -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",

View file

@ -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,

View 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")
})
})

View 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)
}
},
)
}

View file

@ -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)

View file

@ -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,