Skip to content

MCP HTTP OAuth Token Refresh Works via mcp list but Fails During Tool Calls in Active Chat Sessions #23296

@nivbrook

Description

@nivbrook

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 list

Gemini 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:

  1. Authenticate the MCP server with /mcp auth <server>
  2. Use MCP tools successfully in chat
  3. Wait for the access token to expire
  4. Call the MCP tool again in the same chat
  5. The tool call fails with an MCP error
  6. Run gemini mcp list
  7. 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

  1. Configure a remote HTTP MCP server that requires OAuth
  2. Authenticate with /mcp auth <server>
  3. Use an MCP tool successfully in chat
  4. Wait for the short-lived access token to expire
  5. Invoke the same or another MCP tool in the same chat session
  6. Observe an MCP failure
  7. Run gemini mcp list
  8. 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:

  1. 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.
  2. The HTTP transport's automatic re-auth / 401 recovery logic only applies when an auth provider is attached to the transport.
  3. 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.
  4. 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.
  5. Running gemini mcp list forces 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 list

That makes remote OAuth-protected MCP integrations much harder to use in practice, even when the server implements refresh correctly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/agentIssues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Qualitystatus/need-triageIssues that need to be triaged by the triage automation.status/possible-duplicate

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions