Skip to content

Commit 5c99391

Browse files
Kludexdsp-ant
authored andcommitted
Add strict mode to pyright
1 parent 08f4e01 commit 5c99391

File tree

15 files changed

+134
-72
lines changed

15 files changed

+134
-72
lines changed

pyproject.toml

+3-5
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,11 @@ packages = ["src/mcp"]
7676
include = ["src/mcp", "tests"]
7777
venvPath = "."
7878
venv = ".venv"
79-
strict = [
80-
"src/mcp/server/fastmcp/tools/base.py",
81-
"src/mcp/client/*.py"
82-
]
79+
strict = ["src/mcp/**/*.py"]
80+
exclude = ["src/mcp/types.py"]
8381

8482
[tool.ruff.lint]
85-
select = ["E", "F", "I"]
83+
select = ["E", "F", "I", "UP"]
8684
ignore = []
8785

8886
[tool.ruff]

src/mcp/cli/claude.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import sys
66
from pathlib import Path
7+
from typing import Any
78

89
from mcp.server.fastmcp.utilities.logging import get_logger
910

@@ -116,10 +117,7 @@ def update_claude_config(
116117
# Add fastmcp run command
117118
args.extend(["mcp", "run", file_spec])
118119

119-
server_config = {
120-
"command": "uv",
121-
"args": args,
122-
}
120+
server_config: dict[str, Any] = {"command": "uv", "args": args}
123121

124122
# Add environment variables if specified
125123
if env_vars:

src/mcp/server/fastmcp/prompts/base.py

+19-19
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import inspect
44
import json
5-
from collections.abc import Callable
6-
from typing import Any, Awaitable, Literal, Sequence
5+
from collections.abc import Awaitable, Callable, Sequence
6+
from typing import Any, Literal
77

88
import pydantic_core
99
from pydantic import BaseModel, Field, TypeAdapter, validate_call
@@ -19,7 +19,7 @@ class Message(BaseModel):
1919
role: Literal["user", "assistant"]
2020
content: CONTENT_TYPES
2121

22-
def __init__(self, content: str | CONTENT_TYPES, **kwargs):
22+
def __init__(self, content: str | CONTENT_TYPES, **kwargs: Any):
2323
if isinstance(content, str):
2424
content = TextContent(type="text", text=content)
2525
super().__init__(content=content, **kwargs)
@@ -30,7 +30,7 @@ class UserMessage(Message):
3030

3131
role: Literal["user", "assistant"] = "user"
3232

33-
def __init__(self, content: str | CONTENT_TYPES, **kwargs):
33+
def __init__(self, content: str | CONTENT_TYPES, **kwargs: Any):
3434
super().__init__(content=content, **kwargs)
3535

3636

@@ -39,11 +39,13 @@ class AssistantMessage(Message):
3939

4040
role: Literal["user", "assistant"] = "assistant"
4141

42-
def __init__(self, content: str | CONTENT_TYPES, **kwargs):
42+
def __init__(self, content: str | CONTENT_TYPES, **kwargs: Any):
4343
super().__init__(content=content, **kwargs)
4444

4545

46-
message_validator = TypeAdapter(UserMessage | AssistantMessage)
46+
message_validator = TypeAdapter[UserMessage | AssistantMessage](
47+
UserMessage | AssistantMessage
48+
)
4749

4850
SyncPromptResult = (
4951
str | Message | dict[str, Any] | Sequence[str | Message | dict[str, Any]]
@@ -73,12 +75,12 @@ class Prompt(BaseModel):
7375
arguments: list[PromptArgument] | None = Field(
7476
None, description="Arguments that can be passed to the prompt"
7577
)
76-
fn: Callable = Field(exclude=True)
78+
fn: Callable[..., PromptResult | Awaitable[PromptResult]] = Field(exclude=True)
7779

7880
@classmethod
7981
def from_function(
8082
cls,
81-
fn: Callable[..., PromptResult],
83+
fn: Callable[..., PromptResult | Awaitable[PromptResult]],
8284
name: str | None = None,
8385
description: str | None = None,
8486
) -> "Prompt":
@@ -99,7 +101,7 @@ def from_function(
99101
parameters = TypeAdapter(fn).json_schema()
100102

101103
# Convert parameters to PromptArguments
102-
arguments = []
104+
arguments: list[PromptArgument] = []
103105
if "properties" in parameters:
104106
for param_name, param in parameters["properties"].items():
105107
required = param_name in parameters.get("required", [])
@@ -138,25 +140,23 @@ async def render(self, arguments: dict[str, Any] | None = None) -> list[Message]
138140
result = await result
139141

140142
# Validate messages
141-
if not isinstance(result, (list, tuple)):
143+
if not isinstance(result, list | tuple):
142144
result = [result]
143145

144146
# Convert result to messages
145-
messages = []
146-
for msg in result:
147+
messages: list[Message] = []
148+
for msg in result: # type: ignore[reportUnknownVariableType]
147149
try:
148150
if isinstance(msg, Message):
149151
messages.append(msg)
150152
elif isinstance(msg, dict):
151-
msg = message_validator.validate_python(msg)
152-
messages.append(msg)
153+
messages.append(message_validator.validate_python(msg))
153154
elif isinstance(msg, str):
154-
messages.append(
155-
UserMessage(content=TextContent(type="text", text=msg))
156-
)
155+
content = TextContent(type="text", text=msg)
156+
messages.append(UserMessage(content=content))
157157
else:
158-
msg = json.dumps(pydantic_core.to_jsonable_python(msg))
159-
messages.append(Message(role="user", content=msg))
158+
content = json.dumps(pydantic_core.to_jsonable_python(msg))
159+
messages.append(Message(role="user", content=content))
160160
except Exception:
161161
raise ValueError(
162162
f"Could not convert prompt result to message: {msg}"

src/mcp/server/fastmcp/resources/resource_manager.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Resource manager functionality."""
22

3-
from typing import Callable
3+
from collections.abc import Callable
4+
from typing import Any
45

56
from pydantic import AnyUrl
67

@@ -47,7 +48,7 @@ def add_resource(self, resource: Resource) -> Resource:
4748

4849
def add_template(
4950
self,
50-
fn: Callable,
51+
fn: Callable[..., Any],
5152
uri_template: str,
5253
name: str | None = None,
5354
description: str | None = None,

src/mcp/server/fastmcp/resources/templates.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
"""Resource template functionality."""
22

3+
from __future__ import annotations
4+
35
import inspect
46
import re
5-
from typing import Any, Callable
7+
from collections.abc import Callable
8+
from typing import Any
69

710
from pydantic import BaseModel, Field, TypeAdapter, validate_call
811

@@ -20,18 +23,20 @@ class ResourceTemplate(BaseModel):
2023
mime_type: str = Field(
2124
default="text/plain", description="MIME type of the resource content"
2225
)
23-
fn: Callable = Field(exclude=True)
24-
parameters: dict = Field(description="JSON schema for function parameters")
26+
fn: Callable[..., Any] = Field(exclude=True)
27+
parameters: dict[str, Any] = Field(
28+
description="JSON schema for function parameters"
29+
)
2530

2631
@classmethod
2732
def from_function(
2833
cls,
29-
fn: Callable,
34+
fn: Callable[..., Any],
3035
uri_template: str,
3136
name: str | None = None,
3237
description: str | None = None,
3338
mime_type: str | None = None,
34-
) -> "ResourceTemplate":
39+
) -> ResourceTemplate:
3540
"""Create a template from a function."""
3641
func_name = name or fn.__name__
3742
if func_name == "<lambda>":

src/mcp/server/fastmcp/server.py

+13-10
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
import inspect
66
import json
77
import re
8-
from collections.abc import AsyncIterator, Iterable
8+
from collections.abc import AsyncIterator, Callable, Iterable, Sequence
99
from contextlib import (
1010
AbstractAsyncContextManager,
1111
asynccontextmanager,
1212
)
1313
from itertools import chain
14-
from typing import Any, Callable, Generic, Literal, Sequence
14+
from typing import Any, Generic, Literal
1515

1616
import anyio
1717
import pydantic_core
@@ -20,6 +20,7 @@
2020
from pydantic.networks import AnyUrl
2121
from pydantic_settings import BaseSettings, SettingsConfigDict
2222
from starlette.applications import Starlette
23+
from starlette.requests import Request
2324
from starlette.routing import Mount, Route
2425

2526
from mcp.server.fastmcp.exceptions import ResourceError
@@ -88,13 +89,13 @@ class Settings(BaseSettings, Generic[LifespanResultT]):
8889
)
8990

9091
lifespan: (
91-
Callable[["FastMCP"], AbstractAsyncContextManager[LifespanResultT]] | None
92+
Callable[[FastMCP], AbstractAsyncContextManager[LifespanResultT]] | None
9293
) = Field(None, description="Lifespan context manager")
9394

9495

9596
def lifespan_wrapper(
9697
app: FastMCP,
97-
lifespan: Callable[["FastMCP"], AbstractAsyncContextManager[LifespanResultT]],
98+
lifespan: Callable[[FastMCP], AbstractAsyncContextManager[LifespanResultT]],
9899
) -> Callable[[MCPServer[LifespanResultT]], AbstractAsyncContextManager[object]]:
99100
@asynccontextmanager
100101
async def wrap(s: MCPServer[LifespanResultT]) -> AsyncIterator[object]:
@@ -179,7 +180,7 @@ async def list_tools(self) -> list[MCPTool]:
179180
for info in tools
180181
]
181182

182-
def get_context(self) -> "Context[ServerSession, object]":
183+
def get_context(self) -> Context[ServerSession, object]:
183184
"""
184185
Returns a Context object. Note that the context will only be valid
185186
during a request; outside a request, most methods will error.
@@ -478,9 +479,11 @@ def sse_app(self) -> Starlette:
478479
"""Return an instance of the SSE server app."""
479480
sse = SseServerTransport("/messages/")
480481

481-
async def handle_sse(request):
482+
async def handle_sse(request: Request) -> None:
482483
async with sse.connect_sse(
483-
request.scope, request.receive, request._send
484+
request.scope,
485+
request.receive,
486+
request._send, # type: ignore[reportPrivateUsage]
484487
) as streams:
485488
await self._mcp_server.run(
486489
streams[0],
@@ -535,14 +538,14 @@ def _convert_to_content(
535538
if result is None:
536539
return []
537540

538-
if isinstance(result, (TextContent, ImageContent, EmbeddedResource)):
541+
if isinstance(result, TextContent | ImageContent | EmbeddedResource):
539542
return [result]
540543

541544
if isinstance(result, Image):
542545
return [result.to_image_content()]
543546

544-
if isinstance(result, (list, tuple)):
545-
return list(chain.from_iterable(_convert_to_content(item) for item in result))
547+
if isinstance(result, list | tuple):
548+
return list(chain.from_iterable(_convert_to_content(item) for item in result)) # type: ignore[reportUnknownVariableType]
546549

547550
if not isinstance(result, str):
548551
try:

src/mcp/server/fastmcp/tools/tool_manager.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def list_tools(self) -> list[Tool]:
3232

3333
def add_tool(
3434
self,
35-
fn: Callable,
35+
fn: Callable[..., Any],
3636
name: str | None = None,
3737
description: str | None = None,
3838
) -> Tool:

src/mcp/server/fastmcp/utilities/func_metadata.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def pre_parse_json(self, data: dict[str, Any]) -> dict[str, Any]:
8080
dicts (JSON objects) as JSON strings, which can be pre-parsed here.
8181
"""
8282
new_data = data.copy() # Shallow copy
83-
for field_name, field_info in self.arg_model.model_fields.items():
83+
for field_name, _field_info in self.arg_model.model_fields.items():
8484
if field_name not in data.keys():
8585
continue
8686
if isinstance(data[field_name], str):
@@ -177,7 +177,9 @@ def func_metadata(
177177

178178

179179
def _get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any:
180-
def try_eval_type(value, globalns, localns):
180+
def try_eval_type(
181+
value: Any, globalns: dict[str, Any], localns: dict[str, Any]
182+
) -> tuple[Any, bool]:
181183
try:
182184
return eval_type_backport(value, globalns, localns), True
183185
except NameError:

src/mcp/server/fastmcp/utilities/logging.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def configure_logging(
2424
Args:
2525
level: the log level to use
2626
"""
27-
handlers = []
27+
handlers: list[logging.Handler] = []
2828
try:
2929
from rich.console import Console
3030
from rich.logging import RichHandler

src/mcp/server/lowlevel/server.py

+6-8
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ async def main():
6969
import contextvars
7070
import logging
7171
import warnings
72-
from collections.abc import Awaitable, Callable, Iterable
72+
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable
7373
from contextlib import AbstractAsyncContextManager, AsyncExitStack, asynccontextmanager
74-
from typing import Any, AsyncIterator, Generic, TypeVar
74+
from typing import Any, Generic, TypeVar
7575

7676
import anyio
7777
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
@@ -155,9 +155,7 @@ def pkg_version(package: str) -> str:
155155
try:
156156
from importlib.metadata import version
157157

158-
v = version(package)
159-
if v is not None:
160-
return v
158+
return version(package)
161159
except Exception:
162160
pass
163161

@@ -320,7 +318,6 @@ def create_content(data: str | bytes, mime_type: str | None):
320318
contents_list = [
321319
create_content(content_item.content, content_item.mime_type)
322320
for content_item in contents
323-
if isinstance(content_item, ReadResourceContents)
324321
]
325322
return types.ServerResult(
326323
types.ReadResourceResult(
@@ -511,7 +508,8 @@ async def _handle_message(
511508
raise_exceptions: bool = False,
512509
):
513510
with warnings.catch_warnings(record=True) as w:
514-
match message:
511+
# TODO(Marcelo): We should be checking if message is Exception here.
512+
match message: # type: ignore[reportMatchNotExhaustive]
515513
case (
516514
RequestResponder(request=types.ClientRequest(root=req)) as responder
517515
):
@@ -527,7 +525,7 @@ async def _handle_message(
527525

528526
async def _handle_request(
529527
self,
530-
message: RequestResponder,
528+
message: RequestResponder[types.ClientRequest, types.ServerResult],
531529
req: Any,
532530
session: ServerSession,
533531
lifespan_context: LifespanResultT,

src/mcp/shared/memory.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from contextlib import asynccontextmanager
66
from datetime import timedelta
7-
from typing import AsyncGenerator
7+
from typing import Any, AsyncGenerator
88

99
import anyio
1010
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
@@ -52,7 +52,7 @@ async def create_client_server_memory_streams() -> (
5252

5353
@asynccontextmanager
5454
async def create_connected_server_and_client_session(
55-
server: Server,
55+
server: Server[Any],
5656
read_timeout_seconds: timedelta | None = None,
5757
sampling_callback: SamplingFnT | None = None,
5858
list_roots_callback: ListRootsFnT | None = None,

0 commit comments

Comments
 (0)