Register a connected GET listener and return its id. Each listener gets a distinct stream ordinal so its event ids stay globally unique within the mount/session. When subscriptionId is non-empty (a subscriptions/listen stream), every notification delivered to this listener is stamped with it in params._meta["io.modelcontextprotocol/subscriptionId"].
MODE 1 — BROADCAST. Deliver a notification to EVERY connected session's GET listener (the notifications/*/list_changed fan-out): each distinct session MUST be told the list changed, not just the first eligible stream. Listeners are grouped by their session token and the single-stream deliver runs once per distinct token, so the transport's Multiple Connections rule ("MUST NOT broadcast the same message across multiple streams") is still honoured WITHIN a session (only one of a session's streams receives it) while distinct sessions each get their own copy. The session token is otherwise IGNORED: broadcast reaches all sessions, including the unscoped/stateless one. Eligibility per listener is the shared listenerEligible decision. Returns the number of distinct sessions reached.
The distinct, non-empty owner tokens of the currently-connected listeners. Each value is a session whose GET stream can receive a session-scoped server->client request (e.g. ping), so a caller can probe every live session in turn rather than blindly forwarding an empty token that matches no session-scoped listener. The empty (unscoped / stateless / shared) token is excluded, since that path is reached via the no-token ping/requestOnSession.
Frame msg as an SSE event (with a per-stream globally-unique id) and deliver it on exactly ONE connected stream, honouring the transport's Multiple Connections rule that the server "MUST send each of its JSON-RPC messages on only one of the connected streams ... it MUST NOT broadcast the same message across multiple streams." Listeners are tried in registration order; one whose write throws (a disconnected client) is dropped and the next live listener is tried, so the message still lands on a healthy stream and the channel self-heals. Returns 1 if the message was delivered, or 0 when no live listener could receive it.
Frame msg and write it to a single listener (identified by listenerId), rather than broadcasting to all. Used to deliver a per-stream leading event — e.g. the notifications/subscriptions/acknowledged the draft subscriptions/listen stream sends only to its own client. Returns true if the listener received it; false if the id is unknown or its write failed (in which case the listener is dropped).
Number of currently-connected listeners.
Convenience: broadcast a JSON-RPC notification to every listener.
Initiate a ping toward the client on the session's GET SSE stream and block until it acknowledges with the spec-mandated empty result (basic/utilities/ping). This is the server-side counterpart to the client's ping(): it lets a server perform the SHOULD-periodic connection-health check the spec describes for either party. Throws on a client error, a timeout (treat as a stale connection), or when no GET listener is connected for the session. The ping request carries no params, exactly as the spec requires. sessionToken scopes the probe to one session's GET stream (empty == the stateless / shared path); see requestOnSession.
MODE 2 — PUSH TO SESSION. Deliver a notification on exactly ONE connected stream of the ONE session named by sessionToken, used by notifications/resources/updated to a subscriber. The chosen stream still applies its own per-stream SubscriptionFilter (resource-updated URI/type filtering) and per-session gate via the shared listenerEligible decision, so a session's update reaches only a stream that opted into this method (and, for resources/updated, this uri). An empty sessionToken is the unscoped path: every listener is a candidate (the draft self-contained subscriptions/listen stream and the stateless/no-session case), matching the prior single-stream filtered delivery. Returns 1 if delivered, else 0.
Drop a listener (e.g. when its GET stream is closed). Any in-flight server->client request that was delivered on this listener is failed immediately so its awaiter wakes with an McpException instead of hanging for the full timeout. Acquires the delivery mutex so it cannot interleave with an in-progress write or another list mutation.
MODE 3 — REQUEST ON SESSION. Send a server->client JSON-RPC *request* (elicit / sample / roots / server-initiated ping) on the GET SSE stream of the session named by sessionToken, and block until that client responds. The reply arrives on a *separate* POST to the MCP endpoint and is routed through StreamCoordinator.resolve, which wakes this call.
Number of stream ordinals currently retaining replay history. Exposed for tests/diagnostics to verify the LRU bound; the value is at most maxHistoryStreams.
A long-lived server->client SSE channel for *unsolicited* traffic — the stream a client opens with an HTTP GET to the MCP endpoint (basic/transports §Listening for Messages from the Server). One instance is shared across a server mount. Unlike HttpStreamContext, which is bound to one in-flight POST, emit frames the JSON-RPC message as an SSE event with a globally-unique id (via the shared StreamCoordinator ordinal scheme) and writes it to exactly ONE live GET listener, honouring the transport's Multiple Connections rule: "The server MUST send each of its JSON-RPC messages on only one of the connected streams; that is, it MUST NOT broadcast the same message across multiple streams." A listener that fails to write (a disconnected client) is skipped and dropped, so the channel self-heals and the message still lands on a live stream.
The channel serializes delivery and listener-list mutation internally with a vibe TaskMutex, so concurrent fibers cannot interleave the bytes of two SSE frames, reuse an event id, or dangle the listener list across an async write. Callers that ALSO write to the same underlying HTTPServerResponse.bodyWriter outside the channel (e.g. a heartbeat loop or an up-front retry: event on the GET/listen stream) MUST serialize those writes against the channel's listener write callback through a shared per-stream lock, since the channel's mutex guards only its own state and its own writes — not a foreign writer on the same connection. Like the rest of the SDK, this assumes vibe.d's default single-threaded event loop; running the router with HTTPServerOption.distribute or worker threads is unsupported.