Part of #2058. Design: openspec/changes/mcp-tool-outcome-receipts/design.md (D4).
Problem
McpClientManager.ReportToolFailure runs IsAuthFailureMessage over the text of an isError: true tool result and, on a substring match (unauthorized, forbidden, token expired, ...), moves the server to AuthFailed with the remedy Run: netclaw mcp auth <name>.
- A normal business error such as
{"error":"Request failed: 403 Forbidden"} from a tool that proxies a REST API trips the check. For a server that does not use OAuth, netclaw mcp auth is the wrong remedy. A false authentication_failed alert fires, and netclaw mcp list and doctor report auth failed until the next invocation reconnects for nothing.
- A real credential failure on a server that does not use OAuth is not detected. A static bearer or API key that expires mid-session comes back as HTTP 401 on every tool call, but nothing marks the server, so
netclaw mcp list keeps reporting Connected.
The rule must not read header names. A server can authenticate with any header. The daemon already knows whether it uses OAuth for a server: it holds OAuth tokens for it, or the SDK reported a Bearer challenge.
Expected
- A 401 on a tool call or a catalog refresh moves the server to
AuthFailed. The remedy follows the OAuth state, not the config shape: netclaw mcp auth when the daemon holds OAuth tokens for the server or the failure is an OAuth challenge; "check configured credentials or headers" otherwise.
- A 403 on a tool call does not change the server state. It is an
access_denied result only.
- Tool-declared error text demotes a server only when the daemon holds OAuth tokens for it. Any other server stays
Connected, with the Warning log unchanged.
- The connect path uses the same rule. A bare 401 or 403 at
initialize with no tokens and no challenge is AuthFailed with the credentials remedy, not Unreachable.
CreateUnavailableException and netclaw doctor read the remedy from the published status instead of a fixed string or a scheme test of their own.
Tests
- HTTP server with an
X-Api-Key header, no tokens; tool call throws 401. AuthFailed; message names credentials or headers; no netclaw mcp auth; next call reconnects.
- HTTP server with stored OAuth tokens; tool call throws 401.
AuthFailed; remedy names netclaw mcp auth.
- HTTP server, no tokens; tool call throws the SDK's Bearer-challenge
McpException. AuthFailed; remedy names netclaw mcp auth.
- HTTP server; tool call throws 403. Stays
Connected.
- HTTP server, no headers, no tokens;
isError: true with expired-token text. Stays Connected.
- HTTP server with stored tokens;
isError: true with expired-token text. AuthFailed (current behavior).
- Static-header server and stdio server;
isError: true with Forbidden text. Stay Connected.
Part of #2058. Design:
openspec/changes/mcp-tool-outcome-receipts/design.md(D4).Problem
McpClientManager.ReportToolFailurerunsIsAuthFailureMessageover the text of anisError: truetool result and, on a substring match (unauthorized,forbidden,token expired, ...), moves the server toAuthFailedwith the remedyRun: netclaw mcp auth <name>.{"error":"Request failed: 403 Forbidden"}from a tool that proxies a REST API trips the check. For a server that does not use OAuth,netclaw mcp authis the wrong remedy. A falseauthentication_failedalert fires, andnetclaw mcp listanddoctorreportauth faileduntil the next invocation reconnects for nothing.netclaw mcp listkeeps reportingConnected.The rule must not read header names. A server can authenticate with any header. The daemon already knows whether it uses OAuth for a server: it holds OAuth tokens for it, or the SDK reported a Bearer challenge.
Expected
AuthFailed. The remedy follows the OAuth state, not the config shape:netclaw mcp authwhen the daemon holds OAuth tokens for the server or the failure is an OAuth challenge; "check configured credentials or headers" otherwise.access_deniedresult only.Connected, with the Warning log unchanged.initializewith no tokens and no challenge isAuthFailedwith the credentials remedy, notUnreachable.CreateUnavailableExceptionandnetclaw doctorread the remedy from the published status instead of a fixed string or a scheme test of their own.Tests
X-Api-Keyheader, no tokens; tool call throws 401.AuthFailed; message names credentials or headers; nonetclaw mcp auth; next call reconnects.AuthFailed; remedy namesnetclaw mcp auth.McpException.AuthFailed; remedy namesnetclaw mcp auth.Connected.isError: truewith expired-token text. StaysConnected.isError: truewith expired-token text.AuthFailed(current behavior).isError: truewithForbiddentext. StayConnected.