Mixing up Streaming HTTP protocol versions results in connection failures

Open 💬 7 comments Opened Oct 24, 2025 by gitcnd
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of Codex is running?

codex-cli 0.47.0

What subscription do you have?

Plus

Which model were you using?

n/a

What platform is your computer?

Ubuntu WSL1

What issue are you seeing?

codex is not authenticating with some non-stdio MCP servers.

Take the rest with a grain of salt (Claude analysis of the server logs, and your github code):-

The Codex client is sending 2025-06-18 but appears to be expecting 2024-11-05 behavior (immediate close after endpoint event).

Based on the documentation from Context7 about the official rmcp SDK, and examining the Codex code:

🎯 Root Cause Analysis: Client Bug Confirmed

What I Found:

  1. The Official rmcp SDK Documentation shows that for Streamable HTTP transport, the server should:
  • Send SSE headers
  • Send the endpoint event
  • Then continue processing the initialize request synchronously and send the response via SSE
  1. Your Server is Correct: The logs show you're doing exactly that:

``
18:58:39 Streaming HTTP response... (SSE headers + endpoint event)
18:58:39 Processing initial request for session...
18:58:39 JSONRPC Request... method=initialize
18:58:39 Session FIN detected: SSL client closed connection gracefully
``

  1. The Codex Client Bug: Looking at the client code structure:
  • auth_status.rs line 21 uses "2024-11-05" for OAuth discovery
  • But the logs show the client sent "protocolVersion":"2025-06-18" in the initialize request
  • This is the problem! The client is mixing protocol versions and may be expecting immediate JSON response instead of SSE-based response

The Protocol Version Mismatch:

From the MCP spec documentation you showed me earlier and the Context7 docs:

  • 2024-11-05: Original spec where initialize response could be synchronous
  • 2025-06-18: New spec where Streamable HTTP uses SSE for all messages including initialize response

The Codex client is sending 2025-06-18 but appears to be expecting 2024-11-05 behavior (immediate close after endpoint event).

✅ Conclusion: This is 100% a Client Bug

Your server implementation is completely correct. The Codex client (codex-mcp-client v0.47.0) has a bug where it:

  1. Sends protocol version 2025-06-18
  2. Receives the SSE endpoint event
  3. Immediately closes the connection without waiting for the initialize response via SSE
  4. This causes the "connection closed: initialize response" error

Recommended Action:

