Static resource definitions function well for data that remains constant, such as configuration files or historical records. However, modern applications frequently interact with data that changes state rapidly, such as application logs, stock tickers, or collaborative documents. Relying on the client to repeatedly request the same resource to check for updates, a technique known as polling, creates unnecessary network traffic and increases latency. The Model Context Protocol addresses this through a subscription and notification mechanism, allowing the server to alert the client only when relevant data has changed.The Publish-Subscribe Pattern in MCPThe MCP architecture employs a modified publish-subscribe pattern. Unlike traditional streaming where the data itself is pushed continuously, MCP decouples the notification of change from the retrieval of data. When a resource changes, the server sends a lightweight notification to the client. It is then the client's responsibility to initiate a new read request if it requires the updated content.This separation prevents the server from overwhelming the client with large data payloads during high-frequency updates. The client maintains control over when it consumes the bandwidth to fetch the new context.The following diagram illustrates the message flow between an MCP Client and Server during a subscription lifecycle.digraph G { rankdir=LR; node [shape=box, style=filled, fontname="Helvetica", fontsize=10]; edge [fontname="Helvetica", fontsize=9, color="#adb5bd"]; subgraph cluster_0 { label = "Initialization"; color = "#e9ecef"; style = filled; Server [label="MCP Server", fillcolor="#4dabf7", fontcolor="white", color="#4dabf7"]; Client [label="MCP Client\n(LLM Host)", fillcolor="#37b24d", fontcolor="white", color="#37b24d"]; } subgraph cluster_1 { label = "Event Loop"; color = "#f8f9fa"; style = filled; Resource [label="Data Source\n(e.g., Log File)", fillcolor="#ff6b6b", fontcolor="white", color="#ff6b6b"]; } Client -> Server [label="1. resources/subscribe (uri)"]; Server -> Client [label="2. Acknowledgement"]; Resource -> Server [label="3. Data Modified"]; Server -> Client [label="4. notifications/resources/updated (uri)"]; Client -> Server [label="5. resources/read (uri)"]; Server -> Client [label="6. Updated Content Payload"]; }The sequence demonstrates the decoupling of notification and data retrieval. The server notifies the client of a change, triggering a subsequent read request.Declaring Subscriber CapabilitiesBefore a client can subscribe to a resource, the server must explicitly declare that it supports resource notifications. This occurs during the initialization handshake. In the capabilities negotiation phase, the server includes a resources object with a subscribe boolean set to true.If using the standard Python SDK, this configuration is often handled automatically when you register resource handlers. However, understanding the underlying JSON-RPC structure is valuable for debugging. The server initialization response includes:$$ \text{capabilities} = { \text{"resources"}: { \text{"subscribe"}: \text{true}, \text{"listChanged"}: \text{true} } } $$The listChanged capability is distinct from resource subscriptions. It informs the client that the list of available resources (the inventory) has changed, whereas subscribe relates to the content of a specific resource URI.Handling Subscription RequestsWhen a user or an LLM agent decides a specific resource is relevant to the ongoing conversation, the client sends a resources/subscribe request containing the uri of the target resource.Upon receiving this request, the server does not need to return data immediately. Its primary task is to register the connection as an active observer of that URI. In a Python implementation, the server must maintain a mapping between the resource URI and the active client session.Consider a server that exposes a dynamic system log. You first define the resource read logic:from mcp.server.fastmcp import FastMCP mcp = FastMCP("SystemMonitor") # A simulated internal state representing a log file log_state = ["Server started", "Initialization complete"] @mcp.resource("system://logs/recent") def get_recent_logs() -> str: """Returns the most recent log entries.""" return "\n".join(log_state)In this state, the resource is passive. The client can read it, but it will not know if log_state changes.Triggering NotificationsTo make this resource reactive, you must trigger a notification whenever the internal state changes. The MCP SDK provides methods to broadcast this update. The method usually accepts the URI of the resource that was modified.When the data source updates, the server executes the notification logic. This is often tied to an external event loop, a file system watcher, or a database trigger.import asyncio async def append_log(message: str): """Updates the state and notifies subscribers.""" log_state.append(message) # Notify the client that this specific resource has changed # The client will likely issue a new read request immediately after await mcp.server.request_context.session.send_resource_updated( "system://logs/recent" ) # Example background task simulating system activity async def generate_traffic(): while True: await asyncio.sleep(5) await append_log("Heartbeat check: OK")In this example, send_resource_updated constructs the JSON-RPC notification. The payload sent over the transport layer looks like this:{ "method": "notifications/resources/updated", "params": { "uri": "system://logs/recent" } }Notice that the payload does not contain the text "Heartbeat check: OK". It contains only the URI. This design minimizes bandwidth usage. If the client is currently busy or the user has paused the agent, the client can choose to ignore the notification or queue the read request for later.Managing Concurrency and GranularityWhen implementing subscriptions, you must consider the granularity of your URIs. If a resource represents a large dataset, such as a 10MB CSV file, triggering a notification for every single row edit forces the client to re-download the entire file repeatedly. This creates a performance bottleneck.For rapidly changing data, consider two strategies:Granular Resources: Break the data into smaller, addressable chunks (e.g., data://rows/50-100) so updates only trigger small reads.Debouncing: Accumulate changes on the server side and send a notification only once every few seconds, rather than on every micro-update.UnsubscribingThe protocol also supports resources/unsubscribe. Standard clients will unsubscribe when the context window is cleared or when the user explicitly removes a resource from the context. Your server implementation generally relies on the SDK to handle the removal of the session from the subscription list, preventing memory leaks where the server continues trying to notify a disconnected or uninterested client.Verification via InspectorYou can verify your subscription logic using the MCP Inspector. After connecting your server:Navigate to the Resources tab.Locate your target resource (e.g., system://logs/recent).Click the Subscribe button.Trigger an action on your server that modifies the data.Observe the Inspector log. You should see a notifications/resources/updated message followed immediately by a resources/read request initiated automatically by the Inspector.This cycle confirms that your server correctly bridges the gap between internal data changes and the external LLM context.