Skip to content

fix(mcp): surface MCP tool errors as clean attributed messages, not raw JSON (#1495)#1510

Merged
Aaronontheweb merged 5 commits into
netclaw-dev:devfrom
Aaronontheweb:fix/1495-mcp-error-surfacing
Jun 29, 2026
Merged

fix(mcp): surface MCP tool errors as clean attributed messages, not raw JSON (#1495)#1510
Aaronontheweb merged 5 commits into
netclaw-dev:devfrom
Aaronontheweb:fix/1495-mcp-error-surfacing

Conversation

@Aaronontheweb

@Aaronontheweb Aaronontheweb commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

What

Fixes #1495. The issue was filed as "tool execution should fail explicitly when a tool is not loaded," but investigation showed that premise doesn't hold — and pointed at a different, real defect.

Investigation (why the premise was wrong)

  • netclaw only offers the model the loaded tool set (FilterExposedToolsDiscoveredToolCache), so the model can't call an unloaded tool in normal operation — there's no "not loaded" gate to add.
  • Decompiling the MCP SDK (ModelContextProtocol.Core 1.4.0, McpClientTool.InvokeCoreAsync) showed the actual mechanism: on a successful result it returns clean AIContent; but when the server sets isError: true it falls through and returns the entire CallToolResult serialized as a JsonElement.
  • McpClientManager then did result?.ToString(), handing the model a raw blob:
    {"content":[{"type":"text","text":"An error occurred invoking 'edit'"}],"isError":true}
  • That is the "error occurred invoking 'edit'" the reporter saw — the memorizer server's own error text, buried in a JSON dump. The agent couldn't tell a tool-reported failure from a transport/netclaw error, so it misdiagnosed it as "not loaded."

Fix

McpToolResultFormatter: when an invocation result is an isError JsonElement, extract the text content and surface it as:

Error: MCP tool 'memorizer/edit' reported a failure: <server's error text>

Applied at both invocation sites:

  • McpClientManager.InvokeFunctionAsync (session-scoped / shared invoker path) — now threaded with the tool name.
  • McpToolAdapter.ExecuteViaBoundToolAsync (bound-tool path).

Unchanged: successful results, structured (non-error) results (also arrive as JsonElement — must not be reframed as failures), and transport exceptions (keep their existing Error: MCP tool 'X' failed: <ex> wrapper).

Tests

McpToolResultFormatterTests: error → attributed message (single/multiple/empty text blocks); non-error JSON passthrough; plain-string and null passthrough. Daemon MCP suite (59) green; build + headers + slopwatch clean.

Note

This reframes the issue from "not loaded gate" to "MCP error surfacing." If you'd prefer #1495 stay scoped to its original title, I can retitle/close and file this under a fresh issue — but the fix here is the concrete, evidence-backed improvement behind the report.

…aw JSON (netclaw-dev#1495)

netclaw-dev#1495 was filed as "fail explicitly when a tool is not loaded", but investigation
showed that premise doesn't hold: netclaw only offers the model loaded tools, so
it can't call an unloaded one, and there is no "not loaded" gate to add.

The real defect is what the model sees when an MCP tool *fails*. The MCP SDK
(McpClientTool.InvokeCoreAsync) returns clean AIContent for a successful result,
but on isError=true it returns the ENTIRE CallToolResult serialized as a
JsonElement. McpClientManager then did result?.ToString(), handing the model a
raw blob — {"content":[{"type":"text","text":"..."}],"isError":true} — which it
cannot distinguish from a transport error or a netclaw error. That is exactly the
"error occurred invoking 'edit'" the reporter saw: the MCP server's own error
text, buried in a JSON dump, which the agent then misdiagnosed as "not loaded".

Adds McpToolResultFormatter: detects an isError JsonElement result, extracts the
text content, and surfaces it as `Error: MCP tool 'server/tool' reported a
failure: <text>`. Applied at both invocation sites (McpClientManager for the
session-scoped/shared invoker path, McpToolAdapter for the bound-tool path).
Successful and structured (non-error) results pass through unchanged; transport
exceptions keep their existing `Error: MCP tool 'X' failed: <ex>` wrapper.

Tests cover error extraction (single/multiple/empty text blocks), non-error
passthrough, and plain/null results. Daemon MCP suite (59) green; build clean.
Verified findings from the workflow code review:

- Error detail no longer collapses to "no detail provided" when the server puts
  it in structuredContent (or a non-text block): ExtractDetail now falls back to
  structuredContent, so the message never hides detail the old raw blob exposed.
- Consistent attribution: McpClientManager passes the server-qualified
  "server/tool" name (not the bare tool name) to the formatter, matching the
  bound-tool path (McpToolAdapter.Name). Same error now reads the same regardless
  of which invocation path produced it — and names which server failed when two
  expose a same-named tool.
- Structured SUCCESS results (also serialized to a full CallToolResult by the
  SDK) now surface their clean content instead of the {content,structuredContent,
  isError:false} wrapper, so success isn't handed to the model as a raw blob with
  a literal isError field either.

Also documents the MCP failure-surfacing behavior in the netclaw-operations skill
(tools reference) and bumps metadata.version 2.20.0 -> 2.21.0, per the System
Skills Sync Rule.

Tests expanded: structuredContent error-detail fallback, structured-success clean
surfacing (text and structured-only). Formatter+adapter (26) and Daemon MCP (59)
green; headers + slopwatch clean.
@Aaronontheweb Aaronontheweb added tools Issues related to agent tools: file_read, web_search, shell_execute, image processing, etc. mcp Model context protocol server / client issues. reliability Retries, resilience, graceful degradation labels Jun 26, 2026

@Aaronontheweb Aaronontheweb left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Aaronontheweb
Aaronontheweb enabled auto-merge (squash) June 27, 2026 14:14
@Aaronontheweb
Aaronontheweb disabled auto-merge June 27, 2026 14:15
@Aaronontheweb
Aaronontheweb enabled auto-merge (squash) June 29, 2026 14:38

@Aaronontheweb Aaronontheweb left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Aaronontheweb
Aaronontheweb merged commit 62eb842 into netclaw-dev:dev Jun 29, 2026
15 checks passed
@Aaronontheweb
Aaronontheweb deleted the fix/1495-mcp-error-surfacing branch June 29, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mcp Model context protocol server / client issues. reliability Retries, resilience, graceful degradation tools Issues related to agent tools: file_read, web_search, shell_execute, image processing, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tool execution should fail explicitly when tool is not loaded

1 participant