You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue uses the terms in the engineering glossary: tool call, dispatcher, tool result, tool receipt, and outcome category.
Problem
An MCP tool call that ends in an exception becomes a tool result with no tool receipt. The dispatcher records the call as a success.
Current flow at dev (94d2bfce). This pseudocode is schematic. It omits authorization, redaction, and the output bound.
MCP server or transport throws
-> McpClientManager.InvokeSharedAsync
McpException -> return "Error: MCP tool '<server>/<tool>' failed: <message>" (no log)
transport failure -> log at Debug, reconnect, rethrow
-> McpToolAdapter.ExecuteAsync
catch-all -> return "Error: MCP tool '<name>' failed: <message>" (no log, no receipt)
-> DispatchingToolExecutor
string result -> receipt category = success
-> log "Tool executed: {ToolName} ({Duration}ms, {ResultLength} chars)" at Information
Code anchors:
src/Netclaw.Daemon/Mcp/McpClientManager.cs:600-602 and :637-638
src/Netclaw.Actors/Tools/McpToolAdapter.cs:153-156 (also :179-181 on the bound-tool path)
src/Netclaw.Actors/Tools/DispatchingToolExecutor.cs:280-287 (stream path) and :176-196 (non-stream path)
The glossary flow for a terminal exception is: the dispatcher classifies the receipt and throws, then the actor creates the factual failure result. An MCP tool call never reaches that branch. The adapter converts the exception into a tool result before the dispatcher can classify it.
Effects:
At the default log level, daemon.log shows a server that fails each call as a healthy server.
The SDK puts the HTTP status and up to 1024 characters of the server response body in HttpRequestException.Message. Netclaw writes none of that text to a log.
The receipt says success. Actor logic that trusts the receipt sees a good call.
Affected cases: HTTP 5xx, HTTP 429, other HTTP 4xx, JSON-RPC errors (McpException), and HttpClient timeouts.
Not affected: an isError: true tool result. McpClientManager.ReportToolFailure (:1081) logs that case at Warning. Its receipt category is out of scope for this issue.
#2033 added ToolOutcomeResults and the outcome categories. The workspace tools use them. McpToolAdapter does not.
Expected
Owners:
DispatchingToolExecutor owns the tool receipt. The receipt is call-local and ephemeral.
McpToolAdapter supplies the outcome category for an MCP tool call.
McpClientManager owns the log line.
No durable data changes.
Behavior:
A tool-call exception produces a non-success outcome category:
HTTP 401 or 403 → access_denied
HTTP 404 → not_found
all other exceptions → transient_failure
Use one of two paths. Do not add a second outcome mechanism.
Path A: McpToolAdapter completes the receipt with ToolOutcomeResults and returns the factual result.
Path B: McpToolAdapter lets the exception reach the dispatcher. The dispatcher classifies the receipt and throws, per the glossary flow.
McpClientManager logs each tool-call exception at Warning. The line names the server, the tool, the HTTP status when present, and the redacted exception (SecretOutputRedactor.RedactForLogging).
The dispatcher's Tool executed: line stays unchanged. It reports duration and size. The Warning line from McpClientManager is the failure signal.
The tool result stays factual. The receipt grants no authority. Netclaw does not retry or replay the call.
Examples
Positive example. A failed call becomes a failure:
Negative example. A tool-level error keeps its current path:
tools/call -> HTTP 200, {"isError": true, "content": [{"type": "text", "text": "Internal Server Error"}]}
result: "Error: MCP tool 'shortener/search-links' reported a failure: Internal Server Error"
log: [WRN] MCP tool 'shortener/search-links' reported a failure: Internal Server Error (current)
receipt: unchanged by this issue
Tests
A fake IMcpToolInvoker throws HttpRequestException with StatusCode = 500. The receipt category is transient_failure. One Warning line names the server, the tool, and the status.
A fake throws a plain McpException. The receipt category is transient_failure.
Part of #2058.
This issue uses the terms in the engineering glossary: tool call, dispatcher, tool result, tool receipt, and outcome category.
Problem
An MCP tool call that ends in an exception becomes a tool result with no tool receipt. The dispatcher records the call as a success.
Current flow at
dev(94d2bfce). This pseudocode is schematic. It omits authorization, redaction, and the output bound.Code anchors:
src/Netclaw.Daemon/Mcp/McpClientManager.cs:600-602and:637-638src/Netclaw.Actors/Tools/McpToolAdapter.cs:153-156(also:179-181on the bound-tool path)src/Netclaw.Actors/Tools/DispatchingToolExecutor.cs:280-287(stream path) and:176-196(non-stream path)The glossary flow for a terminal exception is: the dispatcher classifies the receipt and throws, then the actor creates the factual failure result. An MCP tool call never reaches that branch. The adapter converts the exception into a tool result before the dispatcher can classify it.
Effects:
daemon.logshows a server that fails each call as a healthy server.HttpRequestException.Message. Netclaw writes none of that text to a log.success. Actor logic that trusts the receipt sees a good call.Affected cases: HTTP 5xx, HTTP 429, other HTTP 4xx, JSON-RPC errors (
McpException), andHttpClienttimeouts.Not affected: an
isError: truetool result.McpClientManager.ReportToolFailure(:1081) logs that case at Warning. Its receipt category is out of scope for this issue.#2033 added
ToolOutcomeResultsand the outcome categories. The workspace tools use them.McpToolAdapterdoes not.Expected
Owners:
DispatchingToolExecutorowns the tool receipt. The receipt is call-local and ephemeral.McpToolAdaptersupplies the outcome category for an MCP tool call.McpClientManagerowns the log line.No durable data changes.
Behavior:
access_deniednot_foundtransient_failureMcpToolAdaptercompletes the receipt withToolOutcomeResultsand returns the factual result.McpToolAdapterlets the exception reach the dispatcher. The dispatcher classifies the receipt and throws, per the glossary flow.McpClientManagerlogs each tool-call exception at Warning. The line names the server, the tool, the HTTP status when present, and the redacted exception (SecretOutputRedactor.RedactForLogging).Tool executed:line stays unchanged. It reports duration and size. The Warning line fromMcpClientManageris the failure signal.Examples
Positive example. A failed call becomes a failure:
Negative example. A tool-level error keeps its current path:
Tests
IMcpToolInvokerthrowsHttpRequestExceptionwithStatusCode = 500. The receipt category istransient_failure. One Warning line names the server, the tool, and the status.McpException. The receipt category istransient_failure.HttpRequestExceptionwithStatusCode = 403. The receipt category isaccess_denied. The server state staysConnected(see Auth-failure detection substring-matches server error text on the MCP tool-call path #2057).isError: trueresult keeps its Warning log.