Skip to content

MCP OAuth: Google-backed servers never get a refresh token; session breaks after ~1h #24906

@forketyfork

Description

@forketyfork

What version of Codex CLI is running?

codex-cli 0.134.0

What subscription do you have?

ChatGPT Enterprise

Which model were you using?

No response

What platform is your computer?

macOS Darwin 25.5.0 arm64 arm

What terminal emulator and version are you using (if applicable)?

No response

Codex doctor report

What issue are you seeing?

Logging into an MCP server that uses Google as its OAuth identity provider succeeds initially, but the stored credentials contain only an access token, with no refresh_token. After the ~1h access token expires, MCP tool calls fail with auth errors and the only recovery is to run codex mcp login <server> again and walk through the full browser flow.

Inspecting the stored credentials confirms the symptom:

{
  "server_name": "<name>",
  "access_token": "<JWT, ~1h lifetime>",
  "expires_at": <approx now + 3600s>,
  "scope": "openid email profile"
  // no `refresh_token` field
}

The credential blob lives in the macOS Keychain under service Codex MCP Credentials, or in $CODEX_HOME/.credentials.json when keyring writes fall back to file.

What steps can reproduce the bug?

  1. Configure any MCP server that uses Google as its OAuth provider, e.g.:

    [mcp_servers.example]
    url = "https://example.com/mcp"
  2. Run codex mcp login example and complete the Google sign-in in the browser.

  3. Inspect the stored credentials (Keychain entry for Codex MCP Credentials with account example|<hash>, or $CODEX_HOME/.credentials.json). No refresh_token field is present.

  4. Wait until the access token expires, or make an MCP tool call shortly after the 1h mark. The session fails to refresh and requires manual relogin.

What is the expected behavior?

After codex mcp login against a Google-backed MCP server, the stored credentials should contain a refresh_token so the session can survive past access-token expiry without forcing the user back through the browser flow. This is how Claude Code's MCP client behaves against the same servers.

Additional information

Root cause (from reading codex-rs/rmcp-client/src/perform_oauth_login.rs): Google OAuth issues a refresh token only when the authorization request includes access_type=offline (typically with prompt=consent). This is a Google-specific query parameter, not part of OAuth scope negotiation. Google does not honor offline_access as a scope, so requesting it via the existing scopes config has no effect. Codex's authorize-URL builder appends only one parameter on top of what rmcp generates (resource=), so access_type=offline is never sent and Google therefore omits the refresh token.

A draft change on a fork (https://github.com/forketyfork/codex/pull/1) explores one possible mitigation for reference (adding extra_authorize_params to the MCP auth configuration).

Metadata

Metadata

Assignees

No one assigned

    Labels

    authIssues related to authentication and accountsbugSomething isn't workingmcpIssues related to the use of model context protocol (MCP) servers

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions