anomalyco / anomalyco/opencode
MCP remote Streamable-HTTP client never sends Mcp-Session-Id header — every tools/call fails with 400 "No valid session ID provided"
@rekram1-node is already working on this.
Since Jul 25, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- Avg merge
- 7h 2m
- Merged PRs (30d)
- 384
Description
Bug Description
opencode's remote MCP Streamable-HTTP client never sends the Mcp-Session-Id request header, even on the very first tools/call after a successful initialize. As a result, every tools/call against a spec-compliant Streamable HTTP server fails with HTTP 400 Bad Request: No valid session ID provided. — tools from that server are never usable, not even once.
This is distinct from #32809 (recovery after session expiry): that issue assumes the first calls succeed and only later calls fail after idle timeout. Here, the session id is missing from the initial call too, so there is no "working" window at all.
Steps to Reproduce
- Configure any remote MCP server that implements the Streamable HTTP transport per the MCP spec, which requires the
Mcp-Session-Idheader on all post-initializerequests. Example config (opencode.jsonc):
"acme": {
"type": "remote",
"url": "https://mcp.example.com/mcp",
"enabled": true,
"headers": {
"Authorization": "Bearer {file:<redacted-token-path>}"
},
"timeout": 120000
}
-
Start opencode and ask the agent to call any tool exposed by that server (e.g.
get_capabilities). -
The call fails with:
Streamable HTTP error: Error POSTing to endpoint: {"jsonrpc":"2.0","error":{"code":-32000,"message":"Bad Request: No valid session ID provided."},"id":null}
Expected Behavior
Per the MCP Streamable HTTP spec, the client must:
- Call
initialize. - Capture the
Mcp-Session-Idresponse header returned by the server. - Include
Mcp-Session-Id: <id>on every subsequent request (notifications/initialized,tools/list,tools/call, etc.).
Actual Behavior
The initialize round-trip succeeds (server returns 200 with mcp-session-id: <uuid> in the response headers), but opencode drops that header and never sends it on subsequent requests. The server (correctly) rejects every post-init request with 400 "No valid session ID provided.".
Verification (curl)
Direct curl against the same server with the same token proves the server, token, and config are correct — the only variable is the Mcp-Session-Id header:
TOKEN=$(cat <redacted-token-path>)
# 1) initialize → 200, server returns mcp-session-id
curl -sS -i -X POST https://mcp.example.com/mcp \
-H "Authorization: Bearer $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":"diag","version":"1.0"}}}'
# HTTP/2 200
# mcp-session-id: 3ffe6b1c-a233-4983-82d9-ab329a18018e
# 2a) tools/call WITHOUT Mcp-Session-Id → 400 (this is what opencode does)
curl -sS -X POST https://mcp.example.com/mcp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_capabilities","arguments":{}}}'
# → {"jsonrpc":"2.0","error":{"code":-32000,"message":"Bad Request: No valid session ID provided."},"id":null} (HTTP 400)
# 2b) tools/call WITH Mcp-Session-Id → 200 (correct behavior)
curl -sS -X POST https://mcp.example.com/mcp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: 3ffe6b1c-a233-4983-82d9-ab329a18018e" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_capabilities","arguments":{}}}'
# → {"result":{"content":[{"type":"text","text":"{...}"}]},"jsonrpc":"2.0","id":3} (HTTP 200)
The 2a/2b delta is exactly one header. opencode is doing 2a.
Affected MCP Servers
Any MCP server that correctly enforces the Streamable HTTP session contract. Confirmed against:
Acme Platform MCPv1.2.0 (custom,https://mcp.example.com/mcp)- Same family as the Atlassian/Jira MCP server in #32809 (uvicorn-based, returns 400 for missing session id)
This is broader than #32809 because it affects the first call, not just post-expiry recovery.
Suggested Fix
In the remote Streamable HTTP transport client (packages/opencode/src/mcp/...):
- After
initializesucceeds, read themcp-session-idresponse header and persist it on the transport instance. - Add
Mcp-Session-Id: <id>to the headers of every subsequent POST (notifications/initialized,tools/list,tools/call,tools/callpolling, etc.). - On a 400/404 indicating session invalidation (per #32809), re-run
initialize, capture a new id, and replay the failed call once.
Environment
- opencode version: 1.18.4
- OS: macOS (darwin)
- Config type:
"type": "remote" - MCP server:
Acme Platform MCPv1.2.0 (Streamable HTTP,mcp-session-idenforced)
Workaround
None from the opencode client. I am currently working around it by calling the MCP server directly via curl with a manually-managed Mcp-Session-Id, which is not viable for normal agent-driven usage.
Related: #32809, #25137, #25650.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.