refactor: split RequestContext between server and client (#1987)

This commit is contained in:
Marcelo Trylesinski 2026-02-03 14:35:07 +01:00 • committed by GitHub
commit b1f7eec3cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 210 additions and 212 deletions

View file

@ -187,7 +187,7 @@ async def run_sse_retry(server_url: str) -> None:
async def default_elicitation_callback( async def default_elicitation_callback(
context: RequestContext[ClientSession, Any], # noqa: ARG001 context: RequestContext[ClientSession],
params: types.ElicitRequestParams, params: types.ElicitRequestParams,
) -> types.ElicitResult | types.ErrorData: ) -> types.ElicitResult | types.ErrorData:
"""Accept elicitation and apply defaults from the schema (SEP-1034).""" """Accept elicitation and apply defaults from the schema (SEP-1034)."""

View file

@ -229,7 +229,6 @@ from contextlib import asynccontextmanager
from dataclasses import dataclass from dataclasses import dataclass
from mcp.server.mcpserver import Context, MCPServer from mcp.server.mcpserver import Context, MCPServer
from mcp.server.session import ServerSession
# Mock database class for example # Mock database class for example
@ -275,7 +274,7 @@ mcp = MCPServer("My App", lifespan=app_lifespan)
# Access type-safe lifespan context in tools # Access type-safe lifespan context in tools
@mcp.tool() @mcp.tool()
def query_db(ctx: Context[ServerSession, AppContext]) -> str: def query_db(ctx: Context[AppContext]) -> str:
"""Tool that uses initialized resources.""" """Tool that uses initialized resources."""
db = ctx.request_context.lifespan_context.db db = ctx.request_context.lifespan_context.db
return db.query() return db.query()
@ -2135,7 +2134,7 @@ server_params = StdioServerParameters(
# Optional: create a sampling callback # Optional: create a sampling callback
async def handle_sampling_message( async def handle_sampling_message(
context: RequestContext[ClientSession, None], params: types.CreateMessageRequestParams context: RequestContext[ClientSession], params: types.CreateMessageRequestParams
) -> types.CreateMessageResult: ) -> types.CreateMessageResult:
print(f"Sampling request: {params.messages}") print(f"Sampling request: {params.messages}")
return types.CreateMessageResult( return types.CreateMessageResult(

View file

@ -371,6 +371,50 @@ async def handle_tool(name: str, arguments: dict) -> list[TextContent]:
await ctx.session.send_progress_notification(ctx.meta["progress_token"], 0.5, 100) await ctx.session.send_progress_notification(ctx.meta["progress_token"], 0.5, 100)
``` ```
### `RequestContext` and `ProgressContext` type parameters simplified
The `RequestContext` class has been split to separate shared fields from server-specific fields. The shared `RequestContext` now only takes 1 type parameter (the session type) instead of 3.
**`RequestContext` changes:**
- Type parameters reduced from `RequestContext[SessionT, LifespanContextT, RequestT]` to `RequestContext[SessionT]`
- Server-specific fields (`lifespan_context`, `experimental`, `request`, `close_sse_stream`, `close_standalone_sse_stream`) moved to new `ServerRequestContext` class in `mcp.server.context`
**`ProgressContext` changes:**
- Type parameters reduced from `ProgressContext[SendRequestT, SendNotificationT, SendResultT, ReceiveRequestT, ReceiveNotificationT]` to `ProgressContext[SessionT]`
**Before (v1):**
```python
from mcp.shared.context import RequestContext, LifespanContextT, RequestT
from mcp.shared.progress import ProgressContext
# RequestContext with 3 type parameters
ctx: RequestContext[ClientSession, LifespanContextT, RequestT]
# ProgressContext with 5 type parameters
progress_ctx: ProgressContext[SendRequestT, SendNotificationT, SendResultT, ReceiveRequestT, ReceiveNotificationT]
```
**After (v2):**
```python
from mcp.shared.context import RequestContext
from mcp.shared.progress import ProgressContext
# RequestContext with 1 type parameter
ctx: RequestContext[ClientSession]
# ProgressContext with 1 type parameter
progress_ctx: ProgressContext[ClientSession]
# For server-specific context with lifespan and request types
from mcp.server.context import ServerRequestContext, LifespanContextT, RequestT
server_ctx: ServerRequestContext[LifespanContextT, RequestT]
```
### Resource URI type changed from `AnyUrl` to `str` ### Resource URI type changed from `AnyUrl` to `str`
The `uri` field on resource-related types now uses `str` instead of Pydantic's `AnyUrl`. This aligns with the [MCP specification schema](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.ts) which defines URIs as plain strings (`uri: string`) without strict URL validation. This change allows relative paths like `users/me` that were previously rejected. The `uri` field on resource-related types now uses `str` instead of Pydantic's `AnyUrl`. This aligns with the [MCP specification schema](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.ts) which defines URIs as plain strings (`uri: string`) without strict URL validation. This change allows relative paths like `users/me` that were previously rejected.

View file

@ -7,7 +7,6 @@ This example demonstrates the spec-compliant polling pattern:
""" """
import asyncio import asyncio
from typing import Any
import click import click
from mcp import ClientSession from mcp import ClientSession
@ -24,7 +23,7 @@ from mcp.types import (
async def elicitation_callback( async def elicitation_callback(
context: RequestContext[ClientSession, Any], context: RequestContext[ClientSession],
params: ElicitRequestParams, params: ElicitRequestParams,
) -> ElicitResult: ) -> ElicitResult:
"""Handle elicitation requests from the server.""" """Handle elicitation requests from the server."""
@ -39,7 +38,7 @@ async def elicitation_callback(
async def sampling_callback( async def sampling_callback(
context: RequestContext[ClientSession, Any], context: RequestContext[ClientSession],
params: CreateMessageRequestParams, params: CreateMessageRequestParams,
) -> CreateMessageResult: ) -> CreateMessageResult:
"""Handle sampling requests from the server.""" """Handle sampling requests from the server."""

View file

@ -19,7 +19,7 @@ server_params = StdioServerParameters(
# Optional: create a sampling callback # Optional: create a sampling callback
async def handle_sampling_message( async def handle_sampling_message(
context: RequestContext[ClientSession, None], params: types.CreateMessageRequestParams context: RequestContext[ClientSession], params: types.CreateMessageRequestParams
) -> types.CreateMessageResult: ) -> types.CreateMessageResult:
print(f"Sampling request: {params.messages}") print(f"Sampling request: {params.messages}")
return types.CreateMessageResult( return types.CreateMessageResult(

View file

@ -38,7 +38,7 @@ from mcp.types import URL_ELICITATION_REQUIRED
async def handle_elicitation( async def handle_elicitation(
context: RequestContext[ClientSession, Any], context: RequestContext[ClientSession],
params: types.ElicitRequestParams, params: types.ElicitRequestParams,
) -> types.ElicitResult | types.ErrorData: ) -> types.ElicitResult | types.ErrorData:
"""Handle elicitation requests from the server. """Handle elicitation requests from the server.

View file

@ -5,7 +5,6 @@ from contextlib import asynccontextmanager
from dataclasses import dataclass from dataclasses import dataclass
from mcp.server.mcpserver import Context, MCPServer from mcp.server.mcpserver import Context, MCPServer
from mcp.server.session import ServerSession
# Mock database class for example # Mock database class for example
@ -51,7 +50,7 @@ mcp = MCPServer("My App", lifespan=app_lifespan)
# Access type-safe lifespan context in tools # Access type-safe lifespan context in tools
@mcp.tool() @mcp.tool()
def query_db(ctx: Context[ServerSession, AppContext]) -> str: def query_db(ctx: Context[AppContext]) -> str:
"""Tool that uses initialized resources.""" """Tool that uses initialized resources."""
db = ctx.request_context.lifespan_context.db db = ctx.request_context.lifespan_context.db
return db.query() return db.query()

View file

@ -11,8 +11,10 @@ Use cases:
- Server polls client's task status via tasks/get, tasks/result, etc. - Server polls client's task status via tasks/get, tasks/result, etc.
""" """
from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Protocol from typing import TYPE_CHECKING, Protocol
from pydantic import TypeAdapter from pydantic import TypeAdapter
@ -32,7 +34,7 @@ class GetTaskHandlerFnT(Protocol):
async def __call__( async def __call__(
self, self,
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.GetTaskRequestParams, params: types.GetTaskRequestParams,
) -> types.GetTaskResult | types.ErrorData: ... # pragma: no branch ) -> types.GetTaskResult | types.ErrorData: ... # pragma: no branch
@ -45,7 +47,7 @@ class GetTaskResultHandlerFnT(Protocol):
async def __call__( async def __call__(
self, self,
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.GetTaskPayloadRequestParams, params: types.GetTaskPayloadRequestParams,
) -> types.GetTaskPayloadResult | types.ErrorData: ... # pragma: no branch ) -> types.GetTaskPayloadResult | types.ErrorData: ... # pragma: no branch
@ -58,7 +60,7 @@ class ListTasksHandlerFnT(Protocol):
async def __call__( async def __call__(
self, self,
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.PaginatedRequestParams | None, params: types.PaginatedRequestParams | None,
) -> types.ListTasksResult | types.ErrorData: ... # pragma: no branch ) -> types.ListTasksResult | types.ErrorData: ... # pragma: no branch
@ -71,7 +73,7 @@ class CancelTaskHandlerFnT(Protocol):
async def __call__( async def __call__(
self, self,
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.CancelTaskRequestParams, params: types.CancelTaskRequestParams,
) -> types.CancelTaskResult | types.ErrorData: ... # pragma: no branch ) -> types.CancelTaskResult | types.ErrorData: ... # pragma: no branch
@ -88,7 +90,7 @@ class TaskAugmentedSamplingFnT(Protocol):
async def __call__( async def __call__(
self, self,
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.CreateMessageRequestParams, params: types.CreateMessageRequestParams,
task_metadata: types.TaskMetadata, task_metadata: types.TaskMetadata,
) -> types.CreateTaskResult | types.ErrorData: ... # pragma: no branch ) -> types.CreateTaskResult | types.ErrorData: ... # pragma: no branch
@ -106,14 +108,14 @@ class TaskAugmentedElicitationFnT(Protocol):
async def __call__( async def __call__(
self, self,
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.ElicitRequestParams, params: types.ElicitRequestParams,
task_metadata: types.TaskMetadata, task_metadata: types.TaskMetadata,
) -> types.CreateTaskResult | types.ErrorData: ... # pragma: no branch ) -> types.CreateTaskResult | types.ErrorData: ... # pragma: no branch
async def default_get_task_handler( async def default_get_task_handler(
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.GetTaskRequestParams, params: types.GetTaskRequestParams,
) -> types.GetTaskResult | types.ErrorData: ) -> types.GetTaskResult | types.ErrorData:
return types.ErrorData( return types.ErrorData(
@ -123,7 +125,7 @@ async def default_get_task_handler(
async def default_get_task_result_handler( async def default_get_task_result_handler(
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.GetTaskPayloadRequestParams, params: types.GetTaskPayloadRequestParams,
) -> types.GetTaskPayloadResult | types.ErrorData: ) -> types.GetTaskPayloadResult | types.ErrorData:
return types.ErrorData( return types.ErrorData(
@ -133,7 +135,7 @@ async def default_get_task_result_handler(
async def default_list_tasks_handler( async def default_list_tasks_handler(
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.PaginatedRequestParams | None, params: types.PaginatedRequestParams | None,
) -> types.ListTasksResult | types.ErrorData: ) -> types.ListTasksResult | types.ErrorData:
return types.ErrorData( return types.ErrorData(
@ -143,7 +145,7 @@ async def default_list_tasks_handler(
async def default_cancel_task_handler( async def default_cancel_task_handler(
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.CancelTaskRequestParams, params: types.CancelTaskRequestParams,
) -> types.CancelTaskResult | types.ErrorData: ) -> types.CancelTaskResult | types.ErrorData:
return types.ErrorData( return types.ErrorData(
@ -153,7 +155,7 @@ async def default_cancel_task_handler(
async def default_task_augmented_sampling( async def default_task_augmented_sampling(
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.CreateMessageRequestParams, params: types.CreateMessageRequestParams,
task_metadata: types.TaskMetadata, task_metadata: types.TaskMetadata,
) -> types.CreateTaskResult | types.ErrorData: ) -> types.CreateTaskResult | types.ErrorData:
@ -164,7 +166,7 @@ async def default_task_augmented_sampling(
async def default_task_augmented_elicitation( async def default_task_augmented_elicitation(
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.ElicitRequestParams, params: types.ElicitRequestParams,
task_metadata: types.TaskMetadata, task_metadata: types.TaskMetadata,
) -> types.CreateTaskResult | types.ErrorData: ) -> types.CreateTaskResult | types.ErrorData:
@ -248,7 +250,7 @@ class ExperimentalTaskHandlers:
async def handle_request( async def handle_request(
self, self,
ctx: RequestContext["ClientSession", Any], ctx: RequestContext[ClientSession],
responder: RequestResponder[types.ServerRequest, types.ClientResult], responder: RequestResponder[types.ServerRequest, types.ClientResult],
) -> None: ) -> None:
"""Handle a task-related request from the server. """Handle a task-related request from the server.

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import logging import logging
from typing import Any, Protocol from typing import Any, Protocol
@ -22,7 +24,7 @@ logger = logging.getLogger("client")
class SamplingFnT(Protocol): class SamplingFnT(Protocol):
async def __call__( async def __call__(
self, self,
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.CreateMessageRequestParams, params: types.CreateMessageRequestParams,
) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData: ... # pragma: no branch ) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData: ... # pragma: no branch
@ -30,22 +32,19 @@ class SamplingFnT(Protocol):
class ElicitationFnT(Protocol): class ElicitationFnT(Protocol):
async def __call__( async def __call__(
self, self,
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.ElicitRequestParams, params: types.ElicitRequestParams,
) -> types.ElicitResult | types.ErrorData: ... # pragma: no branch ) -> types.ElicitResult | types.ErrorData: ... # pragma: no branch
class ListRootsFnT(Protocol): class ListRootsFnT(Protocol):
async def __call__( async def __call__(
self, context: RequestContext["ClientSession", Any] self, context: RequestContext[ClientSession]
) -> types.ListRootsResult | types.ErrorData: ... # pragma: no branch ) -> types.ListRootsResult | types.ErrorData: ... # pragma: no branch
class LoggingFnT(Protocol): class LoggingFnT(Protocol):
async def __call__( async def __call__(self, params: types.LoggingMessageNotificationParams) -> None: ... # pragma: no branch
self,
params: types.LoggingMessageNotificationParams,
) -> None: ... # pragma: no branch
class MessageHandlerFnT(Protocol): class MessageHandlerFnT(Protocol):
@ -62,7 +61,7 @@ async def _default_message_handler(
async def _default_sampling_callback( async def _default_sampling_callback(
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.CreateMessageRequestParams, params: types.CreateMessageRequestParams,
) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData: ) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData:
return types.ErrorData( return types.ErrorData(
@ -72,7 +71,7 @@ async def _default_sampling_callback(
async def _default_elicitation_callback( async def _default_elicitation_callback(
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
params: types.ElicitRequestParams, params: types.ElicitRequestParams,
) -> types.ElicitResult | types.ErrorData: ) -> types.ElicitResult | types.ErrorData:
return types.ErrorData( # pragma: no cover return types.ErrorData( # pragma: no cover
@ -82,7 +81,7 @@ async def _default_elicitation_callback(
async def _default_list_roots_callback( async def _default_list_roots_callback(
context: RequestContext["ClientSession", Any], context: RequestContext[ClientSession],
) -> types.ListRootsResult | types.ErrorData: ) -> types.ListRootsResult | types.ErrorData:
return types.ErrorData( return types.ErrorData(
code=types.INVALID_REQUEST, code=types.INVALID_REQUEST,
@ -153,10 +152,7 @@ class ClientSession(
else None else None
) )
elicitation = ( elicitation = (
types.ElicitationCapability( types.ElicitationCapability(form=types.FormElicitationCapability(), url=types.UrlElicitationCapability())
form=types.FormElicitationCapability(),
url=types.UrlElicitationCapability(),
)
if self._elicitation_callback is not _default_elicitation_callback if self._elicitation_callback is not _default_elicitation_callback
else None else None
) )
@ -414,12 +410,7 @@ class ClientSession(
await self.send_notification(types.RootsListChangedNotification()) await self.send_notification(types.RootsListChangedNotification())
async def _received_request(self, responder: RequestResponder[types.ServerRequest, types.ClientResult]) -> None: async def _received_request(self, responder: RequestResponder[types.ServerRequest, types.ClientResult]) -> None:
ctx = RequestContext[ClientSession, Any]( ctx = RequestContext[ClientSession](request_id=responder.request_id, meta=responder.request_meta, session=self)
request_id=responder.request_id,
meta=responder.request_meta,
session=self,
lifespan_context=None,
)
# Delegate to experimental task handler if applicable # Delegate to experimental task handler if applicable
if self._task_handlers.handles_request(responder.request): if self._task_handlers.handles_request(responder.request):

23
src/mcp/server/context.py Normal file
View file

@ -0,0 +1,23 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Generic
from typing_extensions import TypeVar
from mcp.server.experimental.request_context import Experimental
from mcp.server.session import ServerSession
from mcp.shared.context import RequestContext
from mcp.shared.message import CloseSSEStreamCallback
LifespanContextT = TypeVar("LifespanContextT")
RequestT = TypeVar("RequestT", default=Any)
@dataclass(kw_only=True)
class ServerRequestContext(RequestContext[ServerSession], Generic[LifespanContextT, RequestT]):
lifespan_context: LifespanContextT
experimental: Experimental
request: RequestT | None = None
close_sse_stream: CloseSSEStreamCallback | None = None
close_standalone_sse_stream: CloseSSEStreamCallback | None = None

View file

@ -57,10 +57,7 @@ class Experimental:
return self._client_capabilities.tasks is not None return self._client_capabilities.tasks is not None
def validate_task_mode( def validate_task_mode(
self, self, tool_task_mode: TaskExecutionMode | None, *, raise_error: bool = True
tool_task_mode: TaskExecutionMode | None,
*,
raise_error: bool = True,
) -> ErrorData | None: ) -> ErrorData | None:
"""Validate that the request is compatible with the tool's task execution mode. """Validate that the request is compatible with the tool's task execution mode.
@ -95,12 +92,7 @@ class Experimental:
return error return error
def validate_for_tool( def validate_for_tool(self, tool: Tool, *, raise_error: bool = True) -> ErrorData | None:
self,
tool: Tool,
*,
raise_error: bool = True,
) -> ErrorData | None:
"""Validate that the request is compatible with the given tool. """Validate that the request is compatible with the given tool.
Convenience wrapper around validate_task_mode that extracts the mode from a Tool. Convenience wrapper around validate_task_mode that extracts the mode from a Tool.

View file

@ -91,6 +91,7 @@ from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend, RequireAut
from mcp.server.auth.provider import OAuthAuthorizationServerProvider, TokenVerifier from mcp.server.auth.provider import OAuthAuthorizationServerProvider, TokenVerifier
from mcp.server.auth.routes import build_resource_metadata_url, create_auth_routes, create_protected_resource_routes from mcp.server.auth.routes import build_resource_metadata_url, create_auth_routes, create_protected_resource_routes
from mcp.server.auth.settings import AuthSettings from mcp.server.auth.settings import AuthSettings
from mcp.server.context import ServerRequestContext
from mcp.server.experimental.request_context import Experimental from mcp.server.experimental.request_context import Experimental
from mcp.server.lowlevel.experimental import ExperimentalHandlers from mcp.server.lowlevel.experimental import ExperimentalHandlers
from mcp.server.lowlevel.func_inspection import create_call_wrapper from mcp.server.lowlevel.func_inspection import create_call_wrapper
@ -100,7 +101,6 @@ from mcp.server.session import ServerSession
from mcp.server.streamable_http import EventStore from mcp.server.streamable_http import EventStore
from mcp.server.streamable_http_manager import StreamableHTTPASGIApp, StreamableHTTPSessionManager from mcp.server.streamable_http_manager import StreamableHTTPASGIApp, StreamableHTTPSessionManager
from mcp.server.transport_security import TransportSecuritySettings from mcp.server.transport_security import TransportSecuritySettings
from mcp.shared.context import RequestContext
from mcp.shared.exceptions import MCPError, UrlElicitationRequiredError from mcp.shared.exceptions import MCPError, UrlElicitationRequiredError
from mcp.shared.message import ServerMessageMetadata, SessionMessage from mcp.shared.message import ServerMessageMetadata, SessionMessage
from mcp.shared.session import RequestResponder from mcp.shared.session import RequestResponder
@ -117,16 +117,11 @@ UnstructuredContent: TypeAlias = Iterable[types.ContentBlock]
CombinationContent: TypeAlias = tuple[UnstructuredContent, StructuredContent] CombinationContent: TypeAlias = tuple[UnstructuredContent, StructuredContent]
# This will be properly typed in each Server instance's context # This will be properly typed in each Server instance's context
request_ctx: contextvars.ContextVar[RequestContext[ServerSession, Any, Any]] = contextvars.ContextVar("request_ctx") request_ctx: contextvars.ContextVar[ServerRequestContext[Any, Any]] = contextvars.ContextVar("request_ctx")
class NotificationOptions: class NotificationOptions:
def __init__( def __init__(self, prompts_changed: bool = False, resources_changed: bool = False, tools_changed: bool = False):
self,
prompts_changed: bool = False,
resources_changed: bool = False,
tools_changed: bool = False,
):
self.prompts_changed = prompts_changed self.prompts_changed = prompts_changed
self.resources_changed = resources_changed self.resources_changed = resources_changed
self.tools_changed = tools_changed self.tools_changed = tools_changed
@ -253,9 +248,7 @@ class Server(Generic[LifespanResultT, RequestT]):
return capabilities return capabilities
@property @property
def request_context( def request_context(self) -> ServerRequestContext[LifespanResultT, RequestT]:
self,
) -> RequestContext[ServerSession, LifespanResultT, RequestT]:
"""If called outside of a request context, this will raise a LookupError.""" """If called outside of a request context, this will raise a LookupError."""
return request_ctx.get() return request_ctx.get()
@ -762,12 +755,12 @@ class Server(Generic[LifespanResultT, RequestT]):
if hasattr(req, "params") and req.params is not None: if hasattr(req, "params") and req.params is not None:
task_metadata = getattr(req.params, "task", None) task_metadata = getattr(req.params, "task", None)
token = request_ctx.set( token = request_ctx.set(
RequestContext( ServerRequestContext(
message.request_id, request_id=message.request_id,
message.request_meta, meta=message.request_meta,
session, session=session,
lifespan_context, lifespan_context=lifespan_context,
Experimental( experimental=Experimental(
task_metadata=task_metadata, task_metadata=task_metadata,
_client_capabilities=client_capabilities, _client_capabilities=client_capabilities,
_session=session, _session=session,

View file

@ -14,9 +14,8 @@ from mcp.server.mcpserver.utilities.func_metadata import func_metadata
from mcp.types import ContentBlock, Icon, TextContent from mcp.types import ContentBlock, Icon, TextContent
if TYPE_CHECKING: if TYPE_CHECKING:
from mcp.server.context import LifespanContextT, RequestT
from mcp.server.mcpserver.server import Context from mcp.server.mcpserver.server import Context
from mcp.server.session import ServerSessionT
from mcp.shared.context import LifespanContextT, RequestT
class Message(BaseModel): class Message(BaseModel):
@ -137,7 +136,7 @@ class Prompt(BaseModel):
async def render( async def render(
self, self,
arguments: dict[str, Any] | None = None, arguments: dict[str, Any] | None = None,
context: Context[ServerSessionT, LifespanContextT, RequestT] | None = None, context: Context[LifespanContextT, RequestT] | None = None,
) -> list[Message]: ) -> list[Message]:
"""Render the prompt with arguments.""" """Render the prompt with arguments."""
# Validate required arguments # Validate required arguments

View file

@ -8,9 +8,8 @@ from mcp.server.mcpserver.prompts.base import Message, Prompt
from mcp.server.mcpserver.utilities.logging import get_logger from mcp.server.mcpserver.utilities.logging import get_logger
if TYPE_CHECKING: if TYPE_CHECKING:
from mcp.server.context import LifespanContextT, RequestT
from mcp.server.mcpserver.server import Context from mcp.server.mcpserver.server import Context
from mcp.server.session import ServerSessionT
from mcp.shared.context import LifespanContextT, RequestT
logger = get_logger(__name__) logger = get_logger(__name__)
@ -50,7 +49,7 @@ class PromptManager:
self, self,
name: str, name: str,
arguments: dict[str, Any] | None = None, arguments: dict[str, Any] | None = None,
context: Context[ServerSessionT, LifespanContextT, RequestT] | None = None, context: Context[LifespanContextT, RequestT] | None = None,
) -> list[Message]: ) -> list[Message]:
"""Render a prompt by name with arguments.""" """Render a prompt by name with arguments."""
prompt = self.get_prompt(name) prompt = self.get_prompt(name)

View file

@ -13,9 +13,8 @@ from mcp.server.mcpserver.utilities.logging import get_logger
from mcp.types import Annotations, Icon from mcp.types import Annotations, Icon
if TYPE_CHECKING: if TYPE_CHECKING:
from mcp.server.context import LifespanContextT, RequestT
from mcp.server.mcpserver.server import Context from mcp.server.mcpserver.server import Context
from mcp.server.session import ServerSessionT
from mcp.shared.context import LifespanContextT, RequestT
logger = get_logger(__name__) logger = get_logger(__name__)
@ -82,7 +81,7 @@ class ResourceManager:
return template return template
async def get_resource( async def get_resource(
self, uri: AnyUrl | str, context: Context[ServerSessionT, LifespanContextT, RequestT] | None = None self, uri: AnyUrl | str, context: Context[LifespanContextT, RequestT] | None = None
) -> Resource: ) -> Resource:
"""Get resource by URI, checking concrete resources first, then templates.""" """Get resource by URI, checking concrete resources first, then templates."""
uri_str = str(uri) uri_str = str(uri)

View file

@ -16,9 +16,8 @@ from mcp.server.mcpserver.utilities.func_metadata import func_metadata
from mcp.types import Annotations, Icon from mcp.types import Annotations, Icon
if TYPE_CHECKING: if TYPE_CHECKING:
from mcp.server.context import LifespanContextT, RequestT
from mcp.server.mcpserver.server import Context from mcp.server.mcpserver.server import Context
from mcp.server.session import ServerSessionT
from mcp.shared.context import LifespanContextT, RequestT
class ResourceTemplate(BaseModel): class ResourceTemplate(BaseModel):
@ -100,7 +99,7 @@ class ResourceTemplate(BaseModel):
self, self,
uri: str, uri: str,
params: dict[str, Any], params: dict[str, Any],
context: Context[ServerSessionT, LifespanContextT, RequestT] | None = None, context: Context[LifespanContextT, RequestT] | None = None,
) -> Resource: ) -> Resource:
"""Create a resource from the template with the given parameters.""" """Create a resource from the template with the given parameters."""
try: try:

View file

@ -25,6 +25,7 @@ from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend, RequireAuthMiddleware from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend, RequireAuthMiddleware
from mcp.server.auth.provider import OAuthAuthorizationServerProvider, ProviderTokenVerifier, TokenVerifier from mcp.server.auth.provider import OAuthAuthorizationServerProvider, ProviderTokenVerifier, TokenVerifier
from mcp.server.auth.settings import AuthSettings from mcp.server.auth.settings import AuthSettings
from mcp.server.context import LifespanContextT, RequestT, ServerRequestContext
from mcp.server.elicitation import ElicitationResult, ElicitSchemaModelT, UrlElicitationResult, elicit_with_validation from mcp.server.elicitation import ElicitationResult, ElicitSchemaModelT, UrlElicitationResult, elicit_with_validation
from mcp.server.elicitation import elicit_url as _elicit_url from mcp.server.elicitation import elicit_url as _elicit_url
from mcp.server.lowlevel.helper_types import ReadResourceContents from mcp.server.lowlevel.helper_types import ReadResourceContents
@ -36,13 +37,11 @@ from mcp.server.mcpserver.resources import FunctionResource, Resource, ResourceM
from mcp.server.mcpserver.tools import Tool, ToolManager from mcp.server.mcpserver.tools import Tool, ToolManager
from mcp.server.mcpserver.utilities.context_injection import find_context_parameter from mcp.server.mcpserver.utilities.context_injection import find_context_parameter
from mcp.server.mcpserver.utilities.logging import configure_logging, get_logger from mcp.server.mcpserver.utilities.logging import configure_logging, get_logger
from mcp.server.session import ServerSession, ServerSessionT
from mcp.server.sse import SseServerTransport from mcp.server.sse import SseServerTransport
from mcp.server.stdio import stdio_server from mcp.server.stdio import stdio_server
from mcp.server.streamable_http import EventStore from mcp.server.streamable_http import EventStore
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
from mcp.server.transport_security import TransportSecuritySettings from mcp.server.transport_security import TransportSecuritySettings
from mcp.shared.context import LifespanContextT, RequestContext, RequestT
from mcp.types import Annotations, ContentBlock, GetPromptResult, Icon, ToolAnnotations from mcp.types import Annotations, ContentBlock, GetPromptResult, Icon, ToolAnnotations
from mcp.types import Prompt as MCPPrompt from mcp.types import Prompt as MCPPrompt
from mcp.types import PromptArgument as MCPPromptArgument from mcp.types import PromptArgument as MCPPromptArgument
@ -294,7 +293,7 @@ class MCPServer(Generic[LifespanResultT]):
for info in tools for info in tools
] ]
def get_context(self) -> Context[ServerSession, LifespanResultT, Request]: def get_context(self) -> Context[LifespanResultT, Request]:
"""Returns a Context object. Note that the context will only be valid """Returns a Context object. Note that the context will only be valid
during a request; outside a request, most methods will error. during a request; outside a request, most methods will error.
""" """
@ -972,7 +971,7 @@ class MCPServer(Generic[LifespanResultT]):
raise ValueError(str(e)) raise ValueError(str(e))
class Context(BaseModel, Generic[ServerSessionT, LifespanContextT, RequestT]): class Context(BaseModel, Generic[LifespanContextT, RequestT]):
"""Context object providing access to MCP capabilities. """Context object providing access to MCP capabilities.
This provides a cleaner interface to MCP's RequestContext functionality. This provides a cleaner interface to MCP's RequestContext functionality.
@ -1006,14 +1005,15 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT, RequestT]):
The context is optional - tools that don't need it can omit the parameter. The context is optional - tools that don't need it can omit the parameter.
""" """
_request_context: RequestContext[ServerSessionT, LifespanContextT, RequestT] | None _request_context: ServerRequestContext[LifespanContextT, RequestT] | None
_mcp_server: MCPServer | None _mcp_server: MCPServer | None
def __init__( def __init__(
self, self,
*, *,
request_context: (RequestContext[ServerSessionT, LifespanContextT, RequestT] | None) = None, request_context: ServerRequestContext[LifespanContextT, RequestT] | None = None,
mcp_server: MCPServer | None = None, mcp_server: MCPServer | None = None,
# TODO(Marcelo): We should drop this kwargs parameter.
**kwargs: Any, **kwargs: Any,
): ):
super().__init__(**kwargs) super().__init__(**kwargs)
@ -1028,9 +1028,7 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT, RequestT]):
return self._mcp_server # pragma: no cover return self._mcp_server # pragma: no cover
@property @property
def request_context( def request_context(self) -> ServerRequestContext[LifespanContextT, RequestT]:
self,
) -> RequestContext[ServerSessionT, LifespanContextT, RequestT]:
"""Access to the underlying request context.""" """Access to the underlying request context."""
if self._request_context is None: # pragma: no cover if self._request_context is None: # pragma: no cover
raise ValueError("Context is not available outside of a request") raise ValueError("Context is not available outside of a request")

View file

@ -16,9 +16,8 @@ from mcp.shared.tool_name_validation import validate_and_warn_tool_name
from mcp.types import Icon, ToolAnnotations from mcp.types import Icon, ToolAnnotations
if TYPE_CHECKING: if TYPE_CHECKING:
from mcp.server.context import LifespanContextT, RequestT
from mcp.server.mcpserver.server import Context from mcp.server.mcpserver.server import Context
from mcp.server.session import ServerSessionT
from mcp.shared.context import LifespanContextT, RequestT
class Tool(BaseModel): class Tool(BaseModel):
@ -93,7 +92,7 @@ class Tool(BaseModel):
async def run( async def run(
self, self,
arguments: dict[str, Any], arguments: dict[str, Any],
context: Context[ServerSessionT, LifespanContextT, RequestT] | None = None, context: Context[LifespanContextT, RequestT] | None = None,
convert_result: bool = False, convert_result: bool = False,
) -> Any: ) -> Any:
"""Run the tool with arguments.""" """Run the tool with arguments."""

View file

@ -6,12 +6,11 @@ from typing import TYPE_CHECKING, Any
from mcp.server.mcpserver.exceptions import ToolError from mcp.server.mcpserver.exceptions import ToolError
from mcp.server.mcpserver.tools.base import Tool from mcp.server.mcpserver.tools.base import Tool
from mcp.server.mcpserver.utilities.logging import get_logger from mcp.server.mcpserver.utilities.logging import get_logger
from mcp.shared.context import LifespanContextT, RequestT
from mcp.types import Icon, ToolAnnotations from mcp.types import Icon, ToolAnnotations
if TYPE_CHECKING: if TYPE_CHECKING:
from mcp.server.context import LifespanContextT, RequestT
from mcp.server.mcpserver.server import Context from mcp.server.mcpserver.server import Context
from mcp.server.session import ServerSessionT
logger = get_logger(__name__) logger = get_logger(__name__)
@ -82,7 +81,7 @@ class ToolManager:
self, self,
name: str, name: str,
arguments: dict[str, Any], arguments: dict[str, Any],
context: Context[ServerSessionT, LifespanContextT, RequestT] | None = None, context: Context[LifespanContextT, RequestT] | None = None,
convert_result: bool = False, convert_result: bool = False,
) -> Any: ) -> Any:
"""Call a tool by name with arguments.""" """Call a tool by name with arguments."""

View file

@ -1,30 +1,20 @@
"""Request context for MCP handlers.""" """Request context for MCP handlers."""
from dataclasses import dataclass, field from dataclasses import dataclass
from typing import Any, Generic from typing import Any, Generic
from typing_extensions import TypeVar from typing_extensions import TypeVar
from mcp.shared.message import CloseSSEStreamCallback
from mcp.shared.session import BaseSession from mcp.shared.session import BaseSession
from mcp.types import RequestId, RequestParamsMeta from mcp.types import RequestId, RequestParamsMeta
SessionT = TypeVar("SessionT", bound=BaseSession[Any, Any, Any, Any, Any]) SessionT = TypeVar("SessionT", bound=BaseSession[Any, Any, Any, Any, Any])
LifespanContextT = TypeVar("LifespanContextT")
RequestT = TypeVar("RequestT", default=Any)
@dataclass @dataclass(kw_only=True)
class RequestContext(Generic[SessionT, LifespanContextT, RequestT]): class RequestContext(Generic[SessionT]):
"""Common context for handling incoming requests."""
request_id: RequestId request_id: RequestId
meta: RequestParamsMeta | None meta: RequestParamsMeta | None
session: SessionT session: SessionT
lifespan_context: LifespanContextT
# NOTE: This is typed as Any to avoid circular imports. The actual type is
# mcp.server.experimental.request_context.Experimental, but importing it here
# triggers mcp.server.__init__ -> mcpserver -> tools -> back to this module.
# The Server sets this to an Experimental instance at runtime.
experimental: Any = field(default=None)
request: RequestT | None = None
close_sse_stream: CloseSSEStreamCallback | None = None
close_standalone_sse_stream: CloseSSEStreamCallback | None = None

Some files were not shown because too many files have changed in this diff Show more