Skip to content

fix(transport): return 404 from /register so MCP SDK falls through cleanly#83

Merged
ryaker merged 1 commit into
mainfrom
fix/mcp-sdk-register-404
May 12, 2026
Merged

fix(transport): return 404 from /register so MCP SDK falls through cleanly#83
ryaker merged 1 commit into
mainfrom
fix/mcp-sdk-register-404

Conversation

@ryaker

@ryaker ryaker commented May 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • Problem: Claude Code's MCP UI shows SDK auth failed: Dynamic client registration is not supported by this MCP server. Auth is tunnel-delegated — connect without OAuth. when connecting to the local KMS at http://localhost:8180/mcp.
  • Root cause: PR fix: return JSON stubs for OAuth discovery probes (Re-authenticate error) #38 (51cbdb8) added OAuth discovery stubs for /.well-known/* (returns 404 JSON) and POST /register (returns 501 JSON). The .well-known 404s work correctly. The /register 501 is interpreted by Claude Code's MCP SDK as a fatal DCR-rejected error, instead of "no such endpoint, fall through to unauthenticated."
  • Fix: Route POST /register through the same oauthNotSupported handler the .well-known endpoints already use. Single source of truth for "no OAuth, anywhere," returning a JSON 404 the SDK treats as "fall through."

What changed

src/transport/HttpTransport.ts:

  • Replaced inline 501-returning handler for POST /register with this.app.post('/register', oauthNotSupported).
  • Updated the comment block explaining why 404 (not 501) is the right code for /register.

src/__tests__/HttpTransport.oauth_stubs.test.ts (new, 9 cases):

  • All four endpoints (/.well-known/oauth-protected-resource, /.well-known/oauth-authorization-server, /.well-known/openid-configuration, /register) return:
    • status 404 (was 501 for /register before this PR)
    • Content-Type application/json
    • body has error: "oauth_not_supported"
    • body has error_description (string)
  • On localhost: body includes mcp_auth: "tunnel-delegated" debug field.
  • On non-localhost (Host: kms.yaker.org): body does NOT include mcp_auth — info-disclosure hardening preserved.

What this doesn't solve

The Claude Code SDK arguably shouldn't probe DCR after the .well-known endpoints all return 404 — that's an upstream concern. The 404 workaround is the pragmatic server-side fix and unblocks the user immediately. After merge & rebuild, Rich needs to click "Reconnect" in the Claude Code MCP UI to pick up the change.

Test plan

  • npx tsc --noEmit — clean
  • npm test — 404/404 passing (19 suites, 9 new test cases)
  • npm run build — clean (dist diff matches source change exactly)
  • Baseline curl confirmed: POST /register currently returns 501 against the live daemon
  • Post-merge live verification: curl -s -o /dev/null -w "HTTP %{http_code}\n" -X POST http://localhost:8180/register -H "Content-Type: application/json" -d '{}' should return HTTP 404
  • Rich clicks "Reconnect" in Claude Code MCP UI and the "SDK auth failed" banner is gone

Constraints honored

  • /mcp behavior unchanged
  • .well-known endpoints unchanged (they already worked)
  • No new dependencies
  • No version bumps of @anthropic-ai/sdk or mem0ai
  • dist/ not touched in this PR (gitignored; will be rebuilt post-merge)
  • localhost-only debug field hardening preserved

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • OAuth discovery endpoints now consistently return HTTP 404 responses with standardized error messages.
  • Tests

    • Added comprehensive test suite validating OAuth stub behavior for both local and remote requests.

Review Change Stack

…eanly

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>
@codeant-ai

codeant-ai Bot commented May 12, 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.

@ryaker ryaker merged commit e783278 into main May 12, 2026
1 check was pending
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2ff1102a-5235-4bd4-b21f-bad216242071

📥 Commits

Reviewing files that changed from the base of the PR and between 0d6804b and 72a5677.

📒 Files selected for processing (2)
  • src/__tests__/HttpTransport.oauth_stubs.test.ts
  • src/transport/HttpTransport.ts

📝 Walkthrough

Walkthrough

This PR unifies the /register endpoint's response behavior with other OAuth discovery endpoints by returning HTTP 404 with a consistent error payload instead of 501. Documentation clarifies this alignment, and a new test suite validates the four OAuth endpoint stubs' responses and host-based info-disclosure hardening.

Changes

OAuth Stub Behavior Alignment

Layer / File(s) Summary
Endpoint alignment and documentation
src/transport/HttpTransport.ts
Documentation updated to clarify /register is routed through the same "no OAuth" handler as discovery endpoints. The /register handler changed from returning HTTP 501 with registration_not_supported to HTTP 404 with oauth_not_supported for consistent behavior across all four OAuth probe endpoints.
OAuth stub endpoint validation
src/__tests__/HttpTransport.oauth_stubs.test.ts
New Jest test suite validates that three .well-known discovery endpoints and POST /register all return 404 JSON. For localhost requests, tests assert error: 'oauth_not_supported', presence of debug field mcp_auth: 'tunnel-delegated', and error_description as a string. For non-local requests, tests verify the same 404 JSON but without the mcp_auth debug field (info-disclosure hardening).

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 Four endpoints, one voice,
No OAuth here, oh what choice!
404s align,
Debug fields shine—
Secrets stay safe, let us rejoice!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mcp-sdk-register-404

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 modifies the HttpTransport to return a 404 status code for the /register endpoint, replacing the previous 501 response to allow the MCP SDK to fall back to unauthenticated connections. It also introduces a new test suite to verify OAuth stubs and info-disclosure hardening. Feedback includes a security recommendation to use req.ip instead of req.hostname for local request verification to prevent spoofing, and a suggestion to define shared metadata strings as constants to maintain consistency across the codebase.

})
}
})
this.app.post('/register', oauthNotSupported)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

The oauthNotSupported handler (and the isLocalRequest helper it uses) relies on req.hostname to determine if a request is local for info-disclosure hardening. Since req.hostname is derived from the client-provided Host header (or X-Forwarded-Host when trust proxy is enabled), it can be spoofed by external attackers to leak internal auth metadata. Consider using req.ip or req.socket.remoteAddress for a more reliable local request check.

Comment on lines +56 to +57
error: 'oauth_not_supported',
mcp_auth: 'tunnel-delegated'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The error string 'oauth_not_supported' and the debug field value 'tunnel-delegated' are hardcoded in both the implementation and the tests. Per the project's general rules, these shared metadata keys should be defined as constants in a common location to serve as a single source of truth for handoff contracts between producers and consumers, ensuring consistency and avoiding bugs from typos.

References
  1. Define shared transient metadata keys as constants in a common location to serve as a single source of truth for handoff contracts between producers and consumers, ensuring consistency and avoiding bugs from typos.

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