How Remote MCP Servers Trigger OAuth Sign-In Remote MCP servers must return a 401 with a `WWW-Authenticate` header pointing at RFC 9728 protected-resource metadata to trigger OAuth sign-in in Claude or ChatGPT, but the same 401 causes directories and health checkers to mark servers as down, Jake Marsh reports. CallForMe logged about 700 checker 401s in two days, and one directory listed it as "Unhealthy, 0.0% uptime over 32 days" while it was running fine. Marsh's Taskhold defaults to anonymous access and challenges a list of known OAuth clients, while CallForMe defaults to the 401 and opens up for known checkers, a trade-off he resolves by which mistake costs more. How Remote MCP Servers Trigger OAuth Sign-In Jake Marsh https://deallocatedobjects.com/about When someone pastes your MCP server’s URL into Claude or ChatGPT, a sign-in window should open. What makes that happen is one HTTP response from your server. I run a few remote MCP servers with OAuth, and the response those clients need is the same one that gets a server marked as down in directories. The Response That Starts It the-response-that-starts-it The flow in the MCP authorization spec https://modelcontextprotocol.io/specification/draft/basic/authorization begins when the client’s request gets a 401 with a WWW-Authenticate header pointing at your protected resource metadata: The expose header is there for clients running in a browser. Without it, their JavaScript can’t read the challenge. That URL returns a small JSON document RFC 9728 https://datatracker.ietf.org/doc/html/rfc9728 naming your authorization server: From there the client reads your authorization server’s metadata, registers itself and opens the browser. I serve the metadata at both /.well-known/oauth-protected-resource/mcp and the bare /.well-known/oauth-protected-resource , because some clients try the root first. Two Callers Want Opposite Things two-callers-want-opposite-things If an anonymous initialize returns 200, an OAuth-capable client can decide your server needs no auth. It connects, lists the tools and only fails later on tools/call , with no sign-in window. So those clients need the 401 on their very first request. Directories and health checkers want the opposite. They call initialize and tools/list with no credentials, and many of them read a 401 as an outage. CallForMe https://callforme.tel/?ref=deallocated-post logged about 700 of those 401s from checkers in two days, and one directory listed it as “Unhealthy, 0.0% uptime over 32 days” while it was running fine. Answer By Who’s Asking answer-by-whos-asking So the endpoint looks at the caller. This is the version in Taskhold https://taskhold.com/?ref=deallocated-post , trimmed down. Clients that sign in get challenged, and everything else can read the public parts: The User-Agent catches most clients. Some send something generic like node , and those name themselves in clientInfo inside the initialize body, which is why there’s a second regex. A spoofed User-Agent gets a caller nothing here, since the list only decides who sees a challenge. Every tool that touches data still needs a token, and anonymous requests run against a backend that throws on any call. Which Default To Pick which-default-to-pick Taskhold defaults to anonymous and challenges a list of known OAuth clients. CallForMe does the inverse: it defaults to the 401 and opens up for a list of known checkers. Your choice decides who gets the wrong answer when a caller you’ve never seen shows up: - With a list of OAuth clients, a new client gets a 200 and may not offer sign-in until someone adds it. - With a list of checkers, a new checker gets a 401 and may list you as down. I’d pick by which mistake costs you more. CallForMe also matches checkers by how they describe themselves mcp-?index , health-?check , crawler , so new ones don’t sit on a 401 until I notice them. There are exceptions on both sides. The CI for awesome-remote-mcp-servers https://github.com/punkpeye/awesome-remote-mcp-servers labels a server by its initialize reply: 200 means no auth, and a 401 with resource metadata means OAuth. An OAuth server that wants the right label has to challenge that checker too, so it’s in Taskhold’s full list. Log The 401s log-the-401s Some clients see the challenge and never finish. codex mcp add --url without codex mcp login hit a 401 on every session: in early November my logs showed 7 IPs stuck in one day and no completed Codex sign-ins in 3 days. CallForMe now lets unauthenticated Codex sessions into a public mode that needs no sign-in, and signed-in ones keep their token. Another runtime registered with the OAuth server, never called /authorize and kept retrying hourly. I only found these by logging each 401 with its User-Agent, limited to once per IP and client every 6 hours. Comparing that against completed sign-ins shows which clients get stuck. You can check your own server’s answer with curl by changing the User-Agent: Run it once with that header and once with User-Agent: directory-check . The first should return the 401 and the second a 200, unless your server is one that challenges everyone by default.