Skip to content

fix(mcp): remember tool output validation — schema vs handler mismatches#45

Merged
cdeust merged 1 commit into
mainfrom
fix/mcp-remember-recall-schemas
May 21, 2026
Merged

fix(mcp): remember tool output validation — schema vs handler mismatches#45
cdeust merged 1 commit into
mainfrom
fix/mcp-remember-recall-schemas

Conversation

@cdeust
Copy link
Copy Markdown
Owner

@cdeust cdeust commented May 20, 2026

Summary

The remember MCP tool failed output validation on every code path due to schema/handler mismatches. Three independent fixes in one commit:

  1. memory_id / merged_with type — schema declared string (UUID-style) but the handler returns int (PG bigint). Now integer, aligning with anchor.py / forget.py / get_causal_chain.py which already declare integer.
  2. Rejection paths missing action — schema invariant required: ["stored","action"] was violated by both no_content early-exits in remember.py and by write_gate.build_rejection_response(). Added "action": "rejected" to each.
  3. Action enum mismatch — schema enum was ["stored","merged","rejected"] but the handler forwarded internal curation vocab "create" / "link" untouched, so every successful create/link failed with 'create' is not one of [...]. Normalized at the response boundary in build_response: create/linkstored, mergemerged.

Repro (before)

cortex:recall    → Output validation error: 'memories' is a required property  (separate bug, already fixed in trunk schema)
cortex:remember  → Output validation error: 'NNNN' is not of type 'string'
cortex:remember  → Output validation error: 'action' is a required property
cortex:remember  → Output validation error: 'create' is not one of ['stored', 'merged', 'rejected']

After this PR, the schema invariant required:["stored","action"] and the enum constraint are honoured on every code path (no_content, gate-rejected, created, linked, merged).

Test plan

  • Restart Cortex MCP server
  • Call remember({content: "test memory"}) — expect {stored: true, memory_id: <int>, action: "stored", ...}
  • Call remember({content: ""}) — expect {stored: false, action: "rejected", reason: "no_content"}
  • Call remember({content: "<duplicate of existing>"}) — expect {stored: false, action: "rejected", reason: "<gate reason>", ...} from write gate
  • Call remember({content: "<very similar to existing>", force: false}) — expect {stored: true, action: "merged", merged_with: <int>, ...}
  • All four responses pass MCP output schema validation (no Output validation error)

Files

  • mcp_server/handlers/remember.py — schema types + early-exit action
  • mcp_server/handlers/remember_response.pybuild_response normalization
  • mcp_server/core/write_gate.pybuild_rejection_response action field

🤖 Generated with Claude Code

The remember tool failed MCP output validation on every code path because
the declared schema did not match what the handler actually returns:

(1) memory_id / merged_with declared "type": "string" (UUID-style) but the
    handler returns int (PG bigint). Aligned with anchor.py / forget.py /
    get_causal_chain.py which already declare integer.

(2) Schema invariant required:["stored","action"] was violated by both
    no_content early-exit paths in remember.py and by
    write_gate.build_rejection_response(): they returned {"stored": false,
    ...} with no "action" field. Added "action": "rejected" to each.

(3) Schema enum was ["stored","merged","rejected"] (past-tense outcomes)
    but the handler forwarded the internal curation vocab "create"/"link"
    untouched, so every successful create/link failed with:
        Output validation error: 'create' is not one of [...]
    Normalized at the response boundary in build_response:
        create/link → "stored", merge → "merged".

After these fixes the schema invariant and the enum constraint are both
honoured on every code path (no_content, gate-rejected, created, linked,
merged).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@cdeust cdeust force-pushed the fix/mcp-remember-recall-schemas branch from cf1bf1e to 7514413 Compare May 20, 2026 20:49
@cdeust cdeust merged commit 80c5106 into main May 21, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant