Expand description
§2 module 31 server (COMPOSABLE-HARNESS-DESIGN.md, D7 “full
programmatic RPC/HTTP server”, D8 “remote attach”, D10 “daemon”; §1.9
Obligation 9’s out-of-process half — the in-process SDK already meets
the core commitment via crate::EventSink).
The embedding ladder this module builds:
--output-format stream-json(the CLI’s existing rung, UX-23) — already ships a JSONLcrate::AgentEventstream over stdout. This unit completes it:crate::AgentEvent::to_jsonis now the single canonical projection both that sink AND this module’s notifications share, and it covers the FULL event set (previouslycrate::AgentEvent::BackgroundOutputfell into a generic “unknown” catch-all).- JSONL-RPC over stdio (
run_stdio) — the SDK-out-of-process surface: a parent process drives this agent’s loop over stdin/stdout with{"id","method","params"}request lines, getting back{"id","result"|"error"}responses interleaved with{"event":...}notifications. Parent-process-trusted (same trust model ascrate::mcp::serve_stdio) — no auth token. - The same RPC surface over HTTP (
run_http, D8 “remote attach”) —POST /rpcfor request/response,GET /eventsfor the event stream (SSE-shaped:data: <json>\n\nper line). Unlike stdio, a network client is UNTRUSTED by default, so every request must carry the bearer token (check_auth).
Security posture (this is a listener — the highest-risk module class):
[capabilities.server]is project-forbidden (D-10) — seecrates/cli/src/userconfig.rs’sPROJECT_FORBIDDEN_CAPABILITY_TABLESand this crate’sconfigfile::PROJECT_FORBIDDEN_CAPABILITY_TABLES(both already listed"server"before this unit landed; this module is what makes the listener the strip was already guarding against real).- Default-off: nothing in this module is ever reached unless a caller
explicitly invokes
run_stdio/run_httpAND the CLI’s own gate (capabilities.server.enabled == Some(true), checked before either is called) passed. - Loopback-only HTTP bind by default — enforced by the CALLER (the
CLI’s
servecommand defaultsbindto127.0.0.1:0and only binds elsewhere on an explicitbind/--bindoverride, with a printed exposure warning);run_httpitself binds whatever address it’s given, since the loopback POLICY decision belongs to the config/CLI layer, not the transport. - No permission/sandbox bypass.
RpcEngine::newtakes an already fully-constructedcrate::Agent— the SAMEAgenta localrun/chatsession would build (sameConfig, same permission rules, same sandbox). This module installs NO approval handler of its own and provides no channel for a remote/RPC caller to answer an approval prompt; combined withAgent’s existing fail-closed rule (“absent handler denies” —crates/harness/src/agent.rs’sprepare_tool_call), any tool call that would need interactive approval is DENIED, never silently approved, when driven through this module. Seecrates/harness/tests/server_engine.rsfor a fail-on-revert proof. - Bounded buffering throughout (
SERVER_MAX_LINE_BYTES,SERVER_EVENT_CHANNEL_CAPACITY) — same 16MiB-class discipline P5-2 established forcrate::mcp’s SSE reader, reused here rather than re-derived. - Graceful shutdown: the
shutdownRPC method stops the stdio loop and the HTTP accept loop alike (both select on the sameRpcEngine::wait_for_shutdown) — no orphaned listener/accept task survives ashutdowncall, mirroring P5-3/P5-6’s drop-abort discipline for background work.
Structs§
- Frontend
Request Bridge - Pre-runtime bridge for MCP clients that must receive their elicitation handler before they are consumed into agent tool registration.
- Frontend
WebSocket Server - Lifetime handle for an authenticated
frontend.v2WebSocket listener. Dropping the handle detaches the listener without closing its SDK runtime. - RpcEngine
- The out-of-process RPC driver: wraps one already-constructed
crate::Agentwith thesubmit/interrupt/status/shutdownmethod set (§ module doc). Shared by both transports (run_stdio,run_http) so the method semantics — including the fail-closed permission behavior — can never drift between them. - RpcRequest
- One JSONL-RPC request line a client sends:
{"id", "method", "params"}.paramsdefaults tonullwhen omitted (a method that takes no arguments, e.g.status/shutdown, never requires callers to spell out"params": null}explicitly). - Runtime
Http Credential - One bearer credential and its exact SDK authorization grant.
- Runtime
Status - Protocol-neutral snapshot of one SDK-owned agent runtime.
Enums§
- Runtime
Submit Error - Typed turn failure shared by local, HTTP, ACP, CLI, and language adapters.
Constants§
- SERVER_
EVENT_ CHANNEL_ CAPACITY - Bounded broadcast capacity for the event-notification channel — mirrors
crate::mcp::MCP_SSE_CHANNEL_CAPACITY’s bounded-buffering discipline (P5-2): a slow/absent subscriber can never make the sender block or grow memory unboundedly; a lagging receiver just misses old events (broadcast::error::RecvError::Lagged) rather than stalling the agent loop or accumulating unbounded backlog. - SERVER_
MAX_ LINE_ BYTES - Maximum accepted line/body length (bytes) for both the stdio JSONL-RPC
reader and the HTTP transport’s request line/headers/body — the same
16MiB-class cap P5-2 established for
crate::mcp’s SSE frame reader (MCP_MAX_SSE_FRAME_BYTES), reused here so an adversarial or simply broken client can never make either transport buffer an unbounded amount of data in memory.
Functions§
- generate_
token - Mint a random per-session bearer token (32 bytes, hex-encoded) for the
HTTP transport, when the operator hasn’t configured a fixed
capabilities.server.token. Usesgetrandom(already resolved transitively viareqwest’s rustls/ring stack; promoted to a direct dependency here so this crate can call it directly, rather than rolling a hand-written PRNG for a value that must actually be unguessable). - run_
frontend_ websocket - Publish the language-neutral facade over authenticated WebSocket RPC.
The endpoint accepts only
/frontend/v2, reuses the SDK coordinator, and emits canonical events asfrontend.v2.eventnotifications. - run_
http - Bind
bind(host:port;:0for an OS-assigned ephemeral port) and serve the HTTP transport (D8 “remote attach”) in a background task untilenginesignals shutdown. Returns the actually-bound address (so a caller that asked for port0can learn the real port). Every connection is authenticated per-request viatoken— seecheck_auth. The LOOPBACK-BY-DEFAULT policy decision is the caller’s (see the module doc) — this fn binds whatever address it’s given. - run_
http_ authorized - Bind an SDK HTTP runtime with multiple independently scoped bearer credentials. The token bytes remain server-private; each successful authentication produces the exact authorization grant projected by the shared runtime coordinator.
- run_
http_ authorized_ with_ lease_ ttl - Test/embedder variant of
run_http_authorizedwith an explicit controller lease duration. - run_
stdio - Drive the JSONL-RPC protocol over
reader/writer(the stdio rung — parent-process-trusted, no auth token; see the module doc). Each request line is dispatched on its OWN spawned task so asubmitin-flight never blocks the reader from picking up a subsequentinterrupt/statusline — every outgoing line (a response OR an event notification) is funneled through one mpsc channel into a single writer task, so two concurrent handlers can never interleave a line’s bytes. Returns oncereaderhits EOF or ashutdownrequest lands.