fix(mcp): structured error for param-shape mismatch (#1351)#1500
Conversation
Split the bare except in handle_request tools/call: TypeError matching Python's signature-mismatch shape (gated on handler __qualname__) returns -32602 with parsed parameter name(s); handler-internal TypeError and non-TypeError exceptions stay on the existing generic -32000 path so internals never leak. Adds TestParamShapeDiagnostics with 6 cases. Closes MemPalace#1351 Co-authored-by: anastasiiaanfimova <271004602+anastasiiaanfimova@users.noreply.github.com>
There was a problem hiding this comment.
Code Review
This pull request enhances the MCP server's error reporting by specifically catching TypeError exceptions during tool execution to identify missing required parameters. By verifying the error source against the handler's name, it provides detailed -32602 (Invalid params) responses without leaking internal helper signatures. Feedback suggests refactoring the error handling logic to eliminate duplication between the new TypeError block and the existing catch-all Exception block.
Address gemini-code-assist review on PR MemPalace#1500: dedup the two -32000 return branches in handle_request tools/call. Behavior unchanged; TestParamShapeDiagnostics 6/6 green. Co-authored-by: anastasiiaanfimova <271004602+anastasiiaanfimova@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR improves MCP error reporting by detecting dispatcher-level TypeErrors caused by missing required parameters and returning a structured JSON-RPC -32602 Invalid params error that names the offending parameter(s), instead of the opaque -32000 Internal tool error. The match is gated on the function name in the TypeError message equalling the dispatched handler's __qualname__, so handler-internal TypeErrors and any non-TypeError exception remain on the generic path and cannot leak internal helper/parameter names or filesystem/DB internals.
Changes:
- Adds an
except TypeErrorbranch inhandle_request'stools/calldispatch that regex-matches Python's "missing N required ... arguments" message and returns-32602with the parameter name(s) when the qualname matches. - Moves
tool_args.pop("wait_for_previous", None)above thetry:so it cannot itself produce aTypeErrorcaught by the new branch, and factors the generic-32000response into_internal_tool_error. - Adds
TestParamShapeDiagnosticscovering 6 scenarios: single/multi missing required, handler-internalTypeError(unrelated message and signature-shape), unexpected-kwarg shape, and non-TypeError(RuntimeError) — all asserting no internal-string leakage.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| mempalace/mcp_server.py | Adds qualname-gated TypeError handling for param-shape errors returning -32602; extracts _internal_tool_error; moves wait_for_previous pop outside try. |
| tests/test_mcp_server.py | Adds TestParamShapeDiagnostics with 6 cases asserting structured -32602 for missing-required and generic -32000 (no leakage) for handler-internal/other exceptions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Unknown kwargs (wrong parameter name, e.g. text= instead of content=) were silently dropped by the schema-filter, surfacing only indirectly as a later "Missing required 'X'". Emit -32602 naming the offending kwarg instead — symmetric with #1500's missing-required path. Gated by the existing accepts_var_keyword check; wait_for_previous excluded. Closes #1512. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bumps version 3.3.5 → 3.3.6 across pyproject.toml, version.py, plugin manifests (.claude-plugin/plugin.json, .claude-plugin/marketplace.json, .codex-plugin/plugin.json), README badge, and uv.lock. Flips CHANGELOG.md from ``[Unreleased]`` to ``[3.3.6] — 2026-05-24`` and backfills the major user-facing entries that landed without changelog entries during the cycle: Features: - MemPalace#1555 office-document mining via --mode extract + virtual line numbers - MemPalace#1584 surgical closet pointers with date+line locators (Tier 6a) - MemPalace#1558 + MemPalace#1560 within-wing hallways (entity co-occurrence graph) - MemPalace#1565 cross-wing tunnels auto-promoted from hallways - MemPalace#1578 Hebbian potentiation + Ebbinghaus decay on hallways/tunnels - MemPalace#1236 API-tool transcripts auto-route to wing_api - MemPalace#711 hooks.auto_save toggle for silent-mode sessions - MemPalace#1605 COCA content-word filter for entity detection - MemPalace#1557 case-insensitive entity matching at mine time - MemPalace#1483 multilingual embeddings (embeddinggemma-300m) by default Bug Fixes (selected, user-visible): - MemPalace#1540 silent data loss in three unchunked upsert sites - MemPalace#1538 paragraph chunker oversized chunks - MemPalace#1554 per-file chunk cap too low for transcripts - MemPalace#1562 Windows hook subprocess/ChromaDB deadlock - MemPalace#1529 create_tunnel corrupted hyphenated wing names - MemPalace#1424 save-hook truncated hyphenated project folders - MemPalace#1383 KG cache duplicated graphs for symlinked/cased paths - MemPalace#1466 silent symlink skip now logged - MemPalace#1441 macOS stock-bash 3.2 hook compatibility - MemPalace#1500 / MemPalace#1513 structured JSON-RPC errors on bad MCP input - MemPalace#1523 VACUUM + FTS5 rebuild after repair - MemPalace#1548 FTS5 validation at end of mine - plus MemPalace#1216, MemPalace#1408, MemPalace#1438, MemPalace#1439, MemPalace#1445, MemPalace#1452, MemPalace#1459, MemPalace#1461, MemPalace#1466, MemPalace#1470, MemPalace#1477, MemPalace#1485, MemPalace#1500, MemPalace#1513, MemPalace#1528, MemPalace#1532, MemPalace#1543, MemPalace#1546, MemPalace#1585 Performance: - MemPalace#1474 convo miner pre-fetches mined-set - MemPalace#1487 rebuild_index progress callback - MemPalace#1530 MCP cold-start diagnostics + opt-in warmup Lint passes (ruff 0.15.14); mempalace-mcp entry point alignment verified per RELEASING.md.
What does this PR do?
tools/call, split the bareexcept Exceptionso that aTypeErrormatching Python's signature-mismatch shape returns a structured-32602 Invalid paramsnaming the offending parameter(s), instead of the opaque-32000 Internal tool error. Multi-arg cases list each name.TypeErrorequalling the dispatched handler's__qualname__, so a downstream helper that happens to raise a similar-lookingTypeErrorfalls through to the generic path. Handler-internalTypeErrorand all non-TypeErrorexceptions stay on the existing generic-32000path with no exception strings reaching the client.tool_args.pop("wait_for_previous", None)is moved one line above thetry:so the newexcept TypeErrorbranch only sees exceptions from the dispatched handler call.TestParamShapeDiagnosticsintests/test_mcp_server.pywith 6 cases: single missing-required, two missing-required listing both names, handler-internalTypeErrorwith unrelated message stays generic, handler-internalTypeErrorwith signature-mismatch shape stays generic, handler-raisedgot an unexpected keyword argumentshape stays generic, andRuntimeErrorstays generic with no path leak.How to test
End-to-end repro from #1351:
Before this PR returns
-32000 "Internal tool error". After:-32602 "Missing required parameter 'entry' for tool mempalace_diary_write". With both args omitted:-32602 "Missing required parameters 'agent_name', 'entry' for tool mempalace_diary_write".Checklist
python -m pytest tests/ -v)ruff check .)Why
Addresses the missing-required half of #1351. The reporter surfaces a missing-required call as
-32000 Internal tool error, which hides which parameter is wrong; agents (LLM and otherwise) end up retrying the same wrong-shape call multiple times before checking the schema. The wrong-name case (e.g.text=vscontent=) is silently absorbed by the schema-filter atmempalace/mcp_server.py:2235before dispatch and is being tracked as a follow-up by @anastasiiaanfimova.-32602matches the JSON-RPC 2.0 spec for invalid params and the existing precedent in this file (line 2207 for missingnameon tools/call, line 2251 for the type-coercion failure path). Only parameter names that are already public viatools/listreach the client; handler-internalTypeErrorand any non-TypeErrorexception still resolve to generic-32000, so ChromaDB or filesystem internals never leak.Complementary to the error-hardening direction in #181: the catch-all path stays generic, only param-shape errors become structured.
@anastasiiaanfimova's comment on #1351 outlined the dispatcher-
TypeErrorreformatting approach this PR builds on; she's added asCo-authored-byon the commit.