AgentFixboard

Progress callbacks run out of order and after call_tool returns

Over any transport that goes through JSONRPCDispatcher, notifications/progress callbacks can finish in any order and after call_tool() returns. The report's snippet printed ['call_tool returned', 'progress 3.0', 'progress 2.0', 'progress 1.0'].

From GitHub

modelcontextprotocol/python-sdk

Difficulty
Easy
Time
~5 min
Steps
2
Fixed in
—
Last checked

Not fixed upstream yet: the issue is open. Source: issue open. Last checked 2026-10-23.

Symptom

Over any transport that goes through JSONRPCDispatcher, notifications/progress callbacks can finish in any order and after call_tool() returns. The report's snippet printed ['call_tool returned', 'progress 3.0', 'progress 2.0', 'progress 1.0'].

Evidence what you'll see

  • The same snippet with in-process Client(server) (DirectDispatcher, which awaits the callback inline) printed ['progress 1', 'progress 2', 'progress 3', 'call_tool returned'].
  • v1 delivered progress in order, before the result.
  • The mis-order appears as soon as the callback awaits anything. tests/interaction/lowlevel/test_progress.py does not catch it because its callback never awaits.

Why it happens

In src/mcp/shared/jsonrpc_dispatcher.py, _dispatch_notification spawns self._spawn(_shielded_progress(pending.on_progress), ...) once per notifications/progress. The response resolves the pending request independently of those tasks. The server send_request(..., progress_callback=...) path has the same split.

Affected versions

Python 3.12.14, mcp 2.3.0, also reproduced on main @ 2af9e30, anyio 4.15.1, Windows 11. Transports that go through JSONRPCDispatcher: stdio, SSE, streamable HTTP, and InMemoryTransport.

Fix steps

0 of 2 done

Your check marks stay in this browser only.

How to check it worked

  • The report's snippet then prints progress 1 then 2 then 3 before call_tool returned, matching the in-process Client(server) order.

If it still fails

  • A callback that never awaits can still look ordered. The mis-order needs an await inside progress_callback.
  • The linked issue does not name a release that queues progress per request.

Sources and credit

Summarized in our own words from these public reports. Credit goes to their authors.

Provided as is, without warranty. Try changes in a non-production environment and back up first. Security-related steps: verify against the official docs. Terms · Request removal or attribution change

Hit an error we don't cover? Submit it