sam@latino:~$

sam@latino:~/projects/longhaul$ cat README.md

longhaul

Early-adopter MCP 2026-07-28 release-candidate server in Rust — stateless core, the Tasks extension, and statelessness proven by round-robining one client across two server instances.

lang Rust status active tests  67 repo  github.com/slatino-dev/longhaul

  • [MCP]
  • [protocol implementation]
  • [stateless servers]
  • [axum]
  • [conformance testing]

The MCP 2026-07-28 release candidate deletes the initialize handshake and assumes your server keeps no session — longhaul is a working answer to “fine: how?”, written while the spec is still moving.

This is an implementation log more than a product page. I built a server against the release candidate as it landed, partly to have one, mostly because reading a spec and implementing a spec are different levels of understanding and the second one is the only one that sticks.

log 01 — the handshake is gone

The RC removes initialize. There is no session to set up because there is no session: server/discover answers capability questions statelessly, and everything identity-shaped now rides on each request — clientInfo arrives per-request in _meta, and three headers carry the rest.

one request carries everything
POST /
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: indexer

{ "jsonrpc": "2.0", "method": "tools/call",
  "params": { ..., "_meta": { "clientInfo": { ... } } } }
no handshake, no session — each request is self-describing

The Mcp-Method header is a gift for routing: axum middleware dispatches on it before the body is parsed, so the JSON-RPC layer stays thin. Tool schemas are JSON Schema 2020-12. One error code moved — what was -32002 is now -32602 — which is a small diff in the spec and a grep through every test fixture in practice.

log 02 — long-running work becomes Tasks

The Tasks extension restructures tools/call: instead of blocking on slow work, the call returns a task handle, and the client drives the lifecycle with tasks/get, tasks/update, and tasks/cancel. tasks/list is gone from the RC. A task can pause in InputRequired, ask the client a question, and resume when the answer comes back via tasks/update — a real multi-round-trip, not a callback hack.

The repo’s example server makes this concrete: an indexer whose index_directory task walks a real directory tree with streamed progress, honors cancellation mid-walk, and exercises one full InputRequired round trip. A conformance runner drives the whole protocol surface from the outside.

log 03 — proving statelessness instead of asserting it

Every server claims to be stateless. The signature test makes the claim falsifiable: spin up two longhaul instances that share nothing but a SQLite file, then round-robin one client’s entire task lifecycle across them — create the task on instance A, poll it on B, answer its InputRequired question on A, cancel a sibling on B. If any state hides in process memory, the lifecycle breaks and the test fails.

docs/statelessness.svg
the signature test: one client, two instances, alternating requests — SQLite is the only shared state

Storage sits behind a TaskStore trait — MemoryStore keeps unit tests fast, SqliteStore carries the proof. 67 tests, clippy -D warnings clean.

tests
67 incl. the round-robin proof
task stores
2 memory · SQLite
spec
2026-07-28 release candidate
lint
clippy -D zero warnings

log 04 — what conformance will say

conformance/matrix pending

The conformance runner exists and runs; published results don't, yet. What lands here: per-method conformance for this server against the RC surface, plus task-lifecycle timings across the memory and SQLite stores. Until those runs are written up, this table stays empty rather than approximate.