Skip to content

Keep an MCP session open while a tool runs - #99

Merged
russwyte merged 9 commits into
mainfrom
mcp/session-stays-open
Oct 25, 2026
Merged

russwyte merged 9 commits into
mainfrom
mcp/session-stays-open

Conversation

@russwyte

Copy link
Copy Markdown
Contributor

Closes #98.

An MCP session keeps accepting requests and delivering notifications while a tool is still running. The tool calls Session.publish and does not name the pipe or HTTP. Answers are matched by JSON-RPC id, so a later call can finish first.

What changed

  • stdio reads the next line while a tool runs. initialize is still answered before the next read. One write permit orders a notification ahead of that tool's result.
  • HTTP tracks each request. When Accept contains text/event-stream, the POST body is that call's events, then its result. Otherwise the body stays one JSON response and publish sends nothing.
  • notifications/cancelled interrupts that request's fiber. No result is written for a call interrupted before the result. An unknown id or a malformed body is ignored.
  • End of stdin joins in-flight tools and writes their results. Interrupting stdio, closing the POST stream, or DELETE interrupts them.
  • McpClient.open returns the id, the result, the notifications recorded before that result, and cancel. request is open plus the session timeout.

Breaks

  • stdio responses can arrive before an earlier tool returns.
  • A tool POST is text/event-stream when Accept asks for it. McpClient.http already sends that Accept.
  • McpError.Cancelled is a new case.
  • McpSession.open is new. The ship line is 0.14.0. 0.13.0 is released, and 0.13.1 was only the open patch, so these signatures do not fit it under early-semver.
  • Session.publish outside a request succeeds and sends nothing.
  • Stateless HTTP has one request-id space for the process. Two clients that both use id 1 can cancel each other.

GET stays the session's resource stream. Tool notifications are not delivered there.

Tests

mcp/testFull (85), example/testFull, and docs/testFull (89), JVM. The cross-transport spec runs the issue's list on stdio and HTTP against the real server: overlap, publish-then-result, cancel one call and leave the other running.

Cancelled is a codec and a message. The session hub tracks the fiber for a request id, and the engine turns notifications/cancelled into that interrupt. Publishing a notification is ambient on the request, so a tool does not take a transport.
A closed stdin finishes the tool already read and still writes its result. A JSON POST is tracked the same way as an event stream, so cancelling it answers 202 and the next call still runs.
The waiter is McpError.Cancelled even when the POST then ends. OpenCall.notifications is what the reader recorded before the result. The session stream is ZStream.fromHub, which does not retain a value published before subscribe, so the test no longer polls a fiber to guess that it subscribed.

The ship line is 0.14.0. McpSession.open and McpError.Cancelled do not fit a patch on 0.13.0.
The Agents page runs a pipe session where a later call finishes first, a tool publishes before its result, and cancel stops only that call. The HTTP sample is the POST event stream. The era table no longer says GET is 405.
scalafmtCheckAll rejected the new specs. The 0.14.0 ship line changed the workflow cache epoch.
A for that is the last expression of the block picks up an end marker. Binding it to a val closes the block on the name.
@russwyte
russwyte merged commit 74ae5fa into main Oct 25, 2026
12 checks passed
@russwyte
russwyte deleted the mcp/session-stays-open branch October 25, 2026 21:23
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.

A tool call must not hold the session closed until it returns

1 participant