[BUG] MCP OAuth re-auth UI not shown for page reload/background Streamable HTTP auth failure

Open 💬 0 comments Opened Jun 28, 2026 by omryberko

Preflight Checklist

  • [x] I have searched existing issues and did not find this exact behavior
  • [x] This is a single bug report

What's Wrong?

For a custom Streamable HTTP MCP server that uses OAuth, Claude shows different auth recovery behavior depending on how the expired MCP access token is encountered.

This is not a refresh-token bug. In this setup the server intentionally does not rely on refresh tokens; when the MCP access token expires, an explicit user re-authentication flow is expected.

Observed split:

  1. If the token expires while the conversation is still active, and the user sends a follow-up message that invokes the MCP server, Claude surfaces the OAuth/reconnect modal. Completing the OAuth flow reconnects the MCP server and the conversation can continue.
  2. If the token expires and the user refreshes/reopens the Claude conversation page, Claude appears to reload existing MCP/widget content in the background. That background Streamable HTTP request receives an auth failure, but Claude renders a blocking Streamable HTTP error instead of showing the same OAuth/reconnect UI.

This leaves the existing conversation in a bad state: the user cannot cleanly re-authenticate from the reload path, and sending a new follow-up just to force the reconnect UI dirties the conversation.

What Should Happen?

When a Streamable HTTP MCP request during page reload, conversation rehydration, widget bootstrap, or any other background reload path receives an OAuth 401/auth challenge, Claude should surface the same OAuth/reconnect UI that it surfaces for a user-initiated MCP tool call.

The user should be able to re-authenticate without sending a new chat message.

What Happens Instead?

On page refresh/reopen, Claude shows a hard Streamable HTTP error similar to:

Streamable HTTP error: Error POSTing to endpoint: {
  "type":"error",
  "error":{
    "type":"authentication_error",
    "message":"OAuth token has been invalidated. Re-authentication is required.",
    "details":{"error_code":"mcp_invalid_oauth_token"}
  },
  "request_id":"..."
}

No OAuth/reconnect modal is shown in that reload path.

Steps to Reproduce

  1. Configure a custom Streamable HTTP MCP server using OAuth authorization-code/PKCE.
  2. Have the MCP server issue short-lived access tokens without relying on refresh tokens.
  3. Connect the MCP server in Claude and use a tool successfully in a conversation.
  4. Wait for the MCP access token to expire.
  5. In the still-open conversation, send a follow-up that needs the MCP server.
  • Actual: Claude shows the OAuth/reconnect modal.
  • This part works as expected.
  1. Reconnect, use the MCP server again, then wait for the token to expire again.
  2. Refresh/reopen the Claude conversation page so existing MCP/widget content is rehydrated.
  • Actual: Claude shows a blocking Streamable HTTP auth error.
  • Expected: Claude shows the OAuth/reconnect modal without requiring a new user message.

Why This Seems Client-Side

The same expired-token condition can trigger the OAuth/reconnect UI when reached through a user-initiated MCP call. The failure appears specific to the page reload / background rehydration path, where the auth challenge is rendered as a hard Streamable HTTP error instead of being routed through the OAuth reconnect UI.

Related but not exact matches:

  • #5706 has a comment noting that mid-session tool invocation and startup/session connection use different refresh/auth paths: https://github.com/anthropics/claude-code/issues/5706#issuecomment-3958067044
  • #65036 tracks refresh-token based "Connection expired" behavior, but this report is about explicit re-auth UI not appearing on page reload/background auth failure when refresh tokens are not part of the design.

Claude Surface

Claude web conversation page with a custom Streamable HTTP MCP connector.

Additional Information

This is especially painful for MCP widgets or tool-result content that reloads when the page is refreshed: the user sees a blocking rendering/auth error but has no direct reconnect action in that context.

View original on GitHub ↗