Expand description
The reusable MCP server base: transport, framing, connection handling, the
lifecycle/version machinery, and the resource-subscription registry — agentd’s
served self-MCP (and any other embedder’s server) builds its domain surface on
top by implementing Handler.
The split mirrors the client: this module owns the protocol (how bytes become
requests, how initialize / server/discover / ping are answered across
both eras, how a subscriber is pushed a notifications/resources/updated),
while the embedder owns the domain (which tools exist, which resources are
readable, who may subscribe to what). One Handler trait is the seam.
Transport is deliberately minimal and dependency-light (RFC 0015 §3.6): a
blocking listener, one thread per connection, speaking the same NDJSON JSON-RPC
codec (crate::rpc::frame) as the client. No async, no mio. ServeStream
type-erases unix vs. vsock so the framing, threading, and dispatch are entirely
transport-agnostic (“the unix server with the socket type swapped”).
Structs§
- Subscriber
- A peer subscribed to a resource: which connection, and the writer to push a
notifications/resources/updatedto. Opaque — fields are private; construct + mutate a registry throughregister_subscriber/drop_subscription/remove_conn_subscriptionsand fire pushes through thenotify_*helpers.
Enums§
- Peer
Origin - Which transport a connection arrived on, and therefore its trust domain (RFC
0015 §3.3-§3.4). A generic two-domain model the framework only carries and
hands to the
Handler; the embedder assigns meaning: - Serve
Stream - The served-MCP transport, type-erased to one concrete enum so the connection
registry (
SharedWriter,Subscriber) stays monomorphic across transports while the same connection code serves each. The socket variants areRead + Writewith atry_clone(their write half is shared with the threads that push NDJSON notifications). TheHttpvariant is a write-only SSE sink: an HTTP subscription stream’s write half, so an HTTP subscriber registers in the SAMESubRegistryand the embedder’s existingnotify_*calls reach it transparently — the framing (NDJSON vs SSEdata:events) is chosen per-variant inwrite_notification.
Traits§
- Handler
- The embedder’s domain seam. The framework owns the transport, the framing, the
connection lifecycle, and the subscription registry; the
Handlersupplies the meaning — which tools/resources exist, who may call/read/subscribe to what.
Functions§
- bind_
unix - Bind a unix socket for serving, clearing any stale socket file first. Returned separately from the accept loop so the caller can log/act on a successful bind (or propagate the bind error) before the accept thread starts.
- broadcast_
distinct - Broadcast a payload-free
noteto every DISTINCT writer currently in the registry — for connection-scoped notifications that aren’t tied to a single uri (e.g.notifications/tools/list_changedafter a hot reload changed the tool set). A connection subscribed to several resources is written to once. Dead writers are pruned by their own reader loop. - drop_
subscription - Drop
conn’s subscription to a singleuri(theresources/unsubscribepath). Prunes the uri entry entirely once its last subscriber leaves. - handle_
conn - Serve one accepted connection to completion: the blocking NDJSON read loop.
Requests get a reply (through the shared writer, which a background thread may
also push notifications on — the Mutex serializes them); notifications
(
initialized, …) are read and dropped. On EOF/hangup the connection’s subscriptions are dropped so no push ever targets a dead socket. A write timeout bounds a stalled-but-alive peer so it can’t pin the writer Mutex (and a pushing thread) forever. - lifecycle_
response - Answer the three lifecycle methods every MCP server must handle, in ONE place,
version-aware across both eras — the server-side mirror of the client’s version
negotiation. Returns
Some(response)forinitialize/server/discover/ping, orNoneifreq.methodis a domain method theHandlermust route. - notify_
resource_ updated - Push
notifications/resources/updated{uri}to every current subscriber ofuri, consuming the subscription list (the resource changes exactly once — e.g. a subagent run reaching its terminal status — so no entry should linger after its one event). Best-effort: a write to a dead peer fails and is cleaned up when that connection’s reader loop ends. The lock is released before writing, so a slow/blocked peer can’t stall other notifications. - notify_
resource_ updated_ keep - Like
notify_resource_updatedbut keeps the subscriber list — for resources that change REPEATEDLY (a run aggregate on each spawn, a warm session on each turn boundary,config/effectiveon each reload, an event ring on each batch). Cloning the writers under the lock (then releasing it before writing) keeps the entry intact for the next emission. Dead peers are pruned when their reader loop ends (remove_conn_subscriptions). - register_
subscriber - Register
conn(with itswriter) as a subscriber ofuri, idempotently — a second subscribe from the same connection is a no-op rather than a duplicate push target. The embedder does its own gating (which URIs are subscribable, who may subscribe) before calling this. - remove_
conn_ subscriptions - Drop every subscription held by a (now-closed) connection — called when a connection’s reader loop ends so pushes never target a dead socket.
- spawn_
accept_ unix - Spawn the background accept thread for
listener: one blocking thread per connection, each runninghandle_connagainsthandler. Peers arrive in thePeerOrigin::Managementtrust domain (they dialed a listener). Returns once the accept thread is spawned; a thread-spawn failure is surfaced as the error.
Type Aliases§
- Shared
Writer - A connection’s shared write half — both replies and pushed notifications go
through it, serialized by the Mutex (a reply and a notification can’t interleave
bytes). The
ServeStreamenum keeps this one type across unix + vsock peers. - SubRegistry
uri→ its subscribers. Pushed when a resource changes.Arc-shared with the background threads that mutate resource state (a run reaching a terminal status, a reload landing, an event-ring growth).