claude-code - 💡(How to fix) Fix [BUG] mcp headersHelper not re-invoked when access token expires on long-lived HTTP transport [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#53267Fetched 2026-04-26 05:20:05
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Participants
Timeline (top)
labeled ×6

Error Message

From ~/Library/Caches/claude-cli-nodejs/<project>/mcp-logs-<server>/<file>.jsonl.

Initial connect (works):

Executing headersHelper to get dynamic headers Successfully retrieved 1 headers from headersHelper HTTP transport options: {... "Authorization":"[REDACTED]", "hasAuthProvider":true ...} Successfully connected (transport: http) in 241ms Connection established with capabilities: {... serverVersion: ...}

Next tool call ~1h46min later — note no second Executing headersHelper between:

Calling MCP tool: <name> No token data found Saving discovery state (authServer: https://internal.example.com/) No client info found HTTP connection dropped after 6371s uptime Connection error: HTTP 404: Invalid OAuth error response: SyntaxError: JSON Parse error: Unexpected EOF. Raw body: Tool '<name>' failed after 0s: HTTP 404: Invalid OAuth error response: ...

After /mcp → "Authentication cleared", the transport is torn down and rebuilt; the helper runs again and the next call succeeds:

HTTP transport closed/disconnected, attempting automatic reconnection Initializing HTTP transport to ... Executing headersHelper to get dynamic headers Successfully retrieved 1 headers from headersHelper Successfully connected (transport: http) in 114ms HTTP reconnection successful after 142ms (attempt 1)

Root Cause

headersHelper runs only when a new transport is created. The header it returns is cached on the transport for the transport's entire lifetime — which, for the http transport, is hours, because HTTP/2 keepalive keeps the connection up across long idle periods. When the underlying token expires (~5 min is typical for OIDC access tokens), subsequent requests get 401 from the upstream, and the SDK falls back to its OAuth auth provider instead of re-invoking the helper. If the upstream doesn't implement the MCP OAuth resource handshake, the user sees a Invalid OAuth error response and is stuck until they manually run /mcp → "Authentication cleared".

Code Example

From `~/Library/Caches/claude-cli-nodejs/<project>/mcp-logs-<server>/<file>.jsonl`.

Initial connect (works):


Executing headersHelper to get dynamic headers
Successfully retrieved 1 headers from headersHelper
HTTP transport options: {... "Authorization":"[REDACTED]", "hasAuthProvider":true ...}
Successfully connected (transport: http) in 241ms
Connection established with capabilities: {... serverVersion: ...}


Next tool call ~1h46min later — note no second `Executing headersHelper` between:


Calling MCP tool: <name>
No token data found
Saving discovery state (authServer: https://internal.example.com/)
No client info found
HTTP connection dropped after 6371s uptime
Connection error: HTTP 404: Invalid OAuth error response: SyntaxError: JSON Parse error: Unexpected EOF. Raw body:
Tool '<name>' failed after 0s: HTTP 404: Invalid OAuth error response: ...


After `/mcp`"Authentication cleared", the transport is torn down and rebuilt; the helper runs again and the next call succeeds:


HTTP transport closed/disconnected, attempting automatic reconnection
Initializing HTTP transport to ...
Executing headersHelper to get dynamic headers
Successfully retrieved 1 headers from headersHelper
Successfully connected (transport: http) in 114ms
HTTP reconnection successful after 142ms (attempt 1)

---

"internal": {
     "type": "http",
     "url": "https://internal.example.com/mcp",
     "headersHelper": "echo '{\"Authorization\": \"Bearer '\"$(mycli auth print-access-token)\"'\"}'"
   }
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

headersHelper runs only when a new transport is created. The header it returns is cached on the transport for the transport's entire lifetime — which, for the http transport, is hours, because HTTP/2 keepalive keeps the connection up across long idle periods. When the underlying token expires (~5 min is typical for OIDC access tokens), subsequent requests get 401 from the upstream, and the SDK falls back to its OAuth auth provider instead of re-invoking the helper. If the upstream doesn't implement the MCP OAuth resource handshake, the user sees a Invalid OAuth error response and is stuck until they manually run /mcp → "Authentication cleared".

The doc at https://code.claude.com/docs/en/mcp#use-dynamic-headers-for-custom-authentication says:

The helper runs fresh on each connection (at session start and on reconnect). There is no caching, so your script is responsible for any token reuse.

That framing implies the helper runs often enough that token reuse is the script's responsibility. In practice, "on each connection" means once per transport, and the "no caching" line is misleading — there's effectively a multi-hour cache on the cached Authorization header, and the script can't refresh it because the SDK never asks again. A CLI that does silent token refresh internally (mine does) is moot, because its first invocation is also its last for the lifetime of the transport.

What Should Happen?

The cached header should not silently outlive the underlying token. Either the helper should be re-invoked when the upstream returns 401 (preferable, since headersHelper is a more specific configuration than the OAuth auth provider and should be treated as authoritative when both are present), or the docs should state explicitly that for the http transport, headersHelper runs once per transport and is unsuitable for tokens that expire faster than the transport's lifetime — which excludes the very example use cases the docs list ("Kerberos, short-lived tokens, or an internal SSO").

Error Messages/Logs

From `~/Library/Caches/claude-cli-nodejs/<project>/mcp-logs-<server>/<file>.jsonl`.

Initial connect (works):


Executing headersHelper to get dynamic headers
Successfully retrieved 1 headers from headersHelper
HTTP transport options: {... "Authorization":"[REDACTED]", "hasAuthProvider":true ...}
Successfully connected (transport: http) in 241ms
Connection established with capabilities: {... serverVersion: ...}


Next tool call ~1h46min later — note no second `Executing headersHelper` between:


Calling MCP tool: <name>
No token data found
Saving discovery state (authServer: https://internal.example.com/)
No client info found
HTTP connection dropped after 6371s uptime
Connection error: HTTP 404: Invalid OAuth error response: SyntaxError: JSON Parse error: Unexpected EOF. Raw body:
Tool '<name>' failed after 0s: HTTP 404: Invalid OAuth error response: ...


After `/mcp`"Authentication cleared", the transport is torn down and rebuilt; the helper runs again and the next call succeeds:


HTTP transport closed/disconnected, attempting automatic reconnection
Initializing HTTP transport to ...
Executing headersHelper to get dynamic headers
Successfully retrieved 1 headers from headersHelper
Successfully connected (transport: http) in 114ms
HTTP reconnection successful after 142ms (attempt 1)

Steps to Reproduce

  1. Configure an HTTP MCP server in ~/.claude.json with a headersHelper that returns a short-lived bearer token (e.g. an OIDC access token, ~5 min TTL):

    "internal": {
      "type": "http",
      "url": "https://internal.example.com/mcp",
      "headersHelper": "echo '{\"Authorization\": \"Bearer '\"$(mycli auth print-access-token)\"'\"}'"
    }
  2. The upstream MCP server validates the bearer JWT and returns 401 when it's expired or invalid. It does not implement the MCP OAuth resource handshake (no WWW-Authenticate: Bearer resource_metadata=… on 401s, no /.well-known/oauth-protected-resource).

  3. Start a Claude Code session and call a tool on the MCP server. Works.

  4. Wait until the access token TTL elapses (~5 min in our case). Don't make any MCP calls in the meantime.

  5. Call any tool on the MCP server.

Expected: helper re-runs, fresh token is sent, request succeeds.

Actual: HTTP 404: Invalid OAuth error response: SyntaxError: JSON Parse error: Unexpected EOF. Raw body: with no Executing headersHelper line in the MCP log between step 3 and step 5.

Claude Model

None

Is this a regression?

No, this never worked

Last Working Version

No response

Claude Code Version

2.1.119

Platform

Google Vertex AI

Operating System

macOS

Terminal/Shell

VS Code integrated terminal

Additional Information

No response

extent analysis

TL;DR

The headersHelper should be re-invoked when the upstream returns 401 to refresh the expired token, or the documentation should explicitly state its limitations for short-lived tokens.

Guidance

  • The issue arises from the caching of the Authorization header for the lifetime of the transport, which can be hours, while the underlying token expires in minutes.
  • To mitigate this, consider modifying the headersHelper to handle token refresh or implement a custom authentication mechanism that can refresh the token when it expires.
  • Verify the issue by checking the MCP logs for the absence of Executing headersHelper lines between the initial connect and subsequent tool calls after the token has expired.
  • The documentation should be updated to reflect the actual behavior of the headersHelper and its limitations for short-lived tokens.

Example

No code snippet is provided as the issue is more related to the behavior of the headersHelper and the documentation rather than a specific code problem.

Notes

The provided information suggests that this is not a regression and the issue has never worked as expected. The solution may involve changes to the headersHelper or the documentation to reflect its actual behavior.

Recommendation

Apply a workaround by modifying the headersHelper to handle token refresh or implement a custom authentication mechanism, as the current behavior is not suitable for short-lived tokens.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] mcp headersHelper not re-invoked when access token expires on long-lived HTTP transport [1 participants]