From 302eb9c3f5d57db5b2b7bbe986e44a78b129418d Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Fri, 22 Aug 2025 20:19:22 -0400 Subject: [PATCH] Remove invalid-argument-type ignore and fix type errors (#1588) Co-authored-by: Jeremiah Lowin Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com> --- pyproject.toml | 1 - src/fastmcp/cli/cli.py | 10 ++++----- src/fastmcp/cli/install/claude_code.py | 4 ++-- src/fastmcp/cli/install/claude_desktop.py | 4 ++-- src/fastmcp/cli/install/cursor.py | 4 ++-- src/fastmcp/cli/install/mcp_json.py | 6 +++--- src/fastmcp/cli/run.py | 6 +++--- src/fastmcp/client/auth/oauth.py | 9 ++++++-- src/fastmcp/client/client.py | 4 ++-- src/fastmcp/client/transports.py | 26 +++++++++++++++-------- src/fastmcp/server/context.py | 2 +- src/fastmcp/server/server.py | 6 +++--- src/fastmcp/utilities/inspect.py | 4 ++-- src/fastmcp/utilities/types.py | 3 ++- tests/server/test_context.py | 6 +++--- 15 files changed, 54 insertions(+), 41 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f86613372..20616529f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,7 +124,6 @@ python-version = "3.10" [tool.ty.rules] # Rules with too many errors to fix right now (40+ each) -invalid-argument-type = "ignore" # 40 errors no-matching-overload = "ignore" # 126 errors unknown-argument = "ignore" # 61 errors unresolved-attribute = "ignore" # 60 errors diff --git a/src/fastmcp/cli/cli.py b/src/fastmcp/cli/cli.py index 893762edd..85c09917a 100644 --- a/src/fastmcp/cli/cli.py +++ b/src/fastmcp/cli/cli.py @@ -107,7 +107,7 @@ def version( cyclopts.Parameter( "--copy", help="Copy version information to clipboard", - negative=False, + negative="", ), ] = False, ): @@ -153,7 +153,7 @@ async def dev( cyclopts.Parameter( "--with", help="Additional packages to install", - negative=False, + negative="", ), ] = [], inspector_version: Annotated[ @@ -385,7 +385,7 @@ async def run( cyclopts.Parameter( "--no-banner", help="Don't show the server banner", - negative=False, + negative="", ), ] = False, python: Annotated[ @@ -400,7 +400,7 @@ async def run( cyclopts.Parameter( "--with", help="Additional packages to install (can be used multiple times)", - negative=False, + negative="", ), ] = [], project: Annotated[ @@ -586,7 +586,7 @@ async def inspect( cyclopts.Parameter( "--with", help="Additional packages to install (can be used multiple times)", - negative=False, + negative="", ), ] = [], project: Annotated[ diff --git a/src/fastmcp/cli/install/claude_code.py b/src/fastmcp/cli/install/claude_code.py index 490de9545..b12b2405e 100644 --- a/src/fastmcp/cli/install/claude_code.py +++ b/src/fastmcp/cli/install/claude_code.py @@ -189,7 +189,7 @@ async def claude_code_command( cyclopts.Parameter( "--with", help="Additional packages to install", - negative=False, + negative="", ), ] = [], env_vars: Annotated[ @@ -197,7 +197,7 @@ async def claude_code_command( cyclopts.Parameter( "--env", help="Environment variables in KEY=VALUE format", - negative=False, + negative="", ), ] = [], env_file: Annotated[ diff --git a/src/fastmcp/cli/install/claude_desktop.py b/src/fastmcp/cli/install/claude_desktop.py index 56f5d2bc8..475a0c611 100644 --- a/src/fastmcp/cli/install/claude_desktop.py +++ b/src/fastmcp/cli/install/claude_desktop.py @@ -162,7 +162,7 @@ async def claude_desktop_command( cyclopts.Parameter( "--with", help="Additional packages to install", - negative=False, + negative="", ), ] = [], env_vars: Annotated[ @@ -170,7 +170,7 @@ async def claude_desktop_command( cyclopts.Parameter( "--env", help="Environment variables in KEY=VALUE format", - negative=False, + negative="", ), ] = [], env_file: Annotated[ diff --git a/src/fastmcp/cli/install/cursor.py b/src/fastmcp/cli/install/cursor.py index fd252bf23..4363cefe8 100644 --- a/src/fastmcp/cli/install/cursor.py +++ b/src/fastmcp/cli/install/cursor.py @@ -289,7 +289,7 @@ async def cursor_command( cyclopts.Parameter( "--with", help="Additional packages to install", - negative=False, + negative="", ), ] = [], env_vars: Annotated[ @@ -297,7 +297,7 @@ async def cursor_command( cyclopts.Parameter( "--env", help="Environment variables in KEY=VALUE format", - negative=False, + negative="", ), ] = [], env_file: Annotated[ diff --git a/src/fastmcp/cli/install/mcp_json.py b/src/fastmcp/cli/install/mcp_json.py index b5145c18d..bc845bf2d 100644 --- a/src/fastmcp/cli/install/mcp_json.py +++ b/src/fastmcp/cli/install/mcp_json.py @@ -135,7 +135,7 @@ async def mcp_json_command( cyclopts.Parameter( "--with", help="Additional packages to install", - negative=False, + negative="", ), ] = [], env_vars: Annotated[ @@ -143,7 +143,7 @@ async def mcp_json_command( cyclopts.Parameter( "--env", help="Environment variables in KEY=VALUE format", - negative=False, + negative="", ), ] = [], env_file: Annotated[ @@ -158,7 +158,7 @@ async def mcp_json_command( cyclopts.Parameter( "--copy", help="Copy configuration to clipboard instead of printing to stdout", - negative=False, + negative="", ), ] = False, python: Annotated[ diff --git a/src/fastmcp/cli/run.py b/src/fastmcp/cli/run.py index a78f8193e..8e363c64d 100644 --- a/src/fastmcp/cli/run.py +++ b/src/fastmcp/cli/run.py @@ -86,8 +86,8 @@ async def import_server(file: Path, server_or_factory: str | None = None) -> Any logger.error("Could not load module", extra={"file": str(file)}) sys.exit(1) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) + module = importlib.util.module_from_spec(spec) # type: ignore[arg-type] + spec.loader.exec_module(module) # type: ignore[union-attr] # If no object specified, try common server names if not server_or_factory: @@ -129,7 +129,7 @@ async def import_server(file: Path, server_or_factory: str | None = None) -> Any ) sys.exit(1) - return await _resolve_server_or_factory(obj, file, server_or_factory) + return await _resolve_server_or_factory(obj, file, server_or_factory) # type: ignore[arg-type] async def _resolve_server_or_factory(obj: Any, file: Path, name: str) -> Any: diff --git a/src/fastmcp/client/auth/oauth.py b/src/fastmcp/client/auth/oauth.py index f2418417a..88a8a9d33 100644 --- a/src/fastmcp/client/auth/oauth.py +++ b/src/fastmcp/client/auth/oauth.py @@ -217,8 +217,13 @@ class OAuth(OAuthClientProvider): self.redirect_port = callback_port or find_available_port() redirect_uri = f"http://localhost:{self.redirect_port}/callback" + scopes_str: str if isinstance(scopes, list): - scopes = " ".join(scopes) + scopes_str = " ".join(scopes) + elif scopes is not None: + scopes_str = str(scopes) + else: + scopes_str = "" client_metadata = OAuthClientMetadata( client_name=client_name, @@ -226,7 +231,7 @@ class OAuth(OAuthClientProvider): grant_types=["authorization_code", "refresh_token"], response_types=["code"], # token_endpoint_auth_method="client_secret_post", - scope=scopes, + scope=scopes_str, **(additional_client_metadata or {}), ) diff --git a/src/fastmcp/client/client.py b/src/fastmcp/client/client.py index fa68cf4c5..083fdb3ad 100644 --- a/src/fastmcp/client/client.py +++ b/src/fastmcp/client/client.py @@ -236,7 +236,7 @@ class Client(Generic[ClientTransportT]): self._progress_handler = progress_handler if isinstance(timeout, int | float): - timeout = datetime.timedelta(seconds=timeout) + timeout = datetime.timedelta(seconds=float(timeout)) # handle init handshake timeout if init_timeout is None: @@ -819,7 +819,7 @@ class Client(Generic[ClientTransportT]): """ if isinstance(timeout, int | float): - timeout = datetime.timedelta(seconds=timeout) + timeout = datetime.timedelta(seconds=float(timeout)) result = await self.session.call_tool( name=name, arguments=arguments, diff --git a/src/fastmcp/client/transports.py b/src/fastmcp/client/transports.py index 6600f7619..f01932bd6 100644 --- a/src/fastmcp/client/transports.py +++ b/src/fastmcp/client/transports.py @@ -182,7 +182,7 @@ class SSETransport(ClientTransport): self.httpx_client_factory = httpx_client_factory if isinstance(sse_read_timeout, int | float): - sse_read_timeout = datetime.timedelta(seconds=sse_read_timeout) + sse_read_timeout = datetime.timedelta(seconds=float(sse_read_timeout)) self.sse_read_timeout = sse_read_timeout def _set_auth(self, auth: httpx.Auth | Literal["oauth"] | str | None): @@ -252,7 +252,7 @@ class StreamableHttpTransport(ClientTransport): self.httpx_client_factory = httpx_client_factory if isinstance(sse_read_timeout, int | float): - sse_read_timeout = datetime.timedelta(seconds=sse_read_timeout) + sse_read_timeout = datetime.timedelta(seconds=float(sse_read_timeout)) self.sse_read_timeout = sse_read_timeout def _set_auth(self, auth: httpx.Auth | Literal["oauth"] | str | None): @@ -1024,28 +1024,36 @@ def infer_transport( # the transport is a FastMCP server (2.x or 1.0) elif isinstance(transport, FastMCP | FastMCP1Server): - inferred_transport = FastMCPTransport(mcp=transport) + inferred_transport = FastMCPTransport( + mcp=cast(FastMCP[Any] | FastMCP1Server, transport) + ) # the transport is a path to a script elif isinstance(transport, Path | str) and Path(transport).exists(): if str(transport).endswith(".py"): - inferred_transport = PythonStdioTransport(script_path=transport) + inferred_transport = PythonStdioTransport(script_path=cast(Path, transport)) elif str(transport).endswith(".js"): - inferred_transport = NodeStdioTransport(script_path=transport) + inferred_transport = NodeStdioTransport(script_path=cast(Path, transport)) else: raise ValueError(f"Unsupported script type: {transport}") # the transport is an http(s) URL elif isinstance(transport, AnyUrl | str) and str(transport).startswith("http"): - inferred_transport_type = infer_transport_type_from_url(transport) + inferred_transport_type = infer_transport_type_from_url( + cast(AnyUrl | str, transport) + ) if inferred_transport_type == "sse": - inferred_transport = SSETransport(url=transport) + inferred_transport = SSETransport(url=cast(AnyUrl | str, transport)) else: - inferred_transport = StreamableHttpTransport(url=transport) + inferred_transport = StreamableHttpTransport( + url=cast(AnyUrl | str, transport) + ) # if the transport is a config dict or MCPConfig elif isinstance(transport, dict | MCPConfig): - inferred_transport = MCPConfigTransport(config=transport) + inferred_transport = MCPConfigTransport( + config=cast(dict | MCPConfig, transport) + ) # the transport is an unknown type else: diff --git a/src/fastmcp/server/context.py b/src/fastmcp/server/context.py index 3c17fc3c2..ea97162d5 100644 --- a/src/fastmcp/server/context.py +++ b/src/fastmcp/server/context.py @@ -548,7 +548,7 @@ class Context: if isinstance(validated_data, ScalarElicitationType): return AcceptedElicitation[T](data=validated_data.value) else: - return AcceptedElicitation[T](data=validated_data) + return AcceptedElicitation[T](data=cast(T, validated_data)) elif result.content: raise ValueError( "Elicitation expected an empty response, but received: " diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index efeba0e22..dab21f896 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -1027,7 +1027,7 @@ class FastMCP(Generic[LifespanResultT]): description=description, tags=tags, output_schema=output_schema, - annotations=annotations, + annotations=cast(ToolAnnotations | None, annotations), exclude_args=exclude_args, meta=meta, serializer=self._tool_serializer, @@ -1257,7 +1257,7 @@ class FastMCP(Generic[LifespanResultT]): mime_type=mime_type, tags=tags, enabled=enabled, - annotations=annotations, + annotations=cast(Annotations | None, annotations), meta=meta, ) self.add_template(template) @@ -1272,7 +1272,7 @@ class FastMCP(Generic[LifespanResultT]): mime_type=mime_type, tags=tags, enabled=enabled, - annotations=annotations, + annotations=cast(Annotations | None, annotations), meta=meta, ) self.add_resource(resource) diff --git a/src/fastmcp/utilities/inspect.py b/src/fastmcp/utilities/inspect.py index 18c2c7e94..df38d679e 100644 --- a/src/fastmcp/utilities/inspect.py +++ b/src/fastmcp/utilities/inspect.py @@ -4,7 +4,7 @@ from __future__ import annotations import importlib.metadata from dataclasses import dataclass -from typing import Any +from typing import Any, cast from mcp.server.fastmcp import FastMCP as FastMCP1x @@ -318,4 +318,4 @@ async def inspect_fastmcp(mcp: FastMCP[Any] | FastMCP1x) -> FastMCPInfo: if isinstance(mcp, FastMCP1x): return await inspect_fastmcp_v1(mcp) else: - return await inspect_fastmcp_v2(mcp) + return await inspect_fastmcp_v2(cast(FastMCP[Any], mcp)) diff --git a/src/fastmcp/utilities/types.py b/src/fastmcp/utilities/types.py index 5e2365c0d..f537a74f6 100644 --- a/src/fastmcp/utilities/types.py +++ b/src/fastmcp/utilities/types.py @@ -10,6 +10,7 @@ from pathlib import Path from types import EllipsisType, UnionType from typing import ( Annotated, + Any, Protocol, TypeAlias, TypeVar, @@ -122,7 +123,7 @@ def issubclass_safe(cls: type, base: type) -> bool: return False -def is_class_member_of_type(cls: type, base: type) -> bool: +def is_class_member_of_type(cls: Any, base: type) -> bool: """ Check if cls is a member of base, even if cls is a type variable. diff --git a/tests/server/test_context.py b/tests/server/test_context.py index d386d99d2..9c00ac0e2 100644 --- a/tests/server/test_context.py +++ b/tests/server/test_context.py @@ -85,7 +85,7 @@ class TestParseModelPreferences: def test_parse_model_preferences_invalid_type(self, context): with pytest.raises(ValueError): - _parse_model_preferences(model_preferences=123) # pyright: ignore[reportArgumentType] + _parse_model_preferences(model_preferences=123) # pyright: ignore[reportArgumentType] # type: ignore[invalid-argument-type] class TestSessionId: @@ -97,7 +97,7 @@ class TestSessionId: mock_headers = {"mcp-session-id": "test-session-123"} token = request_ctx.set( - RequestContext( + RequestContext( # type: ignore[arg-type] request_id=0, meta=None, session=MagicMock(wraps={}), @@ -118,7 +118,7 @@ class TestSessionId: from mcp.shared.context import RequestContext token = request_ctx.set( - RequestContext( + RequestContext( # type: ignore[arg-type] request_id=0, meta=None, session=MagicMock(wraps={}),