codex mcp login appears to require dynamic client registration for private OAuth MCP servers; cannot use pre-registered client identity

Open 💬 10 comments Opened Apr 23, 2026 by rickyclegg

Description

I’m seeing an interoperability problem between Codex MCP OAuth login and a private remote MCP server that uses FastMCP (Python) with Okta-backed OAuth.

The server is internal/non-public, so I can’t share the real URL or tenant details, but the behavior is consistent and reproducible with a sanitized config.

The key issue seems to be that Codex is attempting Dynamic Client Registration (DCR), while this server/IdP setup works with a pre-registered OAuth client in other MCP clients.

A fixed callback port is not enough to make this work in Codex.

Environment

What version of Codex CLI is running?

codex-cli 0.122.0

What platform is your computer?

macOS 26.4.1 (build 25E253), arm64

Darwin 25.4.0

What issue are you seeing?

With this config:

mcp_oauth_callback_port = 8080

[mcp_servers.private_internal]
url = "https://redacted.example.internal/mcp"
enabled = true

running:

codex mcp login private_internal

fails with:

Error: Registration failed: Dynamic registration failed: Registration failed: HTTP 403 Forbidden: {"errorCode":"E0000005","errorSummary":"Invalid session","errorLink":"E0000005","errorId":"<redacted>","errorCauses":[]}

This server is a private FastMCP-based streamable HTTP MCP server using Okta for OAuth.

The same server works in another MCP client when configured with a pre-registered OAuth client identity and a fixed callback port.

What I expected

One of these should work:

  1. Codex completes OAuth successfully against this server.
  2. Codex allows configuring a pre-registered/static OAuth client identity per MCP server.
  3. Codex emits a clearer error indicating that this server requires a pre-registered client and that Codex currently only supports DCR for this flow.

What actually happened

Codex appears to attempt Dynamic Client Registration and fails before a usable browser auth flow completes.

Changing the callback port to a fixed value (mcp_oauth_callback_port = 8080) did not change the outcome.

Additional context

This looks related to other Codex MCP OAuth issues involving DCR assumptions and nontrivial OAuth servers:

  • Slack official MCP: Codex fails with Dynamic client registration not supported

https://github.com/openai/codex/issues/13200

  • Supabase MCP: Codex attempted dynamic registration / generated an authorize URL, but the flow was incompatible with the server’s actual expectations

https://github.com/openai/codex/issues/5254

From the Codex docs, I could find support for:

  • codex mcp login
  • mcp_oauth_callback_port
  • mcp_oauth_callback_url
  • per-server scopes
  • per-server oauth_resource

But I could not find a documented way to provide a per-server static OAuth client id / client identity for MCP login.

Why this matters

There are private enterprise MCP servers that are OAuth-protected but do not behave like public DCR-friendly endpoints. If Codex only supports DCR here, that blocks otherwise valid MCP integrations.

Request

Please clarify whether Codex MCP OAuth login is expected to support:

  • private OAuth MCP servers backed by Okta
  • pre-registered/static OAuth client identities
  • FastMCP-based remote servers that do not support Codex-style DCR assumptions

If not, it would help to either:

  • add support for per-server static client configuration, or
  • document that limitation explicitly and fail with a more precise error.

View original on GitHub ↗

10 Comments

tomcarman · 2 months ago

I'm experiencing the same trying to connect Codex to the Salesforce hosted MCP servers.

They explicitly outline their reason for not supporting DCR - https://github.com/forcedotcom/mcp-hosted/wiki/Configuring-an-External-Client-App#why-not-dynamic-client-registration

I imagine this will be the approach of other enterprise applications providing MCP servers - so it would be great to see Codex add support for non-DCR authentications - similar to how its available when adding apps to ChatGPT, as well as Claude, Gemini etc.

madeyexz · 2 months ago

Hit this same wall today. While we wait for upstream support for pre-registered OAuth clients, there's a working bearer-token workaround that gets Codex talking to mcp.slack.com:

TL;DR: Register your own Slack app, run the OAuth code-exchange manually to mint a user token (xoxp-…), then configure Codex with --bearer-token-env-var SLACK_MCP_TOKEN instead of codex mcp login. Bypasses the DCR step entirely. Slack's hosted MCP server accepts the user token as a bearer once the app has MCP server access enabled.

Full step-by-step (including the non-obvious "enable MCP server access on the app" toggle and a cleaned app manifest): https://ianhsiao.me/blog/enabling-slack-mcp-in-codex-and-claude-code-workaround

quotientjon · 2 months ago

This has been an issue for us for a while. We have been trying to work around it by putting a FastMCP Ouath proxy in front of our own services where DCR is not supported but it is clunky...

tomcarman · 2 months ago

