[BUG] claude mcp list/get report "Needs authentication" for an authenticated client rejected by an app-level 403 (re-auth doesn't fix it)
Summary
claude mcp list / claude mcp get report Needs authentication for a remote HTTP MCP server even when the client already completed a full, successful OAuth (DCR + PKCE) flow and holds a valid, unexpired token. The true condition is an application-level 403 returned by the MCP server for a business-authorization reason unrelated to authentication (in our case: "this OAuth client is authenticated, but not yet approved by an administrator"). Re-authenticating does not change the status, because authentication was never the problem.
This looks related to #78220 (bare 403 misclassified as an OAuth challenge) and the now-closed docs issue #58875 (which confirms that as of v2.1.141, HTTP/SSE servers returning 403 on connect are intentionally shown as needs auth rather than failed). Filing separately because our case is not a WAF/CDN block or a connect-time probe — it is a fully authenticated session, well past initial connect, where a specific tool call gets a 403 with a JSON-RPC error body carrying its own machine-readable code (an app-defined authorization-state code, not an auth error), and the client status still collapses that into the same "Needs authentication" bucket as a bare missing/invalid token.
Repro
- Add a remote HTTP MCP server backed by OAuth 2.1 + PKCE + dynamic client registration.
- Complete the full sign-in flow in the browser (
/mcp→ authenticate → consent). The server verifies the bearer token successfully. - The server's own authorization layer separately gates this specific OAuth client behind an app-level allow-list (e.g. "registered but not yet approved by an admin"), and returns a
403with a JSON body like:
``json`
{ "code": "some_app_specific_not_yet_approved_code", "message": "..." }
initialize`/tool calls made on that session.
for both
- Run
claude mcp get <server>orclaude mcp list.
Expected
Some status distinguishable from "you are not authenticated" — e.g. Needs approval, Connected (restricted), or at minimum a status that doesn't imply re-running the OAuth flow will fix anything.
Actual
Both commands report:
Needs authentication
identically to what they'd report for a missing or invalid bearer token. There is no way to tell the two apart from the client status alone. Re-authenticating "succeeds" (a fresh token is minted) and the status does not change, because the token was never invalid.
Impact
The client instructs the user to do the one thing that cannot resolve the problem (re-authenticate), and gives no hint that the actual fix is on a different surface entirely (an application-level approval step). The only way we found to see the true state was to bypass the client's status reporting and issue a raw tool call, then read the JSON-RPC error body's own code field directly.
Ask
- Don't fold every non-2xx response taken during an already-authenticated session into the same
Needs authenticationbucket the client uses for pre-auth state. If a403carries a structured JSON-RPC/HTTP error body (not a bare WAF/HTML block page, which is #78220's case), consider surfacing that body's own error code/message inclaude mcp get/claude mcp listrather than overwriting it with a generic auth-shaped status. - At minimum, distinguish "never authenticated / token invalid" from "authenticated, but the server rejected this specific client for a non-auth reason" in the status text, so re-authenticating isn't the client's only suggested next step.
Environment
- Transport: HTTP (Streamable HTTP), OAuth 2.1 + PKCE + DCR
- Observed via
claude mcp get <server>andclaude mcp listafter a completed browser OAuth consent flow