Report this as a bug to the Codex team at [https://github.com/openai/codex/issues](<https://github.com/openai/codex/issues>) with:

  • Your server logs
  • The fact that your server works perfectly with Cursor IDE
  • Reference to the MCP 2025-06-18 spec requirement that responses come via SSE

Your OAuth implementation and server are working perfectly.

Logs:-

Discovery:-

18:58:39 Connect from ('127.0.0.1', 63924) <br>18:58:39 Request from 127.0.0.1:63924 < {'Method': 'GET', 'Path': '/.well-known/oauth-authorization-server/sse', 'Headers': {'mcp-protocol-version': '2024-11-05', 'accept': '/', 'host': '[127-0-0-1.local.aurafriday.com:31173](<http://127-0-0-1.local.aurafriday.com:31173>)'}, 'Body length': 0} Body: <br>18:58:39 RESponse to 127.0.0.1:63924 > 1174 bytes\\nb'HTTP/1.1 200 OK\\r\\nContent-Type: application/json\\r\\nCache-Control: public, max-age=3600\\r\\nContent-Length: 1045\\r\\nConnection: close\\r\\n\\r\\n{\\n "issuer": "[https://127-0-0-1.local.aurafriday.com:31173](<https://127-0-0-1.local.aurafriday.com:31173>)",\\n "authorization_endpoint": "[https://127-0-0-1.local.aurafriday.com:31173/oauth2/authorize](<https://127-0-0-1.local.aurafriday.com:31173/oauth2/authorize>)",\\n "token_endpoint": "[https://127-0-0-1.local.aurafriday.com:31173/oauth2/token](<https://127-0-0-1.local.aurafriday.com:31173/oauth2/token>)",\\n "registration_endpoint": "[https://127-0-0-1.local.aurafriday.com:31173/oauth2/register](<https://127-0-0-1.local.aurafriday.com:31173/oauth2/register>)",\\n "introspection_endpoint": "[https://127-0-0-1.local.aurafriday.com:31173/oauth2/introspect](<https://127-0-0-1.local.aurafriday.com:31173/oauth2/introspect>)",\\n "revocation_endpoint": "[https://127-0-0-1.local.aurafriday.com:31173/oauth2/revoke](<https://127-0-0-1.local.aurafriday.com:31173/oauth2/revoke>)",\\n "grant_types_supported": \[\\n "authorization_code",\\n "refresh_token"\\n \],\\n "response_types_supported": \[\\n "code"\\n \],\\n "response_modes_supported": \[\\n "query",\\n "form_post"\\n \],\\n "code_challenge_methods_supported": \[\\n "S256"\\n \],\\n "token_endpoint_auth_methods_supported": \[\\n "client_secret_basic",\\n "client_secret_post",\\n "none"\\n \],\\n "scopes_supported": \[\\n "offline_access"\\n \],\\n "claims_parameter_supported": false,\\n "request_parameter_supported": false,\\n "request_uri_parameter_supported": false\\n}'<br>18:58:39 Session Initiated graceful socket shutdown (FIN sent)<br>18:58:39 Session Client socket closed<br>18:58:39 Connect from ('127.0.0.1', 63925)

Auth:-

18:58:39 Request from 127.0.0.1:63925 < {'Method': 'POST', 'Path': '/sse', 'Headers': {'accept': 'text/event-stream, application/json', 'authorization': 'Bearer ', 'content-type': 'application/json', 'host': '[127-0-0-1.local.aurafriday.com:31173](<http://127-0-0-1.local.aurafriday.com:31173>)', 'content-length': '198'}, 'Body length': 198} Body: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{"elicitation":{}},"clientInfo":{"name":"codex-mcp-client","title":"Codex","version":"0.47.0"}}} 18:58:39 Auth Attempting Bearer OAuth for OAuth client: Codex from 127.0.0.1:63925 18:58:39 Auth Successful Bearer OAuth authentication for OAuth client: Codex from 127.0.0.1:63925 18:58:39 Streaming HTTP Parsed initial request: {'jsonrpc': '2.0', 'id': 0, 'method': 'initialize', 'params': {'protocolVersion': '2025-06-18', 'capabilities': {'elicitation': {}}, 'clientInfo': {'name': 'codex-mcp-client', 'title': 'Codex', 'version': '0.47.0'}}} 18:58:39 New streaming HTTP connection from 127.0.0.1:63925, session_id=f9c5483294394a9ca75e32ea1870f67d 18:58:39 Streaming HTTP response to 127.0.0.1:63925 > b'HTTP/1.1 200 OK\\r\\nContent-Type: text/event-stream; charset=utf-8\\r\\nConnection: keep-alive\\r\\nCache-Control: no-store\\r\\nX-Accel-Buffering: no\\r\\nAccess-Control-Allow-Origin: null\\r\\nAccess-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS\\r\\nAccess-Control-Allow-Headers: \*\\r\\nAccess-Control-Allow-Credentials: true\\r\\nAccess-Control-Max-Age: 86400\\r\\n\\r\\nevent: endpoint\\r\\ndata: /messages/?session_id=f9c5483294394a9ca75e32ea1870f67d\\r\\n\\r\\n' 18:58:39 Streaming HTTP Processing initial request for session f9c5483294394a9ca75e32ea1870f67d 18:58:39 JSONRPC Request session=f9c5483294394a9ca75e32ea1870f67d, method=initialize, id=0 18:58:39 Session FIN detected: SSL client 127.0.0.1:63925 closed connection gracefully 18:58:39 Session Disconnect detected for client 127.0.0.1:63925 before sending

What steps can reproduce the bug?

Maybe work out which protocol(s) you want to support, and check if the flow is correct?<br>Claude 4.5 (Above) is not always right.

What is the expected behavior?

OAuth should work.

Additional information

See also: 5588 <br> [Attempts OAuth even when the MCP SSE server does not support oauth](<https://github.com/openai/codex/issues/5588#top>)

View original on GitHub ↗

7 Comments

github-actions[bot] contributor · 8 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #5208
  • #5045
  • #5254
  • #5588

Powered by Codex Action

gpeal contributor · 8 months ago

Is there a public MCP server I can test against? I don't totally trust the AI report attached but can take a look if there is a public repro.

eirenik0 · 6 months ago

@gpeal I see that it's a little bit different but might be related. You could test it with this server, for example, https://docs.z.ai/devpack/mcp/reader-mcp-server.

I see the error:

MCP client for `web-reader` failed to start: MCP startup failed: handshaking with MCP
  server failed: Send message error Transport
  [rmcp::transport::worker::WorkerTransport<rmcp::transport::streamable_http_client::Stre
  amableHttpClientWorker<reqwest::async_impl::client::Client>>] error: Transport channel
  closed, when send initialized notification

And feature experimental_use_rmcp_client = true is doesn't help.

guyinwonder168 · 4 months ago

Confirming this with a fresh repro on 2026-03-15 against the Z.AI public MCP endpoints.

Setup:

  • web-search-prime: https://api.z.ai/api/mcp/web_search_prime/mcp
  • web-reader: https://api.z.ai/api/mcp/web_reader/mcp
  • zread: https://api.z.ai/api/mcp/zread/mcp
  • Codex config uses the documented streamable HTTP fields:
  • bearer_token_env_var = "ZAI_API_KEY"
  • http_headers = { Accept = "application/json, text/event-stream" }

Observed in Codex startup:

MCP client for `web-search-prime` failed to start: MCP startup failed: handshaking with MCP server failed: Send message error Transport [...] error: Transport channel closed, when send initialized notification
MCP client for `web-reader` failed to start: MCP startup failed: handshaking with MCP server failed: Send message error Transport [...] error: Transport channel closed, when send initialized notification
MCP client for `zread` failed to start: MCP startup failed: handshaking with MCP server failed: Send message error Transport [...] error: Transport channel closed, when send initialized notification

What I verified manually outside Codex:

  1. initialize succeeds on all three endpoints when I send:
  • Authorization: Bearer $ZAI_API_KEY
  • Accept: application/json, text/event-stream
  • JSON-RPC initialize
  1. The server returns 200, content-type: text/event-stream, and an mcp-session-id.
  2. The initialize result advertises protocolVersion: "2024-11-05" even when the client asks for 2025-03-26.
  3. Sending notifications/initialized manually with the returned Mcp-Session-Id succeeds with 200.
  4. Sending tools/list manually after that also succeeds.

So this does not look like:

  • bad auth
  • missing Accept: application/json, text/event-stream
  • dead/unreachable endpoint
  • wrong Codex config keys

It looks like a compatibility problem in the Codex streamable HTTP MCP client during/after the initialized step, likely related to protocol negotiation or how this older server implementation is handled.

If useful, I can provide exact curl repro commands, but the short version is: the same session works manually and fails only in Codex with Transport channel closed, when send initialized notification.

guyinwonder168 · 4 months ago

This looks closely related to #5208.

Why I think so:

  • same failure point: Transport channel closed, when send initialized notification
  • same transport family: streamable HTTP MCP
  • same pattern: works in manual/client alternatives, fails specifically in Codex

The difference is that this is still reproducible now with public Z.AI MCP servers, despite #5208 having been closed as fixed via the earlier rust-sdk work.

So this may be either:

  • a regression of the old bug class, or
  • a remaining compatibility gap for servers that respond successfully to initialize but advertise protocolVersion: "2024-11-05" while Codex is initiating with 2025-03-26.

If helpful, I can post exact curl repro commands for the full successful manual sequence (initialize -> notifications/initialized -> tools/list).

guyinwonder168 · 4 months ago

Here is a minimal manual repro that succeeds outside Codex for web_search_prime. The same pattern also works for web_reader and zread by swapping the URL.

export URL='https://api.z.ai/api/mcp/web_search_prime/mcp'
export AUTH="Authorization: Bearer $ZAI_API_KEY"

# 1) initialize
curl -sS -D /tmp/mcp.init.headers -o /tmp/mcp.init.body \
  -X POST "$URL" \
  -H "$AUTH" \
  -H 'Accept: application/json, text/event-stream' \
  -H 'Content-Type: application/json' \
  --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl-repro","version":"0.0.1"}}}'

SESSION_ID=$(awk 'BEGIN{IGNORECASE=1} /^mcp-session-id:/ {print $2}' /tmp/mcp.init.headers | tr -d '\r')

echo '--- initialize headers ---'
sed -n '1,20p' /tmp/mcp.init.headers
echo '--- initialize body ---'
sed -n '1,20p' /tmp/mcp.init.body

# 2) notifications/initialized
curl -sS -D /tmp/mcp.notif.headers -o /tmp/mcp.notif.body \
  -X POST "$URL" \
  -H "$AUTH" \
  -H 'Accept: application/json, text/event-stream' \
  -H 'Content-Type: application/json' \
  -H "Mcp-Session-Id: $SESSION_ID" \
  --data '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}'

echo '--- initialized headers ---'
sed -n '1,20p' /tmp/mcp.notif.headers
echo '--- initialized body ---'
sed -n '1,20p' /tmp/mcp.notif.body

# 3) tools/list
curl -sS -D /tmp/mcp.tools.headers -o /tmp/mcp.tools.body \
  -X POST "$URL" \
  -H "$AUTH" \
  -H 'Accept: application/json, text/event-stream' \
  -H 'Content-Type: application/json' \
  -H "Mcp-Session-Id: $SESSION_ID" \
  --data '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

echo '--- tools/list headers ---'
sed -n '1,20p' /tmp/mcp.tools.headers
echo '--- tools/list body ---'
sed -n '1,40p' /tmp/mcp.tools.body

Expected successful manual behavior:

  • step 1 returns 200, content-type: text/event-stream, and an mcp-session-id
  • step 2 returns 200
  • step 3 returns 200 plus the tool list

In my environment, this full manual sequence succeeds, while Codex startup still fails earlier with:

Transport channel closed, when send initialized notification
caibirdme · 3 months ago

I find a workaround for this

use mcp-proxy, you should install it first

here's the example config.toml

[mcp_servers.zread]
command = "mcp-proxy"
args = ["--headers", "Authorization", "Bearer YOURS", "--transport", "streamablehttp", "https://open.bigmodel.cn/api/mcp/zread/mcp"]

[mcp_servers.web-reader]
command = "mcp-proxy"
args = ["--headers", "Authorization", "Bearer YOURS", "--transport", "streamablehttp", "https://open.bigmodel.cn/api/mcp/web_reader/mcp"]

[mcp_servers.web-search-prime]
command = "mcp-proxy"
args = ["--headers", "Authorization", "Bearer YOURS", "--transport", "streamablehttp", "https://open.bigmodel.cn/api/mcp/web_search_prime/mcp"]