Skip to content

Commit 80c5106

Browse files
authored
fix(mcp): remember tool output validation — schema vs handler mismatches (#45)
1 parent 07383b4 commit 80c5106

3 files changed

Lines changed: 14 additions & 7 deletions

File tree

mcp_server/core/write_gate.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ def build_rejection_response(
141141
"""Build response when write gate rejects the memory."""
142142
return {
143143
"stored": False,
144+
"action": "rejected",
144145
"reason": gate_reason,
145146
"novelty": _describe_signals(
146147
embedding_novelty,

mcp_server/handlers/remember.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@
3939
"description": "True if content landed in the memory store (as new row or a merge into an existing one).",
4040
},
4141
"memory_id": {
42-
"type": "string",
43-
"description": "UUID of the resulting memory row. Present when stored=true.",
42+
"type": "integer",
43+
"description": "ID of the resulting memory row (PG bigint). Present when stored=true.",
4444
},
4545
"action": {
4646
"type": "string",
@@ -52,8 +52,8 @@
5252
"description": "Human-readable explanation of the gate decision (e.g. 'low surprise', 'high entity overlap').",
5353
},
5454
"merged_with": {
55-
"type": "string",
56-
"description": "UUID of the existing memory when action=merged.",
55+
"type": "integer",
56+
"description": "ID of the existing memory (PG bigint) when action=merged.",
5757
},
5858
"heat": {
5959
"type": "number",
@@ -260,15 +260,15 @@ def _parse_args(
260260
async def _handler_impl(args: dict[str, Any] | None = None) -> dict[str, Any]:
261261
"""Store a memory with thermodynamic properties and predictive coding gate."""
262262
if not args or not args.get("content"):
263-
return {"stored": False, "reason": "no_content"}
263+
return {"stored": False, "action": "rejected", "reason": "no_content"}
264264

265265
# Phase 7: harden user-controlled content at the ingestion boundary
266266
# (NFC normalization, control/bidi strip, byte cap).
267267
from mcp_server.shared.content_hardening import harden_content
268268

269269
args["content"] = harden_content(args["content"])
270270
if not args["content"]:
271-
return {"stored": False, "reason": "no_content"}
271+
return {"stored": False, "action": "rejected", "reason": "no_content"}
272272

273273
(
274274
content,

mcp_server/handlers/remember_response.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,16 @@ def build_response(
6666
interf: float,
6767
) -> dict[str, Any]:
6868
"""Build the full success response dict."""
69+
# Normalize internal curation action vocab → schema-canonical enum.
70+
# try_curation returns "create"/"link" (present-tense ops); the public
71+
# schema documents past-tense outcomes (stored / merged / rejected).
72+
schema_action = {"create": "stored", "link": "stored", "merge": "merged"}.get(
73+
action, action
74+
)
6975
result = {
7076
"stored": True,
7177
"memory_id": mem_id,
72-
"action": action,
78+
"action": schema_action,
7379
"store_type": stype,
7480
"domain": domain,
7581
"heat": round(mod["heat"], 4),

0 commit comments

Comments
 (0)