Skip to content

fix: return JSON stubs for OAuth discovery probes (Re-authenticate error)#38

Merged
ryaker merged 2 commits into
mainfrom
fix/mcp-oauth-probe-stubs
Apr 17, 2026
Merged

fix: return JSON stubs for OAuth discovery probes (Re-authenticate error)#38
ryaker merged 2 commits into
mainfrom
fix/mcp-oauth-probe-stubs

Conversation

@ryaker

@ryaker ryaker commented Apr 13, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the long-standing "Re-authenticate" error in Claude Code's MCP UI when reconnecting to `personal-kms` at `localhost:8180`:

```
Error: SDK auth failed: HTTP 404: Invalid OAuth error response: SyntaxError:
JSON Parse error: Unrecognized token '<'. Raw body:

...
Cannot POST /register
\`\`\`

Root cause

When a user clicks Re-authenticate in Claude Code's MCP UI, the client unconditionally probes OAuth discovery endpoints before proceeding — regardless of whether the server actually advertises OAuth:

  • `POST /register` (RFC 7591 dynamic client registration)
  • `GET /.well-known/oauth-authorization-server` (RFC 8414)
  • `GET /.well-known/oauth-protected-resource` (RFC 9728)
  • `GET /.well-known/openid-configuration` (OIDC discovery)

This KMS server does not implement MCP-client OAuth — auth is delegated to the transport layer (Cloudflare tunnel for `kms.yaker.org`, no auth for `localhost:8180`). Before this fix, none of those four paths had any handler, so they fell through to Express's default 404, which returns:

```html

<title>Error</title>
Cannot POST /register
\`\`\`

The MCP client tries to parse that HTML as JSON and crashes with `SyntaxError: Unrecognized token '<'`. The reconnection still works (because it hits `/mcp` not `/register`), but the UI shows a garbled error and the user-experience is broken.

Fix

Four stub handlers in `HttpTransport.setupRoutes` that return well-formed JSON:

Path Status Body
`POST /register` 501 `{"error": "registration_not_supported", "error_description": "...", "mcp_auth": "tunnel-delegated"}`
`GET /.well-known/oauth-authorization-server` 404 `{"error": "not_found", "error_description": "...", "mcp_auth": "tunnel-delegated"}`
`GET /.well-known/oauth-protected-resource` 404 same
`GET /.well-known/openid-configuration` 404 same

The MCP client sees valid JSON → treats "no OAuth advertised" as "no OAuth required" → completes the connection without OAuth. Normal `/mcp` tool calls are unaffected.

Verification

Verified against the live service after `node --watch` hot-reloaded the rebuilt `dist/`:

```bash
$ curl -s -X POST http://localhost:8180/register -H 'Content-Type: application/json' -d '{}'
{"error":"registration_not_supported","error_description":"Dynamic client registration is not supported by this MCP server. Auth is tunnel-delegated — connect without OAuth.","mcp_auth":"tunnel-delegated"}

$ curl -s http://localhost:8180/.well-known/oauth-authorization-server
{"error":"not_found","error_description":"This MCP server does not implement OAuth...","mcp_auth":"tunnel-delegated"}

$ curl -s http://localhost:8180/.well-known/oauth-protected-resource
{"error":"not_found","error_description":"...","mcp_auth":"tunnel-delegated"}

$ curl -s http://localhost:8180/.well-known/openid-configuration
{"error":"not_found","error_description":"...","mcp_auth":"tunnel-delegated"}
```

All four return proper JSON bodies with 404/501 status and no HTML.

Test plan

  • `npx tsc --noEmit` — clean
  • `npm run build` — clean
  • All four probed paths verified against live service returning JSON
  • Reviewer: click Re-authenticate in Claude Code's `/mcp` UI on `personal-kms` after merge — confirm no more "Unrecognized token '<'" error
  • Reviewer: verify normal tool calls still work (regression check)

Scope

🤖 Generated with Claude Code

…ror)

When an MCP client clicks "Re-authenticate" in the Claude Code UI, the
client unconditionally probes OAuth discovery endpoints before proceeding:
  POST /register
  GET  /.well-known/oauth-authorization-server
  GET  /.well-known/oauth-protected-resource
  GET  /.well-known/openid-configuration

This server does NOT implement MCP-client OAuth — auth is delegated to the
transport layer (Cloudflare tunnel for the public endpoint, no auth for
localhost). But Express's default 404 handler returned an HTML body
("<!DOCTYPE html>..."), which the MCP client's JSON parser choked on with
"SyntaxError: Unrecognized token '<'". The Re-authenticate UI would show
a garbled HTTP 404 error even though the underlying connection was fine.

Add four handlers in HttpTransport.setupRoutes that return well-formed
JSON for each probed path:
  - POST /register → 501 registration_not_supported
  - GET /.well-known/* → 404 not_found with mcp_auth=tunnel-delegated

The MCP client sees valid JSON, treats "no OAuth advertised" as "no OAuth
required", and completes connection without OAuth. Normal /mcp tool calls
are unaffected — they don't hit these paths.

Verified against the live service on localhost:8180 after hot-reload:
all four paths now return proper JSON bodies with 404/501 status and
no HTML.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codeant-ai

codeant-ai Bot commented Apr 13, 2026

Copy link
Copy Markdown

User does not have a PR Review subscription.

Go to Team management and add this email to the PR Review subscription.

@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@ryaker has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 24 minutes and 44 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 24 minutes and 44 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d75d6317-c976-4cd0-bb23-cfe259e8163a

📥 Commits

Reviewing files that changed from the base of the PR and between eff278f and 3026cc6.

⛔ Files ignored due to path filters (1)
  • dist/transport/HttpTransport.js is excluded by !**/dist/**
📒 Files selected for processing (1)
  • src/transport/HttpTransport.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mcp-oauth-probe-stubs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces OAuth discovery stubs to the HttpTransport in both JavaScript and TypeScript implementations. These stubs handle requests to well-known OAuth and OpenID configuration paths by returning structured JSON 404 or 501 errors. This change prevents MCP clients from crashing when they encounter default HTML error pages during authentication probes. I have no feedback to provide as there were no review comments to evaluate.

PR #38 review WARNING — the OAuth stub responses leaked the server's auth
architecture (`mcp_auth: "tunnel-delegated"` + a detailed description
mentioning "Cloudflare tunnel") in the response body. On localhost that's
fine — the audience is already trusted and the debug info helps local
development. But kms.yaker.org is a public endpoint behind Cloudflare, and
an attacker who bypasses the tunnel (direct IP probe, SSRF, or during a CF
outage) now gets a machine-readable signal that auth is entirely delegated
to the transport layer. More useful to the attacker than a generic 404.

Fix: detect localhost requests via req.hostname (localhost, 127.0.0.1, ::1)
and only include the detailed error_description + mcp_auth field on those.
Public-facing responses get a generic "OAuth is not available" / "Dynamic
client registration is not supported" body.

Also: renamed the `error` field from `not_found` to `oauth_not_supported`
on the /.well-known/* paths. The reviewer noted that `not_found` isn't a
standard OAuth error code per RFC 6749 §5.2 — `oauth_not_supported` more
accurately describes the condition and signals to future clients that the
server is intentionally not advertising OAuth metadata.

Reviewer also flagged a concern about a pre-existing oauth.enabled config
branch that could collide with these stubs. Verified via grep: there is
NO oauth.enabled conditional in HttpTransport.ts today. The concern came
from the pre-existing dead test file (src/__tests__/transport/HttpTransport.test.ts,
excluded via testPathIgnorePatterns) which references methods that were
removed in PR #22. Non-issue for the current codebase — added a comment
documenting this so future readers don't re-raise.

Verified against live service:
  $ curl http://localhost:8180/.well-known/oauth-authorization-server
  → {"error":"oauth_not_supported","error_description":"This MCP server
     does not implement OAuth...","mcp_auth":"tunnel-delegated"}

  $ curl -H "Host: kms.yaker.org" http://localhost:8180/.well-known/oauth-authorization-server
  → {"error":"oauth_not_supported","error_description":"OAuth is not
     available on this endpoint."}

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ryaker ryaker merged commit 51cbdb8 into main Apr 17, 2026
1 check passed
@ryaker ryaker deleted the fix/mcp-oauth-probe-stubs branch April 17, 2026 03:41
ryaker added a commit that referenced this pull request May 12, 2026
…eanly (#83)

PR #38 (51cbdb8) added OAuth discovery stubs to fix HTML-404 parse errors,
but chose 501 for POST /register. Claude Code's MCP SDK treats 501 as a fatal
"Dynamic client registration is not supported" error and surfaces:

  SDK auth failed: Dynamic client registration is not supported by this
  MCP server. Auth is tunnel-delegated — connect without OAuth.

The user's ~/.claude.json has the server as { type: "http", url: ... } with
no auth, so the SDK should fall through to unauthenticated when discovery
returns "no auth." A 404 reads as "no such endpoint, fall through"; a 501
reads as "DCR explicitly rejected — fatal."

The .well-known/* stubs already return 404 via a shared oauthNotSupported
handler. Route /register through the same handler — single source of truth
for "no OAuth, anywhere," and the SDK probe completes cleanly.

Info-disclosure hardening preserved: mcp_auth: "tunnel-delegated" debug field
only ships on localhost requests; the public Cloudflare-tunneled response
stays generic.

Tests: src/__tests__/HttpTransport.oauth_stubs.test.ts covers all four
endpoints, both the 404-shape contract and the localhost-only debug field
hardening (9 cases). Full suite: 404/404 passing.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant