Summary
The MCP identity proxy (mcp_proxy.py) emits the Mcp-Session-Id response header twice. Its _send_response_headers helper iterates a fixed header list that contains both the Mcp-Session-Id and mcp-session-id spellings. Python's response-header lookup is case-insensitive, so both lookups find the same session ID and the proxy sends it twice.
Clients that combine repeated response headers into a single comma-joined value receive abc123, abc123, echo that combined value as their session ID on the next request, and the MCP server answers 404 Session not found during the initialize to notifications/initialized handshake, so no tools attach. Verified affected clients: Claude Code 2.1.216 and 2.1.217, where native MCP tools silently fail to attach, and the Python MCP SDK streamable HTTP client, which fails with McpError: Session terminated. Claude Code 2.1.202 tolerated the duplicate, which hid the defect.
Reproduction
-
Start the agentchattr server and a proxy-routed wrapper, such as Codex with the default configuration, or instantiate McpIdentityProxy directly in front of the server's /mcp endpoint.
-
Send an initialize request to the proxy and inspect the response headers: Mcp-Session-Id appears twice with the same value. Set AGENTCHATTR_PROXY_PORT to the proxy port printed at wrapper startup (the MCP proxy: port NNNNN line) before running:
AGENTCHATTR_PROXY_PORT=12345
curl -i -X POST "http://127.0.0.1:${AGENTCHATTR_PROXY_PORT}/mcp" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"repro","version":"0.0.1"}}}'
-
Connect with an affected client (the Python MCP SDK streamable HTTP client, or Claude Code 2.1.216 or 2.1.217 configured through the proxy): initialize succeeds, the next request fails with 404 Session not found, and no tools attach.
Expected behavior
The proxy relays exactly one Mcp-Session-Id header, the handshake completes, and tools/list and tool calls work through the proxy for the affected clients.
Secondary defect
do_DELETE collapses every upstream HTTP error into a generic 502, which hides the real session-termination status from clients. It should relay the upstream status, body, and session header, and reserve 502 for network-level failures.
Validation
PR #79 fixes both defects and adds five regression tests: a single case-insensitive session header on initialize, session-ID preservation on follow-up POSTs, DELETE success and HTTP-error relay, and a full SDK-style initialize, notifications/initialized, tools/list lifecycle through the proxy. Verified live: Claude Code 2.1.217 attaches native tools and completes chat_read and chat_send through the patched proxy.
Related work
This is distinct from #73, which was about stale token recovery after sleep and was resolved server-side by #75.
Summary
The MCP identity proxy (
mcp_proxy.py) emits theMcp-Session-Idresponse header twice. Its_send_response_headershelper iterates a fixed header list that contains both theMcp-Session-Idandmcp-session-idspellings. Python's response-header lookup is case-insensitive, so both lookups find the same session ID and the proxy sends it twice.Clients that combine repeated response headers into a single comma-joined value receive
abc123, abc123, echo that combined value as their session ID on the next request, and the MCP server answers404 Session not foundduring theinitializetonotifications/initializedhandshake, so no tools attach. Verified affected clients: Claude Code 2.1.216 and 2.1.217, where native MCP tools silently fail to attach, and the Python MCP SDK streamable HTTP client, which fails withMcpError: Session terminated. Claude Code 2.1.202 tolerated the duplicate, which hid the defect.Reproduction
Start the agentchattr server and a proxy-routed wrapper, such as Codex with the default configuration, or instantiate
McpIdentityProxydirectly in front of the server's/mcpendpoint.Send an initialize request to the proxy and inspect the response headers:
Mcp-Session-Idappears twice with the same value. SetAGENTCHATTR_PROXY_PORTto the proxy port printed at wrapper startup (theMCP proxy: port NNNNNline) before running:Connect with an affected client (the Python MCP SDK streamable HTTP client, or Claude Code 2.1.216 or 2.1.217 configured through the proxy):
initializesucceeds, the next request fails with404 Session not found, and no tools attach.Expected behavior
The proxy relays exactly one
Mcp-Session-Idheader, the handshake completes, andtools/listand tool calls work through the proxy for the affected clients.Secondary defect
do_DELETEcollapses every upstream HTTP error into a generic 502, which hides the real session-termination status from clients. It should relay the upstream status, body, and session header, and reserve 502 for network-level failures.Validation
PR #79 fixes both defects and adds five regression tests: a single case-insensitive session header on initialize, session-ID preservation on follow-up POSTs, DELETE success and HTTP-error relay, and a full SDK-style
initialize,notifications/initialized,tools/listlifecycle through the proxy. Verified live: Claude Code 2.1.217 attaches native tools and completeschat_readandchat_sendthrough the patched proxy.Related work
This is distinct from #73, which was about stale token recovery after sleep and was resolved server-side by #75.