MCP Progress Notifications and Subscriptions: Handling Long-Running Tasks and Live Updates
TL;DR: When an MCP tool takes more than a few seconds to run (such as querying a data warehouse, running end-to-end tests, or scraping batches of web pages), AI host clients frequently freeze or hit 60-second connection timeouts. The Model Context Protocol solves this with event-driven notifications:
notifications/progressreports real-time execution steps via progress tokens,resources/subscribestreams live cache updates without burning model tokens on polling,logging/setLevelroutes structured logs cleanly, andnotifications/cancelledenables graceful process abortion. Here is how to implement all four patterns in production.
When developers first start building MCP servers, they usually write synchronous tools that return results in 200 milliseconds. A tool checks a weather API, reads a local config file, or converts markdown to HTML.
However, real-world engineering workflows are rarely instantaneous. Production tools often execute tasks such as:
- Indexing a 100,000-line code repository for vector search.
- Running a comprehensive Playwright browser test suite across multiple viewports.
- Executing heavy analytical queries across Snowflake or BigQuery.
- Performing batch image processing or video encoding.
If you run these operations inside a naive MCP tool handler, two problems occur immediately:
- The UI Freeze: The user sits in front of Claude Desktop, Cursor, or Zed staring at a generic spinner for 45 seconds with zero feedback on whether the tool is working or deadlocked.
- The Hard Timeout: Many MCP host clients enforce a strict 60-second JSON-RPC roundtrip timeout. If your handler takes 61 seconds, the client forcibly terminates the transport connection, leaving orphaned child processes running in the background.
To build responsive, reliable tools, you need to use the protocol's asynchronous notification primitives.
The 60-Second Black Box Problem
Under standard synchronous execution, communication between the host client and the server is a strict request-response pair:
If the server encounters a network hiccup or a slow database query, the client has no visibility into what is happening.
The Model Context Protocol specification solves this by defining JSON-RPC Notifications. Unlike requests, notifications do not include an id field and do not expect a response from the receiver. They flow asynchronously in either direction across the established transport (whether stdio or Server-Sent Events).
By streaming intermediate status updates, the server satisfies client keep-alive checks, gives the user immediate visual progress indicators, and enables graceful error recovery.
The JSON-RPC Notification Model
The MCP specification defines several core notification types that every production server author should understand:
| Notification Method | Direction | Purpose | Protocol Reference |
|---|---|---|---|
notifications/progress | Server → Client | Reports progress for an active request using a client-supplied token | Tools & Requests |
notifications/message | Server → Client | Emits structured log events at specified RFC 5424 severity levels | Logging Capability |
notifications/resources/updated | Server → Client | Alerts the client that a subscribed resource URI has changed | Resource Subscriptions |
notifications/resources/list_changed | Server → Client | Announces that the catalog of available resources was modified | Dynamic Resources |
notifications/tools/list_changed | Server → Client | Informs the client that tools were dynamically added or removed | Dynamic Tools |
notifications/prompts/list_changed | Server → Client | Informs the client of changes to prompt templates | Dynamic Prompts |
notifications/cancelled | Client → Server | Notifies the server that a pending request was aborted by user/host | Request Lifecycle |
Implementing Progress Notifications Step-by-Step
Let us break down how progress reporting functions on the wire and in code.
1. The Wire Protocol
When a client initiates a request where it wants progress tracking, it injects a progressToken inside the optional _meta parameter:
The progressToken can be either an integer or a string. As the server executes the migration steps, it sends progress updates back to the client:
Key fields in notifications/progress:
progressToken(required): The exact token provided by the client.progress(required): Current completion counter or percentage.total(optional): Total steps or maximum counter. If omitted, the client renders an indeterminate activity indicator.message(optional): Human-readable status update for client UIs.
2. TypeScript Implementation
Here is a complete, production-ready TypeScript implementation using the official @modelcontextprotocol/sdk:
3. Python Implementation with FastMCP
In Python, FastMCP provides an ergonomic Context object that handles progress token checking and notification dispatch automatically:
Live Resource Subscriptions and Change Feeds
While tools are ideal for taking actions, MCP resources represent readable data assets.
In many architectures, developers mistakenly implement "polling tools" (such as check_deployment_status or poll_build_queue) where the AI model is expected to execute a tool every 5 seconds to look for updates. This burns context tokens rapidly, triggers tool overload, and incurs high model inference costs.
The correct protocol primitive is Resource Subscriptions.
The Subscription Sequence Flow
TypeScript Resource Subscription Implementation
Servers declare subscription capabilities during the protocol handshake by specifying capabilities.resources.subscribe = true.
Structured Logging Without Corrupting Stdio Streams
One of the most frequent errors when debugging local MCP servers is the stdio stream corruption issue.
Because stdio transports multiplex JSON-RPC payloads directly over standard input and output streams, any call to console.log() or print() injects raw characters into the pipe, breaking JSON parsing in host applications.
MCP resolves this by providing a dedicated, typed logging subsystem based on RFC 5424 severity standards:
Logging Protocol Implementation
When a client wants to configure logging verbosity, it calls logging/setLevel:
The server responds to this level preference and emits typed log messages:
TypeScript Structured Logger Pattern
Using this pattern, logs are displayed cleanly inside the host client's developer inspection panel without corrupting transport sockets.
Graceful Request Cancellation and Abort Handling
What happens when a developer asks Claude or Cursor to analyze a massive repository, realizes they selected the wrong branch, and clicks the "Stop" button in the chat interface?
Without cancellation handling, your server continues grinding in the background, consuming CPU, locking SQLite databases, and running expensive third-party API queries.
The MCP specification defines notifications/cancelled to communicate user cancellations down to running tools:
Wiring Cancellation to Node.js AbortControllers
Here is how to wire MCP cancellation into standard JavaScript AbortSignal pipelines:
When the client cancels the operation, abortController.abort() immediately terminates the outbound fetch socket, saving network bandwidth and releasing system memory.
Protocol Primitives Decision Matrix
Choosing the right primitive ensures clean architecture, optimal token usage, and great responsiveness:
| Capability | Flow Controller | Primary Use Case | Context Window Cost |
|---|---|---|---|
Tools (tools/call) | AI Model | Explicit deterministic actions with parameters | High (Schemas are weighed on every prompt) |
Progress (notifications/progress) | Server | Real-time status for long operations (>2 sec) | Zero (Out-of-band notification) |
Resources (resources/read) | Host App / User | Attaching documents, configs, and static data | Low (Read on-demand by URI) |
Subscriptions (resources/subscribe) | Server Event | Streaming database, file, or sensor updates | Zero (Updates trigger cache invalidation) |
Prompts (prompts/get) | User (Slash Cmd) | Standardized workflow templates and routines | Zero until explicitly invoked by user |
Sampling (sampling/createMessage) | Server | Requesting LLM sub-agent reasoning from host | Variable (Scoped to delegated prompt) |
Video Walkthroughs and Official Specifications
For an architectural walkthrough of how Model Context Protocol connects LLM clients to external tools and services, watch IBM Technology's engineering deep dive:
To explore additional low-level implementation details and reference codebases:
- Anthropic Engineering Discussion: Watch Why we built and donated the Model Context Protocol (Anthropic) featuring MCP co-creator David Soria Parra.
- Official Model Context Protocol Specification: Review the complete Model Context Protocol Specification for formal schema definitions of JSON-RPC notifications and lifecycle hooks.
- Anthropic MCP GitHub Repository: Inspect the open-source Model Context Protocol TypeScript SDK and Python SDK.
- Architecture Blueprints: Read our companion guides on Architecting Production MCP Servers, How MCP Sampling Works, and Securing Remote MCP Servers.
Production Implementation Checklist
Before shipping an MCP server that handles long-running tasks or streaming data to users:
- Progress Tokens Inspected: Check
extra._meta.progressTokenon heavy tools and emitnotifications/progressevery 1 to 2 seconds. - Stdio Cleanliness: Ensure no library code writes unformatted strings to
stdout. Direct all local debugging tostderrornotifications/message. - Cancellation Handled: Store active request IDs and map them to
AbortControllerinstances or background worker kill switches. - Subscriptions vs Polling: If clients need fresh state from a changing resource, expose
resources/subscriberather than forcing the model into repeated tool calls. - Safe Fallbacks: Always handle clients that do not support optional capabilities gracefully (e.g. continue tool execution if
progressTokenis omitted).
Implementing these real-time primitives transforms your MCP server from an error-prone proof-of-concept into a responsive, enterprise-ready service. Browse our curated directory of production-tested tools on the AllMCPs Server Directory or start building your own with our MCP Server Starter Guide.
Frequently asked questions
What is an MCP progress token and how does it work?
An MCP progress token is an opaque identifier (string or number) supplied by the host client in the _meta property of a request. When a server executes a long-running tool, it sends out-of-band JSON-RPC notifications named notifications/progress tagged with this token, reporting current progress, total expected steps, and descriptive status messages.
How do progress notifications prevent MCP client timeouts?
Host clients like Claude Desktop, Cursor, and Windsurf enforce default request timeouts (typically 60 seconds). Emitting notifications/progress packets resets client-side activity timers and informs the host that the server process is alive and actively processing work, preventing abrupt socket termination.
What is the difference between tool polling and resource subscriptions in MCP?
Tool polling forces the AI model to repeatedly spend reasoning cycles and tokens calling a tool to check for new data. Resource subscriptions (resources/subscribe) establish an event-driven channel where the server automatically emits notifications/resources/updated when data changes, prompting the client to re-read the URI only when necessary.
Why does calling console.log break stdio-based MCP servers?
In stdio transport, stdout is reserved strictly for valid JSON-RPC framing. Printing raw log statements to stdout injects unformatted text into the JSON stream, causing the client JSON parser to crash. Developers must use stderr for local debugging or notifications/message for structured protocol logging.
How does an MCP server handle request cancellation?
When a user interrupts an operation, the client emits a notifications/cancelled message containing the original requestId. Production servers map active request IDs to AbortController instances in Node.js or asyncio Tasks in Python, cleanly terminating spawned child processes and database transactions.