Thanks @madeyexz - I stumbled through the equivalent for Salesforce, and managed to get things working...at least for some testing.

Disclaimer: I would highly recommend against anyone doing the following anywhere other than a dev environment you don't really care about, using raw tokens in env vars is less than ideal!

Also I'm fairly sure the Salesforce token will expire as per the orgs session settings, so probably will need to refresh the token based on that - if its truly a dev/test environment, you can probably just set the session expiry to the maximum 24hrs to minimise how often.

The below is a rehash of @madeyexz 's work (https://ianhsiao.me/blog/enabling-slack-mcp-in-codex-and-claude-code-workaround) - but with the urls etc for Salesforce rather than Slack.

Configure Salesforce

Set up the External App as per https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_mcp_server_eca.htm, but make sure you also enable the Client Credentials Flow

Also make sure you enabled the MCP servers in the Salesforce org - https://help.salesforce.com/s/articleView?id=platform.api_catalog_activate_salesforce_mcp_servers.htm&type=5

Get bearer token

Note: you can probably achieve the steps below to get the bearer token a bit more easily using the Salesforce Postman etc - but heres the manual way...

Go to the following URL in your browser, substituting your client_id. Also amend scopes as needed:

https://test.salesforce.com/services/oauth2/authorize?response_type=code&client_id={CLIENT_ID}&scope=api+refresh_token+offline_access+sfap_api+mcp_api&redirect_uri=http://localhost:8080/callback

Get the code returned from browser url e.g:

http://localhost:8080/callback?code=<CODE>

Call the token endpoint:

curl -X POST https://test.salesforce.com/services/oauth2/token \
-d "client_id={CLIENT_ID}" \
-d "client_secret={CLIENT_SECRET}" \
-d "code={CODE_FROM_ABOVE}" \
-d "grant_type=authorization_code" \
-d "redirect_uri=http://localhost:8080/callback"

Get the bearer token returned e.g:

http://localhost:8080/callback#access_token=<ACCESS_TOKEN>

Test it works (substitute the URL for whichever MCP server you are trying to connect to):

curl -sS -i -X POST https://api.salesforce.com/platform/mcp/v1/sandbox/platform/sobject-all \
  -H "Authorization: Bearer {ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"manual-test","version":"0.1"}}}'

If you get a 200, all good.

Add to Codex

