Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/mcp/server/context.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import logging
from collections.abc import Awaitable, Callable, Mapping
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import Any, Generic, Protocol

import anyio
from mcp_types import LoggingLevel, RequestId, RequestParamsMeta
from pydantic import BaseModel
from typing_extensions import TypeVar, deprecated
Expand Down Expand Up @@ -41,6 +42,7 @@ class ServerRequestContext(Generic[LifespanContextT, RequestT]):
lifespan_context: LifespanContextT
protocol_version: str
method: str
cancel_requested: anyio.Event = field(default_factory=anyio.Event)
params: Mapping[str, Any] | None = None
request_id: RequestId | None = None
meta: RequestParamsMeta | None = None
Expand Down
6 changes: 6 additions & 0 deletions src/mcp/server/mcpserver/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections.abc import Iterable, Mapping
from typing import TYPE_CHECKING, Any, Generic, cast

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

@property
def cancel_requested(self) -> anyio.Event:
"""Set when the client requests cancellation of the current request."""
return self.request_context.cancel_requested

def _nested_invocation(self) -> Context[LifespanContextT, RequestT]:
"""A Context for invoking another handler's function from inside this request.

Expand Down
1 change: 1 addition & 0 deletions src/mcp/server/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ def _make_context(
session=session,
lifespan_context=self.lifespan_state,
method=method,
cancel_requested=dctx.cancel_requested,
params=params,
request_id=dctx.request_id,
meta=meta,
Expand Down
27 changes: 27 additions & 0 deletions tests/server/mcpserver/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1970,6 +1970,33 @@ def _request_context(request: object | None) -> ServerRequestContext[None, objec
)


def test_context_cancel_requested_exposes_request_cancellation_event():
cancellation_event = anyio.Event()

request_context = ServerRequestContext(
session=AsyncMock(),
method="tools/call",
lifespan_context=None,
protocol_version="2025-11-25",
cancel_requested=cancellation_event,
)

ctx = Context(request_context=request_context, mcp_server=MagicMock())

assert ctx.cancel_requested is cancellation_event

cancellation_event.set()

assert ctx.cancel_requested.is_set()


def test_context_cancel_requested_raises_outside_request():
ctx = Context(mcp_server=MagicMock())

with pytest.raises(ValueError, match="Context is not available outside of a request"):
_ = ctx.cancel_requested


def test_context_headers_returns_request_headers():
request = SimpleNamespace(headers={"x-github-user": "octocat"})
ctx = Context(request_context=_request_context(request), mcp_server=MagicMock())
Expand Down
Loading