Skip to content

feat(auth): apply custom headers to OAuth requests (gateway bypass) - #1

Draft
janpio wants to merge 3 commits into
mainfrom
claude/mcp-inspector-auth-all-requests-c2j49h
Draft

janpio wants to merge 3 commits into
mainfrom
claude/mcp-inspector-auth-all-requests-c2j49h

Conversation

@janpio

@janpio janpio commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

Adds an "Also apply to OAuth requests" toggle beneath Custom Headers so the configured headers are attached to the OAuth auxiliary calls too — not just the MCP transport. This lets the Inspector complete OAuth against an MCP server deployed behind a gateway that gates access with a header (the motivating case: Vercel Deployment Protection's Protection Bypass for Automation).

On V1 scope: V1 is in maintenance mode (bug fixes / spec compliance) while V2 is built, so flagging the scope up front. This is mostly a bug fix — OAuth is currently broken against header-gated deployments (discovery/registration/token go out without the header and fail), and this makes it actually work (modelcontextprotocol#879). The opt-in toggle does edge into new-feature territory, so if maintainers would rather fold it into V2, no problem — it's self-contained and fully usable straight from this branch/fork in the meantime.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)

Changes Made

The problem: Custom Headers were only attached to the MCP transport requests. The OAuth auxiliary requests the SDK issues itself — protected-resource / authorization-server metadata discovery, dynamic client registration (/register), and the token exchange (/token) — went out without the custom header and hit the gateway's auth wall, so discovery failed and OAuth never completed.

The fix:

  • New "Also apply to OAuth requests" toggle under Custom Headers (Authentication panel). When on, the custom headers are injected into every outbound request to the target server (transport and the OAuth aux calls); when off (default), behavior is unchanged.
  • Injection lives in one small shared fetch wrapper (customHeaderFetch) threaded into the SDK's auth methods, so it works in both connection modes ("Via Proxy" and direct) and reaches the OAuth calls regardless of which layer issues them.
  • Aligns the post-redirect token exchange in OAuthCallback with the same header-aware fetch (and the proxy fetch, when "Via Proxy" is selected) that the rest of the OAuth flow already uses via createProxyFetch — that standalone route previously issued its auth() call with the browser's default fetch, so neither the proxy nor the custom headers reached it.
  • Headless: --header / MCP_REQUEST_HEADERS feed the Custom Headers list and default the toggle on (opt out with MCP_APPLY_HEADERS_TO_ALL_REQUESTS=false), surfaced via GET /config.
  • README documents the toggle and links Vercel's bypass docs.

Header values are masked in the UI and never logged. Existing headers always win over injected ones (never clobbers an Authorization).

Related Issues

Testing

  • Tested in UI mode
  • Tested in CLI mode
  • Tested with STDIO transport
  • Tested with SSE transport
  • Tested with Streamable HTTP transport
  • Added/updated automated tests
  • Manual testing performed

Test Results and/or Instructions

Verified live against an MCP server behind Vercel Deployment Protection: with the bypass header configured and the toggle on, protected-resource discovery succeeds through the gateway, the client follows authorization_servers to the (separate, unprotected) auth host, login completes.

Unit tests cover the header-injecting fetch wrapper (OAuth discovery/DCR/token injection, existing-header precedence, enabled/blank filtering, and the no-op default). Full client suite + CLI suite pass; prettier + lint clean.

Vercel gotcha: connect to the exact host your server advertises as its OAuth resource (its canonical/production URL). Vercel serves the same deployment under multiple hostnames; using a different alias than the advertised one makes the OAuth resource check fail (this is a server-side metadata concern, not an Inspector bug).

Checklist

  • Code follows the style guidelines (ran npm run prettier-fix)
  • Self-review completed
  • Code is commented where necessary
  • Documentation updated (README)

Breaking Changes

None. The toggle defaults off, so existing flows are unaffected.

🤖 Generated with Claude Code

…. OAuth)

Add an "Apply to All Requests" affordance that injects a configurable set of
extra HTTP headers and/or query params into every outbound request to the
target MCP server — not just the MCP transport POSTs, but also the OAuth
auxiliary calls (protected-resource / authorization-server metadata discovery,
dynamic client registration, and the token exchange).

This unblocks connecting to MCP servers deployed behind a gateway that gates
access with a header or query param (e.g. Vercel Deployment Protection's
x-vercel-protection-bypass). Previously custom headers only reached the
transport, so OAuth discovery/DCR/token requests hit the gateway's auth wall
and the flow never learned the real authorization server.

Implementation:
- New shared fetch wrapper (client/src/lib/requestExtras.ts) that injects the
  extras in one place; threaded into the transport (direct mode) and into the
  SDK auth methods (fetchFn) so it works in both "Via Proxy" and direct modes.
- Query params are embedded into the proxy `url=` target for proxy mode and
  appended to the URL for direct mode; header extras flow through the existing
  custom-header plumbing (forwarded by the proxy via x-custom-auth-headers).
- OAuthCallback now builds the same proxy/extras-aware fetchFn for the token
  exchange, fixing the OAuth-bypasses-Via-Proxy gap (modelcontextprotocol#995).
- UI: new ApplyToAllRequests section in the sidebar (masked values).
- Headless: server CLI flags (--header, --query-param) and env vars
  (MCP_REQUEST_HEADERS, MCP_REQUEST_QUERY_PARAMS) surfaced via /config; the
  `mcp-inspector` launcher forwards them; the `--cli` invoker gains
  --query-param (appended to the server URL on every request).
- Default behavior is unchanged when no extras are configured; no secrets logged.

Tests: unit tests for the shared wrapper (transport + OAuth discovery/DCR/token
injection and the no-op default) and the query-param type helpers, plus CLI
integration tests asserting query-param injection reaches the server.

Refs: modelcontextprotocol#879, modelcontextprotocol#995, modelcontextprotocol#752, modelcontextprotocol#817

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FAFCf8AzpqyNF1oKZNGzh
Simplify the feature per review feedback:

- Remove query-param support entirely. Headers alone cover the gateway-bypass
  use case (e.g. Vercel Deployment Protection accepts its bypass secret as a
  header), so the separate query-param list, plumbing, CLI/env flags, and tests
  are dropped.
- Drop the separate "Apply to All Requests" header list (it duplicated Custom
  Headers). Instead, add a single "Also apply to OAuth requests" toggle beneath
  the existing Custom Headers. When on, the custom headers are injected into the
  OAuth auxiliary requests (metadata discovery, DCR, token exchange) in addition
  to the transport; when off (default), behavior is unchanged.
- Replace the request-extras module with a focused header-only fetch wrapper
  (customHeaderFetch) threaded into the SDK auth methods, useConnection,
  AuthDebugger, and the OAuth callback route.
- Headless: `--header` / MCP_REQUEST_HEADERS now feed the Custom Headers list
  and default the toggle on (opt out via MCP_APPLY_HEADERS_TO_ALL_REQUESTS=false),
  surfaced via GET /config.
- README: document the toggle instead of a query param, and link Vercel's
  Protection Bypass for Automation docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FAFCf8AzpqyNF1oKZNGzh
@janpio janpio changed the title Add "Apply to All Requests" headers & query params (incl. OAuth) feat(auth): apply custom headers to OAuth requests (gateway bypass) Jul 23, 2026
On a normal launch the server reports `defaultCustomHeaders: []` and
`defaultApplyHeadersToAllRequests: false`. The client applied the boolean
unconditionally, so after enabling the toggle and reloading it would render
on (from localStorage) and then flip off once the /config fetch resolved —
and the persistence effect wrote that `false` back, destroying the saved value.

Only apply the headless header/toggle defaults when the server actually
declares header defaults (i.e. --header / MCP_REQUEST_HEADERS were provided);
otherwise leave the user's persisted headers and toggle untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FAFCf8AzpqyNF1oKZNGzh
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.

MCP Inspector Cannot Pass Custom Authentication Headers

2 participants