fix(mcp): expose forget_memory as a separate tool (#1790)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Dhravya Shah 2026-10-10 20:30:29 -07:00 • committed by GitHub
parent 552803c018
commit 709723dea6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 75 additions and 20 deletions

View file

@ -48,7 +48,8 @@ Your assistant chooses these tools automatically. Use this table when you need t
| --- | --- | --- | --- |
| `search_memory` | Semantic recall from one space | `query` (required), `containerTag` | Matching memories |
| `get_profile` | Stable and recent profile context for one space | `containerTag` | Profile facts |
| `add_memory` | Save information or forget outdated information | `content` (required), `action` (`save` or `forget`), `containerTag` | Save or forget confirmation |
| `add_memory` | Save information | `content` (required), `namespace` | Save confirmation |
| `forget_memory` | Forget outdated information | `content` (required), `namespace` | Forget confirmation |
| `list_documents` | Browse stored source documents and their summaries | `page`, `limit`, `containerTag` | Document IDs, titles, types, status, dates, and summaries |
| `get_document` | Read the available content of one document | `documentId` (required) | Document metadata, summary, and available content |
| `list_memories` | Browse recent extracted memory entries and their source document IDs | `page`, `limit`, `containerTag` | Memory IDs, text, versions, and source document IDs |
@ -70,7 +71,7 @@ Use the retrieval tools for different questions:
### Save or forget
`add_memory` saves the supplied `content` by default. Set `action` to `forget` when a fact is outdated or should be removed. There is no separate forget tool.
`add_memory` saves the supplied `content`. Use `forget_memory` when a fact is outdated or should be removed. Both tools use the active space unless you pass `namespace`. The deprecated `containerTag` input and `add_memory` with `action: "forget"` remain supported for existing clients. Because the legacy action can remove information, `add_memory` retains its destructive safety annotation.
If the content is already final, the assistant should use `add_memory`. If you want to review, edit, or choose a space before saving, it should open the `guided-save` widget instead.

View file

@ -55,7 +55,8 @@ The client discovers the OAuth authorization server through
| `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 |
| `add_memory` | Save or forget a memory |
| `add_memory` | Save a memory (legacy `action: "forget"` remains supported) |
| `forget_memory` | Forget a memory matching the supplied content |
### MCP App launchers

View file

@ -10,6 +10,7 @@ import {
const EXPECTED_TOOLS = [
"add_memory",
"fetch-graph-data",
"forget_memory",
"getDocument",
"get_document",
"get_profile",

View file

@ -6,6 +6,7 @@ import {
withLegacyNamespace,
} from "./compat"
import { MEMORY_TOOL_ANNOTATIONS } from "./annotations"
import { forgetMemory } from "./forget-memory"
import { addMemoryOutputSchema, type AddMemoryOutput } from "./output-schemas"
import { textContent, type ToolDeps } from "./types"
@ -14,8 +15,14 @@ export function register(deps: ToolDeps) {
content: z
.string()
.max(200000, "Content exceeds maximum length")
.describe("The memory content to save or forget"),
action: z.enum(["save", "forget"]).optional().default("save"),
.describe("The memory content to save"),
action: z
.enum(["save", "forget"])
.optional()
.default("save")
.describe(
"Deprecated compatibility input. Use forget_memory to forget content; omit action to save.",
),
namespace: optionalNamespaceSchema,
...legacyNamespaceInput,
})
@ -24,30 +31,17 @@ export function register(deps: ToolDeps) {
"add_memory",
{
description:
"Add (save) or forget a memory in the user's ACTIVE space. Defaults to 'save'. The target space is the one the user selected via select-space; pass namespace only to override it. Use 'forget' when information is outdated or the user asks to remove it.",
"Save a memory in the user's ACTIVE space. The target space is the one the user selected via select-space; pass namespace only to override it. To remove outdated information, use the separately exposed forget_memory tool. Legacy action='forget' remains supported for existing clients.",
inputSchema,
outputSchema: addMemoryOutputSchema,
annotations: MEMORY_TOOL_ANNOTATIONS,
},
async (args) => {
if (args.action === "forget") return forgetMemory(deps, args)
try {
const namespace = await deps.resolveNamespace(namespaceArg(args))
const client = deps.getClient(namespace)
if (args.action === "forget") {
const result = await client.forgetMemory(args.content)
const structuredContent: AddMemoryOutput = withLegacyNamespace({
action: "forget",
success: result.success,
namespace: result.namespace,
message: result.message,
})
return {
content: [textContent(result.message)],
structuredContent,
}
}
const result = await client.createMemory(args.content)
const message = `Memory saved (ID: ${result.id}, space: ${result.namespace})`
const structuredContent: AddMemoryOutput = withLegacyNamespace({

View file

@ -0,0 +1,52 @@
import { z } from "zod"
import { optionalNamespaceSchema } from "../namespace"
import { MEMORY_TOOL_ANNOTATIONS } from "./annotations"
import {
legacyNamespaceInput,
namespaceArg,
withLegacyNamespace,
} from "./compat"
import { forgetMemoryOutputSchema } from "./output-schemas"
import { textContent, type ToolDeps } from "./types"
export async function forgetMemory(
deps: ToolDeps,
args: { content: string; namespace?: string; containerTag?: string },
) {
try {
const namespace = await deps.resolveNamespace(namespaceArg(args))
const result = await deps.getClient(namespace).forgetMemory(args.content)
return {
content: [textContent(result.message)],
structuredContent: withLegacyNamespace({
action: "forget" as const,
success: result.success,
namespace: result.namespace,
message: result.message,
}),
}
} catch (error) {
return deps.errorResult(error)
}
}
export function register(deps: ToolDeps) {
deps.server.registerTool(
"forget_memory",
{
description:
"Forget a memory matching the supplied content in the user's ACTIVE space. Use when information is outdated or the user asks to remove it. The target space is the one selected via select-space; pass namespace only to override it.",
inputSchema: z.object({
content: z
.string()
.max(200000, "Content exceeds maximum length")
.describe("The memory content to forget"),
namespace: optionalNamespaceSchema,
...legacyNamespaceInput,
}),
outputSchema: forgetMemoryOutputSchema,
annotations: MEMORY_TOOL_ANNOTATIONS,
},
async (args) => forgetMemory(deps, args),
)
}

View file

@ -1,5 +1,6 @@
import * as addMemory from "./add-memory"
import * as fetchGraphData from "./fetch-graph-data"
import * as forgetMemory from "./forget-memory"
import * as getDocument from "./get-document"
import * as getProfile from "./get-profile"
import * as guidedSave from "./guided-save"
@ -68,6 +69,7 @@ export function registerAllTools(toolDeps: ToolDeps) {
memoryGraph.register(deps)
fetchGraphData.register(deps)
addMemory.register(deps)
forgetMemory.register(deps)
guidedSave.register(deps)
saveMemory.register(deps)
uploadFile.register(deps)

View file

@ -29,6 +29,10 @@ export const addMemoryOutputSchema = z.object({
export type AddMemoryOutput = z.infer<typeof addMemoryOutputSchema>
export const forgetMemoryOutputSchema = addMemoryOutputSchema.extend({
action: z.literal("forget"),
})
export const getDocumentOutputSchema = z.object({
namespace: z.string(),
...legacyNamespaceOutput,