49b9bb6724
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
serve-one
The kernel layer beneath MCPServer.run() / run_server_from_args. Every
transport entry composes the same three pieces: a lowlevel.Server (the
handler registry), a Connection (per-peer state), and a driver — serve_one
for one request → result dict, or serve_connection for a dispatcher loop.
This is what you write to bring up MCP over a custom transport. Uniquely, the
server file here builds the stdio entry by hand instead of importing
stories._hosting.
Run it
# stdio (default — the client spawns server.py as a subprocess; its __main__
# is the hand-built serve_connection loop)
uv run python -m stories.serve_one.client
What to look at
server.py::handle_one—Connection.from_envelope(...)+serve_one(...)returns the raw result dict for one request. No handshake, no streams; the entry owns wire encoding and exception→error mapping.server.py::main—JSONRPCDispatcher+Connection.for_loop(...)+serve_connection(...): exactly whatServer.run()does internally for stdio.server.py::SingleExchangeContext— the per-requestDispatchContexta custom entry must supply. The SDK ships no public concrete class for this yet.client.py— driveshandle_onedirectly and asserts the raw result-dict shape (structuredContent/content), then proves the loop-mode driver works over the wire.
Caveats
- Deep imports —
serve_one,serve_connection, andConnectionare only reachable atmcp.server.runner/mcp.server.connectiontoday; a shortermcp.server.*re-export is tracked for beta. - Lowlevel-only. The drivers take a
lowlevel.ServerandMCPServerhas no public accessor for its underlying one (_lowlevel_serveris private), so there is noMCPServer-tier variant of this story. Build the lowlevelServerdirectly until that accessor lands. - No public
DispatchContext—SingleExchangeContextis hand-rolled boilerplate; a public helper (or aserve_oneoverload that builds one) is tracked for beta. - Lifespan — the transport entry enters
server.lifespan(server)once and threadslifespan_stateto everyhandle_one()call; never enter it per-request. ServerRunneris kernel-internal; never construct it directly. The free-function drivers are the supported surface.
Spec
Architecture — lifecycle · 2026 versioning — discover
See also
legacy_routing/ (composing serve_one behind classify_inbound_request),
dual_era/ (Connection.protocol_version in handlers).