Skip to content

Add CORS to /mcp so browser-based MCP connectors can probe it - #29

Merged
aterga merged 1 commit into
mainfrom
claude/mcp-spec-compliance-9202ka
Jul 4, 2026
Merged

aterga merged 1 commit into
mainfrom
claude/mcp-spec-compliance-9202ka

Conversation

@aterga

@aterga aterga commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

What & why

Browser-based MCP connector UIs (Grok, ChatGPT) call the MCP endpoint via fetch() from their own origin, but /mcp had no CORS — so the browser blocked it at the first probe:

  • OPTIONS /mcp (the CORS preflight, sent without a token) hit the bearer gate and returned 401, so the preflight failed and the real request never happened.
  • POST /mcp → 401 carried no Access-Control-Allow-Origin and did not expose WWW-Authenticate, so a browser couldn't read the response or the auth-discovery hint it needs to find the authorization server.

/oauth/* and /.well-known/* already had a CORS layer; /mcp (the protected_mcp router) never got one. This is a likely cause of the "Connection failed" seen when adding the server as a custom connector.

Fix

Add a CORS layer to the /mcp router as the outermost layer, so:

  • the OPTIONS preflight is answered (2xx + CORS headers) before the bearer gate runs;
  • the 401's WWW-Authenticate is exposed cross-origin (plus mcp-session-id);
  • Authorization is listed explicitly in Access-Control-Allow-Headers — the * wildcard does not cover Authorization per the Fetch spec;
  • allowed methods GET, POST, OPTIONS; allowed headers include content-type, accept, mcp-protocol-version, mcp-session-id, last-event-id.

/oauth/* and /.well-known/* CORS is unchanged.

Testing

  • cargo build clean (one pre-existing macro warning).
  • Verified against a local server from Origin: https://grok.com:
    • OPTIONS /mcp → 200 with access-control-allow-origin: *, allow-methods: GET,POST,OPTIONS, allow-headers: authorization,content-type,accept,mcp-protocol-version,mcp-session-id,last-event-id (was 401).
    • POST /mcp (no token) → 401 now carrying access-control-allow-origin: * and access-control-expose-headers: www-authenticate,mcp-session-id.
    • /.well-known/* CORS still present (no regression).

Notes

  • Branch restarted from main after Restore authorization-code flow via II finish_url redirect (so Claude.ai can connect) #28's squash-merge; this is a fresh PR.
  • Separate, still open: live Claude.ai/Grok connect still dead-ends at II's "You're signed in / you can close this window" screen because the II build deployed at II_URL (beta.id.ai) isn't yet honoring the finish_url we return in the key-request response — our side sends it correctly (verified against the live deployment). That needs the #4086 finish_url-capable II build deployed; it's not addressed here.

🤖 Generated with Claude Code

https://claude.ai/code/session_014T9N8USDfNK5yzznPGg7Ym


Generated by Claude Code

Browser-based MCP clients (Grok/ChatGPT connector UIs) call /mcp via
fetch(), but /mcp had no CORS: the OPTIONS preflight hit the bearer gate
and returned 401, and the 401 responses carried no Access-Control-Allow-
Origin and did not expose WWW-Authenticate, so the browser couldn't read
the auth-discovery hint. Add a CORS layer as the OUTERMOST layer on the
/mcp router so the preflight is answered (2xx) before the token gate, and
the 401's WWW-Authenticate is exposed cross-origin. Authorization is listed
explicitly in allow-headers (the `*` wildcard does not cover it per the
Fetch spec); WWW-Authenticate and mcp-session-id are exposed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014T9N8USDfNK5yzznPGg7Ym

Copilot AI 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.

Pull request overview

This PR adds a dedicated CORS layer to the /mcp router so browser-based MCP connector UIs can successfully complete CORS preflights and read authentication-discovery headers (e.g., WWW-Authenticate) from cross-origin responses.

Changes:

  • Adds a /mcp-scoped CorsLayer configured for GET, POST, OPTIONS.
  • Explicitly allows Authorization and MCP-related request headers and exposes WWW-Authenticate and mcp-session-id in responses.
  • Applies the CORS layer outside the /mcp bearer-token gate so preflights don’t get blocked by authentication.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@aterga
aterga marked this pull request as ready for review July 4, 2026 17:10
@aterga
aterga merged commit 0cf205d into main Jul 4, 2026
1 check passed
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.

3 participants