Codex CLI does not send a User-Agent header on MCP Streamable HTTP requests

Resolved 💬 3 comments Opened Apr 1, 2026 by naji247 Closed Jul 23, 2026
💡 Likely answer: A maintainer (etraut-openai, contributor) responded on this thread — see the highlighted reply below.

What variant of Codex are you using?

CLI

What feature would you like to see?

Codex CLI's MCP client (via RMCP) does not send a User-Agent header on HTTP requests to MCP servers over the Streamable HTTP transport. Most other MCP clients include one, for example, Claude Code sends user-agent: claude-code/2.1.89 (cli).

This matters for MCP server maintainers who rely on standard HTTP headers for analytics, debugging, and client identification. Currently, the only way to identify Codex as the connecting client is through the clientInfo field in the MCP initialize handshake. But for servers running in stateless HTTP mode (where each request creates a new session), clientInfo from the initialization is lost on subsequent requests. The User-Agent header is the standard, transport-level mechanism for client identification that persists across all HTTP requests regardless of session semantics.

Without a User-Agent, Codex's requests are indistinguishable from raw HTTP traffic in server logs, load balancer metrics, and analytics pipelines. This is a gap relative to other MCP clients that do advertise themselves.

The fix is small. Codex already constructs its own reqwest::Client in codex-rs/rmcp-client/src/rmcp_client.rs via build_http_client(). Adding a .user_agent(...) call to the builder using the same name already passed in client_info during MCP initialization ("codex-mcp-client") would produce a header like User-Agent: codex-mcp-client/0.1.0 on every HTTP request. The value would stay in sync with the existing InitializeRequestParams.client_info.name in codex-rs/core/src/mcp_connection_manager.rs if they share a constant.

I have a working implementation on a fork (MCPCat/codex) and would be happy to open a PR if the team is interested. The change touches three files and is roughly a dozen lines total.

Additional information

I'm the founder of MCPCat, an analytics platform for MCP server maintainers. We instrument MCP servers to help developers understand how their tools are being used, by whom, and what value they're providing. We had customer concerns around this because Codex was the only major MCP client that failed to advertise itself properly on stateless HTTP MCP servers, making it invisible in their analytics dashboards. Accurate client identification across all HTTP requests is important for our product and our customers, which is how we discovered this gap.

Our fork with the implementation: https://github.com/MCPCat/codex/tree/feat/mcp-user-agent-header

The change is:

  • Define a shared MCP_CLIENT_NAME constant in codex-rmcp-client
  • Call .user_agent(format!("{}/{}", MCP_CLIENT_NAME, env!("CARGO_PKG_VERSION"))) on the reqwest client builder, placed before user-configured default headers so they can still override
  • Replace the hardcoded "codex-mcp-client" string in mcp_connection_manager.rs with the shared constant

Worth noting that this is not an RMCP (upstream) issue — RMCP intentionally leaves User-Agent to the consumer to set, and reqwest doesn't set one by default. So this is squarely a Codex-level concern.

View original on GitHub ↗

3 Comments

naji247 · 4 months ago

Hey @gpeal @bolinfest @mzeng-openai — tagging you three since you seem to have the most context on the rmcp-client and MCP connection manager code. Would love to hear your thoughts on this and whether you'd be open to a PR. Happy to answer any questions about the use case.

s1korrrr · 4 months ago

I prepared a small upstream-ready branch for this issue:

Repro / current behavior:

  • On current main, the RMCP Streamable HTTP path builds its reqwest::Client through codex-rs/rmcp-client/src/rmcp_client.rs and apply_default_headers().
  • apply_default_headers() only applies configured headers.
  • reqwest does not set a default User-Agent.
  • The MCP initialize.clientInfo.name value was separately hardcoded as "codex-mcp-client", so HTTP requests still lacked a transport-level client identifier.

Approaches I compared:

  • Add .user_agent(...) only in the Streamable HTTP client builder. This is slightly smaller, but easier to miss adjacent RMCP HTTP clients that share the header helper and duplicates override-precedence behavior.
  • Add a default User-Agent inside apply_default_headers(). I chose this because it covers RMCP HTTP clients that share the helper, preserves explicit user-configured User-Agent overrides, and keeps the MCP initialize name aligned through a shared constant.

Fix summary:

  • Defines shared MCP client identity constants in codex-rmcp-client.
  • Adds a default User-Agent: codex-mcp-client/0.0.0 when no explicit User-Agent header is configured.
  • Preserves explicit user-provided User-Agent values.
  • Replaces the hardcoded MCP initialize name with the shared MCP_CLIENT_NAME.
  • Adds focused tests for default insertion and override preservation.

Validation:

  • just fmt
  • just fix -p codex-rmcp-client
  • just fix -p codex-mcp
  • CARGO_NET_GIT_FETCH_WITH_CLI=true cargo test -p codex-rmcp-client default_headers_ --lib -- --nocapture
  • CARGO_NET_GIT_FETCH_WITH_CLI=true cargo check -p codex-mcp
  • git diff --check

Local note: the first Cargo run failed while fetching the unrelated libwebrtc / libyuv git submodule through Cargo's default git transport. I reran validation with CARGO_NET_GIT_FETCH_WITH_CLI=true, which is the remediation Cargo suggested in the error output.

I attempted to open this as a PR from the fork, but GitHub returned s1korrrr does not have the correct permissions to execute CreatePullRequest. Happy to open or refresh the PR from this branch if maintainers enable/invite it.

etraut-openai contributor · 1 month ago

This will be included in the next release.