This commit is contained in:
Aditya kumar singh 2026-09-26 20:48:17 +03:00 • committed by GitHub
commit 7f4fcf4e93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 176 additions and 2 deletions

View file

@ -52,6 +52,7 @@ The client discovers the OAuth authorization server through
| `get_profile` | Get stable and recent profile context for a space |
| `list_documents` | List document metadata and summaries in a space |
| `get_document` | Read one document's available content by ID |
| `delete_document` | Permanently delete a stored document by ID |
| `list_memories` | List extracted memory entries and their source document IDs |
| `list_spaces` | List spaces visible to the authenticated account |
| `who_am_i` | Return identity, access, and active-space context |

View file

@ -9,6 +9,7 @@ import {
const EXPECTED_TOOLS = [
"add_memory",
"delete_document",
"fetch-graph-data",
"get_document",
"get_profile",

View file

@ -37,6 +37,7 @@ const TOOL_SURFACES: Record<string, McpToolSurface> = {
get_profile: "model_tool",
list_documents: "model_tool",
get_document: "model_tool",
delete_document: "model_tool",
list_memories: "model_tool",
list_spaces: "model_tool",
who_am_i: "model_tool",

View file

@ -144,11 +144,12 @@ export class SupermemoryClient {
containerTag?: string,
apiUrl = "https://api.supermemory.ai",
) {
const normalizedApiUrl = apiUrl.trim().replace(/\/+$/, "")
this.bearerToken = bearerToken
this.apiUrl = apiUrl
this.apiUrl = normalizedApiUrl
this.client = new Supermemory({
apiKey: bearerToken,
baseURL: apiUrl,
baseURL: normalizedApiUrl,
timeout: FETCH_TIMEOUT_MS,
defaultHeaders: { "x-sm-source": MCP_SOURCE },
})
@ -236,6 +237,21 @@ export class SupermemoryClient {
}
}
async deleteDocument(
documentId: string,
): Promise<{ success: boolean; message: string; documentId: string }> {
try {
await this.client.documents.delete(documentId)
return {
success: true,
message: `Document ${documentId} deleted successfully.`,
documentId,
}
} catch (error) {
this.handleOperationError("Delete document request", error)
}
}
async search(
query: string,
limit = 10,

View file

@ -0,0 +1,96 @@
import { describe, expect, it, vi } from "vitest"
import { register } from "./delete-document"
import { errorResult, type ToolDeps } from "./types"
function setupHarness() {
let handler: ((args: { documentId: string }) => Promise<unknown>) | undefined
let toolConfig: Record<string, unknown> | undefined
const registerTool = vi.fn(
(_name: string, config: Record<string, unknown>, fn: typeof handler) => {
toolConfig = config
handler = fn
return {}
},
)
const deleteDocument = vi.fn()
const mockClient = {
deleteDocument,
}
const deps: Partial<ToolDeps> = {
server: { registerTool } as unknown as ToolDeps["server"],
getClient: vi.fn().mockReturnValue(mockClient),
errorResult,
}
register(deps as ToolDeps)
if (!handler || !toolConfig) {
throw new Error("delete_document tool failed to register")
}
return {
handler,
toolConfig,
deleteDocument,
}
}
describe("delete_document tool", () => {
it("registers with destructive memory annotations", () => {
const harness = setupHarness()
expect(harness.toolConfig.annotations).toEqual({
readOnlyHint: false,
destructiveHint: true,
idempotentHint: false,
openWorldHint: false,
})
})
it("calls client.deleteDocument and returns structured result", async () => {
const harness = setupHarness()
harness.deleteDocument.mockResolvedValueOnce({
success: true,
message: "Document doc-123 deleted successfully.",
documentId: "doc-123",
})
const result = (await harness.handler({ documentId: "doc-123" })) as {
content: Array<{ type: string; text: string }>
structuredContent: {
success: boolean
documentId: string
message: string
}
}
expect(harness.deleteDocument).toHaveBeenCalledWith("doc-123")
expect(result.structuredContent).toEqual({
success: true,
documentId: "doc-123",
message: "Document doc-123 deleted successfully.",
})
expect(result.content[0]?.text).toContain(
"Document doc-123 deleted successfully.",
)
})
it("handles client errors gracefully", async () => {
const harness = setupHarness()
harness.deleteDocument.mockRejectedValueOnce(
new Error("Document not found"),
)
const result = (await harness.handler({
documentId: "doc-missing",
})) as {
content: Array<{ type: string; text: string }>
isError: boolean
}
expect(result.isError).toBe(true)
expect(result.content[0]?.text).toContain("Document not found")
})
})

View file

@ -0,0 +1,49 @@
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 delete, as returned by list_documents or a memory result",
),
})
deps.server.registerTool(
"delete_document",
{
title: "Delete Document",
description:
"Permanently delete a stored document and its associated memories by document ID. Use list_documents to find the document ID.",
inputSchema,
outputSchema: deleteDocumentOutputSchema,
annotations: MEMORY_TOOL_ANNOTATIONS,
},
async (args) => {
try {
const client = deps.getClient()
const result = await client.deleteDocument(args.documentId)
const structuredContent: DeleteDocumentOutput = {
success: true,
documentId: args.documentId,
message: result.message,
}
return {
content: [textContent(result.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 getProfile from "./get-profile"
@ -21,6 +22,7 @@ export function registerAllTools(deps: ToolDeps) {
getProfile.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({
success: z.boolean(),
documentId: z.string(),
message: z.string(),
})
export type DeleteDocumentOutput = z.infer<typeof deleteDocumentOutputSchema>
export const listDocumentsOutputSchema = z.object({
documents: z.array(documentSummarySchema),
pagination: paginationSchema,