1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
24 small wins to finish your pathNext lesson →
Progress: reporting on a slow tool
A tool that works for a minute and says nothing looks broken. Progress notifications report each step while it runs.
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 shop 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())progress_callback is passed to the call, not to the client, because each call may want different handling. The three reports arrived while the tool was still running, before the result.
import asyncio
from mcp import Client
from shop 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())Without a callback, report_progress does nothing and nothing fails, so a tool can report unconditionally without checking whether anyone is listening.
Try it yourself
- Leave out
totaland print what the callback receives. - Report progress every other order only.
- Make the callback print a bar of
#characters, one per order.
Slow is fine. Stopping is the only problem.