[BUG] Hosted MCP OAuth initiation ignores NODE_EXTRA_CA_CERTS on corporate MITM networks (v2.1.113+)
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_CERTSset to a PEM that includes the corporate rootNODE_OPTIONS=--use-system-caset; corporate roots also present in macOS System keychain- Users cannot disable the VPN/proxy by policy
What works (important for diagnosis)
claude mcp listsucceeds against the same*.mcp.claude.comhosts — provingNODE_EXTRA_CA_CERTSis 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
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