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
61 lines
2.7 KiB
Markdown
61 lines
2.7 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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 what `Server.run()` does internally for
|
|
stdio.
|
|
- `server.py::SingleExchangeContext` — the per-request `DispatchContext` a
|
|
custom entry must supply. The SDK ships no public concrete class for this
|
|
yet.
|
|
- `client.py` — drives `handle_one` directly 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`, and `Connection` are only
|
|
reachable at `mcp.server.runner` / `mcp.server.connection` today; a shorter
|
|
`mcp.server.*` re-export is tracked for beta.
|
|
- **Lowlevel-only.** The drivers take a `lowlevel.Server` and `MCPServer` has
|
|
no public accessor for its underlying one (`_lowlevel_server` is private), so
|
|
there is no `MCPServer`-tier variant of this story. Build the lowlevel
|
|
`Server` directly until that accessor lands.
|
|
- **No public `DispatchContext`** — `SingleExchangeContext` is hand-rolled
|
|
boilerplate; a public helper (or a `serve_one` overload that builds one) is
|
|
tracked for beta.
|
|
- **Lifespan** — the transport entry enters `server.lifespan(server)` **once**
|
|
and threads `lifespan_state` to every `handle_one()` call; never enter it
|
|
per-request.
|
|
- `ServerRunner` is kernel-internal; never construct it directly. The
|
|
free-function drivers are the supported surface.
|
|
|
|
## Spec
|
|
|
|
[Architecture — lifecycle](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle)
|
|
· [2026 versioning — discover](https://modelcontextprotocol.io/specification/draft/server/discover)
|
|
|
|
## See also
|
|
|
|
`legacy_routing/` (composing `serve_one` behind `classify_inbound_request`),
|
|
`dual_era/` (`Connection.protocol_version` in handlers).
|