Resource subscriptions now survive a session reset: MCPServerProxy remembers what it subscribed to and replays it after any re-initialize, so a reconnected client keeps receiving resources/updated without every application writing its own resubscribe dance. (#183, fixes #179)
🔁 Subscriptions are replayed after a re-initialize (#183, fixes #179)
MCP resource subscriptions are per-session server state — they live in the session and evaporate with it, on server restart, session expiry, or any re-initialize. That is correct per spec, and it makes replay inherently a client duty. MCPServerProxy kept no record of what it had subscribed to, so every consumer had to implement its own reconnect-and-resubscribe dance, and forgetting produced a silent failure mode: a client that looks perfectly healthy — POSTs succeed, pings succeed — but never receives another notifications/resources/updated. That is the same symptom 1.10.3 fixed from the server side, reached from the other end.
subscribeResource(uri:)records the URI,unsubscribeResource(uri:)drops it, and every successfulinitializereplays the recorded set before the connection reports ready.- The URI is recorded after the server accepts the request, not before. Inserting on the way in would leave a phantom entry behind a failed
subscribeResource— a disconnected proxy, say — to be silently replayed on every later reconnect, so the recorded set only ever reflects confirmed server-side subscriptions. - A replay failure is logged per URI and does not abort the handshake. One resource that will not resubscribe should not cost you the connection.
disconnect()clears the set: an intentional teardown is a new logical client, not a session to restore.
Still the application's job: the proxy replays subscriptions, not reads. Only the app knows which resources it must also re-fetch to resync state across the gap, so a re-initialize should still be followed by whatever seeding it does on first connect.
🧪 Tests
SubscriptionReplayTests covers the set management (subscribe adds, unsubscribe removes, disconnect() clears) and the end-to-end path over HTTP: connect, subscribe, reconnect without disconnecting to simulate session recovery, wait for the new session's general stream, then broadcast — and assert the notification arrives on the strength of the replayed subscription alone.