fix(mcp): register CallToolRequest handler explicitly + populate _tool_cache #22
Reference in New Issue
Block a user
Delete Branch "fix/mcp-call-tool-dispatch"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
CallToolRequesthandler through the same internalmcp.request_handlers[]API asListToolsRequest(was relying on@mcp.call_tool()decorator that doesn't work onServersubclass).mcp._tool_cacheso the SDK's input-validation pipeline works (was logging 'Tool X not listed, no validation will be performed' silently).Why
The MCP server's
tools/callJSON-RPC method returned no response becauseCallToolRequestwas never registered. Thetools/listpath worked (was manually wired), but every tool call was a silent no-op. Without this, MCP-driven cycles / kanban workers can't drive the orchestrator.Validation
tests/contract/test_mcp_call_dispatch.py(475 lines).damascus mcp-serve, runinitialize/initialized/tools-call, assert valid JSON-RPC response.Refactor the MCP server's handler wiring so both ListToolsRequest and CallToolRequest handlers are registered through the same internal mcp.request_handlers[...] = ... API the SDK's decorators use. This makes the dispatch table visible in one place instead of being split between explicit registration and the @mcp.call_tool() decorator. While verifying the wiring end-to-end I also discovered a real bug: the SDK's _get_cached_tool_definition() returns None for every tool because the manual _list_tools_handler never populated mcp._tool_cache (the SDK's own @mcp.list_tools() decorator does this transparently). That made the SDK log 'Tool X not listed, no validation will be performed' on every tools/call. Pydantic validation in _dispatch still caught bad args, so the bug was silent — but fixing it removes the warning and makes the SDK's input-validation pipeline work as designed. The new _call_tool_handler also wraps the result in a proper ServerResult(CallToolResult(...)) envelope and converts dispatch exceptions into isError=True results, matching the shape the SDK's @server.call_tool() decorator produces. Tests: - New file tests/contract/test_mcp_call_dispatch.py covers: - handler-registration structural assertion - list_items and system_status dispatch + response shape - unknown-tool error result - validation error result (priority_min=-1, bounds invariant) - list-tools regression (7 tools, inputSchema drift guard) - end-to-end stdio round-trip (spawn damascus mcp-serve, run initialize/initialized/tools-call, assert valid JSON-RPC response) - 9 new tests, 23 MCP-related tests total, 54 contract+unit tests pass; mypy clean.