# How Remote MCP Servers Trigger OAuth Sign-In

> Source: <https://deallocatedobjects.com/posts/how-remote-mcp-servers-trigger-oauth-sign-in>
> Published: 2026-11-09 14:00:00+00:00

# 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.
