/** * v2.5.0 Tether Phase 1 — Part S — MCP resource subscription registry. * * Per MCP spec, subscriptions are PER-SESSION: a `subscribe` request binds * the calling client's session to a specific resource URI; the server pushes * `notifications/resources/updated` for that URI; `unsubscribe` (or session * disconnect) drops the binding. We track those bindings here, keyed by URI * so the inbox-event consumer can fan out to exactly the Servers that asked. * * Why a Server-level set, not session-level: * - Each MCP transport (stdio process, HTTP request stream) wraps a single * `Server` instance from `@modelcontextprotocol/sdk`. The SDK already * scopes its `sendResourceUpdated` to the transport bound to that Server. * So holding a Server reference is the unit of "this subscriber". * - Stdio: 1 Server per process, lifetime = process. unsubscribeAllForServer * fires on process exit (transport.onclose). * - HTTP / SSE: 1 Server per active request stream in the StreamableHTTP * wrapper. unsubscribeAllForServer fires on stream close. * * Cleanup discipline: * - Every server.connect() that wires our handlers MUST also wire onclose * to call unsubscribeAllForServer(server). Failing to do so leaks a * dead Server into the registry — sendResourceUpdated against a closed * transport throws, which we swallow + log, so leaks aren't fatal but * they're observable in the warn logs. */ import type { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { type InboxChangedEvent } from "./inbox-events.js"; /** * URI builder for a per-agent inbox. Centralized so the resource handler, * subscription handler, and emitter all use the exact same shape. * * Agent names are validated upstream against [A-Za-z0-9_.-]{1,64} (see Zod * schemas in src/types.ts), so URI escaping isn't strictly required, but * encodeURIComponent keeps us safe if a future regex relaxation slips in * without re-auditing this site. */ export declare function inboxUriFor(agentName: string): string; /** * Inverse of inboxUriFor — extract the agent name from a relay://inbox/ * URI, or null if the URI doesn't match the inbox shape. Returns null on * malformed URIs so callers can surface "unknown resource" errors with the * standard not-found path instead of crashing. */ export declare function agentNameFromInboxUri(uri: string): string | null; /** * v2.7 / Tether Phase 3b — single fan-out path used by BOTH the in-process * bus listener (same-process sender + subscriber) and the cross-process * outbox tail (the polling loop in src/outbox-tail.ts that drains * inbox_events rows produced by stdio writers in a different process). * * Dedup contract: callers MUST pass `eventId` = the row id from * `inbox_events` that produced this change. An event whose id is ≤ * the per-URI high-water mark is dropped silently. This guarantees that * if both the bus and the tail observe the same row (which is the * expected case when sender + subscriber share a process), the subscriber * receives exactly one `sendResourceUpdated`. * * Exported so `outbox-tail.ts` can call it directly; tests can call it * too if they want to bypass the bus + DB layer. */ export declare function broadcastInboxChange(agentName: string, reason: InboxChangedEvent["reason"], eventId: number, source: "bus" | "tail"): void; export declare function subscribe(uri: string, server: Server): void; export declare function unsubscribe(uri: string, server: Server): void; /** * Drop every subscription for a Server. Call from the transport's onclose * handler so a dead session doesn't leak into the registry. */ export declare function unsubscribeAllForServer(server: Server): void; /** Test-only: subscriber count for a URI. Used by the Part S test harness. */ export declare function _subscriberCountForTests(uri: string): number; /** Test-only: drop every subscription + unbind the bus listener. */ export declare function _resetSubscriptionsForTests(): void; //# sourceMappingURL=mcp-subscriptions.d.ts.map