1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
27 small wins to finish your pathNext lesson →
Progress notifications
A progress notification is a message a tool sends while it runs to report how far a slow job has got, before the result is ready.
Last updated: 29 Sep, 2026 · MCP 2.2
This lesson's server goes in a file of its own, progress_demo.py, and shop.py stays as it is.
import asyncio
from mcp.server import MCPServer
from mcp.server.mcpserver import Context
mcp = MCPServer("Shop support")
@mcp.tool()
async def export_orders(order_ids: list[str], ctx: Context) -> str:
"""Export orders to the accounting system, one at a time."""
for done, order_id in enumerate(order_ids, start=1):
await asyncio.sleep(0.1)
await ctx.report_progress(done, total=len(order_ids), message=f"exported {order_id}")
return f"Exported {len(order_ids)} orders."await ctx.report_progress(progress, total, message) sends one notification. progress must go up with each report. total is optional; leave it out when you do not know it rather than guessing.
Listening from the client
import asyncio
from mcp import Client
from progress_demo import mcp
async def main():
async with Client(mcp) as client:
async def show(progress, total, message):
print(f" {progress:.0f}/{total:.0f} {message}")
result = await client.call_tool("export_orders", {"order_ids": ["A17", "B42", "C03"]}, progress_callback=show)
print(result.content[0].text)
asyncio.run(main())Output
1/3 exported A17 2/3 exported B42 3/3 exported C03 Exported 3 orders.
- Three reports arrived while the tool ran, one per order, before the result line.
- The callback goes on the call, not the client, so each call can handle progress its own way.
import asyncio
from mcp import Client
from progress_demo import mcp
async def main():
async with Client(mcp) as client:
result = await client.call_tool("export_orders", {"order_ids": ["A17", "B42"]})
print(result.content[0].text)
asyncio.run(main())Output
Exported 2 orders.
Without a callback, report_progress does nothing and nothing fails, so a tool can report unconditionally without checking whether anyone is listening.
With a callback vs without one
| No progress_callback | With a callback | |
|---|---|---|
| report_progress | Does nothing | Sends each report |
| The tool code | Unchanged | Unchanged |
| The client sees | Only the result | Each step, then the result |
When to report progress
- A tool that loops over many items, like an export or a batch.
- Work that waits on a slow service, so the user knows it is alive.
- Any call that runs long enough to look stuck.
Watch out
progress must increase with each report. Sending the same or a lower number confuses a client drawing a bar, so count the work done, not the work left.Related
- Previous: Lifespan
- Next: Elicitation
Try it yourself
- Leave out
totaland print what the callback receives. - Report progress only for every second order.
- Make the callback print a bar of
#characters, one per order.
Every expert started right here.