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"

Open
#38,891 1 comment 0 reactions 1 assignee View on GitHub

@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

  1. Configure any remote MCP server that implements the Streamable HTTP transport per the MCP spec, which requires the Mcp-Session-Id header on all post-initialize requests. Example config (opencode.jsonc):
"acme": {
  "type": "remote",
  "url": "https://mcp.example.com/mcp",
  "enabled": true,
  "headers": {
    "Authorization": "Bearer {file:<redacted-token-path>}"
  },
  "timeout": 120000
}
  1. Start opencode and ask the agent to call any tool exposed by that server (e.g. get_capabilities).

  2. 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:

  1. Call initialize.
  2. Capture the Mcp-Session-Id response header returned by the server.
  3. 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 MCP v1.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/...):

  1. After initialize succeeds, read the mcp-session-id response header and persist it on the transport instance.
  2. Add Mcp-Session-Id: <id> to the headers of every subsequent POST (notifications/initialized, tools/list, tools/call, tools/call polling, etc.).
  3. 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 MCP v1.2.0 (Streamable HTTP, mcp-session-id enforced)

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.