Skip to content

Refuse GET /mcp instead of answering with a closed stream - #343

Merged
ccomb merged 2 commits into
mainfrom
fix/mcp-get-405
Aug 22, 2026
Merged

Refuse GET /mcp instead of answering with a closed stream#343
ccomb merged 2 commits into
mainfrom
fix/mcp-get-405

Conversation

@ccomb

@ccomb ccomb commented Aug 22, 2026

Copy link
Copy Markdown
Owner

What happens today

GET /mcp is the stream a server uses to speak to a client unprompted. VoLCA never speaks first, so the endpoint answered with an SSE response whose body was empty and already closed.

A client reads a closed stream as a dropped connection and reconnects. The server closes again. The two loop for as long as both are up.

Measured on a live instance holding Agribalyse 3.2, BAFU 2026 v1 and two EF 3.1 method sets, with an MCP client attached: 71 644 GETs in 21 hours, one about every second. The requests themselves are trivial, but each one wakes a process whose live heap is around 6 GB, and the idle collector then runs a full collection. The engine sat at 250 % CPU on a four-core machine, permanently, having done no work at all.

The change

Drop the GET arm. Every other method already fell through to a 405, which is what the protocol asks of a server that offers no server-initiated stream, and a conformant client stops asking after it.

DELETE was already answered that way and stays so.

Why not keep the stream open instead

Holding it open with a periodic keep-alive would also stop the loop, but it means maintaining a stream that never carries anything. 405 states the same fact and costs nothing.

One thing to settle before merge

The GET arm arrived in e087b55, a commit that added five things at once for compatibility with the OpenAI Responses API: the protocol version, the session header, SSE on POST, the 405 for other methods, and this stream. Which of the five that client actually needed was never recorded, and I have not tested against it.

The spec obliges a client to accept a 405 at this endpoint, and a stream returned already closed cannot have carried anything, so the likely answer is that the connector needed the version and the session header. But it is a guess. If someone can point an OpenAI MCP connector at an engine built from this branch, that closes the question.

The same question decides the next release number: docs/release.md reads a removal a working client relies on as minor. The CHANGELOG entry states the judgment that no working client relied on the old answer, since the one behaviour ever observed was the reconnect loop. If the OpenAI check contradicts that, the entry and the number both need revisiting.

ccomb added 2 commits August 22, 2026 19:44
A GET opens the stream a server uses to speak to a client unprompted. The
engine never speaks first, so it answered with an empty stream that closed
at once, which a client reads as a dropped connection and reconnects. The
pair then loops for as long as both are up: one instance took 71 644 GETs
in 21 hours, and since each wake ran a garbage collection over several
gigabytes of loaded data, it burned two and a half cores while idle.

405 is what the protocol asks of a server that offers no such stream, and
the endpoint already answered every other method that way.
A 405 has to carry an Allow header naming the methods the resource does
take (RFC 9110). The arm answered without one, which mattered little while
it only ever saw a stray method, and matters now that every GET lands
there: a GET is the request an off-the-shelf client probes with, and the
header is what tells it to try POST instead.

The CHANGELOG entry also states how the change reads for the next release
number, since the release step decides that by what a release removes.
@ccomb ccomb added the agent-reviewed pr-cycle ran label Aug 22, 2026
@ccomb
ccomb merged commit 81ae2af into main Aug 22, 2026
17 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-reviewed pr-cycle ran

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant