Clean up ty diagnostics in fastmcp_slim for SDK v2 migration

This commit is contained in:
Jeremiah Lowin 2026-07-06 00:47:56 -04:00
commit 962578ed52
No known key found for this signature in database
11 changed files with 77 additions and 38 deletions

View file

@ -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:

View file

@ -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 ---

View file

@ -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):

View file

@ -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,
)
)

View file

@ -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,
)
)

View file

@ -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,
)
)

View file

@ -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(

View file

@ -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",
)

View file

@ -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,

View file

@ -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,

View file

@ -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",