Add pyright strict mode on the whole project (#1254)
This commit is contained in:
parent
ef4e167c1a
commit
c7671e470c
6 changed files with 652 additions and 674 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue