MCP server connects but never asks you to log in
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. The symptom (all of these, same ca
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.
The symptom (all of these, same cause)
Login finishes with no token
mcp login <server>(or the client's
“connect / authenticate” button) ends with something like “the server responded, but no OAuth token
was obtained”.No window, no URLNo browser, no consent URL, no device code is ever shown — the
flow simply never starts.Only the client registration existsThe token directory holds the
*.client.json(the dynamic registration result) and not the*.json
with anaccess_token.Connected, and 401 on everything account-scopedPublic tools (search, catalog,
docs) work; profile, library, orders and credits return401.
The cause: the 401 that never arrives during login
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.
Diagnosis: 4 commands, in this order
Does the server challenge you?Call a tool that needs an account and look for
401+WWW-Authenticatewithresource_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 -30Which authorization server does it advertise?Look for
authorization_servers[]andscopes_supported[].
curl -sS -m 20 "$MCP_URL_ORIGIN/.well-known/oauth-protected-resource"Does the server accept a client created on the fly?Look for
registration_endpoint(you do not have to register a client by hand),
authorization_endpoint,token_endpoint,
code_challenge_methods_supported: S256and
token_endpoint_auth_methods_supported: none(public client + PKCE).
curl -sS -m 20 "$AUTH_SERVER/.well-known/oauth-authorization-server"Did a token land on disk?Registration is not authentication. Only the
*.client.jsonmeans 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.
The fix: force one 401 while the OAuth provider is live
A. Client-native (best)Find the key that marks the server as OAuth and, if the
client has one, the “authenticate now / login” command. Set it, then call an account-scoped tool
from a normal session — most clients run the flow the first time a 401 comes back mid-session. The
keys are usually<server>.auth: oauthplus anoauthblock
(flow: browserordevice,timeout).B. Script the challenge (works everywhere)Import the client's own MCP connection
layer, wrap the connection in its interactive-OAuth context and call an account-scoped tool. The 401
arrives, the provider starts the flow and the consent URL is printed for a human to approve.C. Manual (last resort)Register a client with the server's
registration_endpoint(JSON withredirect_uris,client_name,
grant_types:["authorization_code","refresh_token"],
response_types:["code"],token_endpoint_auth_method:"none"), open the
authorization_endpointwithresponse_type=code,
code_challenge(S256) andstate, catch thecodeon the
loopback redirect, POST it to thetoken_endpointwith thecode_verifier,
then write the token into the client's store in the shape it expects.
The pitfalls that cost the most time
A short timeout cancels the callback waitWith the interactive flow, the wait for
the browser callback happens inside the tool call. If it times out, the human approves, the
code is lost, and the approval must be repeated. Raise the per-call timeout and the OAuth
flow timeout above the time a human needs.registration_endpointpresent, no tokenA client registration file
proves only dynamic registration. Check foraccess_token.An already-open session will not gain the toolsMCP servers are registered when the
agent session starts. After the token lands, open a new session (or restart the client/gateway) —
otherwise you conclude “still broken” while the fix is already in place.Verify with a real call, not with “connected”Passing a login probe means nothing
here: call an account-scoped tool and read the response. Without that output the authentication is
not verified — say so.Refresh tokensAfter the first fix, keep the
refresh_tokenand re-read
the token file when the access token expires, instead of re-running the interactive flow.
How to report it (a shape that leaves no doubt)
`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.
Start now
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.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.