Add pyright strict mode on the whole project (#1254)

This commit is contained in:
Marcelo Trylesinski 2025-08-11 19:56:37 +02:00 • committed by GitHub
commit c7671e470c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 652 additions and 674 deletions

View file

@ -12,13 +12,19 @@ import sys
import tempfile
import textwrap
from pathlib import Path
from typing import TYPE_CHECKING
import anyio
import pytest
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import _create_platform_compatible_process, stdio_client
from tests.shared.test_win32_utils import escape_path_for_python
# TODO(Marcelo): This doesn't seem to be the right path. We should fix this.
if TYPE_CHECKING:
from ..shared.test_win32_utils import escape_path_for_python
else:
from tests.shared.test_win32_utils import escape_path_for_python
@pytest.mark.anyio
@ -52,10 +58,10 @@ async def test_lifespan_cleanup_executed():
from pathlib import Path
from contextlib import asynccontextmanager
from mcp.server.fastmcp import FastMCP
STARTUP_MARKER = {escape_path_for_python(startup_marker)}
CLEANUP_MARKER = {escape_path_for_python(cleanup_marker)}
@asynccontextmanager
async def lifespan(server):
# Write startup marker
@ -65,13 +71,13 @@ async def test_lifespan_cleanup_executed():
finally:
# This cleanup code now runs properly during shutdown
Path(CLEANUP_MARKER).write_text("cleaned up")
mcp = FastMCP("test-server", lifespan=lifespan)
@mcp.tool()
def echo(text: str) -> str:
return text
if __name__ == "__main__":
mcp.run()
""")
@ -160,10 +166,10 @@ async def test_stdin_close_triggers_cleanup():
from pathlib import Path
from contextlib import asynccontextmanager
from mcp.server.fastmcp import FastMCP
STARTUP_MARKER = {escape_path_for_python(startup_marker)}
CLEANUP_MARKER = {escape_path_for_python(cleanup_marker)}
@asynccontextmanager
async def lifespan(server):
# Write startup marker
@ -173,13 +179,13 @@ async def test_stdin_close_triggers_cleanup():
finally:
# This cleanup code runs when stdin closes, enabling graceful shutdown
Path(CLEANUP_MARKER).write_text("cleaned up")
mcp = FastMCP("test-server", lifespan=lifespan)
@mcp.tool()
def echo(text: str) -> str:
return text
if __name__ == "__main__":
# The server should exit gracefully when stdin closes
try:

View file

@ -11,7 +11,7 @@ async def test_messages_are_executed_concurrently_tools():
server = FastMCP("test")
event = anyio.Event()
tool_started = anyio.Event()
call_order = []
call_order: list[str] = []
@server.tool("sleep")
async def sleep_tool():
@ -52,7 +52,7 @@ async def test_messages_are_executed_concurrently_tools_and_resources():
server = FastMCP("test")
event = anyio.Event()
tool_started = anyio.Event()
call_order = []
call_order: list[str] = []
@server.tool("sleep")
async def sleep_tool():

View file

@ -12,6 +12,7 @@ from mcp.types import (
JSONRPCMessage,
JSONRPCNotification,
JSONRPCRequest,
JSONRPCResponse,
NotificationParams,
)
@ -23,8 +24,8 @@ async def test_request_id_match() -> None:
custom_request_id = "test-123"
# Create memory streams for communication
client_writer, client_reader = anyio.create_memory_object_stream(1)
server_writer, server_reader = anyio.create_memory_object_stream(1)
client_writer, client_reader = anyio.create_memory_object_stream[SessionMessage | Exception](1)
server_writer, server_reader = anyio.create_memory_object_stream[SessionMessage | Exception](1)
# Server task to process the request
async def run_server():
@ -85,6 +86,9 @@ async def test_request_id_match() -> None:
response = await server_reader.receive()
# Verify response ID matches request ID
assert isinstance(response, SessionMessage)
assert isinstance(response.message, JSONRPCMessage)
assert isinstance(response.message.root, JSONRPCResponse)
assert response.message.root.id == custom_request_id, "Response ID should match request ID"
# Cancel server task

View file

@ -3,6 +3,7 @@ from contextlib import asynccontextmanager
from dataclasses import dataclass
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.session import ServerSession
class Database: # Replace with your actual DB type
@ -44,7 +45,7 @@ mcp = FastMCP("My App", lifespan=app_lifespan)
# Access type-safe lifespan context in tools
@mcp.tool()
def query_db(ctx: Context) -> str:
def query_db(ctx: Context[ServerSession, AppContext]) -> str:
"""Tool that uses initialized resources"""
db = ctx.request_context.lifespan_context.db
return db.query()

View file

@ -3,15 +3,18 @@
from collections.abc import Sequence
from datetime import timedelta
from pathlib import Path
from typing import Any
import anyio
import pytest
from anyio.abc import TaskStatus
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
from mcp import types
from mcp.client.session import ClientSession
from mcp.server.lowlevel import Server
from mcp.shared.exceptions import McpError
from mcp.shared.message import SessionMessage
from mcp.types import ContentBlock, TextContent
@ -46,7 +49,7 @@ async def test_notification_validation_error(tmp_path: Path):
]
@server.call_tool()
async def slow_tool(name: str, arg) -> Sequence[ContentBlock]:
async def slow_tool(name: str, arguments: dict[str, Any]) -> Sequence[ContentBlock]:
nonlocal request_count
request_count += 1
@ -58,8 +61,8 @@ async def test_notification_validation_error(tmp_path: Path):
return [TextContent(type="text", text=f"unknown {request_count}")]
async def server_handler(
read_stream,
write_stream,
read_stream: MemoryObjectReceiveStream[SessionMessage | Exception],
write_stream: MemoryObjectSendStream[SessionMessage],
task_status: TaskStatus[str] = anyio.TASK_STATUS_IGNORED,
):
with anyio.CancelScope() as scope:
@ -71,7 +74,11 @@ async def test_notification_validation_error(tmp_path: Path):
raise_exceptions=True,
)
async def client(read_stream, write_stream, scope):
async def client(
read_stream: MemoryObjectReceiveStream[SessionMessage | Exception],
write_stream: MemoryObjectSendStream[SessionMessage],
scope: anyio.CancelScope,
):
# Use a timeout that's:
# - Long enough for fast operations (>10ms)
# - Short enough for slow operations (<200ms)
@ -99,8 +106,8 @@ async def test_notification_validation_error(tmp_path: Path):
scope.cancel()
# Run server and client in separate task groups to avoid cancellation
server_writer, server_reader = anyio.create_memory_object_stream(1)
client_writer, client_reader = anyio.create_memory_object_stream(1)
server_writer, server_reader = anyio.create_memory_object_stream[SessionMessage](1)
client_writer, client_reader = anyio.create_memory_object_stream[SessionMessage](1)
async with anyio.create_task_group() as tg:
scope = await tg.start(server_handler, server_reader, client_writer)

View file

@ -1,6 +1,8 @@
# Claude Debug
"""Test for HackerOne vulnerability report #3156202 - malformed input DOS."""
from typing import Any
import anyio
import pytest
@ -118,7 +120,7 @@ async def test_multiple_concurrent_malformed_requests():
),
):
# Send multiple malformed requests concurrently
malformed_requests = []
malformed_requests: list[SessionMessage] = []
for i in range(10):
malformed_request = JSONRPCRequest(
jsonrpc="2.0",
@ -137,7 +139,7 @@ async def test_multiple_concurrent_malformed_requests():
await anyio.sleep(0.2)
# Verify we get error responses for all requests
error_responses = []
error_responses: list[Any] = []
try:
while True:
response_message = write_receive_stream.receive_nowait()