Skip to content

Commit d4c364e

Browse files
authored
test: add regression test for issue #2001 (progress related_request_id)
Adds a dedicated regression test file for issue #2001. Context.report_progress() was silently dropping progress notifications in stateless HTTP / SSE transports because send_progress_notification() was called without related_request_id. The fix (already in main via mcpserver/context.py) passes related_request_id=self.request_id, consistent with send_log_message(). This test file covers: - The primary regression: related_request_id is forwarded on every call - Edge case: no progress_token -> notification is skipped (no-op) - Edge case: integer progress_token (e.g. 0) works correctly Closes #2001
1 parent 616476f commit d4c364e

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""Regression test for https://github.com/modelcontextprotocol/python-sdk/issues/2001.
2+
3+
Progress notifications not delivered via SSE in stateless HTTP mode.
4+
5+
Root cause: Context.report_progress() was calling send_progress_notification()
6+
without passing related_request_id. The SSE / streamable-HTTP transport uses
7+
that field to route server-initiated messages back to the correct client stream;
8+
without it notifications are silently dropped.
9+
10+
Fix: pass related_request_id=self.request_id in send_progress_notification(),
11+
consistent with how send_log_message() already works.
12+
"""
13+
14+
from unittest.mock import AsyncMock, MagicMock
15+
16+
import pytest
17+
18+
from mcp.server.context import ServerRequestContext
19+
from mcp.server.experimental.request_context import Experimental
20+
from mcp.server.mcpserver import Context
21+
22+
pytestmark = pytest.mark.anyio
23+
24+
25+
async def test_report_progress_passes_related_request_id() -> None:
26+
"""report_progress must forward request_id as related_request_id.
27+
28+
Without related_request_id the streamable-HTTP transport cannot route
29+
progress notifications to the correct SSE stream; they are silently
30+
dropped. Regression test for issue #2001.
31+
"""
32+
mock_session = AsyncMock()
33+
mock_session.send_progress_notification = AsyncMock()
34+
35+
request_context = ServerRequestContext(
36+
request_id="req-2001",
37+
session=mock_session,
38+
meta={"progress_token": "tok-progress"},
39+
lifespan_context=None,
40+
experimental=Experimental(),
41+
)
42+
43+
ctx = Context(request_context=request_context, mcp_server=MagicMock())
44+
45+
await ctx.report_progress(25, 100, message="quarter done")
46+
await ctx.report_progress(50, 100)
47+
await ctx.report_progress(100, 100, message="complete")
48+
49+
assert mock_session.send_progress_notification.call_count == 3
50+
51+
mock_session.send_progress_notification.assert_any_call(
52+
progress_token="tok-progress",
53+
progress=25.0,
54+
total=100.0,
55+
message="quarter done",
56+
related_request_id="req-2001",
57+
)
58+
mock_session.send_progress_notification.assert_any_call(
59+
progress_token="tok-progress",
60+
progress=50.0,
61+
total=100.0,
62+
message=None,
63+
related_request_id="req-2001",
64+
)
65+
mock_session.send_progress_notification.assert_any_call(
66+
progress_token="tok-progress",
67+
progress=100.0,
68+
total=100.0,
69+
message="complete",
70+
related_request_id="req-2001",
71+
)
72+
73+
74+
async def test_report_progress_no_token_skips_notification() -> None:
75+
"""report_progress is a no-op when no progress_token is present."""
76+
mock_session = AsyncMock()
77+
mock_session.send_progress_notification = AsyncMock()
78+
79+
request_context = ServerRequestContext(
80+
request_id="req-no-token",
81+
session=mock_session,
82+
meta={},
83+
lifespan_context=None,
84+
experimental=Experimental(),
85+
)
86+
87+
ctx = Context(request_context=request_context, mcp_server=MagicMock())
88+
89+
await ctx.report_progress(50, 100)
90+
91+
mock_session.send_progress_notification.assert_not_called()
92+
93+
94+
async def test_report_progress_integer_token() -> None:
95+
"""report_progress works when progress_token is an integer (e.g. 0)."""
96+
mock_session = AsyncMock()
97+
mock_session.send_progress_notification = AsyncMock()
98+
99+
request_context = ServerRequestContext(
100+
request_id="req-int-token",
101+
session=mock_session,
102+
meta={"progress_token": 0},
103+
lifespan_context=None,
104+
experimental=Experimental(),
105+
)
106+
107+
ctx = Context(request_context=request_context, mcp_server=MagicMock())
108+
109+
await ctx.report_progress(1, 10)
110+
111+
mock_session.send_progress_notification.assert_awaited_once_with(
112+
progress_token=0,
113+
progress=1.0,
114+
total=10.0,
115+
message=None,
116+
related_request_id="req-int-token",
117+
)

0 commit comments

Comments
 (0)