Skip to main content
Glama
README.md
# mcp-oauth-doctor

The step where your MCP server's sign-in breaks.

```sh
npx --allow-git=root github:agentwares/mcp-oauth-doctor https://your-server.example/mcp
```

It walks a remote MCP server's OAuth chain from outside, the way a client does, with **no
credential and no client registration**, and prints:

- **the first broken step**;
- **whose it is**: the server, its identity provider, or a known client bug, with the issue;
- **the fix**, citing the section of the 2026-07-28 MCP authorization spec or the RFC that requires it;
- **a verdict per client** — claude.ai, Claude Code, ChatGPT, VS Code and Cursor — emulated from
  each client's published requirements and the issues filed against it. No client is run.

The same check is a web page, [`/auth?url=`](https://agentwares-mcp-liveness.vercel.app/auth), and
an MCP tool, `mcp_liveness_check_auth` on `https://agentwares-mcp-liveness.vercel.app/mcp`.

## What it walks

1. **The 401.** One unauthenticated request, the way a dual-era client opens (`server/discover` at
   2026-07-28, then `initialize`). It must answer 401 — clients start sign-in only on 401 — with a
   `WWW-Authenticate: Bearer resource_metadata="…"` challenge, or serve the well-known document.
2. **RFC 9728 protected-resource metadata**: the URL in the challenge (a client that has one uses
   it and stops there), then `/.well-known/oauth-protected-resource/<path>`, then the root. Its
   `resource` must be the server's URL; it must name an authorization server.
3. **RFC 8414 or OpenID Connect discovery**, at the locations the spec lists, in its order. A
   document found only where the spec does not tell clients to look is reported as such. The
   `issuer` must be identical to the URL it was looked up for (RFC 8414 §3.3, which clients must
   enforce since 2026-07-28), and `code_challenge_methods_supported` must list `S256` (clients
   must refuse to proceed without it).
4. **How a client gets a client ID**: a client ID metadata document (preferred since 2026-07-28),
   RFC 7591 dynamic registration (deprecated, kept for compatibility), or a pasted one.
5. **Each client's published rules**: which registration it uses, the redirect URIs it sends, and
   known bugs that match this server — every rule with its source and date; a rule seen only in
   an issue is labelled _observed in_, never presented as documented.

A metadata document behind a bot challenge (Cloudflare, Vercel, DataDome, …) is named: clients
fetch it from servers and CLIs, which cannot solve one.

## Options

```text
npx --allow-git=root github:agentwares/mcp-oauth-doctor <url> [--json] [--client <name>]

  --json            the full report as JSON (schema mcp-oauth-doctor/1), plus `next` and `also`
  --client <name>   judge for one client only: claude-ai, claude-code, chatgpt, vscode, cursor
```

Exit code 1 when sign-in is broken or nothing answered, 0 otherwise, 2 for a usage error. Usage
errors carry `code`, `cause`, `fix` and `retryable` (as JSON with `--json`).

## In CI

```yaml
- uses: agentwares/mcp-oauth-doctor@main
  with:
    url: https://your-server.example/mcp
    # client: claude-ai          # one client only
    # fail-on: at_risk           # broken (default) | at_risk | never
```

The Action runs the committed `dist/` from its own checkout (no npm install), writes the report to
the job summary, and sets `verdict`, `first-broken` and `report` (the JSON's path) as outputs.

## A recorded run

Recorded on 9 October 2026 with `pnpm record` (`scripts/record.mjs`): our own servers, live, and
two **synthetic** broken servers that the script serves on 127.0.0.1 for the length of the run
(`src/vendor/mcp-oauth-walk/synthetic.ts`). The synthetic ones are not anyone's real server. Our
own servers ask for no credential, so the doctor says there is no sign-in to check and offers the
free hourly watch. The synthetic servers have a sign-in, but nothing outside this machine can
reach 127.0.0.1, so their hand-off is the watch form left empty, for once a server is deployed,
then the nightly checks.

```text
## Our own server, live (2026-10-09)

$ npx --allow-git=root github:agentwares/mcp-oauth-doctor https://agentwares-mcp-liveness.vercel.app/mcp
mcp-oauth-doctor 0.1.0
https://agentwares-mcp-liveness.vercel.app/mcp · 2026-10-09 18:19 UTC · 0.8 s
Discovery only: no credential sent, no client registered.

The server answered without asking for a credential, so there is no sign-in to break.

Steps
  info  Unauthenticated request  HTTP 200: the server accepts requests without a token, so there is no sign-in to check.

Re-check it every hour and email me when it stops answering: agentcheck's free watch, no account. https://agentwares-agentcheck.vercel.app/watch?target=https%3A%2F%2Fagentwares-mcp-liveness.vercel.app%2Fmcp&ref=mcp-oauth-doctor
(exit 0)

## Our own server, live (2026-10-09)

$ npx --allow-git=root github:agentwares/mcp-oauth-doctor https://agentwares-agentcheck.vercel.app/api/mcp
mcp-oauth-doctor 0.1.0
https://agentwares-agentcheck.vercel.app/api/mcp · 2026-10-09 18:19 UTC · 0.5 s
Discovery only: no credential sent, no client registered.

The server answered without asking for a credential, so there is no sign-in to break.

Steps
  info  Unauthenticated request  HTTP 200: the server accepts requests without a token, so there is no sign-in to check.

Re-check it every hour and email me when it stops answering: agentcheck's free watch, no account. https://agentwares-agentcheck.vercel.app/watch?target=https%3A%2F%2Fagentwares-agentcheck.vercel.app%2Fapi%2Fmcp&ref=mcp-oauth-doctor
(exit 0)

## SYNTHETIC broken server ("issuer-mismatch"), served on 127.0.0.1 for this recording — not anyone's real server

$ npx --allow-git=root github:agentwares/mcp-oauth-doctor http://127.0.0.1:56268/mcp
mcp-oauth-doctor 0.1.0
http://127.0.0.1:56268/mcp · 2026-10-09 18:19 UTC · 0.0 s
Discovery only: no credential sent, no client registered.

Sign-in breaks at "Metadata `issuer` is identical to the advertised authorization server", and
the fix is the identity provider's.

Steps
  ok    Unauthenticated request returns 401                   HTTP 401
  ok    WWW-Authenticate points at the resource metadata      resource_metadata=http://127.0.0.1:56268/.well-known/oauth-protected-resource/mcp
  info  Challenge advertises the scope to request             scope="mcp:tools"
  ok    Protected resource metadata is discoverable           http://127.0.0.1:56268/.well-known/oauth-protected-resource/mcp (header)
  ok    PRM `resource` is this server's canonical URI         resource=http://127.0.0.1:56268/mcp
  ok    PRM lists an authorization server                     http://127.0.0.1:56269/tenant1
  info  Scopes advertised                                     mcp:tools
  ok    Authorization server metadata is discoverable         http://127.0.0.1:56269/.well-known/oauth-authorization-server/tenant1; issuer=http://127.0.0.1:56269; token_e…
  FAIL  Metadata `issuer` is identical to the advertised au…  issuer=http://127.0.0.1:56269 vs http://127.0.0.1:56269/tenant1
  ok    PKCE S256 advertised                                  S256
  ok    Client registration                                   client ID metadata documents and dynamic registration (registration_endpoint=http://127.0.0.1:56269/tenant1/r…
  ok    Refresh tokens supported                              authorization_code, refresh_token
  ok    Public clients can use the token endpoint             none, client_secret_basic

First broken step: Metadata `issuer` is identical to the advertised authorization server
  whose: the identity provider
  what:  issuer=http://127.0.0.1:56269 vs http://127.0.0.1:56269/tenant1
  fix:   Make `issuer` in the metadata exactly the URL listed in the protected-resource
         metadata's authorization_servers (http://127.0.0.1:56269/tenant1), character for
         character, or list exactly the issuer the metadata states.
  spec:  RFC 8414 §3.3; MCP 2026-07-28 "Authorization Server Metadata Discovery"
  about "Issuer mismatch": https://agentwares-mcp-liveness.vercel.app/auth/issuer-mismatch?ref=mcp-oauth-doctor

Clients (emulated from each client's published requirements and known issues; no client was run)
  claude.ai    BLOCKED      Metadata `issuer` is identical to the advertised authorization server: issuer=http://127.0.0.1:56269 vs http://127.0.0.…
  Claude Code  BLOCKED      Metadata `issuer` is identical to the advertised authorization server: issuer=http://127.0.0.1:56269 vs http://127.0.0.…
  ChatGPT      BLOCKED      Metadata `issuer` is identical to the advertised authorization server: issuer=http://127.0.0.1:56269 vs http://127.0.0.…
  VS Code      BLOCKED      Metadata `issuer` is identical to the advertised authorization server: issuer=http://127.0.0.1:56269 vs http://127.0.0.…
  Cursor       BLOCKED      Metadata `issuer` is identical to the advertised authorization server: issuer=http://127.0.0.1:56269 vs http://127.0.0.…

Redirect URIs to allow if your identity provider restricts them
  claude.ai    https://claude.ai/api/mcp/auth_callback
  Claude Code  http://localhost/callback  http://127.0.0.1/callback
  ChatGPT      https://chatgpt.com/connector_platform_oauth_redirect
  VS Code      http://127.0.0.1:33418  https://vscode.dev/redirect
  Cursor       http://localhost:8787/callback  https://www.cursor.com/agents/mcp/oauth/callback  cursor://anysphere.cursor-mcp/oauth/callback

Once it is deployed, re-check it every hour and email me when a step breaks: agentcheck's free watch, no account — it follows the sign-in a new client follows, with these same rules. https://agentwares-agentcheck.vercel.app/watch?ref=mcp-oauth-doctor
Nightly, with history: mcpcheck Server Pro re-runs these sign-in checks against the server every night, with 90 days of history, and emails when one fails ($49 a server a month; the first run is free). https://agentwares-agentcheck.vercel.app/mcpcheck?ref=mcp-oauth-doctor
(exit 1)

## SYNTHETIC broken server ("second-issuer"), served on 127.0.0.1 for this recording — not anyone's real server

$ npx --allow-git=root github:agentwares/mcp-oauth-doctor http://127.0.0.1:56268/mcp --client claude-ai
mcp-oauth-doctor 0.1.0
http://127.0.0.1:56268/mcp · 2026-10-09 18:19 UTC · 0.0 s
Discovery only: no credential sent, no client registered.

Sign-in breaks at "claude.ai cannot sign in", and the fix is the server's (only claude.ai is
affected).

Steps
  ok    Unauthenticated request returns 401               HTTP 401
  ok    WWW-Authenticate points at the resource metadata  resource_metadata=http://127.0.0.1:56268/.well-known/oauth-protected-resource/mcp
  info  Challenge advertises the scope to request         scope="mcp:tools"
  ok    Protected resource metadata is discoverable       http://127.0.0.1:56268/.well-known/oauth-protected-resource/mcp (header)
  ok    PRM `resource` is this server's canonical URI     resource=http://127.0.0.1:56268/mcp
  ok    PRM lists an authorization server                 http://127.0.0.1:56269/gone, http://127.0.0.1:56269/tenant1
  info  Scopes advertised                                 mcp:tools
  ok    Authorization server metadata is discoverable     http://127.0.0.1:56269/.well-known/oauth-authorization-server/tenant1; issuer=http://127.0.0.1:56269/tenant1;…
  ok    PKCE S256 advertised                              S256
  ok    Client registration                               client ID metadata documents and dynamic registration (registration_endpoint=http://127.0.0.1:56269/tenant1/r…
  ok    Refresh tokens supported                          authorization_code, refresh_token
  ok    Public clients can use the token endpoint         none, client_secret_basic

First broken step: claude.ai cannot sign in
  whose: the server — claude.com/docs connectors: authentication (2026-10-08)
  what:  claude.ai uses only the first authorization_servers entry (http://127.0.0.1:56269/gone),
         and it published no metadata; the one that answered is http://127.0.0.1:56269/tenant1
  fix:   Put the authorization server that serves metadata first in authorization_servers, or
         serve metadata for the first one.
  spec:  MCP 2026-07-28 "Authorization Server Location"
  docs:  https://claude.com/docs/connectors/building/authentication

Clients (emulated from each client's published requirements and known issues; no client was run)
  claude.ai  BLOCKED      claude.ai uses only the first authorization_servers entry (http://127.0.0.1:56269/gone), and it published no metadata; …

Redirect URIs to allow if your identity provider restricts them
  claude.ai  https://claude.ai/api/mcp/auth_callback

Once it is deployed, re-check it every hour and email me when a step breaks: agentcheck's free watch, no account — it follows the sign-in a new client follows, with these same rules. https://agentwares-agentcheck.vercel.app/watch?ref=mcp-oauth-doctor
Nightly, with history: mcpcheck Server Pro re-runs these sign-in checks against the server every night, with 90 days of history, and emails when one fails ($49 a server a month; the first run is free). https://agentwares-agentcheck.vercel.app/mcpcheck?ref=mcp-oauth-doctor
(exit 1)
```

## What it will not do

- It never sends a credential, never registers a client and never requests a token.
- It never retries. Each request has a 10-second timeout and the whole walk a 30-second budget.
- It makes one POST (two for a server on the older protocol revision) and GETs of public
  discovery documents. It sends no telemetry and needs no account.

## Error messages people paste

One page each, saying what the message means, its three common causes and how to tell them
apart, with the public issues and their dates:

- [Authorization with the MCP server failed](https://agentwares-mcp-liveness.vercel.app/auth/authorization-with-the-mcp-server-failed)
- [Couldn't reach the MCP server](https://agentwares-mcp-liveness.vercel.app/auth/couldnt-reach-the-mcp-server)
- [Failed to discover OAuth metadata](https://agentwares-mcp-liveness.vercel.app/auth/failed-to-discover-oauth-metadata)
- [Dynamic Client Registration not supported](https://agentwares-mcp-liveness.vercel.app/auth/dynamic-client-registration-not-supported)
- [Incompatible auth server: does not support dynamic client registration](https://agentwares-mcp-liveness.vercel.app/auth/incompatible-auth-server-does-not-support-dynamic-client-registration)
- [Issuer mismatch](https://agentwares-mcp-liveness.vercel.app/auth/issuer-mismatch)
- [invalid_redirect_uri](https://agentwares-mcp-liveness.vercel.app/auth/invalid-redirect-uri)
- [Connection expired](https://agentwares-mcp-liveness.vercel.app/auth/connection-expired)
- [token exchange failed](https://agentwares-mcp-liveness.vercel.app/auth/token-exchange-failed)

## Sources, read 8 October 2026

- MCP authorization, revision 2026-07-28:
  [main](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization),
  [Authorization Server Discovery](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/authorization-server-discovery),
  [Client Registration](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/client-registration),
  [Security Considerations](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/security-considerations).
- claude.ai: [connectors, authentication](https://claude.com/docs/connectors/building/authentication).
- Claude Code: [MCP](https://code.claude.com/docs/en/mcp).
- ChatGPT: [Apps SDK, authentication](https://developers.openai.com/apps-sdk/build/auth).
- VS Code: [MCP configuration](https://code.visualstudio.com/docs/copilot/reference/mcp-configuration),
  [MCP extension guide](https://code.visualstudio.com/api/extension-guides/ai/mcp).
- Cursor: [MCP](https://cursor.com/docs/mcp); client ID metadata documents are not supported yet,
  observed in [forum thread 148096](https://forum.cursor.com/t/mcp-oauth-cimd-support-plans-and-timelines/148096)
  (opened 6 January 2026, staff reply 22 September 2026).

The per-client rules and known issues are in
[`src/vendor/mcp-oauth-walk/clients.ts`](src/vendor/mcp-oauth-walk/clients.ts), each with its
source and date.

## Keep checking

A check is one look. The doctor's last lines say how to keep looking:

- **Every hour, free:** [agentcheck's watch](https://agentwares-agentcheck.vercel.app/watch?ref=mcp-oauth-doctor)
  takes an email address and the server's URL, with no account. For a server with a sign-in it
  follows the sign-in a new client follows, with these same rules, and emails the step when one
  breaks, then again when it is fixed. For a server without one, it emails when the server stops
  answering or its tools change. A server on localhost cannot be watched from outside, so the
  doctor offers the form empty, for once it is deployed.
- **Every night, with history:** [mcpcheck Server Pro](https://agentwares-agentcheck.vercel.app/mcpcheck?ref=mcp-oauth-doctor)
  re-runs these sign-in checks with 90 days of history and emails when one fails ($49 a server a
  month; the first run is free). The doctor offers it to a server with a sign-in, after the watch.

With `--json`, the watch is `next` and Server Pro is `also` (`null` for a server with no sign-in).

## Library

```sh
npm install github:agentwares/mcp-oauth-doctor
```

```ts
import { diagnose } from "mcp-oauth-doctor";
const report = await diagnose("https://your-server.example/mcp", { clients: ["claude-ai"] });
```

Web-standard (`fetch`, `URL`, `AbortSignal`), no runtime dependencies.

MIT, by agentwares.