The client says “connected”, tools/list answers, and every account-scoped
tool returns 401 forever. There is no error to search for — this article is the
missing error message.
mcp login <server> (or the client's
“connect / authenticate” button) ends with something like “the server responded, but no OAuth token
was obtained”.*.client.json (the dynamic registration result) and not the *.json
with an access_token.401.The MCP authorization spec makes the client start the flow reactively: it only discovers the authorization server and begins OAuth when it gets back
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://<host>/.well-known/oauth-protected-resource"
A server that answers initialize and tools/list without requiring auth
(200) and only challenges 401 on account-scoped calls never trips that trigger during a login
probe. The probe sees “connected” and stops — the client exits with no URL, no consent step and no
token.
That is a server-side design choice (public tool surface, private account tools), not a broken server and not a broken client: what fails is the interaction between the two.
401 + WWW-Authenticate with resource_metadata=. A 200 means
the tool is public: pick another (profile / library / credits / orders).
curl -sS -i -m 20 -X POST "$MCP_URL" -H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"<account_scoped_tool>","arguments":{}}}' | head -30authorization_servers[] and scopes_supported[].
curl -sS -m 20 "$MCP_URL_ORIGIN/.well-known/oauth-protected-resource"registration_endpoint (you do not have to register a client by hand),
authorization_endpoint, token_endpoint,
code_challenge_methods_supported: S256 and
token_endpoint_auth_methods_supported: none (public client + PKCE).
curl -sS -m 20 "$AUTH_SERVER/.well-known/oauth-authorization-server"*.client.json means OAuth did not complete.
ls -la "$TOKEN_DIR" # ~/.config/<client>/mcp-tokens/, ~/.mcp/tokens/How to read it: (1) 401 with WWW-Authenticate = the server is correctly
configured, the client just never asked; (3) with registration_endpoint = the flow is
automatic and there is no client to register by hand — whatever the client's error message
suggests.
<server>.auth: oauth plus an oauth block
(flow: browser or device, timeout).registration_endpoint (JSON with redirect_uris, client_name,
grant_types:["authorization_code","refresh_token"],
response_types:["code"], token_endpoint_auth_method:"none"), open the
authorization_endpoint with response_type=code,
code_challenge (S256) and state, catch the code on the
loopback redirect, POST it to the token_endpoint with the code_verifier,
then write the token into the client's store in the shape it expects.registration_endpoint present, no tokenA client registration file
proves only dynamic registration. Check for access_token.refresh_token and re-read
the token file when the access token expires, instead of re-running the interactive flow.CAUSE: <one sentence: which step of the flow never happened>
EVIDENCE: <the 401 + WWW-Authenticate line, the registration_endpoint line, the token file path>
FIX APPLIED: <A | B | C + what changed>
VERIFIED: <the exact account-scoped call and its returned text, or "NOT VERIFIED: <why>">
NEXT: <single next action>
The last line matters most: VERIFIED with the raw output, or the admission that it
could not be checked. “Connected” is not evidence.
The MCP OAuth Debug skill ships the full diagnosis, the one-page checklist and the
reference implementation of fix B (scripts/force-oauth-challenge.py), for any MCP client
and any OAuth-protected server.
See the MCP OAuth Debug skill See all skills
Need it solved inside your project? Guided setup from R$ 1.500, with the first cycle run together.
Sent when I publish, together with the free skill used here. No spam, one-click unsubscribe.