-
Notifications
You must be signed in to change notification settings - Fork 12.9k
MCP HTTP OAuth Token Refresh Works via mcp list but Fails During Tool Calls in Active Chat Sessions #23296
Description
What happened?
When using Gemini CLI with a remote HTTP MCP server protected by OAuth 2.0, authentication succeeds and MCP tools work initially, but after the short-lived access token expires, MCP tool calls inside an already-running chat session fail instead of automatically refreshing the token.
The important detail is that token refresh itself appears to work. If the same expired session is checked from the shell with:
gemini mcp listGemini CLI refreshes the expired token successfully and reconnects the MCP server. However, tool calls from the active chat session still fail once the original access token has expired.
This makes it look like the interactive chat path and the mcp list path are not using the same refresh-aware token handling behavior.
This was observed while integrating a custom remote MCP server that:
- uses HTTP transport
- uses OAuth authorization code flow
- returns refresh tokens
- supports successful refresh-token redemption at the token endpoint
From the client point of view, the flow looks like this:
- Authenticate the MCP server with
/mcp auth <server> - Use MCP tools successfully in chat
- Wait for the access token to expire
- Call the MCP tool again in the same chat
- The tool call fails with an MCP error
- Run
gemini mcp list - Gemini refreshes the token successfully and shows the server as connected
So the hangup is not that refresh tokens are invalid. The hangup is that the active chat session appears to keep using stale authentication state until the MCP connection is rebuilt outside the chat flow.
What did you expect to happen?
Once the access token expires, Gemini CLI should automatically refresh the token for MCP tool calls in the active chat session, or reconnect the MCP transport using the refreshed token before retrying the request.
The expected behavior is that a working OAuth refresh flow should behave consistently across:
gemini mcp list- MCP tool calls inside chat
If mcp list can refresh the token successfully, the same authenticated MCP session should not require a manual reconnect just to continue using tools in chat.
Client information
│ About Gemini CLI │
│ │
│ CLI Version 0.34.0 │
│ Git Commit 49a8655 │
│ Model Auto (Gemini 3) │
│ Sandbox no sandbox │
│ OS darwin │
│ Auth Method Signed in with Google (nivbrook@gmail.com) │
│ Tier Gemini Code Assist in Google One AI Pro │
Login information
No response
Anything else we need to know?
Reproduction summary
- Configure a remote HTTP MCP server that requires OAuth
- Authenticate with
/mcp auth <server> - Use an MCP tool successfully in chat
- Wait for the short-lived access token to expire
- Invoke the same or another MCP tool in the same chat session
- Observe an MCP failure
- Run
gemini mcp list - Observe that Gemini refreshes the token successfully and reconnects the MCP server
Why this appears to be a Gemini CLI issue rather than a server refresh failure
We investigated both the MCP server implementation and the local Gemini CLI source.
Findings:
- The OAuth refresh flow itself succeeds when triggered by
gemini mcp list - After fixing server-side public-client handling, refresh works reliably when Gemini explicitly reconnects
- Claude handles refresh correctly against the same MCP server
- Gemini chat still fails only after the in-chat MCP connection has already been established and the bearer token later expires
This strongly suggests the issue is in Gemini CLI's runtime MCP transport behavior, not in the token endpoint's ability to redeem refresh tokens.
Relevant source-level findings from Gemini CLI
From the locally installed Gemini CLI sources:
@google/gemini-cli-core/dist/src/tools/mcp-client.js@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js
Observed behavior in the code:
- During MCP connection setup for chat, Gemini appears to load a stored access token and inject it directly into the request headers as a static bearer token.
- The HTTP transport's automatic re-auth / 401 recovery logic only applies when an auth provider is attached to the transport.
- In the stored-token path used for existing MCP sessions, the transport appears to be built with a bearer token header but without the same refresh-aware auth provider behavior.
- As a result, once the token used to create that transport expires, later MCP tool calls in the same chat session appear to continue using stale authentication state.
- Running
gemini mcp listforces a new lookup of stored credentials and a refresh-aware reconnection, which is why it succeeds immediately afterward.
In short: mcp list appears to rebuild the MCP connection using refreshed credentials, while the active chat transport appears to keep using stale headers from an earlier connection.
Related behavior
- The bug does not appear to be "refresh token always fails"
- The bug appears to be "chat MCP transport does not refresh/rebind credentials after token expiry"
Practical impact
For remote MCP servers using short-lived access tokens and refresh tokens, Gemini CLI becomes unreliable in longer chat sessions because MCP tools stop working mid-session until the user manually triggers a reconnect path such as:
gemini mcp listThat makes remote OAuth-protected MCP integrations much harder to use in practice, even when the server implements refresh correctly.