Add the bearer token to a shell env or apple keychain (e.g. see @madeyexz 's guide)

Add the MCP servers you wish eg.

codex mcp add salesforce-sobject-all --url https://api.salesforce.com/platform/mcp/v1/sandbox/platform/sobject-all --bearer-token-env-var SF_MCP_TOKEN

codex mcp add salesforce-metadata-experts --url https://api.salesforce.com/platform/mcp/v1/sandbox/platform/metadata-experts --bearer-token-env-var SF_MCP_TOKEN

codex mcp add salesforce-api-context --url https://api.salesforce.com/platform/mcp/v1/sandbox/platform/salesforce-api-context --bearer-token-env-var SF_MCP_TOKEN

Launch codex and you should see the MCP servers starting up, use /mcp to confirm.

Ahmed-aleryani · 1 month ago

Adding below helps

[mcp_servers.<mcp server name>.oauth]
client_id = <id>

Seems to overcome the issue above, but then the issue still happens with the callback where codex automatically appends callback_id to the callback, which requires enabling wildcard_redirect to match codex pattern <your url>:port/callback/:callback_id

canorionen · 1 month ago

Adding a concrete official-hosted MCP repro for the same class of problem: Meta Ads MCP at https://mcp.facebook.com/ads fails during dynamic client registration before any browser/OAuth consent flow opens.

I opened a separate issue with the exact command, config, and error here: #24103

The error is:

{"error":"invalid_client_metadata","error_description":"Dynamic registration is not available for this client."}

This is not a private/internal OAuth server case; it is Meta's official hosted Ads MCP endpoint.

Ahmed-aleryani · 1 month ago

I investigated this further and have a focused implementation prepared in a fork.

Summary of the approach:

  • Supports pre-registered/static MCP OAuth client IDs without adding a new public config schema or broad plumbing changes.
  • Keeps generated callback-id behavior for default local callbacks.
  • Uses configured mcp_oauth_callback_url values verbatim as registered redirect URIs.
  • Adds protected-resource metadata discovery for MCP auth discovery.
  • Uses the caller-provided client only for protected-resource metadata, while using a separate no-default-header client for authorization-server metadata discovered from protected-resource metadata, to avoid forwarding sensitive default headers across origins.
  • Preserves same-server authorization metadata discovery with the configured client for servers that require headers on their own metadata endpoints.
  • Caps server-provided authorization-server candidates from protected-resource metadata.
  • Tightens callback handling so direct http callback URLs must use localhost or a loopback IP with an explicit matching listener port, while public HTTPS registered redirect URLs can still use mcp_oauth_callback_port for the local listener.

Local validation:

  • just fmt
  • just fix -p codex-rmcp-client
  • cargo build -p codex-cli
  • CARGO_NET_GIT_FETCH_WITH_CLI=true just test -p codex-rmcp-client

Fork PR for reference:
https://github.com/Ahmed-aleryani/codex/pull/1

Would a maintainer be open to an upstream PR for this approach?

cc @gpeal because you previously handled related MCP OAuth protected-resource discovery work in #5254 / #5423. No pressure if this belongs with someone else.

sshishigin · 21 days ago

This looks related to #30460 from the redirect-uri side of the same static/pre-registered OAuth client story.

For providers that require pre-registered clients, exact redirect URI matching is often enforced. Codex has mcp_oauth_callback_url, but currently mutates the configured value by appending /<callback_id>, so a client registered for http://localhost:<port>/callback can still fail with a redirect URI mismatch.

#30460 is intentionally narrower than the full pre-registered-client support request: it proposes treating explicit mcp_oauth_callback_url values as exact redirect URIs, while preserving callback-id behavior for generated local redirects.

devansh-jain-18 · 12 days ago

Adding a concrete official-hosted Snowflake Managed MCP repro for the same pre-registered/static OAuth client problem.

Environment:

  • Codex CLI: codex-cli 0.142.4
  • Platform: macOS 26.5.2 (25F84)
  • MCP server: Snowflake Managed MCP, official Snowflake-hosted endpoint

Working endpoint shape, redacted:

https://<account_locator>.<region>.aws.snowflakecomputing.com/api/v2/databases/<database>/schemas/<schema>/mcp-servers/<server>

Repro:

codex mcp add snowflake \
  --url https://<account_locator>.<region>.aws.snowflakecomputing.com/api/v2/databases/<database>/schemas/<schema>/mcp-servers/<server>

codex mcp login snowflake

Observed:

Added global MCP server 'snowflake'.
Detected OAuth support. Starting OAuth flow…
Error: Registration failed: Dynamic registration failed: Registration failed: Dynamic client registration not supported

I verified the Snowflake endpoint itself is advertising OAuth correctly. A request to the MCP URL returns 401 with a WWW-Authenticate: Bearer resource_metadata=... challenge, and the protected resource metadata includes an authorization server and scopes, for example:

{
  "authorization_servers": ["https://<account_locator>.<region>.aws.snowflakecomputing.com/oauth"],
  "scopes_supported": ["session:role:all"]
}

Snowflake's own Managed MCP docs explicitly say the Snowflake-managed MCP server supports OAuth 2.0 but does not support Dynamic Client Registration. It expects an OAuth security integration / pre-registered client instead: Snowflake Managed MCP OAuth setup.

So this is not a private/internal-only case: Snowflake Managed MCP is an official hosted MCP server with OAuth discovery, but Codex currently attempts DCR and fails before any browser consent flow can proceed.

Codex CLI currently exposes --oauth-client-id on codex mcp add, but I could not find a documented way to provide the corresponding OAuth client secret / pre-registered client credentials for this flow. Claude Code documents --client-id, --client-secret, and --callback-port for exactly this non-DCR case, so the missing piece appears to be first-class pre-registered OAuth client support in Codex MCP login.

Expected behavior:

  • Codex should allow configuring a pre-registered OAuth client ID + secret (and fixed callback URI/port) for HTTP MCP servers that do not support DCR, or
  • Codex should emit a precise error that this server requires static/pre-registered OAuth client credentials and Codex does not yet support them.

Workaround: PAT bearer auth may be possible, but Snowflake recommends OAuth over hardcoded tokens, and PAT is weaker operationally for this use case.

Links / references:

devansh-jain-18 · 12 days ago

One more data point from the Snowflake Managed MCP case: this DCR-vs-pre-registered-client mismatch is not unique to Codex. The same Snowflake endpoint also causes DCR-oriented MCP onboarding to fail in other agent CLIs.

In my local check, OpenCode opencode mcp add / OAuth debug initially failed with Incompatible auth server: does not support dynamic client registration against the same Snowflake Managed MCP server. OpenCode became usable only after manually configuring a pre-registered OAuth client (clientId, clientSecret, redirectUri) in config and then running its MCP auth flow.

There are also community reports around Claude Code + Snowflake Managed MCP hitting the same class of issue, though recent Claude Code docs expose pre-registered OAuth client options.

So I think the compatibility bar here should be: Codex should not assume DCR is available for remote OAuth MCP servers. It should provide a first-class pre-registered OAuth client path, or at minimum fail with a very explicit message saying the server requires pre-registration and Codex does not support that flow yet.

Links / references: