Skip to content

Talk to the MCP over HTTP, because stdio cannot work where this runs - #219

Merged
adamjohnwright merged 1 commit into
mainfrom
feat/mcp-http-transport
Sep 15, 2026
Merged

adamjohnwright merged 1 commit into
mainfrom
feat/mcp-http-transport

Conversation

@adamjohnwright

Copy link
Copy Markdown
Contributor

The live destination spawns node and speaks over a pipe. That works on a developer's machine and cannot work in the deployed container:

$ docker exec biochat_beta_guest sh -c 'command -v node || echo NO NODE'
NO NODE

$ docker inspect biochat_beta_guest --format '{{range .HostConfig.Binds}}...'
  /home/awright/git/reactome_chatbot/config.yml:/app/config.yml
  /home/awright/git/reactome_chatbot/embeddings:/app/embeddings

The image is Python, has no node, and doesn't mount reactome-mcp. REACTOME_MCP_SERVER can never be satisfied there.

The feature worked on every machine it was tested on and none that it ships to — the same shape as the streaming bug fixed in #218, and found the same way: by asking where it actually runs rather than where it was written.

Two transports

variable transport where
REACTOME_MCP_URL Streamable HTTP anywhere, including the container
REACTOME_MCP_SERVER stdio, spawning node a developer's machine only

REACTOME_MCP_URL wins when both are set. stdio stays for local development, where the repo and node both exist.

create_mcp_tools already took a ToolCaller Protocol, so the HTTP client drops in beside the stdio one with no change to the tools or the graph.

Two protocol details, both pinned by tests

Easy to get wrong, and neither fails in a way that points at the cause:

  • The session id arrives in the mcp-session-id response header of initialize and must be resent on every later request. Omit it and the server answers 400.
  • Replies come back SSE-framed (event: message / data: {...}) rather than as bare JSON, even for a single reply to a POST. Parsing the body as JSON gets a decode error on text that is perfectly valid. The last data: line wins, since a stream may carry progress notifications first.

Verified against a real server

End to end, and through the compiled graph with a callback handler:

tools over HTTP : 5
species         : **Total:** 96
answer          : Reactome covers a total of 96 species...
streamed        : 46 tokens

8 tests against httpx.MockTransport; full suite passes; ruff, ruff format, mypy clean across 103 files.

What this unblocks

Beta can now have the live destination. Run reactome-mcp as a sibling container and set REACTOME_MCP_URL — no node in the chatbot image, nothing mounted, no change to how the chatbot is built.

🤖 Generated with Claude Code

The live destination spawns `node` and speaks over a pipe. That works on a
developer's machine and cannot work in the deployed container:

    $ docker exec biochat_beta_guest sh -c 'command -v node || echo NO NODE'
    NO NODE
    $ docker inspect ... --format '{{range .HostConfig.Binds}}...'
      /home/awright/git/reactome_chatbot/config.yml:/app/config.yml
      /home/awright/git/reactome_chatbot/embeddings:/app/embeddings

The image is Python, it has no node, and it does not mount reactome-mcp.
`REACTOME_MCP_SERVER` can never be satisfied there. The feature worked on every
machine it was tested on and none that it ships to -- the same shape as the
streaming bug fixed yesterday, found the same way: by asking where it actually
runs rather than where it was written.

reactome-mcp gained Streamable HTTP, which is what a sibling container or a
hosted instance offers. `REACTOME_MCP_URL` uses it, and wins over
`REACTOME_MCP_SERVER` when both are set. stdio stays for local development,
where the repo and node both exist.

Two protocol details are pinned by tests, because both are easy to get wrong
and neither fails in a way that points at the cause:

  - the session id arrives in the `mcp-session-id` **response header** of
    initialize and must be resent on every later request; omit it and the
    server answers 400.
  - replies come back **SSE-framed** (`event: message` / `data: {...}`) rather
    than as bare JSON, even for a single reply to a POST. Parsing the body as
    JSON gets a decode error on text that is perfectly valid. The last data
    line wins, since a stream may carry progress notifications first.

`create_mcp_tools` already took a `ToolCaller` Protocol, so the HTTP client
drops in beside the stdio one with no change to the tools or the graph.

Verified against a real server, end to end and through the graph:

    tools over HTTP : 5
    species         : **Total:** 96
    answer          : Reactome covers a total of 96 species...
    streamed        : 46 tokens

Eight tests against a mock transport, and the full suite passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adamjohnwright
adamjohnwright merged commit 79b64e6 into main Sep 15, 2026
10 checks passed
@adamjohnwright
adamjohnwright deleted the feat/mcp-http-transport branch September 15, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant