[BUG] Hosted MCP OAuth initiation ignores NODE_EXTRA_CA_CERTS on corporate MITM networks (v2.1.113+)

Resolved 💬 4 comments Opened May 3, 2026 by hagaiwe Closed May 8, 2026

Bug description

On corporate networks that intercept TLS with a custom root CA (Zscaler, Netskope, device-level CA, etc.), triggering OAuth for hosted MCP servers (gmail, gcal at *.mcp.claude.com) via /mcp or mcp__<server>__authenticate fails with:

SDK auth failed: self signed certificate in certificate chain

Environment

  • Claude Code v2.1.121 (native/Bun binary)
  • macOS 15
  • Corporate MITM proxy injects its root CA into the TLS chain
  • NODE_EXTRA_CA_CERTS set to a PEM that includes the corporate root
  • NODE_OPTIONS=--use-system-ca set; corporate roots also present in macOS System keychain
  • Users cannot disable the VPN/proxy by policy

What works (important for diagnosis)

  • claude mcp list succeeds against the same *.mcp.claude.com hosts — proving NODE_EXTRA_CA_CERTS is being honored by the main MCP HTTP transport.
  • Token refresh (401/403 re-auth) for MCP servers that were authenticated previously (from a non-MITM network) also succeeds — proving the refresh path is CA-aware.

What fails

Only first-time OAuth initiation for hosted MCP servers. Discovery, dynamic client registration, and the initial token exchange all fail TLS validation.

Likely root cause

Since v2.1.113 Claude Code ships as a Bun-compiled native binary. Bun's global fetch uses BoringSSL and does not honor NODE_EXTRA_CA_CERTS — extra CAs must be passed per-call via init.tls.ca.

Almost everywhere in the binary, HTTPS calls are wrapped with a CA-aware helper that reads NODE_EXTRA_CA_CERTS and threads it into init.tls.ca. StreamableHTTPClientTransport builds its internal fetch this way, and — crucially — passes that same CA-aware fetch into the MCP SDK's auth() helper when handling 401/403 re-auth. That is why refreshes work.

The OAuth initiation path appears to invoke the MCP SDK's auth() without a custom fetchFn, so the SDK falls back to the global Bun.fetch with no tls.ca override, and TLS validation fails against the corporate chain.

Proposed fix

In the OAuth initiation code path for hosted MCP servers, pass a CA-aware fetchFn (the same one used by the transport's internal fetch wrapper, or a freshly-built one using the existing CA-loading helper) into the MCP SDK's auth() call. Symmetric with how the transport's own 401/403 re-auth path already works.

Workaround for users

For first-time bootstrap only:

NODE_TLS_REJECT_UNAUTHORIZED=0 claude
# inside the session:
/mcp authenticate <server>

Quit and relaunch normally afterward — the stored tokens keep working through the CA-aware refresh path. NODE_TLS_REJECT_UNAUTHORIZED=0 disables TLS verification for every Bun.fetch call in that session, so it should be used only for the bootstrap and never exported to a shell.

Related issues

  • #44754 — per-server TLS config feature request; overlapping root cause
  • #4053 — WebFetch exhibits the same "some paths honor the CA, some don't" pattern
  • #50115 — docs still describe a Node.js runtime post-v2.1.113 Bun switch

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