Subscriptions
resources/subscribe over SSE and resource.wait_and_read wait-many. Push hints plus version-based recovery.
The push half of the protocol. Push is a hint path: it tells your agent a resource may be stale, then your agent reads durable state. Sprout-generated resource versions are the source of truth for recovery.
There are two active-session push shapes today. They are for a running agent session that wants to react while it is already awake, not a requirement that every client hold a background connection forever.
- SSE subscriptions: call
resources/subscribe; the server upgrades the response to SSE and pushesnotifications/resources/updatedJSON-RPC frames while the active session stream is alive. - Wait-many fallback: call
resource.wait_and_readwith several resource URIs plus the last versions you processed. The server returns immediately for bootstrap/stale resources, or briefly holds a fresh active-session request when capacity allows.
Planned Always-on/background push for a home-agent daemon or managed realtime provider is a separate shape. Until that exists, clients should close subscriptions when the active agent session ends and recover later with version echo.
Some resources are available today. Others are coming soon. Each gets its own page below.
Subscribable resources
sprout://child/{childId}/screentime/requests: pending and recently-resolved screen-time unlock requests. Livesprout://child/{childId}/gems: current balance and latest transactions. Livesprout://child/{childId}/today: the kid's today view. Pushes on task land, complete, review.sprout://family/activity: family-wide event stream. The resource that makes event-driven heartbeats real.How it works
SSE subscribe
- Your agent issues
resources/subscribewith a URI. - The server upgrades that HTTP response to SSE; the connection stays open for the active agent session.
- On every state change, the server emits
{ "jsonrpc": "2.0", "method": "notifications/resources/updated", "params": { "uri": "<uri>" } }. - Your agent reacts. Typical pattern:
resources/read <uri>to fetch the current state. resources/unsubscribecloses the stream cleanly.
One active MCP session can hold many subscriptions. Notifications interleave on the same stream; demultiplex by params.uri. Close the stream when the active session is done.
Wait-many fallback
Use resource.wait_and_read when your MCP client cannot consume an SSE stream or when you intentionally want bounded long-poll behavior during an active agent session.
{
"resources": [
{ "uri": "sprout://child/<childId>/gems", "sinceVersion": "opaque-version" },
{ "uri": "sprout://child/<childId>/screentime/requests", "sinceVersion": "opaque-version" }
],
"timeoutMs": 15000,
"includeState": true
}- Omit
sinceVersiononly for bootstrap. Bootstrap returns current versions immediately and does not hold a connection. - If any supplied version is stale, the tool returns
status: "changed"immediately with all stale resources. - If all supplied versions are current and
timeoutMs > 0, the server may hold the request briefly if capacity allows. - If capacity is unavailable, the tool returns
status: "no_change"withretryAfterMs. Treat that as a slower polling signal, not data loss.
Version contract
Sprout generates resource versions. Clients do not compute versions; they store and echo the versions Sprout returned.
- Bootstrap each watched URI with no
sinceVersion. - Store the returned
uri -> versionmap. - For SSE, read the resource after each notification and update the stored version from the read response.
- For
resource.wait_and_read, pass your stored versions and update the map from every returned row. - After reconnects, process restarts, timeouts, deploys, or uncertainty, re-check with your last processed versions.
SSE can deliver every notification frame while the active session connection is alive. resource.wait_and_read returns snapshots and may coalesce several updates into one changed row per resource. Both rely on version echo for resync.
Delivery semantics
Apply across every subscribable resource.
- Latency: ~1 second from server-side change to client push.
- Ordering: SSE frames are emitted per live stream. Do not build correctness on frame ordering alone; read current state and compare versions.
- Duplicates: rare but possible across reconnects. Treat each notification as a hint to re-read, not as the truth itself.
- Backpressure: if your client falls behind, the server drops the connection; reconnect and re-subscribe.
- Reconnect:
resources/unsubscribeis best practice but not required. Dropped connections free their server-side subscriptions automatically. - State after notify: the notification only names the URI; the new state is fetched via
resources/read <uri>. Keeps push frames small and lets the read shape evolve independently. - Capacity: held waits and SSE streams consume server connection capacity. Use one multi-resource wait instead of parallel waits for the same user, and do not leave active-session streams open as background listeners.
Errors
PERMISSION_DENIED: token missing the scope required to read the underlying resource. Subscriptions are gated by the read scope of the URI.DOMAIN_NOT_FOUND: URI references a child / family not in scope of this token.BAD_INPUT: malformed URI or unknown resource template.INTERNAL_ERROR: server-side fault. Reconnect with backoff.
See also
- Model: Pull, push, realtime: where subscriptions sit in the protocol model.
- Model: Activity: consumer of the planned family/activity push.
- Model: Heartbeat: the event-driven trigger the activity push enables.