Subscribe to MCP Resource Changes
After initialization, inspect the server’s `resources` capability and subscribe only when `subscribe` is `true`. Treat `notifications/resources/updated` as invalidation: read the named URI again for current content. Send `resources/unsubscribe` when interest ends, and clear every remaining subscription when the session terminates so the server retains no stale state.
Prerequisites
Complete MCP initialization before attempting a subscription. Under the MCP 2025-11-25 lifecycle, initialization is where the client and server agree on a protocol version and exchange capabilities. Normal requests begin only after the client sends notifications/initialized.
You also need the exact resource URI. Obtain it through resources/list, a resource template, or application configuration, and confirm that resources/read succeeds. If that part is not implemented yet, start with discovering and reading MCP resources.
Keep two pieces of session-scoped client state: the capabilities returned by the server and the set of resource URIs for which subscription requests succeeded. The server needs the corresponding per-session subscription set so it can stop notifications and release retained state later.
Steps
-
Check the negotiated capability before subscribing.
In the server’s initialization result, inspect
capabilities.resources.subscribe. The MCP 2025-11-25 resources specification makes subscriptions optional: a client can subscribe to a resource URI only when the server advertises the resourcesubscribecapability.{ "capabilities": { "resources": { "subscribe": true } } }Test the nested value explicitly. The presence of
capabilities.resourcesmeans the server exposes readable resources; it does not by itself promise subscriptions. Likewise,listChanged: trueconcerns changes to the list of available resources, not changes to the contents of one resource.If
subscribeis absent or false, do not sendresources/subscribe. Keep ordinaryresources/readavailable, but do not pretend the client has a live change signal. This is also a compatibility boundary: when a hosted server changes its advertised capabilities, the client must follow the newly negotiated contract rather than reuse assumptions from an earlier connection. That belongs in the same review as other hosted MCP server breaking changes. -
Install the update handler, then request the subscription.
Register the client’s handler for
notifications/resources/updatedbefore sending the subscription request. The handler needs access to the active session, the subscribed URI set, and the code that performsresources/read.Send
resources/subscribewith the exact resource URI:{ "jsonrpc": "2.0", "id": 41, "method": "resources/subscribe", "params": { "uri": "file:///project/config.json" } }Record the URI under the active session after the request succeeds. On the server, associate that URI with the requesting session rather than placing it in an unowned global set. The subscription has a concrete cost even while nothing changes: the server retains enough state to know which session should receive a later notification.
Subscribe only for resources whose changes the client will act on. A subscription that nobody consumes retains server state without keeping any useful client content current.
-
Read the resource to establish the current content.
A successful subscription arranges notifications; it does not replace
resources/read. Request the resource after subscribing and store or display the returned contents according to the host application’s needs:{ "jsonrpc": "2.0", "id": 42, "method": "resources/read", "params": { "uri": "file:///project/config.json" } }Keep the roles separate in the implementation. The subscription tracks interest in a URI. The read result supplies text or binary content. Do not treat a completed
resources/subscriberequest as evidence that a previous cached value is current. -
Treat an update notification as invalidation, not content.
When the subscribed resource changes, the server sends a notification naming the resource:
{ "jsonrpc": "2.0", "method": "notifications/resources/updated", "params": { "uri": "file:///project/config.json" } }The notification has no resource body. The client still sends
resources/readfor the named URI to obtain current content. In practical protocol terms, each processed change consists of one server-to-client notification followed by one client request and one server response for the reread. Subscriptions therefore remove the need to guess when a change happened, but they do not remove the read round trip.The MCP 2025-11-25 schema definitions describe
ResourceUpdatedNotificationas notice that a resource changed and may need to be read again. They also define the notification as JSON-RPC without anid, so the client does not send a response to the notification itself. The follow-upresources/readis the request that produces current content.Route the notification by both session and URI. Then perform the read through the same active session and replace the client’s old representation only with the read result. This prevents an update signal from being mistaken for data.
-
Unsubscribe as soon as the resource is no longer needed.
Provide an explicit stop path in the same component that starts the subscription. When a document closes, a watched item is removed, or another application action ends interest in the URI, send
resources/unsubscribe:{ "jsonrpc": "2.0", "id": 43, "method": "resources/unsubscribe", "params": { "uri": "file:///project/config.json" } }The unsubscribe URI must identify the previous subscription. After the request succeeds, remove it from the client’s active set. The server should remove the matching entry from its session-owned subscription state and stop sending
notifications/resources/updatedfor it.Do not make connection shutdown the only normal way to stop watching one resource. Long-lived sessions can outlast individual documents, tasks, or views; retaining every old subscription until shutdown leaves server state attached to work the client has already finished.
-
Clear remaining subscriptions when the session terminates.
Explicit unsubscribe covers the normal end of interest. Session cleanup covers everything still registered when the connection ends. MCP defines no separate shutdown request: stdio shutdown uses the underlying process streams, while HTTP shutdown is signalled through the associated connection or connections.
On the client, discard the subscription set when its owning session terminates. On the server, treat transport or session termination as a cleanup event and remove every subscription owned by that session. This is necessary even when the client normally sends
resources/unsubscribe, because retained subscription state must not outlive the session that created it.For a stateful HTTP implementation, make the ownership and cleanup rule part of MCP Streamable HTTP session management. The important boundary is the MCP session: closing one view may remove one subscription, while terminating the session removes all subscriptions that remain.
Expected result
The client subscribes only after the server advertises support, obtains current content through resources/read, rereads the URI after each notifications/resources/updated signal, and sends resources/unsubscribe when interest ends. When the MCP session terminates, neither side retains subscription state belonging to that session.
Sources
- MCP 2025-11-25 lifecyclemodelcontextprotocol.io
- MCP 2025-11-25 resources specificationmodelcontextprotocol.io
- MCP 2025-11-25 schema definitionsmodelcontextprotocol.io
See also
How MCP servers advertise URI templates, attach template-level metadata, and turn user or model input into concrete resource reads.
How MCP clients declare relevant filesystem roots, report changes, and rely on operating-system controls to enforce access.
How MCP servers request model completions through clients while clients retain control of credentials, models, context, permissions, and approval.
Negotiate sampling tool support, choose a tool mode, execute calls on the server, and return matched results without moving control to the model.