Skip to content

Commit e6944a5

Browse files
committed
fix(server): expose request cancellation on MCPServer context
1 parent 0921d94 commit e6944a5

4 files changed

Lines changed: 37 additions & 1 deletion

File tree

src/mcp/server/context.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import logging
22
from collections.abc import Awaitable, Callable, Mapping
3-
from dataclasses import dataclass
3+
from dataclasses import dataclass, field
44
from typing import Any, Generic, Protocol
55

6+
import anyio
67
from mcp_types import LoggingLevel, RequestId, RequestParamsMeta
78
from pydantic import BaseModel
89
from typing_extensions import TypeVar, deprecated
@@ -41,6 +42,7 @@ class ServerRequestContext(Generic[LifespanContextT, RequestT]):
4142
lifespan_context: LifespanContextT
4243
protocol_version: str
4344
method: str
45+
cancel_requested: anyio.Event = field(default_factory=anyio.Event)
4446
params: Mapping[str, Any] | None = None
4547
request_id: RequestId | None = None
4648
meta: RequestParamsMeta | None = None

src/mcp/server/mcpserver/context.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from collections.abc import Iterable, Mapping
44
from typing import TYPE_CHECKING, Any, Generic, cast
55

6+
import anyio
67
from mcp_types import ClientCapabilities, InputRequiredResult, InputResponseRequestParams, InputResponses, LoggingLevel
78
from pydantic import AnyUrl, BaseModel
89
from typing_extensions import deprecated
@@ -98,6 +99,11 @@ def request_context(self) -> ServerRequestContext[LifespanContextT, RequestT]:
9899
raise ValueError("Context is not available outside of a request")
99100
return self._request_context
100101

102+
@property
103+
def cancel_requested(self) -> anyio.Event:
104+
"""Set when the client requests cancellation of the current request."""
105+
return self.request_context.cancel_requested
106+
101107
def _nested_invocation(self) -> Context[LifespanContextT, RequestT]:
102108
"""A Context for invoking another handler's function from inside this request.
103109

src/mcp/server/runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ def _make_context(
329329
session=session,
330330
lifespan_context=self.lifespan_state,
331331
method=method,
332+
cancel_requested=dctx.cancel_requested,
332333
params=params,
333334
request_id=dctx.request_id,
334335
meta=meta,

tests/server/mcpserver/test_server.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,6 +1970,33 @@ def _request_context(request: object | None) -> ServerRequestContext[None, objec
19701970
)
19711971

19721972

1973+
def test_context_cancel_requested_exposes_request_cancellation_event():
1974+
cancellation_event = anyio.Event()
1975+
1976+
request_context = ServerRequestContext(
1977+
session=AsyncMock(),
1978+
method="tools/call",
1979+
lifespan_context=None,
1980+
protocol_version="2025-11-25",
1981+
cancel_requested=cancellation_event,
1982+
)
1983+
1984+
ctx = Context(request_context=request_context, mcp_server=MagicMock())
1985+
1986+
assert ctx.cancel_requested is cancellation_event
1987+
1988+
cancellation_event.set()
1989+
1990+
assert ctx.cancel_requested.is_set()
1991+
1992+
1993+
def test_context_cancel_requested_raises_outside_request():
1994+
ctx = Context(mcp_server=MagicMock())
1995+
1996+
with pytest.raises(ValueError, match="Context is not available outside of a request"):
1997+
_ = ctx.cancel_requested
1998+
1999+
19732000
def test_context_headers_returns_request_headers():
19742001
request = SimpleNamespace(headers={"x-github-user": "octocat"})
19752002
ctx = Context(request_context=_request_context(request), mcp_server=MagicMock())

0 commit comments

Comments
 (0)