mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-01 02:01:40 +00:00
Merge eb841e9807 into cfa6c7cb17
This commit is contained in:
commit
7f4fcf4e93
8 changed files with 176 additions and 2 deletions
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
|
||||
const EXPECTED_TOOLS = [
|
||||
"add_memory",
|
||||
"delete_document",
|
||||
"fetch-graph-data",
|
||||
"get_document",
|
||||
"get_profile",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
96
apps/mcp/src/server/tools/delete-document.test.ts
Normal file
96
apps/mcp/src/server/tools/delete-document.test.ts
Normal 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")
|
||||
})
|
||||
})
|
||||
49
apps/mcp/src/server/tools/delete-document.ts
Normal file
49
apps/mcp/src/server/tools/delete-document.ts
Normal 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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue