Problem
The current generic mcp_invoke_tool(server_name, tool_name, arguments) surface is unreliable when LLMs need to call MCP tools with non-trivial arguments. Observed failure mode (rockbot-agent-5b48d44999-kk9sq, patrol run 2026-05-19):
- Subagent loaded
calendar-mcp/send_email schema via mcp_get_service_details (which lists to/subject/body as required).
- 30 seconds later, called
mcp_invoke_tool for send_email with only server_name and tool_name — no nested arguments, no flat fields, nothing.
- MCP server returned
'to' was not provided, which sounds like the tool is broken rather than the call shape is wrong.
- Subagent rationalized: "
mcp_invoke_tool itself doesn't carry the nested email arguments in this tool surface" and pivoted to a spawn_wisps Direct/Mcp step — which succeeded because the wisp JSON literal hard-codes the params shape.
This is the self-deceptive failure loop called out in design/self-repair.md. The agent saved its rationalization to working memory ("prior direct mcp_invoke_tool calls lost/ignored parameters"), so future runs treat the lie as fact and pre-emptively detour through wisps. We've now seen this on calendar-mcp/get_calendar_events, calendar-mcp/get_email_details, and calendar-mcp/send_email.
Note: the framework's mcp_invoke_tool path is not actually broken — list_accounts (no-arg) succeeded through the exact same surface in the same trace. The problem is that LLMs struggle to author the nested arguments payload for the generic wrapper.
Proposed direction
Replace the generic mcp_invoke_tool surface with dynamically-registered per-tool wrapper tools. After mcp_get_service_details (or eagerly at server registration), the framework registers tools shaped like:
calendar-mcp__send_email(to: string[], subject: string, body: string, accountId?: string, ...)
calendar-mcp__list_accounts()
calendar-mcp__get_calendar_events(timeZone: string, startDate: string, ...)
Each wrapper carries the inner MCP tool's JSON schema as its own parameter schema, flattened. The LLM sees typed, named parameters — same shape that Claude Desktop, Cursor, Continue, and other working MCP hosts expose. It becomes nearly impossible to emit an empty call: the model has to fill the declared parameters or omit only optional ones.
Open design questions
- Tool-count blow-up. Each MCP server exposes 5–30 tools; 5 servers = 50–150 wrappers in the tool list. Need a story for what gets registered eagerly vs. lazily — possibly
mcp_list_services returns the wrappers per-server and mcp_get_service_details activates them in the session's tool registry.
- Schema freshness. MCP tool schemas can change when the server is upgraded. Need cache invalidation on server reconnect.
- Naming.
calendar-mcp__send_email vs. mcp_calendar_mcp_send_email vs. some other separator. Has to be stable, parseable, and not collide with built-in tools.
- Backwards compat. Keep
mcp_invoke_tool as a fallback escape hatch (for tools we haven't pre-registered, or for prompt-text-driven cases), or rip it out entirely.
- Wisp interaction. Wisp Direct/Mcp gateway currently routes through
mcp_invoke_tool under the hood. Probably keep routing wisps through the generic path since wisp JSON already carries the shape correctly; or rewrite the router to call the typed wrapper.
- Recovery.
McpRecoveryExecutor currently runs on mcp_invoke_tool results. Confirm it still triggers per-wrapper or needs refactor.
- MCP changelog/tool discovery race. When a new MCP server is registered mid-session, the wrappers need to appear in the LLM's tool list before the next iteration — currently the tool list is built once per turn.
- Token budget. 50–150 extra tool descriptions in every prompt is a real cost. The eager-vs-lazy decision dominates this.
Related
- #(this PR series) — pending typed schema-error nudge that addresses the symptom inside
McpManagementExecutor without a full refactor. That's a fine stopgap; this issue covers the durable fix.
design/self-repair.md — documents the rationalization-loop pattern this issue eliminates.
- Patrol-directive working memory entry warning that direct
mcp_invoke_tool "loses arguments" needs eviction once the typed wrappers ship.
Acceptance criteria (draft)
🤖 Generated with Claude Code
Problem
The current generic
mcp_invoke_tool(server_name, tool_name, arguments)surface is unreliable when LLMs need to call MCP tools with non-trivial arguments. Observed failure mode (rockbot-agent-5b48d44999-kk9sq, patrol run 2026-05-19):calendar-mcp/send_emailschema viamcp_get_service_details(which liststo/subject/bodyas required).mcp_invoke_toolforsend_emailwith onlyserver_nameandtool_name— no nestedarguments, no flat fields, nothing.'to' was not provided, which sounds like the tool is broken rather than the call shape is wrong.mcp_invoke_toolitself doesn't carry the nested email arguments in this tool surface" and pivoted to aspawn_wispsDirect/Mcp step — which succeeded because the wisp JSON literal hard-codes theparamsshape.This is the self-deceptive failure loop called out in
design/self-repair.md. The agent saved its rationalization to working memory ("prior direct mcp_invoke_tool calls lost/ignored parameters"), so future runs treat the lie as fact and pre-emptively detour through wisps. We've now seen this oncalendar-mcp/get_calendar_events,calendar-mcp/get_email_details, andcalendar-mcp/send_email.Note: the framework's
mcp_invoke_toolpath is not actually broken —list_accounts(no-arg) succeeded through the exact same surface in the same trace. The problem is that LLMs struggle to author the nestedargumentspayload for the generic wrapper.Proposed direction
Replace the generic
mcp_invoke_toolsurface with dynamically-registered per-tool wrapper tools. Aftermcp_get_service_details(or eagerly at server registration), the framework registers tools shaped like:Each wrapper carries the inner MCP tool's JSON schema as its own parameter schema, flattened. The LLM sees typed, named parameters — same shape that Claude Desktop, Cursor, Continue, and other working MCP hosts expose. It becomes nearly impossible to emit an empty call: the model has to fill the declared parameters or omit only optional ones.
Open design questions
mcp_list_servicesreturns the wrappers per-server andmcp_get_service_detailsactivates them in the session's tool registry.calendar-mcp__send_emailvs.mcp_calendar_mcp_send_emailvs. some other separator. Has to be stable, parseable, and not collide with built-in tools.mcp_invoke_toolas a fallback escape hatch (for tools we haven't pre-registered, or for prompt-text-driven cases), or rip it out entirely.mcp_invoke_toolunder the hood. Probably keep routing wisps through the generic path since wisp JSON already carries the shape correctly; or rewrite the router to call the typed wrapper.McpRecoveryExecutorcurrently runs onmcp_invoke_toolresults. Confirm it still triggers per-wrapper or needs refactor.Related
McpManagementExecutorwithout a full refactor. That's a fine stopgap; this issue covers the durable fix.design/self-repair.md— documents the rationalization-loop pattern this issue eliminates.mcp_invoke_tool"loses arguments" needs eviction once the typed wrappers ship.Acceptance criteria (draft)
design/covering open questions 1–8 above with chosen answers.mcp_invoke_toolfor tools with required parameters; they emit the typed wrapper.send_emailcall from a subagent shows the typed-wrapper schema + populated arguments incallContent.Arguments.🤖 Generated with Claude Code