diff --git a/fastmcp_slim/fastmcp/cli/apps_dev.py b/fastmcp_slim/fastmcp/cli/apps_dev.py index 49cbc771d..2636b9189 100644 --- a/fastmcp_slim/fastmcp/cli/apps_dev.py +++ b/fastmcp_slim/fastmcp/cli/apps_dev.py @@ -1217,7 +1217,7 @@ async def _list_tools(mcp_url: str) -> list[dict[str, Any]]: return [] try: - async with streamable_http_client(mcp_url) as (read, write, _): # noqa: SIM117 + async with streamable_http_client(mcp_url) as (read, write): # noqa: SIM117 async with ClientSession(read, write) as session: await session.initialize() result = await session.list_tools() @@ -1232,15 +1232,14 @@ async def _read_mcp_resource(mcp_url: str, uri: str) -> str | None: try: from mcp import ClientSession from mcp.client.streamable_http import streamable_http_client - from pydantic import AnyUrl except ImportError: return None try: - async with streamable_http_client(mcp_url) as (read, write, _): # noqa: SIM117 + async with streamable_http_client(mcp_url) as (read, write): # noqa: SIM117 async with ClientSession(read, write) as session: await session.initialize() - result = await session.read_resource(AnyUrl(uri)) + result = await session.read_resource(uri) for content in result.contents: text = getattr(content, "text", None) if text: diff --git a/fastmcp_slim/fastmcp/client/client.py b/fastmcp_slim/fastmcp/client/client.py index aab21fed7..53f340c8f 100644 --- a/fastmcp_slim/fastmcp/client/client.py +++ b/fastmcp_slim/fastmcp/client/client.py @@ -862,17 +862,25 @@ class Client( message: str | None = None, ) -> None: """Send a progress notification.""" - await self.session.send_progress_notification( + # Deprecated upstream in SDK v2 but deliberately kept per compat directive; + # removed with the multi-round-trip follow-up. + await self.session.send_progress_notification( # ty: ignore[deprecated] progress_token, progress, total, message ) async def set_logging_level(self, level: mcp_types.LoggingLevel) -> None: """Send a logging/setLevel request.""" - await self._await_with_session_monitoring(self.session.set_logging_level(level)) + # Deprecated upstream in SDK v2 but deliberately kept per compat directive; + # removed with the multi-round-trip follow-up. + await self._await_with_session_monitoring( + self.session.set_logging_level(level) # ty: ignore[deprecated] + ) async def send_roots_list_changed(self) -> None: """Send a roots/list_changed notification.""" - await self.session.send_roots_list_changed() + # Deprecated upstream in SDK v2 but deliberately kept per compat directive; + # removed with the multi-round-trip follow-up. + await self.session.send_roots_list_changed() # ty: ignore[deprecated] # --- Completion --- diff --git a/fastmcp_slim/fastmcp/client/elicitation.py b/fastmcp_slim/fastmcp/client/elicitation.py index 155586ae6..2b9ce5db4 100644 --- a/fastmcp_slim/fastmcp/client/elicitation.py +++ b/fastmcp_slim/fastmcp/client/elicitation.py @@ -5,7 +5,7 @@ from typing import Any, Generic, TypeAlias import mcp_types from mcp import ClientSession -from mcp.client.session import ElicitationFnT +from mcp.client.session import ClientRequestContext, ElicitationFnT from mcp_types import ElicitRequestFormParams, ElicitRequestParams from mcp_types import ElicitResult as MCPElicitResult from pydantic_core import to_jsonable_python @@ -39,7 +39,7 @@ def create_elicitation_callback( elicitation_handler: ElicitationHandler, ) -> ElicitationFnT: async def _elicitation_handler( - context: RequestContext[ClientSession, LifespanContextT], + context: ClientRequestContext, params: ElicitRequestParams, ) -> MCPElicitResult | mcp_types.ErrorData: try: @@ -53,8 +53,14 @@ def create_elicitation_callback( # URL-based elicitation doesn't have a schema response_type = None + # The public ElicitationHandler alias is typed against the + # subscriptable RequestContext shim; the runtime object is the SDK's + # ClientRequestContext, passed through opaquely. result = await elicitation_handler( - params.message, response_type, params, context + params.message, + response_type, + params, + context, # ty: ignore[invalid-argument-type] ) # if the user returns data, we assume they've accepted the elicitation if not isinstance(result, ElicitResult): diff --git a/fastmcp_slim/fastmcp/client/mixins/prompts.py b/fastmcp_slim/fastmcp/client/mixins/prompts.py index 21f234777..c31a09b46 100644 --- a/fastmcp_slim/fastmcp/client/mixins/prompts.py +++ b/fastmcp_slim/fastmcp/client/mixins/prompts.py @@ -164,18 +164,19 @@ class ClientPromptsMixin: # If meta provided, use send_request for SEP-1686 task support if propagated_meta: - task_dict = propagated_meta.get("modelcontextprotocol.io/task") + # SDK v2: GetPromptRequestParams has no `task` field, so prompt + # gets cannot be submitted as background tasks over the wire and + # always graceful-degrade to immediate execution (sdk-feedback #3). request = mcp_types.GetPromptRequest( params=mcp_types.GetPromptRequestParams( name=name, arguments=serialized_arguments, - task=mcp_types.TaskMetadata(**task_dict) if task_dict else None, _meta=request_meta, # type: ignore[unknown-argument] # pydantic alias ) ) result = await self._await_with_session_monitoring( self.session.send_request( - request=request, # type: ignore[arg-type] # ty:ignore[invalid-argument-type] + request=request, # type: ignore[arg-type] result_type=mcp_types.GetPromptResult, ) ) @@ -301,11 +302,14 @@ class ClientPromptsMixin: "utf-8" ) + # SDK v2: GetPromptRequestParams has no `task` field, so this request + # cannot carry task metadata over the wire and the server graceful- + # degrades to immediate execution (sdk-feedback #3). `ttl` is retained on + # the public API but has no wire representation here. request = mcp_types.GetPromptRequest( params=mcp_types.GetPromptRequestParams( name=name, arguments=serialized_arguments, - task=mcp_types.TaskMetadata(ttl=ttl), _meta=request_meta, # type: ignore[unknown-argument] # pydantic alias ) ) @@ -313,7 +317,7 @@ class ClientPromptsMixin: # Server returns CreateTaskResult (task accepted) or GetPromptResult (graceful degradation) wrapped_result = await self._await_with_session_monitoring( self.session.send_request( - request=request, # type: ignore[arg-type] # ty:ignore[invalid-argument-type] + request=request, # type: ignore[arg-type] result_type=PromptTaskResponseUnion, ) ) diff --git a/fastmcp_slim/fastmcp/client/mixins/resources.py b/fastmcp_slim/fastmcp/client/mixins/resources.py index 850fd3192..d0c6f50e2 100644 --- a/fastmcp_slim/fastmcp/client/mixins/resources.py +++ b/fastmcp_slim/fastmcp/client/mixins/resources.py @@ -232,18 +232,19 @@ class ClientResourcesMixin: # If meta provided, use send_request for SEP-1686 task support if propagated_meta: - task_dict = propagated_meta.get("modelcontextprotocol.io/task") - # SDK v2: ReadResourceRequestParams.uri is a plain string. + # SDK v2: ReadResourceRequestParams has no `task` field, so + # resource reads cannot be submitted as background tasks over the + # wire and always graceful-degrade to immediate execution + # (sdk-feedback #3). The uri is a plain string on the wire. request = mcp_types.ReadResourceRequest( params=mcp_types.ReadResourceRequestParams( uri=uri_str, - task=mcp_types.TaskMetadata(**task_dict) if task_dict else None, _meta=request_meta, # type: ignore[unknown-argument] # pydantic alias ) ) result = await self._await_with_session_monitoring( self.session.send_request( - request=request, # type: ignore[arg-type] # ty:ignore[invalid-argument-type] + request=request, # type: ignore[arg-type] result_type=mcp_types.ReadResourceResult, ) ) @@ -363,10 +364,13 @@ class ClientResourcesMixin: # are stored under the AnyUrl-normalized form, so normalize to match. uri_str = str(AnyUrl(uri)) if isinstance(uri, str) else str(uri) + # SDK v2: ReadResourceRequestParams has no `task` field, so this request + # cannot carry task metadata over the wire and the server graceful- + # degrades to immediate execution (sdk-feedback #3). `ttl` is retained on + # the public API but has no wire representation here. request = mcp_types.ReadResourceRequest( params=mcp_types.ReadResourceRequestParams( uri=uri_str, - task=mcp_types.TaskMetadata(ttl=ttl), _meta=request_meta, # type: ignore[unknown-argument] # pydantic alias ) ) @@ -374,7 +378,7 @@ class ClientResourcesMixin: # Server returns CreateTaskResult (task accepted) or ReadResourceResult (graceful degradation) wrapped_result = await self._await_with_session_monitoring( self.session.send_request( - request=request, # type: ignore[arg-type] # ty:ignore[invalid-argument-type] + request=request, # type: ignore[arg-type] result_type=ResourceTaskResponseUnion, ) ) diff --git a/fastmcp_slim/fastmcp/client/mixins/task_management.py b/fastmcp_slim/fastmcp/client/mixins/task_management.py index 2c2e9355e..a011b138a 100644 --- a/fastmcp_slim/fastmcp/client/mixins/task_management.py +++ b/fastmcp_slim/fastmcp/client/mixins/task_management.py @@ -67,7 +67,7 @@ class ClientTaskManagementMixin: request = GetTaskRequest(params=GetTaskRequestParams(task_id=task_id)) return await self._await_with_session_monitoring( self.session.send_request( - request=request, # type: ignore[arg-type] # ty:ignore[invalid-argument-type] + request=request, # type: ignore[arg-type] result_type=GetTaskResult, ) ) @@ -94,7 +94,7 @@ class ClientTaskManagementMixin: # Return raw result - Task classes handle type-specific parsing result = await self._await_with_session_monitoring( self.session.send_request( - request=request, # type: ignore[arg-type] # ty:ignore[invalid-argument-type] + request=request, # type: ignore[arg-type] result_type=_RawTaskPayloadResult, ) ) @@ -132,7 +132,7 @@ class ClientTaskManagementMixin: request = ListTasksRequest(params=params) server_response = await self._await_with_session_monitoring( self.session.send_request( - request=request, # type: ignore[invalid-argument-type] # ty:ignore[invalid-argument-type] + request=request, # type: ignore[invalid-argument-type] result_type=mcp_types.ListTasksResult, ) ) @@ -172,7 +172,7 @@ class ClientTaskManagementMixin: request = CancelTaskRequest(params=CancelTaskRequestParams(task_id=task_id)) return await self._await_with_session_monitoring( self.session.send_request( - request=request, # type: ignore[invalid-argument-type] # ty:ignore[invalid-argument-type] + request=request, # type: ignore[invalid-argument-type] result_type=mcp_types.CancelTaskResult, ) ) diff --git a/fastmcp_slim/fastmcp/client/roots.py b/fastmcp_slim/fastmcp/client/roots.py index ee2001920..cb655c1fb 100644 --- a/fastmcp_slim/fastmcp/client/roots.py +++ b/fastmcp_slim/fastmcp/client/roots.py @@ -5,7 +5,7 @@ from typing import TypeAlias, cast import mcp_types import pydantic from mcp import ClientSession -from mcp.client.session import ListRootsFnT +from mcp.client.session import ClientRequestContext, ListRootsFnT from fastmcp.client._sdk_context_shim import LifespanContextT, RequestContext @@ -49,7 +49,7 @@ def _create_roots_callback_from_roots( roots = convert_roots_list(roots) async def _roots_callback( - context: RequestContext[ClientSession, LifespanContextT], + context: ClientRequestContext, ) -> mcp_types.ListRootsResult: return mcp_types.ListRootsResult(roots=roots) @@ -61,10 +61,13 @@ def _create_roots_callback_from_fn( | Callable[[RequestContext[ClientSession, LifespanContextT]], Awaitable[RootsList]], ) -> ListRootsFnT: async def _roots_callback( - context: RequestContext[ClientSession, LifespanContextT], + context: ClientRequestContext, ) -> mcp_types.ListRootsResult | mcp_types.ErrorData: try: - roots = fn(context) + # The public RootsHandler alias is typed against the subscriptable + # RequestContext shim; the runtime object is the SDK's + # ClientRequestContext, passed through opaquely. + roots = fn(context) # ty: ignore[invalid-argument-type] if inspect.isawaitable(roots): roots = await roots return mcp_types.ListRootsResult( diff --git a/fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py b/fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py index 33eea92cb..603f8ad0e 100644 --- a/fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py +++ b/fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py @@ -947,7 +947,7 @@ class OAuthProxy(OAuthProvider, ConsentMixin): self._resource_url, ) raise AuthorizeError( - error="invalid_target", # type: ignore[arg-type] # ty:ignore[invalid-argument-type] + error="invalid_target", # type: ignore[arg-type] error_description="Resource does not match this server", ) diff --git a/fastmcp_slim/fastmcp/server/context.py b/fastmcp_slim/fastmcp/server/context.py index 849ec5de0..65340306c 100644 --- a/fastmcp_slim/fastmcp/server/context.py +++ b/fastmcp_slim/fastmcp/server/context.py @@ -803,7 +803,9 @@ class Context: async def list_roots(self) -> list[Root]: """List the roots available to the server, as indicated by the client.""" - result = await self.session.list_roots() + # Deprecated upstream in SDK v2 but deliberately kept per compat directive; + # removed with the multi-round-trip follow-up. + result = await self.session.list_roots() # ty: ignore[deprecated] return result.roots async def send_notification( @@ -1501,7 +1503,9 @@ async def _log_to_server_and_client( extra=data.extra, ) - await session.send_log_message( + # Deprecated upstream in SDK v2 but deliberately kept per compat directive; + # removed with the multi-round-trip follow-up. + await session.send_log_message( # ty: ignore[deprecated] level=level, data=data, logger=logger_name, diff --git a/fastmcp_slim/fastmcp/server/sampling/run.py b/fastmcp_slim/fastmcp/server/sampling/run.py index 506d445e2..1fba19a5b 100644 --- a/fastmcp_slim/fastmcp/server/sampling/run.py +++ b/fastmcp_slim/fastmcp/server/sampling/run.py @@ -230,7 +230,10 @@ async def call_sampling_handler( tools=sdk_tools, tool_choice=tool_choice, ), - context.request_context, + # SamplingHandler is typed against the SDK's RequestContext placeholder, + # but FastMCP hands handlers its own FastMCPRequestContext wrapper at + # runtime; the two aren't structurally related in the type system. + context.request_context, # ty: ignore[invalid-argument-type] ) if inspect.isawaitable(result): @@ -555,7 +558,9 @@ async def sample_step_impl( tool_choice=effective_tool_choice, ) else: - response = await context.session.create_message( + # Deprecated upstream in SDK v2 but deliberately kept per compat + # directive; removed with the multi-round-trip follow-up. + response = await context.session.create_message( # ty: ignore[deprecated] messages=current_messages, system_prompt=system_prompt, temperature=temperature, diff --git a/fastmcp_slim/fastmcp/server/server.py b/fastmcp_slim/fastmcp/server/server.py index 2580e729d..bf162d875 100644 --- a/fastmcp_slim/fastmcp/server/server.py +++ b/fastmcp_slim/fastmcp/server/server.py @@ -412,7 +412,7 @@ class FastMCP( self._started: asyncio.Event = asyncio.Event() # Generate random ID if no name provided - self._mcp_server: LowLevelServer[LifespanResultT, Any] = LowLevelServer[ + self._mcp_server: LowLevelServer[LifespanResultT] = LowLevelServer[ LifespanResultT ]( fastmcp=self, @@ -1260,7 +1260,9 @@ class FastMCP( message=mcp_types.CallToolRequestParams( name=name, arguments=arguments or {}, - _meta=_version_request_meta(version), # type: ignore[unknown-argument] # pydantic alias + # `_meta` carries the app-level `fastmcp` version key, which the + # reserved-key RequestParamsMeta TypedDict can't express statically. + _meta=_version_request_meta(version), # type: ignore[unknown-argument] # ty: ignore[invalid-argument-type] ), source="client", type="request", @@ -1428,7 +1430,9 @@ class FastMCP( mw_context = MiddlewareContext( message=mcp_types.ReadResourceRequestParams( uri=str(uri), - _meta=_version_request_meta(version), # type: ignore[unknown-argument] # pydantic alias + # `_meta` carries the app-level `fastmcp` version key, which the + # reserved-key RequestParamsMeta TypedDict can't express statically. + _meta=_version_request_meta(version), # type: ignore[unknown-argument] # ty: ignore[invalid-argument-type] ), source="client", type="request", @@ -1605,7 +1609,9 @@ class FastMCP( message=mcp_types.GetPromptRequestParams( name=name, arguments=arguments, - _meta=_version_request_meta(version), # type: ignore[unknown-argument] # pydantic alias + # `_meta` carries the app-level `fastmcp` version key, which the + # reserved-key RequestParamsMeta TypedDict can't express statically. + _meta=_version_request_meta(version), # type: ignore[unknown-argument] # ty: ignore[invalid-argument-type] ), source="client", type="request",