From 6c71dcf9f7a1f8525aac14be19b2bfef463e5b20 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 16 Jan 2026 15:13:01 +0100 Subject: [PATCH 1/5] chore: add D212 lint rule to enforce Google-style docstrings Github-Issue: #1890 --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e1b175c769..4925e603db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -127,6 +127,7 @@ extend-exclude = ["README.md"] select = [ "C4", # flake8-comprehensions "C90", # mccabe + "D212", # pydocstyle: multi-line docstring summary should start at the first line "E", # pycodestyle "F", # pyflakes "I", # isort @@ -135,7 +136,9 @@ select = [ "UP", # pyupgrade ] ignore = ["PERF203", "PLC0415", "PLR0402"] -mccabe.max-complexity = 24 # Default is 10 + +[tool.ruff.lint.mccabe] +max-complexity = 24 # Default is 10 [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] From cc533af72ddf438ae5316ac19032c78abc637b29 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 16 Jan 2026 15:13:06 +0100 Subject: [PATCH 2/5] style: fix D212 violations - move docstring summaries to first line --- examples/fastmcp/complex_inputs.py | 3 +- examples/fastmcp/desktop.py | 3 +- .../fastmcp/direct_call_tool_result_return.py | 4 +- examples/fastmcp/echo.py | 4 +- examples/fastmcp/icons_demo.py | 3 +- examples/fastmcp/logging_and_progress.py | 4 +- examples/fastmcp/memory.py | 3 +- examples/fastmcp/parameter_descriptions.py | 4 +- examples/fastmcp/screenshot.py | 6 +- examples/fastmcp/simple_echo.py | 4 +- examples/fastmcp/text_me.py | 3 +- examples/fastmcp/unicode_example.py | 6 +- examples/fastmcp/weather_structured.py | 3 +- .../mcp_simple_auth/auth_server.py | 12 +-- .../mcp_simple_auth/legacy_as_server.py | 6 +- .../simple-auth/mcp_simple_auth/server.py | 12 +-- .../mcp_simple_auth/simple_auth_provider.py | 6 +- .../__main__.py | 7 +- .../snippets/clients/completion_client.py | 5 +- .../snippets/clients/display_utilities.py | 5 +- examples/snippets/clients/oauth_client.py | 3 +- .../snippets/clients/pagination_client.py | 4 +- examples/snippets/clients/stdio_client.py | 5 +- examples/snippets/clients/streamable_basic.py | 5 +- .../snippets/servers/fastmcp_quickstart.py | 3 +- examples/snippets/servers/lowlevel/basic.py | 3 +- .../lowlevel/direct_call_tool_result.py | 5 +- .../snippets/servers/lowlevel/lifespan.py | 5 +- .../servers/lowlevel/structured_output.py | 5 +- examples/snippets/servers/oauth_server.py | 5 +- .../snippets/servers/pagination_example.py | 4 +- .../snippets/servers/streamable_config.py | 5 +- .../servers/streamable_http_basic_mounting.py | 3 +- .../servers/streamable_http_host_mounting.py | 3 +- .../streamable_http_multiple_servers.py | 3 +- .../servers/streamable_http_path_config.py | 3 +- .../servers/streamable_starlette_mount.py | 5 +- scripts/update_readme_snippets.py | 3 +- src/mcp/client/auth/__init__.py | 3 +- .../auth/extensions/client_credentials.py | 3 +- src/mcp/client/auth/oauth2.py | 9 +- src/mcp/client/auth/utils.py | 18 ++-- src/mcp/client/experimental/__init__.py | 3 +- src/mcp/client/experimental/task_handlers.py | 3 +- src/mcp/client/experimental/tasks.py | 21 ++-- src/mcp/client/session_group.py | 3 +- src/mcp/client/sse.py | 3 +- src/mcp/client/stdio/__init__.py | 15 +-- src/mcp/client/streamable_http.py | 3 +- src/mcp/client/websocket.py | 9 +- src/mcp/os/posix/utilities.py | 7 +- src/mcp/os/win32/utilities.py | 29 ++---- src/mcp/server/auth/__init__.py | 4 +- src/mcp/server/auth/handlers/__init__.py | 4 +- src/mcp/server/auth/handlers/revoke.py | 8 +- src/mcp/server/auth/handlers/token.py | 4 +- src/mcp/server/auth/middleware/__init__.py | 4 +- .../server/auth/middleware/auth_context.py | 6 +- src/mcp/server/auth/middleware/bearer_auth.py | 10 +- src/mcp/server/auth/middleware/client_auth.py | 9 +- src/mcp/server/auth/provider.py | 27 ++--- src/mcp/server/auth/routes.py | 9 +- src/mcp/server/experimental/__init__.py | 3 +- .../server/experimental/request_context.py | 18 ++-- .../server/experimental/session_features.py | 21 ++-- src/mcp/server/experimental/task_context.py | 33 +++---- .../experimental/task_result_handler.py | 24 ++--- src/mcp/server/experimental/task_support.py | 15 +-- src/mcp/server/fastmcp/server.py | 10 +- .../server/fastmcp/utilities/func_metadata.py | 6 +- src/mcp/server/lowlevel/experimental.py | 3 +- src/mcp/server/lowlevel/func_inspection.py | 3 +- src/mcp/server/lowlevel/server.py | 3 +- src/mcp/server/models.py | 3 +- src/mcp/server/session.py | 3 +- src/mcp/server/sse.py | 12 +-- src/mcp/server/stdio.py | 6 +- src/mcp/server/streamable_http.py | 29 ++---- src/mcp/server/streamable_http_manager.py | 15 +-- src/mcp/server/validation.py | 12 +-- src/mcp/server/websocket.py | 3 +- src/mcp/shared/auth.py | 16 +-- src/mcp/shared/context.py | 4 +- src/mcp/shared/exceptions.py | 10 +- src/mcp/shared/experimental/__init__.py | 3 +- src/mcp/shared/experimental/tasks/__init__.py | 3 +- .../shared/experimental/tasks/capabilities.py | 12 +-- src/mcp/shared/experimental/tasks/context.py | 18 ++-- src/mcp/shared/experimental/tasks/helpers.py | 15 +-- .../tasks/in_memory_task_store.py | 6 +- .../experimental/tasks/message_queue.py | 36 +++---- src/mcp/shared/experimental/tasks/polling.py | 6 +- src/mcp/shared/experimental/tasks/resolver.py | 6 +- src/mcp/shared/experimental/tasks/store.py | 34 +++---- src/mcp/shared/memory.py | 7 +- src/mcp/shared/message.py | 3 +- src/mcp/shared/metadata_utils.py | 3 +- src/mcp/shared/response_router.py | 12 +-- src/mcp/shared/session.py | 27 ++--- src/mcp/types.py | 88 ++++++----------- tests/client/test_auth.py | 4 +- tests/client/test_http_unicode.py | 3 +- tests/client/test_notification_response.py | 6 +- tests/client/test_output_schema_validation.py | 3 +- tests/client/test_resource_cleanup.py | 3 +- tests/client/test_scope_bug_1630.py | 6 +- tests/client/test_stdio.py | 25 ++--- .../tasks/server/test_integration.py | 3 +- .../tasks/server/test_run_task_flow.py | 14 +-- .../tasks/test_elicitation_scenarios.py | 21 ++-- .../experimental/tasks/test_message_queue.py | 4 +- .../tasks/test_spec_compliance.py | 98 ++++++------------- .../test_1027_win_unreachable_cleanup.py | 9 +- ...est_1363_race_condition_streamable_http.py | 12 +-- tests/issues/test_552_windows_hang.py | 3 +- tests/issues/test_malformed_input.py | 7 +- .../auth/middleware/test_auth_context.py | 4 +- .../auth/middleware/test_bearer_auth.py | 4 +- tests/server/auth/test_error_handling.py | 4 +- tests/server/auth/test_protected_resource.py | 4 +- tests/server/auth/test_provider.py | 4 +- tests/server/fastmcp/auth/__init__.py | 4 +- .../fastmcp/auth/test_auth_integration.py | 4 +- tests/server/fastmcp/test_elicitation.py | 4 +- tests/server/fastmcp/test_func_metadata.py | 9 +- tests/server/fastmcp/test_integration.py | 3 +- tests/server/test_completion_with_context.py | 4 +- tests/server/test_session_race_condition.py | 6 +- tests/shared/test_session.py | 13 +-- tests/shared/test_streamable_http.py | 6 +- tests/test_types.py | 3 +- 131 files changed, 394 insertions(+), 823 deletions(-) diff --git a/examples/fastmcp/complex_inputs.py b/examples/fastmcp/complex_inputs.py index e859165a97..b55d4f725b 100644 --- a/examples/fastmcp/complex_inputs.py +++ b/examples/fastmcp/complex_inputs.py @@ -1,5 +1,4 @@ -""" -FastMCP Complex inputs Example +"""FastMCP Complex inputs Example Demonstrates validation via pydantic with complex models. """ diff --git a/examples/fastmcp/desktop.py b/examples/fastmcp/desktop.py index add7f515bc..8ea62c70f8 100644 --- a/examples/fastmcp/desktop.py +++ b/examples/fastmcp/desktop.py @@ -1,5 +1,4 @@ -""" -FastMCP Desktop Example +"""FastMCP Desktop Example A simple example that exposes the desktop directory as a resource. """ diff --git a/examples/fastmcp/direct_call_tool_result_return.py b/examples/fastmcp/direct_call_tool_result_return.py index 85d5a25979..2218af49b4 100644 --- a/examples/fastmcp/direct_call_tool_result_return.py +++ b/examples/fastmcp/direct_call_tool_result_return.py @@ -1,6 +1,4 @@ -""" -FastMCP Echo Server with direct CallToolResult return -""" +"""FastMCP Echo Server with direct CallToolResult return""" from typing import Annotated diff --git a/examples/fastmcp/echo.py b/examples/fastmcp/echo.py index 7bdbcdce6b..9f01e60ca2 100644 --- a/examples/fastmcp/echo.py +++ b/examples/fastmcp/echo.py @@ -1,6 +1,4 @@ -""" -FastMCP Echo Server -""" +"""FastMCP Echo Server""" from mcp.server.fastmcp import FastMCP diff --git a/examples/fastmcp/icons_demo.py b/examples/fastmcp/icons_demo.py index 6f6da6b9e1..47601c0356 100644 --- a/examples/fastmcp/icons_demo.py +++ b/examples/fastmcp/icons_demo.py @@ -1,5 +1,4 @@ -""" -FastMCP Icons Demo Server +"""FastMCP Icons Demo Server Demonstrates using icons with tools, resources, prompts, and implementation. """ diff --git a/examples/fastmcp/logging_and_progress.py b/examples/fastmcp/logging_and_progress.py index 91c2b806dd..016155233a 100644 --- a/examples/fastmcp/logging_and_progress.py +++ b/examples/fastmcp/logging_and_progress.py @@ -1,6 +1,4 @@ -""" -FastMCP Echo Server that sends log messages and progress updates to the client -""" +"""FastMCP Echo Server that sends log messages and progress updates to the client""" import asyncio diff --git a/examples/fastmcp/memory.py b/examples/fastmcp/memory.py index ca38075f8e..cc87ea930c 100644 --- a/examples/fastmcp/memory.py +++ b/examples/fastmcp/memory.py @@ -4,8 +4,7 @@ # uv pip install 'pydantic-ai-slim[openai]' asyncpg numpy pgvector -""" -Recursive memory system inspired by the human brain's clustering of memories. +"""Recursive memory system inspired by the human brain's clustering of memories. Uses OpenAI's 'text-embedding-3-small' model and pgvector for efficient similarity search. """ diff --git a/examples/fastmcp/parameter_descriptions.py b/examples/fastmcp/parameter_descriptions.py index dc56e91821..307ae5cedd 100644 --- a/examples/fastmcp/parameter_descriptions.py +++ b/examples/fastmcp/parameter_descriptions.py @@ -1,6 +1,4 @@ -""" -FastMCP Example showing parameter descriptions -""" +"""FastMCP Example showing parameter descriptions""" from pydantic import Field diff --git a/examples/fastmcp/screenshot.py b/examples/fastmcp/screenshot.py index 12549b351a..2c73c9847c 100644 --- a/examples/fastmcp/screenshot.py +++ b/examples/fastmcp/screenshot.py @@ -1,5 +1,4 @@ -""" -FastMCP Screenshot Example +"""FastMCP Screenshot Example Give Claude a tool to capture and view screenshots. """ @@ -15,8 +14,7 @@ @mcp.tool() def take_screenshot() -> Image: - """ - Take a screenshot of the user's screen and return it as an image. Use + """Take a screenshot of the user's screen and return it as an image. Use this tool anytime the user wants you to look at something they're doing. """ import pyautogui diff --git a/examples/fastmcp/simple_echo.py b/examples/fastmcp/simple_echo.py index c26152646f..d0fa597004 100644 --- a/examples/fastmcp/simple_echo.py +++ b/examples/fastmcp/simple_echo.py @@ -1,6 +1,4 @@ -""" -FastMCP Echo Server -""" +"""FastMCP Echo Server""" from mcp.server.fastmcp import FastMCP diff --git a/examples/fastmcp/text_me.py b/examples/fastmcp/text_me.py index 2434dcddd9..8a8dea351a 100644 --- a/examples/fastmcp/text_me.py +++ b/examples/fastmcp/text_me.py @@ -2,8 +2,7 @@ # dependencies = [] # /// -""" -FastMCP Text Me Server +"""FastMCP Text Me Server -------------------------------- This defines a simple FastMCP server that sends a text message to a phone number via https://surgemsg.com/. diff --git a/examples/fastmcp/unicode_example.py b/examples/fastmcp/unicode_example.py index bb487f6180..a598397845 100644 --- a/examples/fastmcp/unicode_example.py +++ b/examples/fastmcp/unicode_example.py @@ -1,5 +1,4 @@ -""" -Example FastMCP server that uses Unicode characters in various places to help test +"""Example FastMCP server that uses Unicode characters in various places to help test Unicode handling in tools and inspectors. """ @@ -10,8 +9,7 @@ @mcp.tool(description="🌟 A tool that uses various Unicode characters in its description: á é í ó ú ñ 漢字 🎉") def hello_unicode(name: str = "世界", greeting: str = "¡Hola") -> str: - """ - A simple tool that demonstrates Unicode handling in: + """A simple tool that demonstrates Unicode handling in: - Tool description (emojis, accents, CJK characters) - Parameter defaults (CJK characters) - Return values (Spanish punctuation, emojis) diff --git a/examples/fastmcp/weather_structured.py b/examples/fastmcp/weather_structured.py index 60c24a8f53..af4e435dfd 100644 --- a/examples/fastmcp/weather_structured.py +++ b/examples/fastmcp/weather_structured.py @@ -1,5 +1,4 @@ -""" -FastMCP Weather Example with Structured Output +"""FastMCP Weather Example with Structured Output Demonstrates how to use structured output with tools to return well-typed, validated data that clients can easily process. diff --git a/examples/servers/simple-auth/mcp_simple_auth/auth_server.py b/examples/servers/simple-auth/mcp_simple_auth/auth_server.py index 80a2e8b8a3..9d13fffe42 100644 --- a/examples/servers/simple-auth/mcp_simple_auth/auth_server.py +++ b/examples/servers/simple-auth/mcp_simple_auth/auth_server.py @@ -1,5 +1,4 @@ -""" -Authorization Server for MCP Split Demo. +"""Authorization Server for MCP Split Demo. This server handles OAuth flows, client registration, and token issuance. Can be replaced with enterprise authorization servers like Auth0, Entra ID, etc. @@ -41,8 +40,7 @@ class AuthServerSettings(BaseModel): class SimpleAuthProvider(SimpleOAuthProvider): - """ - Authorization Server provider with simple demo authentication. + """Authorization Server provider with simple demo authentication. This provider: 1. Issues MCP tokens after simple credential authentication @@ -98,8 +96,7 @@ async def login_callback_handler(request: Request) -> Response: # Add token introspection endpoint (RFC 7662) for Resource Servers async def introspect_handler(request: Request) -> Response: - """ - Token introspection endpoint for Resource Servers. + """Token introspection endpoint for Resource Servers. Resource Servers call this endpoint to validate tokens without needing direct access to token storage. @@ -157,8 +154,7 @@ async def run_server(server_settings: AuthServerSettings, auth_settings: SimpleA @click.command() @click.option("--port", default=9000, help="Port to listen on") def main(port: int) -> int: - """ - Run the MCP Authorization Server. + """Run the MCP Authorization Server. This server handles OAuth flows and can be used by multiple Resource Servers. diff --git a/examples/servers/simple-auth/mcp_simple_auth/legacy_as_server.py b/examples/servers/simple-auth/mcp_simple_auth/legacy_as_server.py index de60f2a872..ac9dfcb571 100644 --- a/examples/servers/simple-auth/mcp_simple_auth/legacy_as_server.py +++ b/examples/servers/simple-auth/mcp_simple_auth/legacy_as_server.py @@ -1,5 +1,4 @@ -""" -Legacy Combined Authorization Server + Resource Server for MCP. +"""Legacy Combined Authorization Server + Resource Server for MCP. This server implements the old spec where MCP servers could act as both AS and RS. Used for backwards compatibility testing with the new split AS/RS architecture. @@ -87,8 +86,7 @@ async def login_callback_handler(request: Request) -> Response: @app.tool() async def get_time() -> dict[str, Any]: - """ - Get the current server time. + """Get the current server time. This tool demonstrates that system information can be protected by OAuth authentication. User must be authenticated to access it. diff --git a/examples/servers/simple-auth/mcp_simple_auth/server.py b/examples/servers/simple-auth/mcp_simple_auth/server.py index 5668316523..28d2565429 100644 --- a/examples/servers/simple-auth/mcp_simple_auth/server.py +++ b/examples/servers/simple-auth/mcp_simple_auth/server.py @@ -1,5 +1,4 @@ -""" -MCP Resource Server with Token Introspection. +"""MCP Resource Server with Token Introspection. This server validates tokens via Authorization Server introspection and serves MCP resources. Demonstrates RFC 9728 Protected Resource Metadata for AS/RS separation. @@ -47,8 +46,7 @@ class ResourceServerSettings(BaseSettings): def create_resource_server(settings: ResourceServerSettings) -> FastMCP: - """ - Create MCP Resource Server with token introspection. + """Create MCP Resource Server with token introspection. This server: 1. Provides protected resource metadata (RFC 9728) @@ -80,8 +78,7 @@ def create_resource_server(settings: ResourceServerSettings) -> FastMCP: @app.tool() async def get_time() -> dict[str, Any]: - """ - Get the current server time. + """Get the current server time. This tool demonstrates that system information can be protected by OAuth authentication. User must be authenticated to access it. @@ -114,8 +111,7 @@ async def get_time() -> dict[str, Any]: help="Enable RFC 8707 resource validation", ) def main(port: int, auth_server: str, transport: Literal["sse", "streamable-http"], oauth_strict: bool) -> int: - """ - Run the MCP Resource Server. + """Run the MCP Resource Server. This server: - Provides RFC 9728 Protected Resource Metadata diff --git a/examples/servers/simple-auth/mcp_simple_auth/simple_auth_provider.py b/examples/servers/simple-auth/mcp_simple_auth/simple_auth_provider.py index e3a25d3e8c..e244e0fd94 100644 --- a/examples/servers/simple-auth/mcp_simple_auth/simple_auth_provider.py +++ b/examples/servers/simple-auth/mcp_simple_auth/simple_auth_provider.py @@ -1,5 +1,4 @@ -""" -Simple OAuth provider for MCP servers. +"""Simple OAuth provider for MCP servers. This module contains a basic OAuth implementation using hardcoded user credentials for demonstration purposes. No external authentication provider is required. @@ -47,8 +46,7 @@ class SimpleAuthSettings(BaseSettings): class SimpleOAuthProvider(OAuthAuthorizationServerProvider[AuthorizationCode, RefreshToken, AccessToken]): - """ - Simple OAuth provider for demo purposes. + """Simple OAuth provider for demo purposes. This provider handles the OAuth flow by: 1. Providing a simple login form for demo credentials diff --git a/examples/servers/structured-output-lowlevel/mcp_structured_output_lowlevel/__main__.py b/examples/servers/structured-output-lowlevel/mcp_structured_output_lowlevel/__main__.py index d730d1daf6..49eba9464d 100644 --- a/examples/servers/structured-output-lowlevel/mcp_structured_output_lowlevel/__main__.py +++ b/examples/servers/structured-output-lowlevel/mcp_structured_output_lowlevel/__main__.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Example low-level MCP server demonstrating structured output support. +"""Example low-level MCP server demonstrating structured output support. This example shows how to use the low-level server API to return structured data from tools, with automatic validation against output @@ -49,9 +48,7 @@ async def list_tools() -> list[types.Tool]: @server.call_tool() async def call_tool(name: str, arguments: dict[str, Any]) -> Any: - """ - Handle tool call with structured output. - """ + """Handle tool call with structured output.""" if name == "get_weather": # city = arguments["city"] # Would be used with real weather API diff --git a/examples/snippets/clients/completion_client.py b/examples/snippets/clients/completion_client.py index 1d2aea1ae7..dc0c1b4f72 100644 --- a/examples/snippets/clients/completion_client.py +++ b/examples/snippets/clients/completion_client.py @@ -1,6 +1,5 @@ -""" -cd to the `examples/snippets` directory and run: - uv run completion-client +"""cd to the `examples/snippets` directory and run: +uv run completion-client """ import asyncio diff --git a/examples/snippets/clients/display_utilities.py b/examples/snippets/clients/display_utilities.py index 047e821c3d..40e31cf2b1 100644 --- a/examples/snippets/clients/display_utilities.py +++ b/examples/snippets/clients/display_utilities.py @@ -1,6 +1,5 @@ -""" -cd to the `examples/snippets` directory and run: - uv run display-utilities-client +"""cd to the `examples/snippets` directory and run: +uv run display-utilities-client """ import asyncio diff --git a/examples/snippets/clients/oauth_client.py b/examples/snippets/clients/oauth_client.py index 140b38aedb..6d605afa92 100644 --- a/examples/snippets/clients/oauth_client.py +++ b/examples/snippets/clients/oauth_client.py @@ -1,5 +1,4 @@ -""" -Before running, specify running MCP RS server URL. +"""Before running, specify running MCP RS server URL. To spin up RS server locally, see examples/servers/simple-auth/README.md diff --git a/examples/snippets/clients/pagination_client.py b/examples/snippets/clients/pagination_client.py index fd266e4623..b9b8c23ae7 100644 --- a/examples/snippets/clients/pagination_client.py +++ b/examples/snippets/clients/pagination_client.py @@ -1,6 +1,4 @@ -""" -Example of consuming paginated MCP endpoints from a client. -""" +"""Example of consuming paginated MCP endpoints from a client.""" import asyncio diff --git a/examples/snippets/clients/stdio_client.py b/examples/snippets/clients/stdio_client.py index 08d7cfcdbc..b594a217b1 100644 --- a/examples/snippets/clients/stdio_client.py +++ b/examples/snippets/clients/stdio_client.py @@ -1,6 +1,5 @@ -""" -cd to the `examples/snippets/clients` directory and run: - uv run client +"""cd to the `examples/snippets/clients` directory and run: +uv run client """ import asyncio diff --git a/examples/snippets/clients/streamable_basic.py b/examples/snippets/clients/streamable_basic.py index 071ea81553..87e16f4ba9 100644 --- a/examples/snippets/clients/streamable_basic.py +++ b/examples/snippets/clients/streamable_basic.py @@ -1,6 +1,5 @@ -""" -Run from the repository root: - uv run examples/snippets/clients/streamable_basic.py +"""Run from the repository root: +uv run examples/snippets/clients/streamable_basic.py """ import asyncio diff --git a/examples/snippets/servers/fastmcp_quickstart.py b/examples/snippets/servers/fastmcp_quickstart.py index de78dbe907..f762e908ab 100644 --- a/examples/snippets/servers/fastmcp_quickstart.py +++ b/examples/snippets/servers/fastmcp_quickstart.py @@ -1,5 +1,4 @@ -""" -FastMCP quickstart example. +"""FastMCP quickstart example. Run from the repository root: uv run examples/snippets/servers/fastmcp_quickstart.py diff --git a/examples/snippets/servers/lowlevel/basic.py b/examples/snippets/servers/lowlevel/basic.py index a5c4149df7..ee01b84268 100644 --- a/examples/snippets/servers/lowlevel/basic.py +++ b/examples/snippets/servers/lowlevel/basic.py @@ -1,5 +1,4 @@ -""" -Run from the repository root: +"""Run from the repository root: uv run examples/snippets/servers/lowlevel/basic.py """ diff --git a/examples/snippets/servers/lowlevel/direct_call_tool_result.py b/examples/snippets/servers/lowlevel/direct_call_tool_result.py index 4c83abd32f..967dc0cbaa 100644 --- a/examples/snippets/servers/lowlevel/direct_call_tool_result.py +++ b/examples/snippets/servers/lowlevel/direct_call_tool_result.py @@ -1,6 +1,5 @@ -""" -Run from the repository root: - uv run examples/snippets/servers/lowlevel/direct_call_tool_result.py +"""Run from the repository root: +uv run examples/snippets/servers/lowlevel/direct_call_tool_result.py """ import asyncio diff --git a/examples/snippets/servers/lowlevel/lifespan.py b/examples/snippets/servers/lowlevel/lifespan.py index 2ae7c10358..89ef0385a2 100644 --- a/examples/snippets/servers/lowlevel/lifespan.py +++ b/examples/snippets/servers/lowlevel/lifespan.py @@ -1,6 +1,5 @@ -""" -Run from the repository root: - uv run examples/snippets/servers/lowlevel/lifespan.py +"""Run from the repository root: +uv run examples/snippets/servers/lowlevel/lifespan.py """ from collections.abc import AsyncIterator diff --git a/examples/snippets/servers/lowlevel/structured_output.py b/examples/snippets/servers/lowlevel/structured_output.py index 6bc4384a61..a99a1ac635 100644 --- a/examples/snippets/servers/lowlevel/structured_output.py +++ b/examples/snippets/servers/lowlevel/structured_output.py @@ -1,6 +1,5 @@ -""" -Run from the repository root: - uv run examples/snippets/servers/lowlevel/structured_output.py +"""Run from the repository root: +uv run examples/snippets/servers/lowlevel/structured_output.py """ import asyncio diff --git a/examples/snippets/servers/oauth_server.py b/examples/snippets/servers/oauth_server.py index a8f078d2de..226a3ec6e1 100644 --- a/examples/snippets/servers/oauth_server.py +++ b/examples/snippets/servers/oauth_server.py @@ -1,6 +1,5 @@ -""" -Run from the repository root: - uv run examples/snippets/servers/oauth_server.py +"""Run from the repository root: +uv run examples/snippets/servers/oauth_server.py """ from pydantic import AnyHttpUrl diff --git a/examples/snippets/servers/pagination_example.py b/examples/snippets/servers/pagination_example.py index aa67750b54..7ed30365ce 100644 --- a/examples/snippets/servers/pagination_example.py +++ b/examples/snippets/servers/pagination_example.py @@ -1,6 +1,4 @@ -""" -Example of implementing pagination with MCP server decorators. -""" +"""Example of implementing pagination with MCP server decorators.""" import mcp.types as types from mcp.server.lowlevel import Server diff --git a/examples/snippets/servers/streamable_config.py b/examples/snippets/servers/streamable_config.py index f09c950b4b..ca68102242 100644 --- a/examples/snippets/servers/streamable_config.py +++ b/examples/snippets/servers/streamable_config.py @@ -1,6 +1,5 @@ -""" -Run from the repository root: - uv run examples/snippets/servers/streamable_config.py +"""Run from the repository root: +uv run examples/snippets/servers/streamable_config.py """ from mcp.server.fastmcp import FastMCP diff --git a/examples/snippets/servers/streamable_http_basic_mounting.py b/examples/snippets/servers/streamable_http_basic_mounting.py index 6694b801b1..7a32dbef96 100644 --- a/examples/snippets/servers/streamable_http_basic_mounting.py +++ b/examples/snippets/servers/streamable_http_basic_mounting.py @@ -1,5 +1,4 @@ -""" -Basic example showing how to mount StreamableHTTP server in Starlette. +"""Basic example showing how to mount StreamableHTTP server in Starlette. Run from the repository root: uvicorn examples.snippets.servers.streamable_http_basic_mounting:app --reload diff --git a/examples/snippets/servers/streamable_http_host_mounting.py b/examples/snippets/servers/streamable_http_host_mounting.py index 176ecffea4..57da57f7b7 100644 --- a/examples/snippets/servers/streamable_http_host_mounting.py +++ b/examples/snippets/servers/streamable_http_host_mounting.py @@ -1,5 +1,4 @@ -""" -Example showing how to mount StreamableHTTP server using Host-based routing. +"""Example showing how to mount StreamableHTTP server using Host-based routing. Run from the repository root: uvicorn examples.snippets.servers.streamable_http_host_mounting:app --reload diff --git a/examples/snippets/servers/streamable_http_multiple_servers.py b/examples/snippets/servers/streamable_http_multiple_servers.py index dd2fd6b7dc..cf6c6985d2 100644 --- a/examples/snippets/servers/streamable_http_multiple_servers.py +++ b/examples/snippets/servers/streamable_http_multiple_servers.py @@ -1,5 +1,4 @@ -""" -Example showing how to mount multiple StreamableHTTP servers with path configuration. +"""Example showing how to mount multiple StreamableHTTP servers with path configuration. Run from the repository root: uvicorn examples.snippets.servers.streamable_http_multiple_servers:app --reload diff --git a/examples/snippets/servers/streamable_http_path_config.py b/examples/snippets/servers/streamable_http_path_config.py index 1fb91489e9..1dcceeeeb7 100644 --- a/examples/snippets/servers/streamable_http_path_config.py +++ b/examples/snippets/servers/streamable_http_path_config.py @@ -1,5 +1,4 @@ -""" -Example showing path configuration when mounting FastMCP. +"""Example showing path configuration when mounting FastMCP. Run from the repository root: uvicorn examples.snippets.servers.streamable_http_path_config:app --reload diff --git a/examples/snippets/servers/streamable_starlette_mount.py b/examples/snippets/servers/streamable_starlette_mount.py index 5ca38be3de..c33dc71bc7 100644 --- a/examples/snippets/servers/streamable_starlette_mount.py +++ b/examples/snippets/servers/streamable_starlette_mount.py @@ -1,6 +1,5 @@ -""" -Run from the repository root: - uvicorn examples.snippets.servers.streamable_starlette_mount:app --reload +"""Run from the repository root: +uvicorn examples.snippets.servers.streamable_starlette_mount:app --reload """ import contextlib diff --git a/scripts/update_readme_snippets.py b/scripts/update_readme_snippets.py index d325333fff..8d1f198230 100755 --- a/scripts/update_readme_snippets.py +++ b/scripts/update_readme_snippets.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Update README.md with live code snippets from example files. +"""Update README.md with live code snippets from example files. This script finds specially marked code blocks in README.md and updates them with the actual code from the referenced files. diff --git a/src/mcp/client/auth/__init__.py b/src/mcp/client/auth/__init__.py index 252dfd9e4c..ab3179ecb9 100644 --- a/src/mcp/client/auth/__init__.py +++ b/src/mcp/client/auth/__init__.py @@ -1,5 +1,4 @@ -""" -OAuth2 Authentication implementation for HTTPX. +"""OAuth2 Authentication implementation for HTTPX. Implements authorization code flow with PKCE and automatic token refresh. """ diff --git a/src/mcp/client/auth/extensions/client_credentials.py b/src/mcp/client/auth/extensions/client_credentials.py index 510cdb2d61..07f6180bf1 100644 --- a/src/mcp/client/auth/extensions/client_credentials.py +++ b/src/mcp/client/auth/extensions/client_credentials.py @@ -1,5 +1,4 @@ -""" -OAuth client credential extensions for MCP. +"""OAuth client credential extensions for MCP. Provides OAuth providers for machine-to-machine authentication flows: - ClientCredentialsOAuthProvider: For client_credentials with client_id + client_secret diff --git a/src/mcp/client/auth/oauth2.py b/src/mcp/client/auth/oauth2.py index ddc61ef663..8e699b57a0 100644 --- a/src/mcp/client/auth/oauth2.py +++ b/src/mcp/client/auth/oauth2.py @@ -1,5 +1,4 @@ -""" -OAuth2 Authentication implementation for HTTPX. +"""OAuth2 Authentication implementation for HTTPX. Implements authorization code flow with PKCE and automatic token refresh. """ @@ -215,8 +214,7 @@ def prepare_token_auth( class OAuthClientProvider(httpx.Auth): - """ - OAuth2 authentication for httpx. + """OAuth2 authentication for httpx. Handles OAuth flow with automatic client registration and token storage. """ @@ -268,8 +266,7 @@ def __init__( self._initialized = False async def _handle_protected_resource_response(self, response: httpx.Response) -> bool: - """ - Handle protected resource metadata discovery response. + """Handle protected resource metadata discovery response. Per SEP-985, supports fallback when discovery fails at one URL. diff --git a/src/mcp/client/auth/utils.py b/src/mcp/client/auth/utils.py index b4426be7f8..1ce57818dc 100644 --- a/src/mcp/client/auth/utils.py +++ b/src/mcp/client/auth/utils.py @@ -20,8 +20,7 @@ def extract_field_from_www_auth(response: Response, field_name: str) -> str | None: - """ - Extract field from WWW-Authenticate header. + """Extract field from WWW-Authenticate header. Returns: Field value if found in WWW-Authenticate header, None otherwise @@ -42,8 +41,7 @@ def extract_field_from_www_auth(response: Response, field_name: str) -> str | No def extract_scope_from_www_auth(response: Response) -> str | None: - """ - Extract scope parameter from WWW-Authenticate header as per RFC6750. + """Extract scope parameter from WWW-Authenticate header as per RFC6750. Returns: Scope string if found in WWW-Authenticate header, None otherwise @@ -52,8 +50,7 @@ def extract_scope_from_www_auth(response: Response) -> str | None: def extract_resource_metadata_from_www_auth(response: Response) -> str | None: - """ - Extract protected resource metadata URL from WWW-Authenticate header as per RFC9728. + """Extract protected resource metadata URL from WWW-Authenticate header as per RFC9728. Returns: Resource metadata URL if found in WWW-Authenticate header, None otherwise @@ -65,8 +62,7 @@ def extract_resource_metadata_from_www_auth(response: Response) -> str | None: def build_protected_resource_metadata_discovery_urls(www_auth_url: str | None, server_url: str) -> list[str]: - """ - Build ordered list of URLs to try for protected resource metadata discovery. + """Build ordered list of URLs to try for protected resource metadata discovery. Per SEP-985, the client MUST: 1. Try resource_metadata from WWW-Authenticate header (if present) @@ -127,8 +123,7 @@ def get_client_metadata_scopes( def build_oauth_authorization_server_metadata_discovery_urls(auth_server_url: str | None, server_url: str) -> list[str]: - """ - Generate ordered list of (url, type) tuples for discovery attempts. + """Generate ordered list of (url, type) tuples for discovery attempts. Args: auth_server_url: URL for the OAuth Authorization Metadata URL if found, otherwise None @@ -173,8 +168,7 @@ def build_oauth_authorization_server_metadata_discovery_urls(auth_server_url: st async def handle_protected_resource_response( response: Response, ) -> ProtectedResourceMetadata | None: - """ - Handle protected resource metadata discovery response. + """Handle protected resource metadata discovery response. Per SEP-985, supports fallback when discovery fails at one URL. diff --git a/src/mcp/client/experimental/__init__.py b/src/mcp/client/experimental/__init__.py index b6579b191e..8d74cb3044 100644 --- a/src/mcp/client/experimental/__init__.py +++ b/src/mcp/client/experimental/__init__.py @@ -1,5 +1,4 @@ -""" -Experimental client features. +"""Experimental client features. WARNING: These APIs are experimental and may change without notice. """ diff --git a/src/mcp/client/experimental/task_handlers.py b/src/mcp/client/experimental/task_handlers.py index c6e8957f03..6b233cd072 100644 --- a/src/mcp/client/experimental/task_handlers.py +++ b/src/mcp/client/experimental/task_handlers.py @@ -1,5 +1,4 @@ -""" -Experimental task handler protocols for server -> client requests. +"""Experimental task handler protocols for server -> client requests. This module provides Protocol types and default handlers for when servers send task-related requests to clients (the reverse of normal client -> server flow). diff --git a/src/mcp/client/experimental/tasks.py b/src/mcp/client/experimental/tasks.py index 4eb8dcf5da..1b38255495 100644 --- a/src/mcp/client/experimental/tasks.py +++ b/src/mcp/client/experimental/tasks.py @@ -1,5 +1,4 @@ -""" -Experimental client-side task support. +"""Experimental client-side task support. This module provides client methods for interacting with MCP tasks. @@ -37,8 +36,7 @@ class ExperimentalClientFeatures: - """ - Experimental client features for tasks and other experimental APIs. + """Experimental client features for tasks and other experimental APIs. WARNING: These APIs are experimental and may change without notice. @@ -108,8 +106,7 @@ async def call_tool_as_task( ) async def get_task(self, task_id: str) -> types.GetTaskResult: - """ - Get the current status of a task. + """Get the current status of a task. Args: task_id: The task identifier @@ -131,8 +128,7 @@ async def get_task_result( task_id: str, result_type: type[ResultT], ) -> ResultT: - """ - Get the result of a completed task. + """Get the result of a completed task. The result type depends on the original request type: - tools/call tasks return CallToolResult @@ -158,8 +154,7 @@ async def list_tasks( self, cursor: str | None = None, ) -> types.ListTasksResult: - """ - List all tasks. + """List all tasks. Args: cursor: Optional pagination cursor @@ -176,8 +171,7 @@ async def list_tasks( ) async def cancel_task(self, task_id: str) -> types.CancelTaskResult: - """ - Cancel a running task. + """Cancel a running task. Args: task_id: The task identifier @@ -195,8 +189,7 @@ async def cancel_task(self, task_id: str) -> types.CancelTaskResult: ) async def poll_task(self, task_id: str) -> AsyncIterator[types.GetTaskResult]: - """ - Poll a task until it reaches a terminal status. + """Poll a task until it reaches a terminal status. Yields GetTaskResult for each poll, allowing the caller to react to status changes (e.g., handle input_required). Exits when task reaches diff --git a/src/mcp/client/session_group.py b/src/mcp/client/session_group.py index 47137294d6..31b9d475d6 100644 --- a/src/mcp/client/session_group.py +++ b/src/mcp/client/session_group.py @@ -1,5 +1,4 @@ -""" -SessionGroup concurrently manages multiple MCP session connections. +"""SessionGroup concurrently manages multiple MCP session connections. Tools, resources, and prompts are aggregated across servers. Servers may be connected to or disconnected from at any point after initialization. diff --git a/src/mcp/client/sse.py b/src/mcp/client/sse.py index 50c6a468d6..13d5ecb1ea 100644 --- a/src/mcp/client/sse.py +++ b/src/mcp/client/sse.py @@ -37,8 +37,7 @@ async def sse_client( auth: httpx.Auth | None = None, on_session_created: Callable[[str], None] | None = None, ): - """ - Client transport for SSE. + """Client transport for SSE. `sse_read_timeout` determines how long (in seconds) the client will wait for a new event before disconnecting. All other HTTP operations are controlled by `timeout`. diff --git a/src/mcp/client/stdio/__init__.py b/src/mcp/client/stdio/__init__.py index a0af4168b4..5ab541da88 100644 --- a/src/mcp/client/stdio/__init__.py +++ b/src/mcp/client/stdio/__init__.py @@ -49,8 +49,7 @@ def get_default_environment() -> dict[str, str]: - """ - Returns a default environment object including only environment variables deemed + """Returns a default environment object including only environment variables deemed safe to inherit. """ env: dict[str, str] = {} @@ -104,8 +103,7 @@ class StdioServerParameters(BaseModel): @asynccontextmanager async def stdio_client(server: StdioServerParameters, errlog: TextIO = sys.stderr): - """ - Client transport for stdio: this will connect to a server by spawning a + """Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout. """ read_stream: MemoryObjectReceiveStream[SessionMessage | Exception] @@ -217,8 +215,7 @@ async def stdin_writer(): def _get_executable_command(command: str) -> str: - """ - Get the correct executable command normalized for the current platform. + """Get the correct executable command normalized for the current platform. Args: command: Base command (e.g., 'uvx', 'npx') @@ -239,8 +236,7 @@ async def _create_platform_compatible_process( errlog: TextIO = sys.stderr, cwd: Path | str | None = None, ): - """ - Creates a subprocess in a platform-compatible way. + """Creates a subprocess in a platform-compatible way. Unix: Creates process in a new session/process group for killpg support Windows: Creates process in a Job Object for reliable child termination @@ -260,8 +256,7 @@ async def _create_platform_compatible_process( async def _terminate_process_tree(process: Process | FallbackProcess, timeout_seconds: float = 2.0) -> None: - """ - Terminate a process and all its children using platform-specific methods. + """Terminate a process and all its children using platform-specific methods. Unix: Uses os.killpg() for atomic process group termination Windows: Uses Job Objects via pywin32 for reliable child process cleanup diff --git a/src/mcp/client/streamable_http.py b/src/mcp/client/streamable_http.py index 7e53681010..75dcd5e891 100644 --- a/src/mcp/client/streamable_http.py +++ b/src/mcp/client/streamable_http.py @@ -509,8 +509,7 @@ async def streamable_http_client( ], None, ]: - """ - Client transport for StreamableHTTP. + """Client transport for StreamableHTTP. Args: url: The MCP server endpoint URL. diff --git a/src/mcp/client/websocket.py b/src/mcp/client/websocket.py index 4596410e72..71860be00a 100644 --- a/src/mcp/client/websocket.py +++ b/src/mcp/client/websocket.py @@ -19,8 +19,7 @@ async def websocket_client( tuple[MemoryObjectReceiveStream[SessionMessage | Exception], MemoryObjectSendStream[SessionMessage]], None, ]: - """ - WebSocket client transport for MCP, symmetrical to the server version. + """WebSocket client transport for MCP, symmetrical to the server version. Connects to 'url' using the 'mcp' subprotocol, then yields: (read_stream, write_stream) @@ -46,8 +45,7 @@ async def websocket_client( async with ws_connect(url, subprotocols=[Subprotocol("mcp")]) as ws: async def ws_reader(): - """ - Reads text messages from the WebSocket, parses them as JSON-RPC messages, + """Reads text messages from the WebSocket, parses them as JSON-RPC messages, and sends them into read_stream_writer. """ async with read_stream_writer: @@ -61,8 +59,7 @@ async def ws_reader(): await read_stream_writer.send(exc) async def ws_writer(): - """ - Reads JSON-RPC messages from write_stream_reader and + """Reads JSON-RPC messages from write_stream_reader and sends them to the server. """ async with write_stream_reader: diff --git a/src/mcp/os/posix/utilities.py b/src/mcp/os/posix/utilities.py index dd1aea363a..0e9d74cf3c 100644 --- a/src/mcp/os/posix/utilities.py +++ b/src/mcp/os/posix/utilities.py @@ -1,6 +1,4 @@ -""" -POSIX-specific functionality for stdio client operations. -""" +"""POSIX-specific functionality for stdio client operations.""" import logging import os @@ -13,8 +11,7 @@ async def terminate_posix_process_tree(process: Process, timeout_seconds: float = 2.0) -> None: - """ - Terminate a process and all its children on POSIX systems. + """Terminate a process and all its children on POSIX systems. Uses os.killpg() for atomic process group termination. diff --git a/src/mcp/os/win32/utilities.py b/src/mcp/os/win32/utilities.py index 962be0229b..fa4e4b399b 100644 --- a/src/mcp/os/win32/utilities.py +++ b/src/mcp/os/win32/utilities.py @@ -1,6 +1,4 @@ -""" -Windows-specific functionality for stdio client operations. -""" +"""Windows-specific functionality for stdio client operations.""" import logging import shutil @@ -34,8 +32,7 @@ def get_windows_executable_command(command: str) -> str: - """ - Get the correct executable command normalized for Windows. + """Get the correct executable command normalized for Windows. On Windows, commands might exist with specific extensions (.exe, .cmd, etc.) that need to be located for proper execution. @@ -66,8 +63,7 @@ def get_windows_executable_command(command: str) -> str: class FallbackProcess: - """ - A fallback process wrapper for Windows to handle async I/O + """A fallback process wrapper for Windows to handle async I/O when using subprocess.Popen, which provides sync-only FileIO objects. This wraps stdin and stdout into async-compatible @@ -140,8 +136,7 @@ async def create_windows_process( errlog: TextIO | None = sys.stderr, cwd: Path | str | None = None, ) -> Process | FallbackProcess: - """ - Creates a subprocess in a Windows-compatible way with Job Object support. + """Creates a subprocess in a Windows-compatible way with Job Object support. Attempt to use anyio's open_process for async subprocess creation. In some cases this will throw NotImplementedError on Windows, e.g. @@ -199,8 +194,7 @@ async def _create_windows_fallback_process( errlog: TextIO | None = sys.stderr, cwd: Path | str | None = None, ) -> FallbackProcess: - """ - Create a subprocess using subprocess.Popen as a fallback when anyio fails. + """Create a subprocess using subprocess.Popen as a fallback when anyio fails. This function wraps the sync subprocess.Popen in an async-compatible interface. """ @@ -231,9 +225,7 @@ async def _create_windows_fallback_process( def _create_job_object() -> int | None: - """ - Create a Windows Job Object configured to terminate all processes when closed. - """ + """Create a Windows Job Object configured to terminate all processes when closed.""" if sys.platform != "win32" or not win32job: return None @@ -250,8 +242,7 @@ def _create_job_object() -> int | None: def _maybe_assign_process_to_job(process: Process | FallbackProcess, job: JobHandle | None) -> None: - """ - Try to assign a process to a job object. If assignment fails + """Try to assign a process to a job object. If assignment fails for any reason, the job handle is closed. """ if not job: @@ -279,8 +270,7 @@ def _maybe_assign_process_to_job(process: Process | FallbackProcess, job: JobHan async def terminate_windows_process_tree(process: Process | FallbackProcess, timeout_seconds: float = 2.0) -> None: - """ - Terminate a process and all its children on Windows. + """Terminate a process and all its children on Windows. If the process has an associated job object, it will be terminated. Otherwise, falls back to basic process termination. @@ -318,8 +308,7 @@ async def terminate_windows_process_tree(process: Process | FallbackProcess, tim "Process termination is now handled internally by the stdio_client context manager." ) async def terminate_windows_process(process: Process | FallbackProcess): - """ - Terminate a Windows process. + """Terminate a Windows process. Note: On Windows, terminating a process with process.terminate() doesn't always guarantee immediate process termination. diff --git a/src/mcp/server/auth/__init__.py b/src/mcp/server/auth/__init__.py index 6888ffe8d9..61b60e3487 100644 --- a/src/mcp/server/auth/__init__.py +++ b/src/mcp/server/auth/__init__.py @@ -1,3 +1 @@ -""" -MCP OAuth server authorization components. -""" +"""MCP OAuth server authorization components.""" diff --git a/src/mcp/server/auth/handlers/__init__.py b/src/mcp/server/auth/handlers/__init__.py index e99a62de1a..fd8a462b37 100644 --- a/src/mcp/server/auth/handlers/__init__.py +++ b/src/mcp/server/auth/handlers/__init__.py @@ -1,3 +1 @@ -""" -Request handlers for MCP authorization endpoints. -""" +"""Request handlers for MCP authorization endpoints.""" diff --git a/src/mcp/server/auth/handlers/revoke.py b/src/mcp/server/auth/handlers/revoke.py index fa8cfc99d0..68a3392b4f 100644 --- a/src/mcp/server/auth/handlers/revoke.py +++ b/src/mcp/server/auth/handlers/revoke.py @@ -15,9 +15,7 @@ class RevocationRequest(BaseModel): - """ - # See https://datatracker.ietf.org/doc/html/rfc7009#section-2.1 - """ + """# See https://datatracker.ietf.org/doc/html/rfc7009#section-2.1""" token: str token_type_hint: Literal["access_token", "refresh_token"] | None = None @@ -36,9 +34,7 @@ class RevocationHandler: client_authenticator: ClientAuthenticator async def handle(self, request: Request) -> Response: - """ - Handler for the OAuth 2.0 Token Revocation endpoint. - """ + """Handler for the OAuth 2.0 Token Revocation endpoint.""" try: client = await self.client_authenticator.authenticate_request(request) except AuthenticationError as e: # pragma: no cover diff --git a/src/mcp/server/auth/handlers/token.py b/src/mcp/server/auth/handlers/token.py index 7e8294ce6e..0d3c247c27 100644 --- a/src/mcp/server/auth/handlers/token.py +++ b/src/mcp/server/auth/handlers/token.py @@ -55,9 +55,7 @@ class TokenRequest( class TokenErrorResponse(BaseModel): - """ - See https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 - """ + """See https://datatracker.ietf.org/doc/html/rfc6749#section-5.2""" error: TokenErrorCode error_description: str | None = None diff --git a/src/mcp/server/auth/middleware/__init__.py b/src/mcp/server/auth/middleware/__init__.py index ba3ff63c34..ab07d84161 100644 --- a/src/mcp/server/auth/middleware/__init__.py +++ b/src/mcp/server/auth/middleware/__init__.py @@ -1,3 +1 @@ -""" -Middleware for MCP authorization. -""" +"""Middleware for MCP authorization.""" diff --git a/src/mcp/server/auth/middleware/auth_context.py b/src/mcp/server/auth/middleware/auth_context.py index e2116c3bfd..1d34a5546b 100644 --- a/src/mcp/server/auth/middleware/auth_context.py +++ b/src/mcp/server/auth/middleware/auth_context.py @@ -11,8 +11,7 @@ def get_access_token() -> AccessToken | None: - """ - Get the access token from the current context. + """Get the access token from the current context. Returns: The access token if an authenticated user is available, None otherwise. @@ -22,8 +21,7 @@ def get_access_token() -> AccessToken | None: class AuthContextMiddleware: - """ - Middleware that extracts the authenticated user from the request + """Middleware that extracts the authenticated user from the request and sets it in a contextvar for easy access throughout the request lifecycle. This middleware should be added after the AuthenticationMiddleware in the diff --git a/src/mcp/server/auth/middleware/bearer_auth.py b/src/mcp/server/auth/middleware/bearer_auth.py index 64c9b8841f..6825c00b9e 100644 --- a/src/mcp/server/auth/middleware/bearer_auth.py +++ b/src/mcp/server/auth/middleware/bearer_auth.py @@ -20,9 +20,7 @@ def __init__(self, auth_info: AccessToken): class BearerAuthBackend(AuthenticationBackend): - """ - Authentication backend that validates Bearer tokens using a TokenVerifier. - """ + """Authentication backend that validates Bearer tokens using a TokenVerifier.""" def __init__(self, token_verifier: TokenVerifier): self.token_verifier = token_verifier @@ -50,8 +48,7 @@ async def authenticate(self, conn: HTTPConnection): class RequireAuthMiddleware: - """ - Middleware that requires a valid Bearer token in the Authorization header. + """Middleware that requires a valid Bearer token in the Authorization header. This will validate the token with the auth provider and store the resulting auth info in the request state. @@ -63,8 +60,7 @@ def __init__( required_scopes: list[str], resource_metadata_url: AnyHttpUrl | None = None, ): - """ - Initialize the middleware. + """Initialize the middleware. Args: app: ASGI application diff --git a/src/mcp/server/auth/middleware/client_auth.py b/src/mcp/server/auth/middleware/client_auth.py index 6126c6e4f9..4e4d9be2f6 100644 --- a/src/mcp/server/auth/middleware/client_auth.py +++ b/src/mcp/server/auth/middleware/client_auth.py @@ -17,8 +17,7 @@ def __init__(self, message: str): class ClientAuthenticator: - """ - ClientAuthenticator is a callable which validates requests from a client + """ClientAuthenticator is a callable which validates requests from a client application, used to verify /token calls. If, during registration, the client requested to be issued a secret, the authenticator asserts that /token calls must be authenticated with @@ -28,8 +27,7 @@ class ClientAuthenticator: """ def __init__(self, provider: OAuthAuthorizationServerProvider[Any, Any, Any]): - """ - Initialize the dependency. + """Initialize the dependency. Args: provider: Provider to look up client information @@ -37,8 +35,7 @@ def __init__(self, provider: OAuthAuthorizationServerProvider[Any, Any, Any]): self.provider = provider async def authenticate_request(self, request: Request) -> OAuthClientInformationFull: - """ - Authenticate a client from an HTTP request. + """Authenticate a client from an HTTP request. Extracts client credentials from the appropriate location based on the client's registered authentication method and validates them. diff --git a/src/mcp/server/auth/provider.py b/src/mcp/server/auth/provider.py index 96296c148e..9fb30c1406 100644 --- a/src/mcp/server/auth/provider.py +++ b/src/mcp/server/auth/provider.py @@ -105,8 +105,7 @@ async def verify_token(self, token: str) -> AccessToken | None: class OAuthAuthorizationServerProvider(Protocol, Generic[AuthorizationCodeT, RefreshTokenT, AccessTokenT]): async def get_client(self, client_id: str) -> OAuthClientInformationFull | None: - """ - Retrieves client information by client ID. + """Retrieves client information by client ID. Implementors MAY raise NotImplementedError if dynamic client registration is disabled in ClientRegistrationOptions. @@ -119,8 +118,7 @@ async def get_client(self, client_id: str) -> OAuthClientInformationFull | None: """ async def register_client(self, client_info: OAuthClientInformationFull) -> None: - """ - Saves client information as part of registering it. + """Saves client information as part of registering it. Implementors MAY raise NotImplementedError if dynamic client registration is disabled in ClientRegistrationOptions. @@ -133,8 +131,7 @@ async def register_client(self, client_info: OAuthClientInformationFull) -> None """ async def authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str: - """ - Called as part of the /authorize endpoint, and returns a URL that the client + """Called as part of the /authorize endpoint, and returns a URL that the client will be redirected to. Many MCP implementations will redirect to a third-party provider to perform a second OAuth exchange with that provider. In this sort of setup, the client @@ -178,8 +175,7 @@ async def authorize(self, client: OAuthClientInformationFull, params: Authorizat async def load_authorization_code( self, client: OAuthClientInformationFull, authorization_code: str ) -> AuthorizationCodeT | None: - """ - Loads an AuthorizationCode by its code. + """Loads an AuthorizationCode by its code. Args: client: The client that requested the authorization code. @@ -193,8 +189,7 @@ async def load_authorization_code( async def exchange_authorization_code( self, client: OAuthClientInformationFull, authorization_code: AuthorizationCodeT ) -> OAuthToken: - """ - Exchanges an authorization code for an access token and refresh token. + """Exchanges an authorization code for an access token and refresh token. Args: client: The client exchanging the authorization code. @@ -209,8 +204,7 @@ async def exchange_authorization_code( ... async def load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshTokenT | None: - """ - Loads a RefreshToken by its token string. + """Loads a RefreshToken by its token string. Args: client: The client that is requesting to load the refresh token. @@ -227,8 +221,7 @@ async def exchange_refresh_token( refresh_token: RefreshTokenT, scopes: list[str], ) -> OAuthToken: - """ - Exchanges a refresh token for an access token and refresh token. + """Exchanges a refresh token for an access token and refresh token. Implementations SHOULD rotate both the access token and refresh token. @@ -246,8 +239,7 @@ async def exchange_refresh_token( ... async def load_access_token(self, token: str) -> AccessTokenT | None: - """ - Loads an access token by its token. + """Loads an access token by its token. Args: token: The access token to verify. @@ -260,8 +252,7 @@ async def revoke_token( self, token: AccessTokenT | RefreshTokenT, ) -> None: - """ - Revokes an access or refresh token. + """Revokes an access or refresh token. If the given token is invalid or already revoked, this method should do nothing. diff --git a/src/mcp/server/auth/routes.py b/src/mcp/server/auth/routes.py index e45f623afa..08f735f362 100644 --- a/src/mcp/server/auth/routes.py +++ b/src/mcp/server/auth/routes.py @@ -22,8 +22,7 @@ def validate_issuer_url(url: AnyHttpUrl): - """ - Validate that the issuer URL meets OAuth 2.0 requirements. + """Validate that the issuer URL meets OAuth 2.0 requirements. Args: url: The issuer URL to validate @@ -188,8 +187,7 @@ def build_metadata( def build_resource_metadata_url(resource_server_url: AnyHttpUrl) -> AnyHttpUrl: - """ - Build RFC 9728 compliant protected resource metadata URL. + """Build RFC 9728 compliant protected resource metadata URL. Inserts /.well-known/oauth-protected-resource between host and resource path as specified in RFC 9728 §3.1. @@ -213,8 +211,7 @@ def create_protected_resource_routes( resource_name: str | None = None, resource_documentation: AnyHttpUrl | None = None, ) -> list[Route]: - """ - Create routes for OAuth 2.0 Protected Resource Metadata (RFC 9728). + """Create routes for OAuth 2.0 Protected Resource Metadata (RFC 9728). Args: resource_url: The URL of this resource server diff --git a/src/mcp/server/experimental/__init__.py b/src/mcp/server/experimental/__init__.py index 824bb8b8be..fd1db623f2 100644 --- a/src/mcp/server/experimental/__init__.py +++ b/src/mcp/server/experimental/__init__.py @@ -1,5 +1,4 @@ -""" -Server-side experimental features. +"""Server-side experimental features. WARNING: These APIs are experimental and may change without notice. diff --git a/src/mcp/server/experimental/request_context.py b/src/mcp/server/experimental/request_context.py index 7e80d792f7..14059f7f3f 100644 --- a/src/mcp/server/experimental/request_context.py +++ b/src/mcp/server/experimental/request_context.py @@ -1,5 +1,4 @@ -""" -Experimental request context features. +"""Experimental request context features. This module provides the Experimental class which gives access to experimental features within a request context, such as task-augmented request handling. @@ -32,8 +31,7 @@ @dataclass class Experimental: - """ - Experimental features context for task-augmented requests. + """Experimental features context for task-augmented requests. Provides helpers for validating task execution compatibility and running tasks with automatic lifecycle management. @@ -64,8 +62,7 @@ def validate_task_mode( *, raise_error: bool = True, ) -> ErrorData | None: - """ - Validate that the request is compatible with the tool's task execution mode. + """Validate that the request is compatible with the tool's task execution mode. Per MCP spec: - "required": Clients MUST invoke as task. Server returns -32601 if not. @@ -110,8 +107,7 @@ def validate_for_tool( *, raise_error: bool = True, ) -> ErrorData | None: - """ - Validate that the request is compatible with the given tool. + """Validate that the request is compatible with the given tool. Convenience wrapper around validate_task_mode that extracts the mode from a Tool. @@ -126,8 +122,7 @@ def validate_for_tool( return self.validate_task_mode(mode, raise_error=raise_error) def can_use_tool(self, tool_task_mode: TaskExecutionMode | None) -> bool: - """ - Check if this client can use a tool with the given task mode. + """Check if this client can use a tool with the given task mode. Useful for filtering tool lists or providing warnings. Returns False if tool requires "required" but client doesn't support tasks. @@ -150,8 +145,7 @@ async def run_task( task_id: str | None = None, model_immediate_response: str | None = None, ) -> CreateTaskResult: - """ - Create a task, spawn background work, and return CreateTaskResult immediately. + """Create a task, spawn background work, and return CreateTaskResult immediately. This is the recommended way to handle task-augmented tool calls. It: 1. Creates a task in the store diff --git a/src/mcp/server/experimental/session_features.py b/src/mcp/server/experimental/session_features.py index 57efab75b2..2bccf66031 100644 --- a/src/mcp/server/experimental/session_features.py +++ b/src/mcp/server/experimental/session_features.py @@ -1,5 +1,4 @@ -""" -Experimental server session features for server→client task operations. +"""Experimental server session features for server→client task operations. This module provides the server-side equivalent of ExperimentalClientFeatures, allowing the server to send task-augmented requests to the client and poll for results. @@ -25,8 +24,7 @@ class ExperimentalServerSessionFeatures: - """ - Experimental server session features for server→client task operations. + """Experimental server session features for server→client task operations. This provides the server-side equivalent of ExperimentalClientFeatures, allowing the server to send task-augmented requests to the client and @@ -42,8 +40,7 @@ def __init__(self, session: "ServerSession") -> None: self._session = session async def get_task(self, task_id: str) -> types.GetTaskResult: - """ - Send tasks/get to the client to get task status. + """Send tasks/get to the client to get task status. Args: task_id: The task identifier @@ -61,8 +58,7 @@ async def get_task_result( task_id: str, result_type: type[ResultT], ) -> ResultT: - """ - Send tasks/result to the client to retrieve the final result. + """Send tasks/result to the client to retrieve the final result. Args: task_id: The task identifier @@ -77,8 +73,7 @@ async def get_task_result( ) async def poll_task(self, task_id: str) -> AsyncIterator[types.GetTaskResult]: - """ - Poll a client task until it reaches terminal status. + """Poll a client task until it reaches terminal status. Yields GetTaskResult for each poll, allowing the caller to react to status changes. Exits when task reaches a terminal status. @@ -101,8 +96,7 @@ async def elicit_as_task( *, ttl: int = 60000, ) -> types.ElicitResult: - """ - Send a task-augmented elicitation to the client and poll until complete. + """Send a task-augmented elicitation to the client and poll until complete. The client will create a local task, process the elicitation asynchronously, and return the result when ready. This method handles the full flow: @@ -160,8 +154,7 @@ async def create_message_as_task( tools: list[types.Tool] | None = None, tool_choice: types.ToolChoice | None = None, ) -> types.CreateMessageResult: - """ - Send a task-augmented sampling request and poll until complete. + """Send a task-augmented sampling request and poll until complete. The client will create a local task, process the sampling request asynchronously, and return the result when ready. diff --git a/src/mcp/server/experimental/task_context.py b/src/mcp/server/experimental/task_context.py index 4eadab2164..feb1df652f 100644 --- a/src/mcp/server/experimental/task_context.py +++ b/src/mcp/server/experimental/task_context.py @@ -1,5 +1,4 @@ -""" -ServerTaskContext - Server-integrated task context with elicitation and sampling. +"""ServerTaskContext - Server-integrated task context with elicitation and sampling. This wraps the pure TaskContext and adds server-specific functionality: - Elicitation (task.elicit()) @@ -51,8 +50,7 @@ class ServerTaskContext: - """ - Server-integrated task context with elicitation and sampling. + """Server-integrated task context with elicitation and sampling. This wraps a pure TaskContext and adds server-specific functionality: - elicit() for sending elicitation requests to the client @@ -83,8 +81,7 @@ def __init__( queue: TaskMessageQueue, handler: TaskResultHandler | None = None, ): - """ - Create a ServerTaskContext. + """Create a ServerTaskContext. Args: task: The Task object @@ -123,8 +120,7 @@ def request_cancellation(self) -> None: # Enhanced methods with notifications async def update_status(self, message: str, *, notify: bool = True) -> None: - """ - Update the task's status message. + """Update the task's status message. Args: message: The new status message @@ -135,8 +131,7 @@ async def update_status(self, message: str, *, notify: bool = True) -> None: await self._send_notification() async def complete(self, result: Result, *, notify: bool = True) -> None: - """ - Mark the task as completed with the given result. + """Mark the task as completed with the given result. Args: result: The task result @@ -147,8 +142,7 @@ async def complete(self, result: Result, *, notify: bool = True) -> None: await self._send_notification() async def fail(self, error: str, *, notify: bool = True) -> None: - """ - Mark the task as failed with an error message. + """Mark the task as failed with an error message. Args: error: The error message @@ -204,8 +198,7 @@ async def elicit( message: str, requested_schema: ElicitRequestedSchema, ) -> ElicitResult: - """ - Send an elicitation request via the task message queue. + """Send an elicitation request via the task message queue. This method: 1. Checks client capability @@ -270,8 +263,7 @@ async def elicit_url( url: str, elicitation_id: str, ) -> ElicitResult: - """ - Send a URL mode elicitation request via the task message queue. + """Send a URL mode elicitation request via the task message queue. This directs the user to an external URL for out-of-band interactions like OAuth flows, credential collection, or payment processing. @@ -347,8 +339,7 @@ async def create_message( tools: list[Tool] | None = None, tool_choice: ToolChoice | None = None, ) -> CreateMessageResult: - """ - Send a sampling request via the task message queue. + """Send a sampling request via the task message queue. This method: 1. Checks client capability @@ -434,8 +425,7 @@ async def elicit_as_task( *, ttl: int = 60000, ) -> ElicitResult: - """ - Send a task-augmented elicitation via the queue, then poll client. + """Send a task-augmented elicitation via the queue, then poll client. This is for use inside a task-augmented tool call when you want the client to handle the elicitation as its own task. The elicitation request is queued @@ -520,8 +510,7 @@ async def create_message_as_task( tools: list[Tool] | None = None, tool_choice: ToolChoice | None = None, ) -> CreateMessageResult: - """ - Send a task-augmented sampling request via the queue, then poll client. + """Send a task-augmented sampling request via the queue, then poll client. This is for use inside a task-augmented tool call when you want the client to handle the sampling as its own task. The request is queued and delivered diff --git a/src/mcp/server/experimental/task_result_handler.py b/src/mcp/server/experimental/task_result_handler.py index 85b1259a77..078de66286 100644 --- a/src/mcp/server/experimental/task_result_handler.py +++ b/src/mcp/server/experimental/task_result_handler.py @@ -1,5 +1,4 @@ -""" -TaskResultHandler - Integrated handler for tasks/result endpoint. +"""TaskResultHandler - Integrated handler for tasks/result endpoint. This implements the dequeue-send-wait pattern from the MCP Tasks spec: 1. Dequeue all pending messages for the task @@ -36,8 +35,7 @@ class TaskResultHandler: - """ - Handler for tasks/result that implements the message queue pattern. + """Handler for tasks/result that implements the message queue pattern. This handler: 1. Dequeues pending messages (elicitations, notifications) for the task @@ -75,8 +73,7 @@ async def send_message( session: ServerSession, message: SessionMessage, ) -> None: - """ - Send a message via the session. + """Send a message via the session. This is a helper for delivering queued task messages. """ @@ -88,8 +85,7 @@ async def handle( session: ServerSession, request_id: RequestId, ) -> GetTaskPayloadResult: - """ - Handle a tasks/result request. + """Handle a tasks/result request. This implements the dequeue-send-wait loop: 1. Dequeue all pending messages @@ -144,8 +140,7 @@ async def _deliver_queued_messages( session: ServerSession, request_id: RequestId, ) -> None: - """ - Dequeue and send all pending messages for a task. + """Dequeue and send all pending messages for a task. Each message is sent via the session's write stream with relatedRequestId set so responses route back to this stream. @@ -172,8 +167,7 @@ async def _deliver_queued_messages( await self.send_message(session, session_message) async def _wait_for_task_update(self, task_id: str) -> None: - """ - Wait for task to be updated (status change or new message). + """Wait for task to be updated (status change or new message). Races between store update and queue message - first one wins. """ @@ -199,8 +193,7 @@ async def wait_for_queue() -> None: tg.start_soon(wait_for_queue) def route_response(self, request_id: RequestId, response: dict[str, Any]) -> bool: - """ - Route a response back to the waiting resolver. + """Route a response back to the waiting resolver. This is called when a response arrives for a queued request. @@ -218,8 +211,7 @@ def route_response(self, request_id: RequestId, response: dict[str, Any]) -> boo return False def route_error(self, request_id: RequestId, error: ErrorData) -> bool: - """ - Route an error back to the waiting resolver. + """Route an error back to the waiting resolver. Args: request_id: The request ID from the error response diff --git a/src/mcp/server/experimental/task_support.py b/src/mcp/server/experimental/task_support.py index dbb2ed6d2b..23b5d9cc89 100644 --- a/src/mcp/server/experimental/task_support.py +++ b/src/mcp/server/experimental/task_support.py @@ -1,5 +1,4 @@ -""" -TaskSupport - Configuration for experimental task support. +"""TaskSupport - Configuration for experimental task support. This module provides the TaskSupport class which encapsulates all the infrastructure needed for task-augmented requests: store, queue, and handler. @@ -21,8 +20,7 @@ @dataclass class TaskSupport: - """ - Configuration for experimental task support. + """Configuration for experimental task support. Encapsulates the task store, message queue, result handler, and task group for spawning background work. @@ -65,8 +63,7 @@ def task_group(self) -> TaskGroup: @asynccontextmanager async def run(self) -> AsyncIterator[None]: - """ - Run the task support lifecycle. + """Run the task support lifecycle. This creates a task group for spawning background task work. Called automatically by Server.run(). @@ -84,8 +81,7 @@ async def run(self) -> AsyncIterator[None]: self._task_group = None def configure_session(self, session: ServerSession) -> None: - """ - Configure a session for task support. + """Configure a session for task support. This registers the result handler as a response router so that responses to queued requests (elicitation, sampling) are routed @@ -100,8 +96,7 @@ def configure_session(self, session: ServerSession) -> None: @classmethod def in_memory(cls) -> "TaskSupport": - """ - Create in-memory task support. + """Create in-memory task support. Suitable for development, testing, and single-process servers. For distributed systems, provide custom store and queue implementations. diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 6b0ad7b03a..75f2d2237f 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -304,8 +304,7 @@ async def list_tools(self) -> list[MCPTool]: ] def get_context(self) -> Context[ServerSession, LifespanResultT, Request]: - """ - Returns a Context object. Note that the context will only be valid + """Returns a Context object. Note that the context will only be valid during a request; outside a request, most methods will error. """ try: @@ -683,8 +682,7 @@ def custom_route( name: str | None = None, include_in_schema: bool = True, ): - """ - Decorator to register a custom HTTP route on the FastMCP server. + """Decorator to register a custom HTTP route on the FastMCP server. Allows adding arbitrary HTTP endpoints outside the standard MCP protocol, which can be useful for OAuth callbacks, health checks, or admin APIs. @@ -1074,9 +1072,7 @@ async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) - class StreamableHTTPASGIApp: - """ - ASGI application for Streamable HTTP server transport. - """ + """ASGI application for Streamable HTTP server transport.""" def __init__(self, session_manager: StreamableHTTPSessionManager): self.session_manager = session_manager diff --git a/src/mcp/server/fastmcp/utilities/func_metadata.py b/src/mcp/server/fastmcp/utilities/func_metadata.py index 65b31e2b53..be2296594a 100644 --- a/src/mcp/server/fastmcp/utilities/func_metadata.py +++ b/src/mcp/server/fastmcp/utilities/func_metadata.py @@ -95,8 +95,7 @@ async def call_fn_with_arg_validation( return fn(**arguments_parsed_dict) def convert_result(self, result: Any) -> Any: - """ - Convert the result of a function call to the appropriate format for + """Convert the result of a function call to the appropriate format for the lowlevel server tool call handler: - If output_model is None, return the unstructured content directly. @@ -499,8 +498,7 @@ class DictModel(RootModel[dict_annotation]): def _convert_to_content( result: Any, ) -> Sequence[ContentBlock]: - """ - Convert a result to a sequence of content objects. + """Convert a result to a sequence of content objects. Note: This conversion logic comes from previous versions of FastMCP and is being retained for purposes of backwards compatibility. It produces different unstructured diff --git a/src/mcp/server/lowlevel/experimental.py b/src/mcp/server/lowlevel/experimental.py index 1ff01b01d7..2c5addb6fc 100644 --- a/src/mcp/server/lowlevel/experimental.py +++ b/src/mcp/server/lowlevel/experimental.py @@ -88,8 +88,7 @@ def enable_tasks( store: TaskStore | None = None, queue: TaskMessageQueue | None = None, ) -> TaskSupport: - """ - Enable experimental task support. + """Enable experimental task support. This sets up the task infrastructure and auto-registers default handlers for tasks/get, tasks/result, tasks/list, and tasks/cancel. diff --git a/src/mcp/server/lowlevel/func_inspection.py b/src/mcp/server/lowlevel/func_inspection.py index 6231aa8954..d176970902 100644 --- a/src/mcp/server/lowlevel/func_inspection.py +++ b/src/mcp/server/lowlevel/func_inspection.py @@ -7,8 +7,7 @@ def create_call_wrapper(func: Callable[..., R], request_type: type[T]) -> Callable[[T], R]: - """ - Create a wrapper function that knows how to call func with the request object. + """Create a wrapper function that knows how to call func with the request object. Returns a wrapper function that takes the request and calls func appropriately. diff --git a/src/mcp/server/lowlevel/server.py b/src/mcp/server/lowlevel/server.py index 98db528859..9d600a6b8e 100644 --- a/src/mcp/server/lowlevel/server.py +++ b/src/mcp/server/lowlevel/server.py @@ -1,5 +1,4 @@ -""" -MCP Server Module +"""MCP Server Module This module provides a framework for creating an MCP (Model Context Protocol) server. It allows you to easily define and handle various types of requests and notifications diff --git a/src/mcp/server/models.py b/src/mcp/server/models.py index eb972e33a5..a6cd093d97 100644 --- a/src/mcp/server/models.py +++ b/src/mcp/server/models.py @@ -1,5 +1,4 @@ -""" -This module provides simpler types to use with the server for managing prompts +"""This module provides simpler types to use with the server for managing prompts and tools. """ diff --git a/src/mcp/server/session.py b/src/mcp/server/session.py index eebec5bb47..6f80615ff5 100644 --- a/src/mcp/server/session.py +++ b/src/mcp/server/session.py @@ -1,5 +1,4 @@ -""" -ServerSession Module +"""ServerSession Module This module provides the ServerSession class, which manages communication between the server and client in the MCP (Model Context Protocol) framework. It is most commonly diff --git a/src/mcp/server/sse.py b/src/mcp/server/sse.py index 6ea0b4292f..46849eb82e 100644 --- a/src/mcp/server/sse.py +++ b/src/mcp/server/sse.py @@ -1,5 +1,4 @@ -""" -SSE Server Transport Module +"""SSE Server Transport Module This module implements a Server-Sent Events (SSE) transport layer for MCP servers. @@ -62,8 +61,7 @@ async def handle_sse(request): class SseServerTransport: - """ - SSE server transport for MCP. This class provides _two_ ASGI applications, + """SSE server transport for MCP. This class provides _two_ ASGI applications, suitable to be used with a framework like Starlette and a server like Hypercorn: 1. connect_sse() is an ASGI application which receives incoming GET requests, @@ -78,8 +76,7 @@ class SseServerTransport: _security: TransportSecurityMiddleware def __init__(self, endpoint: str, security_settings: TransportSecuritySettings | None = None) -> None: - """ - Creates a new SSE server transport, which will direct the client to POST + """Creates a new SSE server transport, which will direct the client to POST messages to the relative path given. Args: @@ -180,8 +177,7 @@ async def sse_writer(): async with anyio.create_task_group() as tg: async def response_wrapper(scope: Scope, receive: Receive, send: Send): - """ - The EventSourceResponse returning signals a client close / disconnect. + """The EventSourceResponse returning signals a client close / disconnect. In this case we close our side of the streams to signal the client that the connection has been closed. """ diff --git a/src/mcp/server/stdio.py b/src/mcp/server/stdio.py index 22fe116a7c..d494d075fa 100644 --- a/src/mcp/server/stdio.py +++ b/src/mcp/server/stdio.py @@ -1,5 +1,4 @@ -""" -Stdio Server Transport Module +"""Stdio Server Transport Module This module provides functionality for creating an stdio-based transport layer that can be used to communicate with an MCP client through standard input/output @@ -35,8 +34,7 @@ async def stdio_server( stdin: anyio.AsyncFile[str] | None = None, stdout: anyio.AsyncFile[str] | None = None, ): - """ - Server transport for stdio: this communicates with an MCP client by reading + """Server transport for stdio: this communicates with an MCP client by reading from the current process' stdin and writing to stdout. """ # Purposely not using context managers for these, as we don't want to close diff --git a/src/mcp/server/streamable_http.py b/src/mcp/server/streamable_http.py index 8f488557b2..137a7da397 100644 --- a/src/mcp/server/streamable_http.py +++ b/src/mcp/server/streamable_http.py @@ -1,5 +1,4 @@ -""" -StreamableHTTP Server Transport Module +"""StreamableHTTP Server Transport Module This module implements an HTTP transport layer with Streamable HTTP. @@ -71,9 +70,7 @@ @dataclass class EventMessage: - """ - A JSONRPCMessage with an optional event ID for stream resumability. - """ + """A JSONRPCMessage with an optional event ID for stream resumability.""" message: JSONRPCMessage event_id: str | None = None @@ -83,14 +80,11 @@ class EventMessage: class EventStore(ABC): - """ - Interface for resumability support via event storage. - """ + """Interface for resumability support via event storage.""" @abstractmethod async def store_event(self, stream_id: StreamId, message: JSONRPCMessage | None) -> EventId: - """ - Stores an event for later retrieval. + """Stores an event for later retrieval. Args: stream_id: ID of the stream the event belongs to @@ -107,8 +101,7 @@ async def replay_events_after( last_event_id: EventId, send_callback: EventCallback, ) -> StreamId | None: - """ - Replays events that occurred after the specified event ID. + """Replays events that occurred after the specified event ID. Args: last_event_id: The ID of the last event the client received @@ -121,8 +114,7 @@ async def replay_events_after( class StreamableHTTPServerTransport: - """ - HTTP server transport with event streaming support for MCP. + """HTTP server transport with event streaming support for MCP. Handles JSON-RPC messages in HTTP POST requests with SSE streaming. Supports optional JSON responses and session management. @@ -143,8 +135,7 @@ def __init__( security_settings: TransportSecuritySettings | None = None, retry_interval: int | None = None, ) -> None: - """ - Initialize a new StreamableHTTP server transport. + """Initialize a new StreamableHTTP server transport. Args: mcp_session_id: Optional session identifier for this connection. @@ -655,8 +646,7 @@ async def sse_writer(): return async def _handle_get_request(self, request: Request, send: Send) -> None: # pragma: no cover - """ - Handle GET request to establish SSE. + """Handle GET request to establish SSE. This allows the server to communicate to the client without the client first sending data via HTTP POST. The server can send JSON-RPC requests @@ -875,8 +865,7 @@ async def _validate_protocol_version(self, request: Request, send: Send) -> bool return True async def _replay_events(self, last_event_id: str, request: Request, send: Send) -> None: # pragma: no cover - """ - Replays events that would have been sent after the specified event ID. + """Replays events that would have been sent after the specified event ID. Only used when resumability is enabled. """ event_store = self._event_store diff --git a/src/mcp/server/streamable_http_manager.py b/src/mcp/server/streamable_http_manager.py index 6a16724177..6a17f9c535 100644 --- a/src/mcp/server/streamable_http_manager.py +++ b/src/mcp/server/streamable_http_manager.py @@ -28,8 +28,7 @@ class StreamableHTTPSessionManager: - """ - Manages StreamableHTTP sessions with optional resumability via event store. + """Manages StreamableHTTP sessions with optional resumability via event store. This class abstracts away the complexity of session management, event storage, and request handling for StreamableHTTP transports. It handles: @@ -85,8 +84,7 @@ def __init__( @contextlib.asynccontextmanager async def run(self) -> AsyncIterator[None]: - """ - Run the session manager with proper lifecycle management. + """Run the session manager with proper lifecycle management. This creates and manages the task group for all session operations. @@ -130,8 +128,7 @@ async def handle_request( receive: Receive, send: Send, ) -> None: - """ - Process ASGI request with proper session handling and transport setup. + """Process ASGI request with proper session handling and transport setup. Dispatches to the appropriate handler based on stateless mode. @@ -155,8 +152,7 @@ async def _handle_stateless_request( receive: Receive, send: Send, ) -> None: - """ - Process request in stateless mode - creating a new transport for each request. + """Process request in stateless mode - creating a new transport for each request. Args: scope: ASGI scope @@ -204,8 +200,7 @@ async def _handle_stateful_request( receive: Receive, send: Send, ) -> None: - """ - Process request in stateful mode - maintaining session state between requests. + """Process request in stateful mode - maintaining session state between requests. Args: scope: ASGI scope diff --git a/src/mcp/server/validation.py b/src/mcp/server/validation.py index 003a9d1236..192b9d4923 100644 --- a/src/mcp/server/validation.py +++ b/src/mcp/server/validation.py @@ -1,5 +1,4 @@ -""" -Shared validation functions for server requests. +"""Shared validation functions for server requests. This module provides validation logic for sampling and elicitation requests that is shared across normal and task-augmented code paths. @@ -17,8 +16,7 @@ def check_sampling_tools_capability(client_caps: ClientCapabilities | None) -> bool: - """ - Check if the client supports sampling tools capability. + """Check if the client supports sampling tools capability. Args: client_caps: The client's declared capabilities @@ -40,8 +38,7 @@ def validate_sampling_tools( tools: list[Tool] | None, tool_choice: ToolChoice | None, ) -> None: - """ - Validate that the client supports sampling tools if tools are being used. + """Validate that the client supports sampling tools if tools are being used. Args: client_caps: The client's declared capabilities @@ -62,8 +59,7 @@ def validate_sampling_tools( def validate_tool_use_result_messages(messages: list[SamplingMessage]) -> None: - """ - Validate tool_use/tool_result message structure per SEP-1577. + """Validate tool_use/tool_result message structure per SEP-1577. This validation ensures: 1. Messages with tool_result content contain ONLY tool_result content diff --git a/src/mcp/server/websocket.py b/src/mcp/server/websocket.py index 3aacc467d1..9dde5e016c 100644 --- a/src/mcp/server/websocket.py +++ b/src/mcp/server/websocket.py @@ -15,8 +15,7 @@ @asynccontextmanager # pragma: no cover async def websocket_server(scope: Scope, receive: Receive, send: Send): - """ - WebSocket server transport for MCP. This is an ASGI application, suitable to be + """WebSocket server transport for MCP. This is an ASGI application, suitable to be used with a framework like Starlette and a server like Hypercorn. """ diff --git a/src/mcp/shared/auth.py b/src/mcp/shared/auth.py index d3290997e5..bf03a8b8dd 100644 --- a/src/mcp/shared/auth.py +++ b/src/mcp/shared/auth.py @@ -4,9 +4,7 @@ class OAuthToken(BaseModel): - """ - See https://datatracker.ietf.org/doc/html/rfc6749#section-5.1 - """ + """See https://datatracker.ietf.org/doc/html/rfc6749#section-5.1""" access_token: str token_type: Literal["Bearer"] = "Bearer" @@ -35,8 +33,7 @@ def __init__(self, message: str): class OAuthClientMetadata(BaseModel): - """ - RFC 7591 OAuth 2.0 Dynamic Client Registration metadata. + """RFC 7591 OAuth 2.0 Dynamic Client Registration metadata. See https://datatracker.ietf.org/doc/html/rfc7591#section-2 for the full specification. """ @@ -94,8 +91,7 @@ def validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl: class OAuthClientInformationFull(OAuthClientMetadata): - """ - RFC 7591 OAuth 2.0 Dynamic Client Registration full response + """RFC 7591 OAuth 2.0 Dynamic Client Registration full response (client information plus metadata). """ @@ -106,8 +102,7 @@ class OAuthClientInformationFull(OAuthClientMetadata): class OAuthMetadata(BaseModel): - """ - RFC 8414 OAuth 2.0 Authorization Server Metadata. + """RFC 8414 OAuth 2.0 Authorization Server Metadata. See https://datatracker.ietf.org/doc/html/rfc8414#section-2 """ @@ -136,8 +131,7 @@ class OAuthMetadata(BaseModel): class ProtectedResourceMetadata(BaseModel): - """ - RFC 9728 OAuth 2.0 Protected Resource Metadata. + """RFC 9728 OAuth 2.0 Protected Resource Metadata. See https://datatracker.ietf.org/doc/html/rfc9728#section-2 """ diff --git a/src/mcp/shared/context.py b/src/mcp/shared/context.py index 5cf6588c9e..f54a2efabc 100644 --- a/src/mcp/shared/context.py +++ b/src/mcp/shared/context.py @@ -1,6 +1,4 @@ -""" -Request context for MCP handlers. -""" +"""Request context for MCP handlers.""" from dataclasses import dataclass, field from typing import Any, Generic diff --git a/src/mcp/shared/exceptions.py b/src/mcp/shared/exceptions.py index 609e5f3f32..d8bc17b7ab 100644 --- a/src/mcp/shared/exceptions.py +++ b/src/mcp/shared/exceptions.py @@ -6,9 +6,7 @@ class McpError(Exception): - """ - Exception type raised when an error arrives over an MCP connection. - """ + """Exception type raised when an error arrives over an MCP connection.""" error: ErrorData @@ -19,8 +17,7 @@ def __init__(self, error: ErrorData): class StatelessModeNotSupported(RuntimeError): - """ - Raised when attempting to use a method that is not supported in stateless mode. + """Raised when attempting to use a method that is not supported in stateless mode. Server-to-client requests (sampling, elicitation, list_roots) are not supported in stateless HTTP mode because there is no persistent connection @@ -37,8 +34,7 @@ def __init__(self, method: str): class UrlElicitationRequiredError(McpError): - """ - Specialized error for when a tool requires URL mode elicitation(s) before proceeding. + """Specialized error for when a tool requires URL mode elicitation(s) before proceeding. Servers can raise this error from tool handlers to indicate that the client must complete one or more URL elicitations before the request can be processed. diff --git a/src/mcp/shared/experimental/__init__.py b/src/mcp/shared/experimental/__init__.py index 9b1b1479cb..fa6940acc6 100644 --- a/src/mcp/shared/experimental/__init__.py +++ b/src/mcp/shared/experimental/__init__.py @@ -1,5 +1,4 @@ -""" -Pure experimental MCP features (no server dependencies). +"""Pure experimental MCP features (no server dependencies). WARNING: These APIs are experimental and may change without notice. diff --git a/src/mcp/shared/experimental/tasks/__init__.py b/src/mcp/shared/experimental/tasks/__init__.py index 37d81af50b..52793e408b 100644 --- a/src/mcp/shared/experimental/tasks/__init__.py +++ b/src/mcp/shared/experimental/tasks/__init__.py @@ -1,5 +1,4 @@ -""" -Pure task state management for MCP. +"""Pure task state management for MCP. WARNING: These APIs are experimental and may change without notice. diff --git a/src/mcp/shared/experimental/tasks/capabilities.py b/src/mcp/shared/experimental/tasks/capabilities.py index 37c6597ec2..ec9e53e854 100644 --- a/src/mcp/shared/experimental/tasks/capabilities.py +++ b/src/mcp/shared/experimental/tasks/capabilities.py @@ -1,5 +1,4 @@ -""" -Tasks capability checking utilities. +"""Tasks capability checking utilities. This module provides functions for checking and requiring task-related capabilities. All tasks capability logic is centralized here to keep @@ -21,8 +20,7 @@ def check_tasks_capability( required: ClientTasksCapability, client: ClientTasksCapability, ) -> bool: - """ - Check if client's tasks capability matches the required capability. + """Check if client's tasks capability matches the required capability. Args: required: The capability being checked for @@ -78,8 +76,7 @@ def has_task_augmented_sampling(caps: ClientCapabilities) -> bool: def require_task_augmented_elicitation(client_caps: ClientCapabilities | None) -> None: - """ - Raise McpError if client doesn't support task-augmented elicitation. + """Raise McpError if client doesn't support task-augmented elicitation. Args: client_caps: The client's declared capabilities, or None if not initialized @@ -97,8 +94,7 @@ def require_task_augmented_elicitation(client_caps: ClientCapabilities | None) - def require_task_augmented_sampling(client_caps: ClientCapabilities | None) -> None: - """ - Raise McpError if client doesn't support task-augmented sampling. + """Raise McpError if client doesn't support task-augmented sampling. Args: client_caps: The client's declared capabilities, or None if not initialized diff --git a/src/mcp/shared/experimental/tasks/context.py b/src/mcp/shared/experimental/tasks/context.py index c7f0da0fcd..ed0d2b91b6 100644 --- a/src/mcp/shared/experimental/tasks/context.py +++ b/src/mcp/shared/experimental/tasks/context.py @@ -1,5 +1,4 @@ -""" -TaskContext - Pure task state management. +"""TaskContext - Pure task state management. This module provides TaskContext, which manages task state without any server/session dependencies. It can be used standalone for distributed @@ -11,8 +10,7 @@ class TaskContext: - """ - Pure task state management - no session dependencies. + """Pure task state management - no session dependencies. This class handles: - Task state (status, result) @@ -54,8 +52,7 @@ def is_cancelled(self) -> bool: return self._cancelled def request_cancellation(self) -> None: - """ - Request cancellation of this task. + """Request cancellation of this task. This sets is_cancelled=True. Task work should check this periodically and exit gracefully if set. @@ -63,8 +60,7 @@ def request_cancellation(self) -> None: self._cancelled = True async def update_status(self, message: str) -> None: - """ - Update the task's status message. + """Update the task's status message. Args: message: The new status message @@ -75,8 +71,7 @@ async def update_status(self, message: str) -> None: ) async def complete(self, result: Result) -> None: - """ - Mark the task as completed with the given result. + """Mark the task as completed with the given result. Args: result: The task result @@ -88,8 +83,7 @@ async def complete(self, result: Result) -> None: ) async def fail(self, error: str) -> None: - """ - Mark the task as failed with an error message. + """Mark the task as failed with an error message. Args: error: The error message diff --git a/src/mcp/shared/experimental/tasks/helpers.py b/src/mcp/shared/experimental/tasks/helpers.py index cfd2629357..95055be828 100644 --- a/src/mcp/shared/experimental/tasks/helpers.py +++ b/src/mcp/shared/experimental/tasks/helpers.py @@ -1,5 +1,4 @@ -""" -Helper functions for pure task management. +"""Helper functions for pure task management. These helpers work with pure TaskContext and don't require server dependencies. For server-integrated task helpers, use mcp.server.experimental. @@ -36,8 +35,7 @@ def is_terminal(status: TaskStatus) -> bool: - """ - Check if a task status represents a terminal state. + """Check if a task status represents a terminal state. Terminal states are those where the task has finished and will not change. @@ -54,8 +52,7 @@ async def cancel_task( store: TaskStore, task_id: str, ) -> CancelTaskResult: - """ - Cancel a task with spec-compliant validation. + """Cancel a task with spec-compliant validation. Per spec: "Receivers MUST reject cancellation of terminal status tasks with -32602 (Invalid params)" @@ -111,8 +108,7 @@ def create_task_state( metadata: TaskMetadata, task_id: str | None = None, ) -> Task: - """ - Create a Task object with initial state. + """Create a Task object with initial state. This is a helper for TaskStore implementations. @@ -139,8 +135,7 @@ async def task_execution( task_id: str, store: TaskStore, ) -> AsyncIterator[TaskContext]: - """ - Context manager for safe task execution (pure, no server dependencies). + """Context manager for safe task execution (pure, no server dependencies). Loads a task from the store and provides a TaskContext for the work. If an unhandled exception occurs, the task is automatically marked as failed diff --git a/src/mcp/shared/experimental/tasks/in_memory_task_store.py b/src/mcp/shared/experimental/tasks/in_memory_task_store.py index e499211088..42f4fb7035 100644 --- a/src/mcp/shared/experimental/tasks/in_memory_task_store.py +++ b/src/mcp/shared/experimental/tasks/in_memory_task_store.py @@ -1,5 +1,4 @@ -""" -In-memory implementation of TaskStore for demonstration purposes. +"""In-memory implementation of TaskStore for demonstration purposes. This implementation stores all tasks in memory and provides automatic cleanup based on the TTL duration specified in the task metadata using lazy expiration. @@ -29,8 +28,7 @@ class StoredTask: class InMemoryTaskStore(TaskStore): - """ - A simple in-memory implementation of TaskStore. + """A simple in-memory implementation of TaskStore. Features: - Automatic TTL-based cleanup (lazy expiration) diff --git a/src/mcp/shared/experimental/tasks/message_queue.py b/src/mcp/shared/experimental/tasks/message_queue.py index 69b6609887..018c2b7b26 100644 --- a/src/mcp/shared/experimental/tasks/message_queue.py +++ b/src/mcp/shared/experimental/tasks/message_queue.py @@ -1,5 +1,4 @@ -""" -TaskMessageQueue - FIFO queue for task-related messages. +"""TaskMessageQueue - FIFO queue for task-related messages. This implements the core message queue pattern from the MCP Tasks spec. When a handler needs to send a request (like elicitation) during a task-augmented @@ -25,8 +24,7 @@ @dataclass class QueuedMessage: - """ - A message queued for delivery via tasks/result. + """A message queued for delivery via tasks/result. Messages are stored with their type and a resolver for requests that expect responses. @@ -49,8 +47,7 @@ class QueuedMessage: class TaskMessageQueue(ABC): - """ - Abstract interface for task message queuing. + """Abstract interface for task message queuing. This is a FIFO queue that stores messages to be delivered via `tasks/result`. When a task-augmented handler calls elicit() or sends a notification, the @@ -65,8 +62,7 @@ class TaskMessageQueue(ABC): @abstractmethod async def enqueue(self, task_id: str, message: QueuedMessage) -> None: - """ - Add a message to the queue for a task. + """Add a message to the queue for a task. Args: task_id: The task identifier @@ -75,8 +71,7 @@ async def enqueue(self, task_id: str, message: QueuedMessage) -> None: @abstractmethod async def dequeue(self, task_id: str) -> QueuedMessage | None: - """ - Remove and return the next message from the queue. + """Remove and return the next message from the queue. Args: task_id: The task identifier @@ -87,8 +82,7 @@ async def dequeue(self, task_id: str) -> QueuedMessage | None: @abstractmethod async def peek(self, task_id: str) -> QueuedMessage | None: - """ - Return the next message without removing it. + """Return the next message without removing it. Args: task_id: The task identifier @@ -99,8 +93,7 @@ async def peek(self, task_id: str) -> QueuedMessage | None: @abstractmethod async def is_empty(self, task_id: str) -> bool: - """ - Check if the queue is empty for a task. + """Check if the queue is empty for a task. Args: task_id: The task identifier @@ -111,8 +104,7 @@ async def is_empty(self, task_id: str) -> bool: @abstractmethod async def clear(self, task_id: str) -> list[QueuedMessage]: - """ - Remove and return all messages from the queue. + """Remove and return all messages from the queue. This is useful for cleanup when a task is cancelled or completed. @@ -125,8 +117,7 @@ async def clear(self, task_id: str) -> list[QueuedMessage]: @abstractmethod async def wait_for_message(self, task_id: str) -> None: - """ - Wait until a message is available in the queue. + """Wait until a message is available in the queue. This blocks until either: 1. A message is enqueued for this task @@ -138,8 +129,7 @@ async def wait_for_message(self, task_id: str) -> None: @abstractmethod async def notify_message_available(self, task_id: str) -> None: - """ - Signal that a message is available for a task. + """Signal that a message is available for a task. This wakes up any coroutines waiting in wait_for_message(). @@ -149,8 +139,7 @@ async def notify_message_available(self, task_id: str) -> None: class InMemoryTaskMessageQueue(TaskMessageQueue): - """ - In-memory implementation of TaskMessageQueue. + """In-memory implementation of TaskMessageQueue. This is suitable for single-process servers. For distributed systems, implement TaskMessageQueue with Redis, RabbitMQ, etc. @@ -227,8 +216,7 @@ async def notify_message_available(self, task_id: str) -> None: self._events[task_id].set() def cleanup(self, task_id: str | None = None) -> None: - """ - Clean up queues and events. + """Clean up queues and events. Args: task_id: If provided, clean up only this task. Otherwise clean up all. diff --git a/src/mcp/shared/experimental/tasks/polling.py b/src/mcp/shared/experimental/tasks/polling.py index 18cc262277..e4e13b6640 100644 --- a/src/mcp/shared/experimental/tasks/polling.py +++ b/src/mcp/shared/experimental/tasks/polling.py @@ -1,5 +1,4 @@ -""" -Shared polling utilities for task operations. +"""Shared polling utilities for task operations. This module provides generic polling logic that works for both client→server and server→client task polling. @@ -20,8 +19,7 @@ async def poll_until_terminal( task_id: str, default_interval_ms: int = 500, ) -> AsyncIterator[GetTaskResult]: - """ - Poll a task until it reaches terminal status. + """Poll a task until it reaches terminal status. This is a generic utility that works for both client→server and server→client polling. The caller provides the get_task function appropriate for their direction. diff --git a/src/mcp/shared/experimental/tasks/resolver.py b/src/mcp/shared/experimental/tasks/resolver.py index f27425b2c6..1d233a9309 100644 --- a/src/mcp/shared/experimental/tasks/resolver.py +++ b/src/mcp/shared/experimental/tasks/resolver.py @@ -1,5 +1,4 @@ -""" -Resolver - An anyio-compatible future-like object for async result passing. +"""Resolver - An anyio-compatible future-like object for async result passing. This provides a simple way to pass a result (or exception) from one coroutine to another without depending on asyncio.Future. @@ -13,8 +12,7 @@ class Resolver(Generic[T]): - """ - A simple resolver for passing results between coroutines. + """A simple resolver for passing results between coroutines. Unlike asyncio.Future, this works with any anyio-compatible async backend. diff --git a/src/mcp/shared/experimental/tasks/store.py b/src/mcp/shared/experimental/tasks/store.py index 71fb4511b8..7de97d40ca 100644 --- a/src/mcp/shared/experimental/tasks/store.py +++ b/src/mcp/shared/experimental/tasks/store.py @@ -1,6 +1,4 @@ -""" -TaskStore - Abstract interface for task state storage. -""" +"""TaskStore - Abstract interface for task state storage.""" from abc import ABC, abstractmethod @@ -8,8 +6,7 @@ class TaskStore(ABC): - """ - Abstract interface for task state storage. + """Abstract interface for task state storage. This is a pure storage interface - it doesn't manage execution. Implementations can use in-memory storage, databases, Redis, etc. @@ -23,8 +20,7 @@ async def create_task( metadata: TaskMetadata, task_id: str | None = None, ) -> Task: - """ - Create a new task. + """Create a new task. Args: metadata: Task metadata (ttl, etc.) @@ -39,8 +35,7 @@ async def create_task( @abstractmethod async def get_task(self, task_id: str) -> Task | None: - """ - Get a task by ID. + """Get a task by ID. Args: task_id: The task identifier @@ -56,8 +51,7 @@ async def update_task( status: TaskStatus | None = None, status_message: str | None = None, ) -> Task: - """ - Update a task's status and/or message. + """Update a task's status and/or message. Args: task_id: The task identifier @@ -76,8 +70,7 @@ async def update_task( @abstractmethod async def store_result(self, task_id: str, result: Result) -> None: - """ - Store the result for a task. + """Store the result for a task. Args: task_id: The task identifier @@ -89,8 +82,7 @@ async def store_result(self, task_id: str, result: Result) -> None: @abstractmethod async def get_result(self, task_id: str) -> Result | None: - """ - Get the stored result for a task. + """Get the stored result for a task. Args: task_id: The task identifier @@ -104,8 +96,7 @@ async def list_tasks( self, cursor: str | None = None, ) -> tuple[list[Task], str | None]: - """ - List tasks with pagination. + """List tasks with pagination. Args: cursor: Optional cursor for pagination @@ -116,8 +107,7 @@ async def list_tasks( @abstractmethod async def delete_task(self, task_id: str) -> bool: - """ - Delete a task. + """Delete a task. Args: task_id: The task identifier @@ -128,8 +118,7 @@ async def delete_task(self, task_id: str) -> bool: @abstractmethod async def wait_for_update(self, task_id: str) -> None: - """ - Wait until the task status changes. + """Wait until the task status changes. This blocks until either: 1. The task status changes @@ -146,8 +135,7 @@ async def wait_for_update(self, task_id: str) -> None: @abstractmethod async def notify_update(self, task_id: str) -> None: - """ - Signal that a task has been updated. + """Signal that a task has been updated. This wakes up any coroutines waiting in wait_for_update(). diff --git a/src/mcp/shared/memory.py b/src/mcp/shared/memory.py index e35c487b92..7be607fe1f 100644 --- a/src/mcp/shared/memory.py +++ b/src/mcp/shared/memory.py @@ -1,6 +1,4 @@ -""" -In-memory transports -""" +"""In-memory transports""" from __future__ import annotations @@ -17,8 +15,7 @@ @asynccontextmanager async def create_client_server_memory_streams() -> AsyncGenerator[tuple[MessageStream, MessageStream], None]: - """ - Creates a pair of bidirectional memory streams for client-server communication. + """Creates a pair of bidirectional memory streams for client-server communication. Returns: A tuple of (client_streams, server_streams) where each is a tuple of diff --git a/src/mcp/shared/message.py b/src/mcp/shared/message.py index 81503eaaa7..9dedd2e5d3 100644 --- a/src/mcp/shared/message.py +++ b/src/mcp/shared/message.py @@ -1,5 +1,4 @@ -""" -Message wrapper with metadata support. +"""Message wrapper with metadata support. This module defines a wrapper type that combines JSONRPCMessage with metadata to support transport-specific features like resumability. diff --git a/src/mcp/shared/metadata_utils.py b/src/mcp/shared/metadata_utils.py index e3f49daf48..2b66996bde 100644 --- a/src/mcp/shared/metadata_utils.py +++ b/src/mcp/shared/metadata_utils.py @@ -8,8 +8,7 @@ def get_display_name(obj: Tool | Resource | Prompt | ResourceTemplate | Implementation) -> str: - """ - Get the display name for an MCP object with proper precedence. + """Get the display name for an MCP object with proper precedence. This is a client-side utility function designed to help MCP clients display human-readable names in their user interfaces. When servers provide a 'title' diff --git a/src/mcp/shared/response_router.py b/src/mcp/shared/response_router.py index 31796157fe..7ec4a443c1 100644 --- a/src/mcp/shared/response_router.py +++ b/src/mcp/shared/response_router.py @@ -1,5 +1,4 @@ -""" -ResponseRouter - Protocol for pluggable response routing. +"""ResponseRouter - Protocol for pluggable response routing. This module defines a protocol for routing JSON-RPC responses to alternative handlers before falling back to the default response stream mechanism. @@ -20,8 +19,7 @@ class ResponseRouter(Protocol): - """ - Protocol for routing responses to alternative handlers. + """Protocol for routing responses to alternative handlers. Implementations check if they have a pending request for the given ID and deliver the response/error to the appropriate handler. @@ -37,8 +35,7 @@ def route_response(self, request_id, response): """ def route_response(self, request_id: RequestId, response: dict[str, Any]) -> bool: - """ - Try to route a response to a pending request handler. + """Try to route a response to a pending request handler. Args: request_id: The JSON-RPC request ID from the response @@ -50,8 +47,7 @@ def route_response(self, request_id: RequestId, response: dict[str, Any]) -> boo ... # pragma: no cover def route_error(self, request_id: RequestId, error: ErrorData) -> bool: - """ - Try to route an error to a pending request handler. + """Try to route an error to a pending request handler. Args: request_id: The JSON-RPC request ID from the error response diff --git a/src/mcp/shared/session.py b/src/mcp/shared/session.py index e0a3bc42c1..8006933541 100644 --- a/src/mcp/shared/session.py +++ b/src/mcp/shared/session.py @@ -163,8 +163,7 @@ class BaseSession( ReceiveNotificationT, ], ): - """ - Implements an MCP "session" on top of read/write streams, including features + """Implements an MCP "session" on top of read/write streams, including features like request/response linking, notifications, and progress. This class is an async context manager that automatically starts processing @@ -199,8 +198,7 @@ def __init__( self._exit_stack = AsyncExitStack() def add_response_router(self, router: ResponseRouter) -> None: - """ - Register a response router to handle responses for non-standard requests. + """Register a response router to handle responses for non-standard requests. Response routers are checked in order before falling back to the default response stream mechanism. This is used by TaskResultHandler to route @@ -241,8 +239,7 @@ async def send_request( metadata: MessageMetadata = None, progress_callback: ProgressFnT | None = None, ) -> ReceiveResultT: - """ - Sends a request and wait for a response. Raises an McpError if the + """Sends a request and wait for a response. Raises an McpError if the response contains an error. If a request read timeout is provided, it will take precedence over the session read timeout. @@ -314,8 +311,7 @@ async def send_notification( notification: SendNotificationT, related_request_id: RequestId | None = None, ) -> None: - """ - Emits a notification, which is a one-way message that does not expect + """Emits a notification, which is a one-way message that does not expect a response. """ # Some transport implementations may need to set the related_request_id @@ -454,8 +450,7 @@ async def _receive_loop(self) -> None: self._response_streams.clear() def _normalize_request_id(self, response_id: RequestId) -> RequestId: - """ - Normalize a response ID to match how request IDs are stored. + """Normalize a response ID to match how request IDs are stored. Since the client always sends integer IDs, we normalize string IDs to integers when possible. This matches the TypeScript SDK approach: @@ -475,8 +470,7 @@ def _normalize_request_id(self, response_id: RequestId) -> RequestId: return response_id async def _handle_response(self, message: SessionMessage) -> None: - """ - Handle an incoming response or error message. + """Handle an incoming response or error message. Checks response routers first (e.g., for task-related responses), then falls back to the normal response stream mechanism. @@ -514,8 +508,7 @@ async def _handle_response(self, message: SessionMessage) -> None: await self._handle_incoming(RuntimeError(f"Received response with an unknown request ID: {message}")) async def _received_request(self, responder: RequestResponder[ReceiveRequestT, SendResultT]) -> None: - """ - Can be overridden by subclasses to handle a request without needing to + """Can be overridden by subclasses to handle a request without needing to listen on the message stream. If the request is responded to within this method, it will not be @@ -523,8 +516,7 @@ async def _received_request(self, responder: RequestResponder[ReceiveRequestT, S """ async def _received_notification(self, notification: ReceiveNotificationT) -> None: - """ - Can be overridden by subclasses to handle a notification without needing + """Can be overridden by subclasses to handle a notification without needing to listen on the message stream. """ @@ -535,8 +527,7 @@ async def send_progress_notification( total: float | None = None, message: str | None = None, ) -> None: - """ - Sends a progress notification for a request that is currently being + """Sends a progress notification for a request that is currently being processed. """ diff --git a/src/mcp/types.py b/src/mcp/types.py index a43461f07a..b2afd977df 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -36,8 +36,7 @@ class MCPModel(BaseModel): class TaskMetadata(MCPModel): - """ - Metadata for augmenting a request with task execution. + """Metadata for augmenting a request with task execution. Include this in the `task` field of the request parameters. """ @@ -262,8 +261,7 @@ class RootsCapability(MCPModel): class SamplingContextCapability(MCPModel): - """ - Capability for context inclusion during sampling. + """Capability for context inclusion during sampling. Indicates support for non-'none' values in the includeContext parameter. SOFT-DEPRECATED: New implementations should use tools parameter instead. @@ -271,8 +269,7 @@ class SamplingContextCapability(MCPModel): class SamplingToolsCapability(MCPModel): - """ - Capability indicating support for tool calling during sampling. + """Capability indicating support for tool calling during sampling. When present in ClientCapabilities.sampling, indicates that the client supports the tools and toolChoice parameters in sampling requests. @@ -301,9 +298,7 @@ class ElicitationCapability(MCPModel): class SamplingCapability(MCPModel): - """ - Sampling capability structure, allowing fine-grained capability advertisement. - """ + """Sampling capability structure, allowing fine-grained capability advertisement.""" context: SamplingContextCapability | None = None """ @@ -469,8 +464,7 @@ class ServerCapabilities(MCPModel): class RelatedTaskMetadata(MCPModel): - """ - Metadata for associating messages with a task. + """Metadata for associating messages with a task. Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`. """ @@ -546,8 +540,7 @@ class GetTaskPayloadRequest(Request[GetTaskPayloadRequestParams, Literal["tasks/ class GetTaskPayloadResult(Result): - """ - The response to a tasks/result request. + """The response to a tasks/result request. The structure matches the result type of the original request. For example, a tools/call task would return the CallToolResult structure. """ @@ -586,8 +579,7 @@ class TaskStatusNotificationParams(NotificationParams, Task): class TaskStatusNotification(Notification[TaskStatusNotificationParams, Literal["notifications/tasks/status"]]): - """ - An optional notification from the receiver to the requestor, informing them that a task's status has changed. + """An optional notification from the receiver to the requestor, informing them that a task's status has changed. Receivers are not required to send these notifications """ @@ -605,8 +597,7 @@ class InitializeRequestParams(RequestParams): class InitializeRequest(Request[InitializeRequestParams, Literal["initialize"]]): - """ - This request is sent from the client to the server when it first connects, asking it + """This request is sent from the client to the server when it first connects, asking it to begin initialization. """ @@ -626,8 +617,7 @@ class InitializeResult(Result): class InitializedNotification(Notification[NotificationParams | None, Literal["notifications/initialized"]]): - """ - This notification is sent from the client to the server after initialization has + """This notification is sent from the client to the server after initialization has finished. """ @@ -636,8 +626,7 @@ class InitializedNotification(Notification[NotificationParams | None, Literal["n class PingRequest(Request[RequestParams | None, Literal["ping"]]): - """ - A ping, issued by either the server or the client, to check that the other party is + """A ping, issued by either the server or the client, to check that the other party is still alive. """ @@ -668,8 +657,7 @@ class ProgressNotificationParams(NotificationParams): class ProgressNotification(Notification[ProgressNotificationParams, Literal["notifications/progress"]]): - """ - An out-of-band notification used to inform the receiver of a progress update for a + """An out-of-band notification used to inform the receiver of a progress update for a long-running request. """ @@ -814,8 +802,7 @@ class ReadResourceResult(Result): class ResourceListChangedNotification( Notification[NotificationParams | None, Literal["notifications/resources/list_changed"]] ): - """ - An optional notification from the server to the client, informing it that the list + """An optional notification from the server to the client, informing it that the list of resources it can read from has changed. """ @@ -834,8 +821,7 @@ class SubscribeRequestParams(RequestParams): class SubscribeRequest(Request[SubscribeRequestParams, Literal["resources/subscribe"]]): - """ - Sent from the client to request resources/updated notifications from the server + """Sent from the client to request resources/updated notifications from the server whenever a particular resource changes. """ @@ -851,8 +837,7 @@ class UnsubscribeRequestParams(RequestParams): class UnsubscribeRequest(Request[UnsubscribeRequestParams, Literal["resources/unsubscribe"]]): - """ - Sent from the client to request cancellation of resources/updated notifications from + """Sent from the client to request cancellation of resources/updated notifications from the server. """ @@ -873,8 +858,7 @@ class ResourceUpdatedNotificationParams(NotificationParams): class ResourceUpdatedNotification( Notification[ResourceUpdatedNotificationParams, Literal["notifications/resources/updated"]] ): - """ - A notification from the server to the client, informing it that a resource has + """A notification from the server to the client, informing it that a resource has changed and may need to be read again. """ @@ -990,8 +974,7 @@ class AudioContent(MCPModel): class ToolUseContent(MCPModel): - """ - Content representing an assistant's request to invoke a tool. + """Content representing an assistant's request to invoke a tool. This content type appears in assistant messages when the LLM wants to call a tool during sampling. The server should execute the tool and return a ToolResultContent @@ -1018,8 +1001,7 @@ class ToolUseContent(MCPModel): class ToolResultContent(MCPModel): - """ - Content representing the result of a tool execution. + """Content representing the result of a tool execution. This content type appears in user messages as a response to a ToolUseContent from the assistant. It contains the output of executing the requested tool. @@ -1083,8 +1065,7 @@ def content_as_list(self) -> list[SamplingMessageContentBlock]: class EmbeddedResource(MCPModel): - """ - The contents of a resource, embedded into a prompt or tool call result. + """The contents of a resource, embedded into a prompt or tool call result. It is up to the client how best to render embedded resources for the benefit of the LLM and/or the user. @@ -1101,8 +1082,7 @@ class EmbeddedResource(MCPModel): class ResourceLink(Resource): - """ - A resource that the server is capable of reading, included in a prompt or tool call result. + """A resource that the server is capable of reading, included in a prompt or tool call result. Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. """ @@ -1132,8 +1112,7 @@ class GetPromptResult(Result): class PromptListChangedNotification( Notification[NotificationParams | None, Literal["notifications/prompts/list_changed"]] ): - """ - An optional notification from the server to the client, informing it that the list + """An optional notification from the server to the client, informing it that the list of prompts it offers has changed. """ @@ -1148,8 +1127,7 @@ class ListToolsRequest(PaginatedRequest[Literal["tools/list"]]): class ToolAnnotations(MCPModel): - """ - Additional properties describing a Tool to clients. + """Additional properties describing a Tool to clients. NOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of @@ -1266,8 +1244,7 @@ class CallToolResult(Result): class ToolListChangedNotification(Notification[NotificationParams | None, Literal["notifications/tools/list_changed"]]): - """ - An optional notification from the server to the client, informing it that the list + """An optional notification from the server to the client, informing it that the list of tools it offers has changed. """ @@ -1324,8 +1301,7 @@ class ModelHint(MCPModel): class ModelPreferences(MCPModel): - """ - The server's preferences for model selection, requested by the client during + """The server's preferences for model selection, requested by the client during sampling. Because LLMs can vary along multiple dimensions, choosing the "best" model is @@ -1373,8 +1349,7 @@ class ModelPreferences(MCPModel): class ToolChoice(MCPModel): - """ - Controls tool usage behavior during sampling. + """Controls tool usage behavior during sampling. Allows the server to specify whether and how the LLM should use tools in its response. @@ -1550,8 +1525,7 @@ class CompleteResult(Result): class ListRootsRequest(Request[RequestParams | None, Literal["roots/list"]]): - """ - Sent from the server to request a list of root URIs from the client. Roots allow + """Sent from the server to request a list of root URIs from the client. Roots allow servers to ask for specific directories or files to operate on. A common example for roots is providing a set of repositories or directories a server should operate on. @@ -1587,8 +1561,7 @@ class Root(MCPModel): class ListRootsResult(Result): - """ - The client's response to a roots/list request from the server. + """The client's response to a roots/list request from the server. This result contains an array of Root objects, each representing a root directory or file that the server can operate on. """ @@ -1599,8 +1572,7 @@ class ListRootsResult(Result): class RootsListChangedNotification( Notification[NotificationParams | None, Literal["notifications/roots/list_changed"]] ): - """ - A notification from the client to the server, informing it that the list of + """A notification from the client to the server, informing it that the list of roots has changed. This notification should be sent whenever the client adds, removes, or @@ -1628,8 +1600,7 @@ class CancelledNotificationParams(NotificationParams): class CancelledNotification(Notification[CancelledNotificationParams, Literal["notifications/cancelled"]]): - """ - This notification can be sent by either side to indicate that it is canceling a + """This notification can be sent by either side to indicate that it is canceling a previously-issued request. """ @@ -1647,8 +1618,7 @@ class ElicitCompleteNotificationParams(NotificationParams): class ElicitCompleteNotification( Notification[ElicitCompleteNotificationParams, Literal["notifications/elicitation/complete"]] ): - """ - A notification from the server to the client, informing it that a URL mode + """A notification from the server to the client, informing it that a URL mode elicitation has been completed. Clients MAY use the notification to automatically retry requests that received a diff --git a/tests/client/test_auth.py b/tests/client/test_auth.py index 6df63b0bfc..2f531cc653 100644 --- a/tests/client/test_auth.py +++ b/tests/client/test_auth.py @@ -1,6 +1,4 @@ -""" -Tests for refactored OAuth client authentication implementation. -""" +"""Tests for refactored OAuth client authentication implementation.""" import base64 import time diff --git a/tests/client/test_http_unicode.py b/tests/client/test_http_unicode.py index d671a7e1c2..f368c30182 100644 --- a/tests/client/test_http_unicode.py +++ b/tests/client/test_http_unicode.py @@ -1,5 +1,4 @@ -""" -Tests for Unicode handling in streamable HTTP transport. +"""Tests for Unicode handling in streamable HTTP transport. Verifies that Unicode text is correctly transmitted and received in both directions (server→client and client→server) using the streamable HTTP transport. diff --git a/tests/client/test_notification_response.py b/tests/client/test_notification_response.py index 7500abee73..e05edb14dc 100644 --- a/tests/client/test_notification_response.py +++ b/tests/client/test_notification_response.py @@ -1,5 +1,4 @@ -""" -Tests for StreamableHTTP client transport with non-SDK servers. +"""Tests for StreamableHTTP client transport with non-SDK servers. These tests verify client behavior when interacting with servers that don't follow SDK conventions. @@ -110,8 +109,7 @@ def non_sdk_server(non_sdk_server_port: int) -> Generator[None, None, None]: @pytest.mark.anyio async def test_non_compliant_notification_response(non_sdk_server: None, non_sdk_server_port: int) -> None: - """ - This test verifies that the client ignores unexpected responses to notifications: the spec states they should + """This test verifies that the client ignores unexpected responses to notifications: the spec states they should either be 202 + no response body, or 4xx + optional error body (https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#sending-messages-to-the-server), but some servers wrongly return other 2xx codes (e.g. 204). For now we simply ignore unexpected responses diff --git a/tests/client/test_output_schema_validation.py b/tests/client/test_output_schema_validation.py index 24f7b2b69c..714352ad55 100644 --- a/tests/client/test_output_schema_validation.py +++ b/tests/client/test_output_schema_validation.py @@ -14,8 +14,7 @@ @contextmanager def bypass_server_output_validation(): - """ - Context manager that bypasses server-side output validation. + """Context manager that bypasses server-side output validation. This simulates a malicious or non-compliant server that doesn't validate its outputs, allowing us to test client-side validation. """ diff --git a/tests/client/test_resource_cleanup.py b/tests/client/test_resource_cleanup.py index cc6c5059fd..f47299cf8b 100644 --- a/tests/client/test_resource_cleanup.py +++ b/tests/client/test_resource_cleanup.py @@ -11,8 +11,7 @@ @pytest.mark.anyio async def test_send_request_stream_cleanup(): - """ - Test that send_request properly cleans up streams when an exception occurs. + """Test that send_request properly cleans up streams when an exception occurs. This test mocks out most of the session functionality to focus on stream cleanup. """ diff --git a/tests/client/test_scope_bug_1630.py b/tests/client/test_scope_bug_1630.py index 7884718c1e..fafa510075 100644 --- a/tests/client/test_scope_bug_1630.py +++ b/tests/client/test_scope_bug_1630.py @@ -1,5 +1,4 @@ -""" -Regression test for issue #1630: OAuth2 scope incorrectly set to resource_metadata URL. +"""Regression test for issue #1630: OAuth2 scope incorrectly set to resource_metadata URL. This test verifies that when a 401 response contains both resource_metadata and scope in the WWW-Authenticate header, the actual scope is used (not the resource_metadata URL). @@ -37,8 +36,7 @@ async def set_client_info(self, client_info: OAuthClientInformationFull) -> None @pytest.mark.anyio async def test_401_uses_www_auth_scope_not_resource_metadata_url(): - """ - Regression test for #1630: Ensure scope is extracted from WWW-Authenticate header, + """Regression test for #1630: Ensure scope is extracted from WWW-Authenticate header, not the resource_metadata URL. When a 401 response contains: diff --git a/tests/client/test_stdio.py b/tests/client/test_stdio.py index b6c60581f6..61b7ce4faf 100644 --- a/tests/client/test_stdio.py +++ b/tests/client/test_stdio.py @@ -106,8 +106,7 @@ async def test_stdio_client_nonexistent_command(): @pytest.mark.anyio async def test_stdio_client_universal_cleanup(): - """ - Test that stdio_client completes cleanup within reasonable time + """Test that stdio_client completes cleanup within reasonable time even when connected to processes that exit slowly. """ @@ -159,9 +158,7 @@ async def test_stdio_client_universal_cleanup(): @pytest.mark.anyio @pytest.mark.skipif(sys.platform == "win32", reason="Windows signal handling is different") async def test_stdio_client_sigint_only_process(): # pragma: no cover - """ - Test cleanup with a process that ignores SIGTERM but responds to SIGINT. - """ + """Test cleanup with a process that ignores SIGTERM but responds to SIGINT.""" # Create a Python script that ignores SIGTERM but handles SIGINT script_content = textwrap.dedent( """ @@ -225,8 +222,7 @@ def sigint_handler(signum, frame): class TestChildProcessCleanup: - """ - Tests for child process cleanup functionality using _terminate_process_tree. + """Tests for child process cleanup functionality using _terminate_process_tree. These tests verify that child processes are properly terminated when the parent is killed, addressing the issue where processes like npx spawn child processes @@ -252,8 +248,7 @@ class TestChildProcessCleanup: @pytest.mark.anyio @pytest.mark.filterwarnings("ignore::ResourceWarning" if sys.platform == "win32" else "default") async def test_basic_child_process_cleanup(self): - """ - Test basic parent-child process cleanup. + """Test basic parent-child process cleanup. Parent spawns a single child process that writes continuously to a file. """ # Create a marker file for the child process to write to @@ -344,8 +339,7 @@ async def test_basic_child_process_cleanup(self): @pytest.mark.anyio @pytest.mark.filterwarnings("ignore::ResourceWarning" if sys.platform == "win32" else "default") async def test_nested_process_tree(self): - """ - Test nested process tree cleanup (parent → child → grandchild). + """Test nested process tree cleanup (parent → child → grandchild). Each level writes to a different file to verify all processes are terminated. """ # Create temporary files for each process level @@ -440,8 +434,7 @@ async def test_nested_process_tree(self): @pytest.mark.anyio @pytest.mark.filterwarnings("ignore::ResourceWarning" if sys.platform == "win32" else "default") async def test_early_parent_exit(self): - """ - Test cleanup when parent exits during termination sequence. + """Test cleanup when parent exits during termination sequence. Tests the race condition where parent might die during our termination sequence but we can still clean up the children via the process group. """ @@ -517,8 +510,7 @@ def handle_term(sig, frame): @pytest.mark.anyio async def test_stdio_client_graceful_stdin_exit(): - """ - Test that a process exits gracefully when stdin is closed, + """Test that a process exits gracefully when stdin is closed, without needing SIGTERM or SIGKILL. """ # Create a Python script that exits when stdin is closed @@ -573,8 +565,7 @@ async def test_stdio_client_graceful_stdin_exit(): @pytest.mark.anyio async def test_stdio_client_stdin_close_ignored(): - """ - Test that when a process ignores stdin closure, the shutdown sequence + """Test that when a process ignores stdin closure, the shutdown sequence properly escalates to SIGTERM. """ # Create a Python script that ignores stdin closure but responds to SIGTERM diff --git a/tests/experimental/tasks/server/test_integration.py b/tests/experimental/tasks/server/test_integration.py index 3d7d89c344..db18ef3599 100644 --- a/tests/experimental/tasks/server/test_integration.py +++ b/tests/experimental/tasks/server/test_integration.py @@ -62,8 +62,7 @@ class AppContext: @pytest.mark.anyio async def test_task_lifecycle_with_task_execution() -> None: - """ - Test the complete task lifecycle using the task_execution pattern. + """Test the complete task lifecycle using the task_execution pattern. This demonstrates the recommended way to implement task-augmented tools: 1. Create task in store diff --git a/tests/experimental/tasks/server/test_run_task_flow.py b/tests/experimental/tasks/server/test_run_task_flow.py index ebfc427891..13c702a1cf 100644 --- a/tests/experimental/tasks/server/test_run_task_flow.py +++ b/tests/experimental/tasks/server/test_run_task_flow.py @@ -1,5 +1,4 @@ -""" -Tests for the simplified task API: enable_tasks() + run_task() +"""Tests for the simplified task API: enable_tasks() + run_task() This tests the recommended user flow: 1. server.experimental.enable_tasks() - one-line setup @@ -45,8 +44,7 @@ @pytest.mark.anyio async def test_run_task_basic_flow() -> None: - """ - Test the basic run_task flow without elicitation. + """Test the basic run_task flow without elicitation. 1. enable_tasks() sets up handlers 2. Client calls tool with task field @@ -143,9 +141,7 @@ async def run_client() -> None: @pytest.mark.anyio async def test_run_task_auto_fails_on_exception() -> None: - """ - Test that run_task automatically fails the task when work raises. - """ + """Test that run_task automatically fails the task when work raises.""" server = Server("test-run-task-fail") server.experimental.enable_tasks() @@ -210,9 +206,7 @@ async def run_client() -> None: @pytest.mark.anyio async def test_enable_tasks_auto_registers_handlers() -> None: - """ - Test that enable_tasks() auto-registers get_task, list_tasks, cancel_task handlers. - """ + """Test that enable_tasks() auto-registers get_task, list_tasks, cancel_task handlers.""" server = Server("test-enable-tasks") # Before enable_tasks, no task capabilities diff --git a/tests/experimental/tasks/test_elicitation_scenarios.py b/tests/experimental/tasks/test_elicitation_scenarios.py index 9044156047..1cefe847da 100644 --- a/tests/experimental/tasks/test_elicitation_scenarios.py +++ b/tests/experimental/tasks/test_elicitation_scenarios.py @@ -1,5 +1,4 @@ -""" -Tests for the four elicitation scenarios with tasks. +"""Tests for the four elicitation scenarios with tasks. This tests all combinations of tool call types and elicitation types: 1. Normal tool call + Normal elicitation (session.elicit) @@ -178,8 +177,7 @@ async def handle_get_task_result( @pytest.mark.anyio async def test_scenario1_normal_tool_normal_elicitation() -> None: - """ - Scenario 1: Normal tool call with normal elicitation. + """Scenario 1: Normal tool call with normal elicitation. Server calls session.elicit() directly, client responds immediately. """ @@ -259,8 +257,7 @@ async def run_client() -> None: @pytest.mark.anyio async def test_scenario2_normal_tool_task_augmented_elicitation() -> None: - """ - Scenario 2: Normal tool call with task-augmented elicitation. + """Scenario 2: Normal tool call with task-augmented elicitation. Server calls session.experimental.elicit_as_task(), client creates a task for the elicitation and returns CreateTaskResult. Server polls client. @@ -340,8 +337,7 @@ async def run_client() -> None: @pytest.mark.anyio async def test_scenario3_task_augmented_tool_normal_elicitation() -> None: - """ - Scenario 3: Task-augmented tool call with normal elicitation. + """Scenario 3: Task-augmented tool call with normal elicitation. Client calls tool as task. Inside the task, server uses task.elicit() which queues the request and delivers via tasks/result. @@ -442,8 +438,7 @@ async def run_client() -> None: @pytest.mark.anyio async def test_scenario4_task_augmented_tool_task_augmented_elicitation() -> None: - """ - Scenario 4: Task-augmented tool call with task-augmented elicitation. + """Scenario 4: Task-augmented tool call with task-augmented elicitation. Client calls tool as task. Inside the task, server uses task.elicit_as_task() which sends task-augmented elicitation. Client creates its own task for the @@ -553,8 +548,7 @@ async def run_client() -> None: @pytest.mark.anyio async def test_scenario2_sampling_normal_tool_task_augmented_sampling() -> None: - """ - Scenario 2 for sampling: Normal tool call with task-augmented sampling. + """Scenario 2 for sampling: Normal tool call with task-augmented sampling. Server calls session.experimental.create_message_as_task(), client creates a task for the sampling and returns CreateTaskResult. Server polls client. @@ -636,8 +630,7 @@ async def run_client() -> None: @pytest.mark.anyio async def test_scenario4_sampling_task_augmented_tool_task_augmented_sampling() -> None: - """ - Scenario 4 for sampling: Task-augmented tool call with task-augmented sampling. + """Scenario 4 for sampling: Task-augmented tool call with task-augmented sampling. Client calls tool as task. Inside the task, server uses task.create_message_as_task() which sends task-augmented sampling. Client creates its own task for the sampling, diff --git a/tests/experimental/tasks/test_message_queue.py b/tests/experimental/tasks/test_message_queue.py index 86d6875cc4..a8517e535c 100644 --- a/tests/experimental/tasks/test_message_queue.py +++ b/tests/experimental/tasks/test_message_queue.py @@ -1,6 +1,4 @@ -""" -Tests for TaskMessageQueue and InMemoryTaskMessageQueue. -""" +"""Tests for TaskMessageQueue and InMemoryTaskMessageQueue.""" from datetime import datetime, timezone diff --git a/tests/experimental/tasks/test_spec_compliance.py b/tests/experimental/tasks/test_spec_compliance.py index 36ffc50d3d..d00ce40a45 100644 --- a/tests/experimental/tasks/test_spec_compliance.py +++ b/tests/experimental/tasks/test_spec_compliance.py @@ -1,5 +1,4 @@ -""" -Tasks Spec Compliance Tests +"""Tasks Spec Compliance Tests =========================== Test structure mirrors: https://modelcontextprotocol.io/specification/draft/basic/utilities/tasks.md @@ -72,8 +71,7 @@ async def handle_cancel(req: CancelTaskRequest) -> CancelTaskResult: def test_server_with_get_task_handler_declares_requests_tools_call_capability() -> None: - """ - Server with get_task handler declares tasks.requests.tools.call capability. + """Server with get_task handler declares tasks.requests.tools.call capability. (get_task is required for task-augmented tools/call support) """ server: Server = Server("test") @@ -141,8 +139,7 @@ async def handle_get(req: GetTaskRequest) -> GetTaskResult: class TestClientCapabilities: - """ - Clients declare: + """Clients declare: - tasks.list — supports listing operations - tasks.cancel — supports cancellation - tasks.requests.sampling.createMessage — task-augmented sampling @@ -155,8 +152,7 @@ def test_client_declares_tasks_capability(self) -> None: class TestToolLevelNegotiation: - """ - Tools in tools/list responses include execution.taskSupport with values: + """Tools in tools/list responses include execution.taskSupport with values: - Not present or "forbidden": No task augmentation allowed - "optional": Task augmentation allowed at requestor discretion - "required": Task augmentation is mandatory @@ -188,8 +184,7 @@ def test_tool_execution_task_required_accepts_task_augmented_call(self) -> None: class TestCapabilityNegotiation: - """ - Requestors SHOULD only augment requests with a task if the corresponding + """Requestors SHOULD only augment requests with a task if the corresponding capability has been declared by the receiver. Receivers that do not declare the task capability for a request type @@ -198,23 +193,20 @@ class TestCapabilityNegotiation: """ def test_receiver_without_capability_ignores_task_metadata(self) -> None: - """ - Receiver without task capability MUST process request normally, + """Receiver without task capability MUST process request normally, ignoring task-augmentation metadata. """ pytest.skip("TODO") def test_receiver_with_capability_may_require_task_augmentation(self) -> None: - """ - Receivers that declare task capability MAY return error (-32600) + """Receivers that declare task capability MAY return error (-32600) for non-task-augmented requests, requiring task augmentation. """ pytest.skip("TODO") class TestTaskStatusLifecycle: - """ - Tasks begin in working status and follow valid transitions: + """Tasks begin in working status and follow valid transitions: working → input_required → working → terminal working → terminal (directly) input_required → terminal (directly) @@ -271,8 +263,7 @@ def test_cancelled_is_terminal(self) -> None: class TestInputRequiredStatus: - """ - When a receiver needs information to proceed, it moves the task to input_required. + """When a receiver needs information to proceed, it moves the task to input_required. The requestor should call tasks/result to retrieve input requests. The task must include io.modelcontextprotocol/related-task metadata in associated requests. """ @@ -282,16 +273,14 @@ def test_input_required_status_retrievable_via_tasks_get(self) -> None: pytest.skip("TODO") def test_input_required_related_task_metadata_in_requests(self) -> None: - """ - Task MUST include io.modelcontextprotocol/related-task metadata + """Task MUST include io.modelcontextprotocol/related-task metadata in associated requests. """ pytest.skip("TODO") class TestCreatingTask: - """ - Request structure: + """Request structure: {"method": "tools/call", "params": {"name": "...", "arguments": {...}, "task": {"ttl": 60000}}} Response (CreateTaskResult): @@ -337,8 +326,7 @@ def test_receiver_may_override_requested_ttl(self) -> None: pytest.skip("TODO") def test_model_immediate_response_in_meta(self) -> None: - """ - Receiver MAY include io.modelcontextprotocol/model-immediate-response + """Receiver MAY include io.modelcontextprotocol/model-immediate-response in _meta to provide immediate response while task executes. """ # Verify the constant has the correct value per spec @@ -372,8 +360,7 @@ def test_model_immediate_response_in_meta(self) -> None: class TestGettingTaskStatus: - """ - Request: {"method": "tasks/get", "params": {"taskId": "..."}} + """Request: {"method": "tasks/get", "params": {"taskId": "..."}} Response: Returns full Task object with current status and pollInterval. """ @@ -399,8 +386,7 @@ def test_tasks_get_nonexistent_task_id_returns_error(self) -> None: class TestRetrievingResults: - """ - Request: {"method": "tasks/result", "params": {"taskId": "..."}} + """Request: {"method": "tasks/result", "params": {"taskId": "..."}} Response: The actual operation result structure (e.g., CallToolResult). This call blocks until terminal status. @@ -423,8 +409,7 @@ def test_tasks_result_includes_related_task_metadata(self) -> None: pytest.skip("TODO") def test_tasks_result_returns_error_for_failed_task(self) -> None: - """ - tasks/result returns the same error the underlying request + """tasks/result returns the same error the underlying request would have produced for failed tasks. """ pytest.skip("TODO") @@ -435,8 +420,7 @@ def test_tasks_result_invalid_task_id_returns_error(self) -> None: class TestListingTasks: - """ - Request: {"method": "tasks/list", "params": {"cursor": "optional"}} + """Request: {"method": "tasks/list", "params": {"cursor": "optional"}} Response: Array of tasks with pagination support via nextCursor. """ @@ -462,8 +446,7 @@ def test_tasks_list_invalid_cursor_returns_error(self) -> None: class TestCancellingTasks: - """ - Request: {"method": "tasks/cancel", "params": {"taskId": "..."}} + """Request: {"method": "tasks/cancel", "params": {"taskId": "..."}} Response: Returns the task object with status: "cancelled". """ @@ -493,8 +476,7 @@ def test_tasks_cancel_invalid_task_id_returns_error(self) -> None: class TestStatusNotifications: - """ - Receivers MAY send: {"method": "notifications/tasks/status", "params": {...}} + """Receivers MAY send: {"method": "notifications/tasks/status", "params": {...}} These are optional; requestors MUST NOT rely on them and SHOULD continue polling. """ @@ -512,8 +494,7 @@ def test_status_notification_contains_status(self) -> None: class TestTaskManagement: - """ - - Receivers generate unique task IDs as strings + """- Receivers generate unique task IDs as strings - Tasks must begin in working status - createdAt timestamps must be ISO 8601 formatted - Receivers may override requested ttl but must return actual value @@ -535,8 +516,7 @@ def test_receiver_may_delete_tasks_after_ttl(self) -> None: pytest.skip("TODO") def test_related_task_metadata_in_task_messages(self) -> None: - """ - All task-related messages MUST include io.modelcontextprotocol/related-task + """All task-related messages MUST include io.modelcontextprotocol/related-task in _meta. """ pytest.skip("TODO") @@ -555,8 +535,7 @@ def test_tasks_cancel_does_not_require_related_task_metadata(self) -> None: class TestResultHandling: - """ - - Receivers must return CreateTaskResult immediately upon accepting task-augmented requests + """- Receivers must return CreateTaskResult immediately upon accepting task-augmented requests - tasks/result must return exactly what the underlying request would return - tasks/result blocks for non-terminal tasks; must unblock upon reaching terminal status """ @@ -575,8 +554,7 @@ def test_tasks_result_for_tool_call_returns_call_tool_result(self) -> None: class TestProgressTracking: - """ - Task-augmented requests support progress notifications using the progressToken + """Task-augmented requests support progress notifications using the progressToken mechanism, which remains valid throughout the task lifetime. """ @@ -590,8 +568,7 @@ def test_progress_notifications_sent_during_task_execution(self) -> None: class TestProtocolErrors: - """ - Protocol Errors (JSON-RPC standard codes): + """Protocol Errors (JSON-RPC standard codes): - -32600 (Invalid request): Non-task requests to endpoint requiring task augmentation - -32602 (Invalid params): Invalid/nonexistent taskId, invalid cursor, cancel terminal task - -32603 (Internal error): Server-side execution failures @@ -623,8 +600,7 @@ def test_internal_error_for_server_failure(self) -> None: class TestTaskExecutionErrors: - """ - When underlying requests fail, the task moves to failed status. + """When underlying requests fail, the task moves to failed status. - tasks/get response should include statusMessage explaining failure - tasks/result returns same error the underlying request would have produced - For tool calls, isError: true moves task to failed status @@ -648,8 +624,7 @@ def test_tool_call_is_error_true_moves_to_failed(self) -> None: class TestTaskObject: - """ - Task Object fields: + """Task Object fields: - taskId: String identifier - status: Current execution state - statusMessage: Optional human-readable description @@ -684,8 +659,7 @@ def test_task_poll_interval_is_optional(self) -> None: class TestRelatedTaskMetadata: - """ - Related Task Metadata structure: + """Related Task Metadata structure: {"_meta": {"io.modelcontextprotocol/related-task": {"taskId": "..."}}} """ @@ -699,42 +673,34 @@ def test_related_task_metadata_contains_task_id(self) -> None: class TestAccessAndIsolation: - """ - - Task IDs enable access to sensitive results + """- Task IDs enable access to sensitive results - Authorization context binding is essential where available - For non-authorized environments: strong entropy IDs, strict TTL limits """ def test_task_bound_to_authorization_context(self) -> None: - """ - Receivers receiving authorization context MUST bind tasks to that context. - """ + """Receivers receiving authorization context MUST bind tasks to that context.""" pytest.skip("TODO") def test_reject_task_operations_outside_authorization_context(self) -> None: - """ - Receivers MUST reject task operations for tasks outside + """Receivers MUST reject task operations for tasks outside requestor's authorization context. """ pytest.skip("TODO") def test_non_authorized_environments_use_secure_ids(self) -> None: - """ - For non-authorized environments, receivers SHOULD use + """For non-authorized environments, receivers SHOULD use cryptographically secure IDs. """ pytest.skip("TODO") def test_non_authorized_environments_use_shorter_ttls(self) -> None: - """ - For non-authorized environments, receivers SHOULD use shorter TTLs. - """ + """For non-authorized environments, receivers SHOULD use shorter TTLs.""" pytest.skip("TODO") class TestResourceLimits: - """ - Receivers should: + """Receivers should: - Enforce concurrent task limits per requestor - Implement maximum TTL constraints - Clean up expired tasks promptly diff --git a/tests/issues/test_1027_win_unreachable_cleanup.py b/tests/issues/test_1027_win_unreachable_cleanup.py index 43044e4a98..0c569edb2a 100644 --- a/tests/issues/test_1027_win_unreachable_cleanup.py +++ b/tests/issues/test_1027_win_unreachable_cleanup.py @@ -1,5 +1,4 @@ -""" -Regression test for issue #1027: Ensure cleanup procedures run properly during shutdown +"""Regression test for issue #1027: Ensure cleanup procedures run properly during shutdown Issue #1027 reported that cleanup code after "yield" in lifespan was unreachable when processes were terminated. This has been fixed by implementing the MCP spec-compliant @@ -23,8 +22,7 @@ @pytest.mark.anyio async def test_lifespan_cleanup_executed(): - """ - Regression test ensuring MCP server cleanup code runs during shutdown. + """Regression test ensuring MCP server cleanup code runs during shutdown. This test verifies that the fix for issue #1027 works correctly by: 1. Starting an MCP server that writes a marker file on startup @@ -124,8 +122,7 @@ def echo(text: str) -> str: @pytest.mark.anyio @pytest.mark.filterwarnings("ignore::ResourceWarning" if sys.platform == "win32" else "default") async def test_stdin_close_triggers_cleanup(): - """ - Regression test verifying the stdin-based graceful shutdown mechanism. + """Regression test verifying the stdin-based graceful shutdown mechanism. This test ensures the core fix for issue #1027 continues to work by: 1. Manually managing a server process diff --git a/tests/issues/test_1363_race_condition_streamable_http.py b/tests/issues/test_1363_race_condition_streamable_http.py index 49242d6d8b..caa6db46ec 100644 --- a/tests/issues/test_1363_race_condition_streamable_http.py +++ b/tests/issues/test_1363_race_condition_streamable_http.py @@ -90,8 +90,7 @@ def stop(self) -> None: def check_logs_for_race_condition_errors(caplog: pytest.LogCaptureFixture, test_name: str) -> None: - """ - Check logs for ClosedResourceError and other race condition errors. + """Check logs for ClosedResourceError and other race condition errors. Args: caplog: pytest log capture fixture @@ -121,8 +120,7 @@ def check_logs_for_race_condition_errors(caplog: pytest.LogCaptureFixture, test_ @pytest.mark.anyio async def test_race_condition_invalid_accept_headers(caplog: pytest.LogCaptureFixture): - """ - Test the race condition with invalid Accept headers. + """Test the race condition with invalid Accept headers. This test reproduces the exact scenario described in issue #1363: - Send POST request with incorrect Accept headers (missing either application/json or text/event-stream) @@ -196,8 +194,7 @@ async def test_race_condition_invalid_accept_headers(caplog: pytest.LogCaptureFi @pytest.mark.anyio async def test_race_condition_invalid_content_type(caplog: pytest.LogCaptureFixture): - """ - Test the race condition with invalid Content-Type headers. + """Test the race condition with invalid Content-Type headers. This test reproduces the race condition scenario with Content-Type validation failure. """ @@ -237,8 +234,7 @@ async def test_race_condition_invalid_content_type(caplog: pytest.LogCaptureFixt @pytest.mark.anyio async def test_race_condition_message_router_async_for(caplog: pytest.LogCaptureFixture): - """ - Uses json_response=True to trigger the `if self.is_json_response_enabled` branch, + """Uses json_response=True to trigger the `if self.is_json_response_enabled` branch, which reproduces the ClosedResourceError when message_router is suspended in async for loop while transport cleanup closes streams concurrently. """ diff --git a/tests/issues/test_552_windows_hang.py b/tests/issues/test_552_windows_hang.py index 972659c2b7..1adb5d80cb 100644 --- a/tests/issues/test_552_windows_hang.py +++ b/tests/issues/test_552_windows_hang.py @@ -13,8 +13,7 @@ @pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific test") # pragma: no cover @pytest.mark.anyio async def test_windows_stdio_client_with_session(): - """ - Test the exact scenario from issue #552: Using ClientSession with stdio_client. + """Test the exact scenario from issue #552: Using ClientSession with stdio_client. This reproduces the original bug report where stdio_client hangs on Windows 11 when used with ClientSession. diff --git a/tests/issues/test_malformed_input.py b/tests/issues/test_malformed_input.py index 078beb7a58..34498ba747 100644 --- a/tests/issues/test_malformed_input.py +++ b/tests/issues/test_malformed_input.py @@ -20,8 +20,7 @@ @pytest.mark.anyio async def test_malformed_initialize_request_does_not_crash_server(): - """ - Test that malformed initialize requests return proper error responses + """Test that malformed initialize requests return proper error responses instead of crashing the server (HackerOne #3156202). """ # Create in-memory streams for testing @@ -101,9 +100,7 @@ async def test_malformed_initialize_request_does_not_crash_server(): @pytest.mark.anyio async def test_multiple_concurrent_malformed_requests(): - """ - Test that multiple concurrent malformed requests don't crash the server. - """ + """Test that multiple concurrent malformed requests don't crash the server.""" # Create in-memory streams for testing read_send_stream, read_receive_stream = anyio.create_memory_object_stream[SessionMessage | Exception](100) write_send_stream, write_receive_stream = anyio.create_memory_object_stream[SessionMessage](100) diff --git a/tests/server/auth/middleware/test_auth_context.py b/tests/server/auth/middleware/test_auth_context.py index 1cca4df5ab..2364909221 100644 --- a/tests/server/auth/middleware/test_auth_context.py +++ b/tests/server/auth/middleware/test_auth_context.py @@ -1,6 +1,4 @@ -""" -Tests for the AuthContext middleware components. -""" +"""Tests for the AuthContext middleware components.""" import time diff --git a/tests/server/auth/middleware/test_bearer_auth.py b/tests/server/auth/middleware/test_bearer_auth.py index e13ab96390..bd14e294c2 100644 --- a/tests/server/auth/middleware/test_bearer_auth.py +++ b/tests/server/auth/middleware/test_bearer_auth.py @@ -1,6 +1,4 @@ -""" -Tests for the BearerAuth middleware components. -""" +"""Tests for the BearerAuth middleware components.""" import time from typing import Any, cast diff --git a/tests/server/auth/test_error_handling.py b/tests/server/auth/test_error_handling.py index 436bae05af..f8c7991476 100644 --- a/tests/server/auth/test_error_handling.py +++ b/tests/server/auth/test_error_handling.py @@ -1,6 +1,4 @@ -""" -Tests for OAuth error handling in the auth handlers. -""" +"""Tests for OAuth error handling in the auth handlers.""" import base64 import hashlib diff --git a/tests/server/auth/test_protected_resource.py b/tests/server/auth/test_protected_resource.py index bf6502a302..594541420e 100644 --- a/tests/server/auth/test_protected_resource.py +++ b/tests/server/auth/test_protected_resource.py @@ -1,6 +1,4 @@ -""" -Integration tests for MCP Oauth Protected Resource. -""" +"""Integration tests for MCP Oauth Protected Resource.""" from urllib.parse import urlparse diff --git a/tests/server/auth/test_provider.py b/tests/server/auth/test_provider.py index 7fe6213497..89a7cbedeb 100644 --- a/tests/server/auth/test_provider.py +++ b/tests/server/auth/test_provider.py @@ -1,6 +1,4 @@ -""" -Tests for mcp.server.auth.provider module. -""" +"""Tests for mcp.server.auth.provider module.""" from mcp.server.auth.provider import construct_redirect_uri diff --git a/tests/server/fastmcp/auth/__init__.py b/tests/server/fastmcp/auth/__init__.py index 64d318ec46..c932e236e3 100644 --- a/tests/server/fastmcp/auth/__init__.py +++ b/tests/server/fastmcp/auth/__init__.py @@ -1,3 +1 @@ -""" -Tests for the MCP server auth components. -""" +"""Tests for the MCP server auth components.""" diff --git a/tests/server/fastmcp/auth/test_auth_integration.py b/tests/server/fastmcp/auth/test_auth_integration.py index 6a1605bdb4..5000c7b386 100644 --- a/tests/server/fastmcp/auth/test_auth_integration.py +++ b/tests/server/fastmcp/auth/test_auth_integration.py @@ -1,6 +1,4 @@ -""" -Integration tests for MCP authorization components. -""" +"""Integration tests for MCP authorization components.""" import base64 import hashlib diff --git a/tests/server/fastmcp/test_elicitation.py b/tests/server/fastmcp/test_elicitation.py index a8bf2815c5..efed572e4b 100644 --- a/tests/server/fastmcp/test_elicitation.py +++ b/tests/server/fastmcp/test_elicitation.py @@ -1,6 +1,4 @@ -""" -Test the elicitation feature using stdio transport. -""" +"""Test the elicitation feature using stdio transport.""" from typing import Any diff --git a/tests/server/fastmcp/test_func_metadata.py b/tests/server/fastmcp/test_func_metadata.py index 0b65ca64dc..8d3ac6ec50 100644 --- a/tests/server/fastmcp/test_func_metadata.py +++ b/tests/server/fastmcp/test_func_metadata.py @@ -448,8 +448,7 @@ def test_complex_function_json_schema(): def test_str_vs_int(): - """ - Test that string values are kept as strings even when they contain numbers, + """Test that string values are kept as strings even when they contain numbers, while numbers are parsed correctly. """ @@ -463,8 +462,7 @@ def func_with_str_and_int(a: str, b: int): # pragma: no cover def test_str_annotation_preserves_json_string(): - """ - Regression test for PR #1113: Ensure that when a parameter is annotated as str, + """Regression test for PR #1113: Ensure that when a parameter is annotated as str, valid JSON strings are NOT parsed into Python objects. This test would fail before the fix (JSON string would be parsed to dict) @@ -514,8 +512,7 @@ def process_json_config(config: str, enabled: bool = True) -> str: # pragma: no @pytest.mark.anyio async def test_str_annotation_runtime_validation(): - """ - Regression test for PR #1113: Test runtime validation with string parameters + """Regression test for PR #1113: Test runtime validation with string parameters containing valid JSON to ensure they are passed as strings, not parsed objects. """ diff --git a/tests/server/fastmcp/test_integration.py b/tests/server/fastmcp/test_integration.py index 3531f7629d..726843b92f 100644 --- a/tests/server/fastmcp/test_integration.py +++ b/tests/server/fastmcp/test_integration.py @@ -1,5 +1,4 @@ -""" -Integration tests for FastMCP server functionality. +"""Integration tests for FastMCP server functionality. These tests validate the proper functioning of FastMCP features using focused, single-feature servers across different transports (SSE and StreamableHTTP). diff --git a/tests/server/test_completion_with_context.py b/tests/server/test_completion_with_context.py index bbaa4018f8..19c591340d 100644 --- a/tests/server/test_completion_with_context.py +++ b/tests/server/test_completion_with_context.py @@ -1,6 +1,4 @@ -""" -Tests for completion handler with context functionality. -""" +"""Tests for completion handler with context functionality.""" from typing import Any diff --git a/tests/server/test_session_race_condition.py b/tests/server/test_session_race_condition.py index 42c5578b02..aa256f5b0e 100644 --- a/tests/server/test_session_race_condition.py +++ b/tests/server/test_session_race_condition.py @@ -1,5 +1,4 @@ -""" -Test for race condition fix in initialization flow. +"""Test for race condition fix in initialization flow. This test verifies that requests can be processed immediately after responding to InitializeRequest, without waiting for InitializedNotification. @@ -20,8 +19,7 @@ @pytest.mark.anyio async def test_request_immediately_after_initialize_response(): - """ - Test that requests are accepted immediately after initialize response. + """Test that requests are accepted immediately after initialize response. This reproduces the race condition in stateful HTTP mode where: 1. Client sends InitializeRequest diff --git a/tests/shared/test_session.py b/tests/shared/test_session.py index 0656a01a6f..be5ab48626 100644 --- a/tests/shared/test_session.py +++ b/tests/shared/test_session.py @@ -129,8 +129,7 @@ async def make_request(session: ClientSession): @pytest.mark.anyio async def test_response_id_type_mismatch_string_to_int(): - """ - Test that responses with string IDs are correctly matched to requests sent with + """Test that responses with string IDs are correctly matched to requests sent with integer IDs. This handles the case where a server returns "id": "0" (string) but the client @@ -185,8 +184,7 @@ async def make_request(client_session: ClientSession): @pytest.mark.anyio async def test_error_response_id_type_mismatch_string_to_int(): - """ - Test that error responses with string IDs are correctly matched to requests + """Test that error responses with string IDs are correctly matched to requests sent with integer IDs. This handles the case where a server returns an error with "id": "0" (string) @@ -242,8 +240,7 @@ async def make_request(client_session: ClientSession): @pytest.mark.anyio async def test_response_id_non_numeric_string_no_match(): - """ - Test that responses with non-numeric string IDs don't incorrectly match + """Test that responses with non-numeric string IDs don't incorrectly match integer request IDs. If a server returns "id": "abc" (non-numeric string), it should not match @@ -295,9 +292,7 @@ async def make_request(client_session: ClientSession): @pytest.mark.anyio async def test_connection_closed(): - """ - Test that pending requests are cancelled when the connection is closed remotely. - """ + """Test that pending requests are cancelled when the connection is closed remotely.""" ev_closed = anyio.Event() ev_response = anyio.Event() diff --git a/tests/shared/test_streamable_http.py b/tests/shared/test_streamable_http.py index 1b552f6eaf..8838eb62b3 100644 --- a/tests/shared/test_streamable_http.py +++ b/tests/shared/test_streamable_http.py @@ -1,5 +1,4 @@ -""" -Tests for the StreamableHTTP server and client transport. +"""Tests for the StreamableHTTP server and client transport. Contains tests for both server and client sides of the StreamableHTTP transport. """ @@ -2184,8 +2183,7 @@ async def on_resumption_token(token: str) -> None: async def test_standalone_get_stream_reconnection( event_server: tuple[SimpleEventStore, str], ) -> None: - """ - Test that standalone GET stream automatically reconnects after server closes it. + """Test that standalone GET stream automatically reconnects after server closes it. Verifies: 1. Client receives notification 1 via GET stream diff --git a/tests/test_types.py b/tests/test_types.py index 5dff962b5f..7a9576c0be 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -51,8 +51,7 @@ async def test_jsonrpc_request(): @pytest.mark.anyio async def test_method_initialization(): - """ - Test that the method is automatically set on object creation. + """Test that the method is automatically set on object creation. Testing just for InitializeRequest to keep the test simple, but should be set for other types as well. """ initialize_request = InitializeRequest( From 39546421ff1cc10a37173a2ac823ec2b15a69e1b Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 16 Jan 2026 15:15:37 +0100 Subject: [PATCH 3/5] docs: update README snippets with new docstring format --- README.md | 79 ++++++++++++++++++++----------------------------------- 1 file changed, 29 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 523d9a7272..468e1d85da 100644 --- a/README.md +++ b/README.md @@ -136,8 +136,7 @@ Let's create a simple MCP server that exposes a calculator tool and some data: ```python -""" -FastMCP quickstart example. +"""FastMCP quickstart example. Run from the repository root: uv run examples/snippets/servers/fastmcp_quickstart.py @@ -727,9 +726,8 @@ Client usage: ```python -""" -cd to the `examples/snippets` directory and run: - uv run completion-client +"""cd to the `examples/snippets` directory and run: +uv run completion-client """ import asyncio @@ -1004,9 +1002,8 @@ MCP servers can use authentication by providing an implementation of the `TokenV ```python -""" -Run from the repository root: - uv run examples/snippets/servers/oauth_server.py +"""Run from the repository root: +uv run examples/snippets/servers/oauth_server.py """ from pydantic import AnyHttpUrl @@ -1245,9 +1242,8 @@ Note that `uv run mcp run` or `uv run mcp dev` only supports server using FastMC ```python -""" -Run from the repository root: - uv run examples/snippets/servers/streamable_config.py +"""Run from the repository root: +uv run examples/snippets/servers/streamable_config.py """ from mcp.server.fastmcp import FastMCP @@ -1283,9 +1279,8 @@ You can mount multiple FastMCP servers in a Starlette application: ```python -""" -Run from the repository root: - uvicorn examples.snippets.servers.streamable_starlette_mount:app --reload +"""Run from the repository root: +uvicorn examples.snippets.servers.streamable_starlette_mount:app --reload """ import contextlib @@ -1394,8 +1389,7 @@ You can mount the StreamableHTTP server to an existing ASGI server using the `st ```python -""" -Basic example showing how to mount StreamableHTTP server in Starlette. +"""Basic example showing how to mount StreamableHTTP server in Starlette. Run from the repository root: uvicorn examples.snippets.servers.streamable_http_basic_mounting:app --reload @@ -1442,8 +1436,7 @@ _Full example: [examples/snippets/servers/streamable_http_basic_mounting.py](htt ```python -""" -Example showing how to mount StreamableHTTP server using Host-based routing. +"""Example showing how to mount StreamableHTTP server using Host-based routing. Run from the repository root: uvicorn examples.snippets.servers.streamable_http_host_mounting:app --reload @@ -1490,8 +1483,7 @@ _Full example: [examples/snippets/servers/streamable_http_host_mounting.py](http ```python -""" -Example showing how to mount multiple StreamableHTTP servers with path configuration. +"""Example showing how to mount multiple StreamableHTTP servers with path configuration. Run from the repository root: uvicorn examples.snippets.servers.streamable_http_multiple_servers:app --reload @@ -1548,8 +1540,7 @@ _Full example: [examples/snippets/servers/streamable_http_multiple_servers.py](h ```python -""" -Example showing path configuration when mounting FastMCP. +"""Example showing path configuration when mounting FastMCP. Run from the repository root: uvicorn examples.snippets.servers.streamable_http_path_config:app --reload @@ -1644,9 +1635,8 @@ For more control, you can use the low-level server implementation directly. This ```python -""" -Run from the repository root: - uv run examples/snippets/servers/lowlevel/lifespan.py +"""Run from the repository root: +uv run examples/snippets/servers/lowlevel/lifespan.py """ from collections.abc import AsyncIterator @@ -1761,8 +1751,7 @@ The lifespan API provides: ```python -""" -Run from the repository root: +"""Run from the repository root: uv run examples/snippets/servers/lowlevel/basic.py """ @@ -1840,9 +1829,8 @@ The low-level server supports structured output for tools, allowing you to retur ```python -""" -Run from the repository root: - uv run examples/snippets/servers/lowlevel/structured_output.py +"""Run from the repository root: +uv run examples/snippets/servers/lowlevel/structured_output.py """ import asyncio @@ -1943,9 +1931,8 @@ For full control over the response including the `_meta` field (for passing data ```python -""" -Run from the repository root: - uv run examples/snippets/servers/lowlevel/direct_call_tool_result.py +"""Run from the repository root: +uv run examples/snippets/servers/lowlevel/direct_call_tool_result.py """ import asyncio @@ -2023,9 +2010,7 @@ For servers that need to handle large datasets, the low-level server provides pa ```python -""" -Example of implementing pagination with MCP server decorators. -""" +"""Example of implementing pagination with MCP server decorators.""" import mcp.types as types from mcp.server.lowlevel import Server @@ -2068,9 +2053,7 @@ _Full example: [examples/snippets/servers/pagination_example.py](https://github. ```python -""" -Example of consuming paginated MCP endpoints from a client. -""" +"""Example of consuming paginated MCP endpoints from a client.""" import asyncio @@ -2129,9 +2112,8 @@ The SDK provides a high-level client interface for connecting to MCP servers usi ```python -""" -cd to the `examples/snippets/clients` directory and run: - uv run client +"""cd to the `examples/snippets/clients` directory and run: +uv run client """ import asyncio @@ -2221,9 +2203,8 @@ Clients can also connect using [Streamable HTTP transport](https://modelcontextp ```python -""" -Run from the repository root: - uv run examples/snippets/clients/streamable_basic.py +"""Run from the repository root: +uv run examples/snippets/clients/streamable_basic.py """ import asyncio @@ -2261,9 +2242,8 @@ When building MCP clients, the SDK provides utilities to help display human-read ```python -""" -cd to the `examples/snippets` directory and run: - uv run display-utilities-client +"""cd to the `examples/snippets` directory and run: +uv run display-utilities-client """ import asyncio @@ -2346,8 +2326,7 @@ The SDK includes [authorization support](https://modelcontextprotocol.io/specifi ```python -""" -Before running, specify running MCP RS server URL. +"""Before running, specify running MCP RS server URL. To spin up RS server locally, see examples/servers/simple-auth/README.md From ec700a9c0fd8016a1d80412b56d9d91f38888ae8 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 16 Jan 2026 16:54:07 +0100 Subject: [PATCH 4/5] style: fix additional D212 violations from recent main changes --- .../mcp_conformance_auth_client/__init__.py | 18 ++++++------------ .../mcp_simple_auth_client/main.py | 3 +-- .../mcp_sse_polling_client/main.py | 3 +-- .../mcp_everything_server/server.py | 3 +-- .../mcp_simple_pagination/server.py | 3 +-- .../mcp_simple_streamablehttp/event_store.py | 10 +++------- .../mcp_sse_polling_demo/event_store.py | 6 ++---- .../mcp_sse_polling_demo/server.py | 3 +-- 8 files changed, 16 insertions(+), 33 deletions(-) diff --git a/examples/clients/conformance-auth-client/mcp_conformance_auth_client/__init__.py b/examples/clients/conformance-auth-client/mcp_conformance_auth_client/__init__.py index 9066d49af2..15d8274177 100644 --- a/examples/clients/conformance-auth-client/mcp_conformance_auth_client/__init__.py +++ b/examples/clients/conformance-auth-client/mcp_conformance_auth_client/__init__.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -MCP OAuth conformance test client. +"""MCP OAuth conformance test client. This client is designed to work with the MCP conformance test framework. It automatically handles OAuth flows without user interaction by programmatically @@ -89,8 +88,7 @@ async def set_client_info(self, client_info: OAuthClientInformationFull) -> None class ConformanceOAuthCallbackHandler: - """ - OAuth callback handler that automatically fetches the authorization URL + """OAuth callback handler that automatically fetches the authorization URL and extracts the auth code, without requiring user interaction. This mimics the behavior of the TypeScript ConformanceOAuthProvider. @@ -101,8 +99,7 @@ def __init__(self): self._state: str | None = None async def handle_redirect(self, authorization_url: str) -> None: - """ - Fetch the authorization URL and extract the auth code from the redirect. + """Fetch the authorization URL and extract the auth code from the redirect. The conformance test server returns a redirect with the auth code, so we can capture it programmatically. @@ -148,8 +145,7 @@ async def handle_callback(self) -> tuple[str, str | None]: async def run_authorization_code_client(server_url: str) -> None: - """ - Run the conformance test client with authorization code flow. + """Run the conformance test client with authorization code flow. This function: 1. Connects to the MCP server with OAuth authorization code flow @@ -180,8 +176,7 @@ async def run_authorization_code_client(server_url: str) -> None: async def run_client_credentials_jwt_client(server_url: str) -> None: - """ - Run the conformance test client with client credentials flow using private_key_jwt (SEP-1046). + """Run the conformance test client with client credentials flow using private_key_jwt (SEP-1046). This function: 1. Connects to the MCP server with OAuth client_credentials grant @@ -223,8 +218,7 @@ async def run_client_credentials_jwt_client(server_url: str) -> None: async def run_client_credentials_basic_client(server_url: str) -> None: - """ - Run the conformance test client with client credentials flow using client_secret_basic. + """Run the conformance test client with client credentials flow using client_secret_basic. This function: 1. Connects to the MCP server with OAuth client_credentials grant diff --git a/examples/clients/simple-auth-client/mcp_simple_auth_client/main.py b/examples/clients/simple-auth-client/mcp_simple_auth_client/main.py index e95d36aa61..684222dec1 100644 --- a/examples/clients/simple-auth-client/mcp_simple_auth_client/main.py +++ b/examples/clients/simple-auth-client/mcp_simple_auth_client/main.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Simple MCP client example with OAuth authentication support. +"""Simple MCP client example with OAuth authentication support. This client connects to an MCP server using streamable HTTP transport with OAuth. diff --git a/examples/clients/sse-polling-client/mcp_sse_polling_client/main.py b/examples/clients/sse-polling-client/mcp_sse_polling_client/main.py index c00e20f2af..533fce3789 100644 --- a/examples/clients/sse-polling-client/mcp_sse_polling_client/main.py +++ b/examples/clients/sse-polling-client/mcp_sse_polling_client/main.py @@ -1,5 +1,4 @@ -""" -SSE Polling Demo Client +"""SSE Polling Demo Client Demonstrates the client-side auto-reconnect for SSE polling pattern. diff --git a/examples/servers/everything-server/mcp_everything_server/server.py b/examples/servers/everything-server/mcp_everything_server/server.py index 7b18d9e949..db6b09f3fb 100644 --- a/examples/servers/everything-server/mcp_everything_server/server.py +++ b/examples/servers/everything-server/mcp_everything_server/server.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -MCP Everything Server - Conformance Test Server +"""MCP Everything Server - Conformance Test Server Server implementing all MCP features for conformance testing based on Conformance Server Specification. """ diff --git a/examples/servers/simple-pagination/mcp_simple_pagination/server.py b/examples/servers/simple-pagination/mcp_simple_pagination/server.py index f9a64919a2..74e9e3e82b 100644 --- a/examples/servers/simple-pagination/mcp_simple_pagination/server.py +++ b/examples/servers/simple-pagination/mcp_simple_pagination/server.py @@ -1,5 +1,4 @@ -""" -Simple MCP server demonstrating pagination for tools, resources, and prompts. +"""Simple MCP server demonstrating pagination for tools, resources, and prompts. This example shows how to use the paginated decorators to handle large lists of items that need to be split across multiple pages. diff --git a/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/event_store.py b/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/event_store.py index 0c3081ed64..3501fa47ce 100644 --- a/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/event_store.py +++ b/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/event_store.py @@ -1,5 +1,4 @@ -""" -In-memory event store for demonstrating resumability functionality. +"""In-memory event store for demonstrating resumability functionality. This is a simple implementation intended for examples and testing, not for production use where a persistent storage solution would be more appropriate. @@ -18,9 +17,7 @@ @dataclass class EventEntry: - """ - Represents an event entry in the event store. - """ + """Represents an event entry in the event store.""" event_id: EventId stream_id: StreamId @@ -28,8 +25,7 @@ class EventEntry: class InMemoryEventStore(EventStore): - """ - Simple in-memory implementation of the EventStore interface for resumability. + """Simple in-memory implementation of the EventStore interface for resumability. This is primarily intended for examples and testing, not for production use where a persistent storage solution would be more appropriate. diff --git a/examples/servers/sse-polling-demo/mcp_sse_polling_demo/event_store.py b/examples/servers/sse-polling-demo/mcp_sse_polling_demo/event_store.py index 75f98cdd49..c77bddef36 100644 --- a/examples/servers/sse-polling-demo/mcp_sse_polling_demo/event_store.py +++ b/examples/servers/sse-polling-demo/mcp_sse_polling_demo/event_store.py @@ -1,5 +1,4 @@ -""" -In-memory event store for demonstrating resumability functionality. +"""In-memory event store for demonstrating resumability functionality. This is a simple implementation intended for examples and testing, not for production use where a persistent storage solution would be more appropriate. @@ -26,8 +25,7 @@ class EventEntry: class InMemoryEventStore(EventStore): - """ - Simple in-memory implementation of the EventStore interface for resumability. + """Simple in-memory implementation of the EventStore interface for resumability. This is primarily intended for examples and testing, not for production use where a persistent storage solution would be more appropriate. diff --git a/examples/servers/sse-polling-demo/mcp_sse_polling_demo/server.py b/examples/servers/sse-polling-demo/mcp_sse_polling_demo/server.py index 6a5c714361..94a9320af0 100644 --- a/examples/servers/sse-polling-demo/mcp_sse_polling_demo/server.py +++ b/examples/servers/sse-polling-demo/mcp_sse_polling_demo/server.py @@ -1,5 +1,4 @@ -""" -SSE Polling Demo Server +"""SSE Polling Demo Server Demonstrates the SSE polling pattern with close_sse_stream() for long-running tasks. From 0971cbb1dbda505cacfb791a5af74b6595b5067b Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 16 Jan 2026 16:59:25 +0100 Subject: [PATCH 5/5] style: fix D212 violations in client module --- src/mcp/client/_memory.py | 9 +++------ src/mcp/client/client.py | 18 ++++++------------ 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/mcp/client/_memory.py b/src/mcp/client/_memory.py index 8b959e3c4d..b84def34f9 100644 --- a/src/mcp/client/_memory.py +++ b/src/mcp/client/_memory.py @@ -16,8 +16,7 @@ class InMemoryTransport: - """ - In-memory transport for testing MCP servers without network overhead. + """In-memory transport for testing MCP servers without network overhead. This transport starts the server in a background task and provides streams for client-side communication. The server is automatically @@ -43,8 +42,7 @@ def __init__( *, raise_exceptions: bool = False, ) -> None: - """ - Initialize the in-memory transport. + """Initialize the in-memory transport. Args: server: The MCP server to connect to (Server or FastMCP instance) @@ -63,8 +61,7 @@ async def connect( ], None, ]: - """ - Connect to the server and return streams for communication. + """Connect to the server and return streams for communication. Yields: A tuple of (read_stream, write_stream) for bidirectional communication diff --git a/src/mcp/client/client.py b/src/mcp/client/client.py index 699a24f046..ff2a231be8 100644 --- a/src/mcp/client/client.py +++ b/src/mcp/client/client.py @@ -67,8 +67,7 @@ def __init__( client_info: types.Implementation | None = None, elicitation_callback: ElicitationFnT | None = None, ) -> None: - """ - Initialize the client with a server. + """Initialize the client with a server. Args: server: The MCP server to connect to (Server or FastMCP instance) @@ -139,8 +138,7 @@ async def __aexit__( @property def session(self) -> ClientSession: - """ - Get the underlying ClientSession. + """Get the underlying ClientSession. This provides access to the full ClientSession API for advanced use cases. @@ -194,8 +192,7 @@ async def list_resource_templates( return await self.session.list_resource_templates(params=params) async def read_resource(self, uri: str | AnyUrl) -> types.ReadResourceResult: - """ - Read a resource from the server. + """Read a resource from the server. Args: uri: The URI of the resource to read @@ -222,8 +219,7 @@ async def call_tool( *, meta: dict[str, Any] | None = None, ) -> types.CallToolResult: - """ - Call a tool on the server. + """Call a tool on the server. Args: name: The name of the tool to call @@ -255,8 +251,7 @@ async def get_prompt( name: str, arguments: dict[str, str] | None = None, ) -> types.GetPromptResult: - """ - Get a prompt from the server. + """Get a prompt from the server. Args: name: The name of the prompt @@ -273,8 +268,7 @@ async def complete( argument: dict[str, str], context_arguments: dict[str, str] | None = None, ) -> types.CompleteResult: - """ - Get completions for a prompt or resource template argument. + """Get completions for a prompt or resource template argument. Args: ref: Reference to the prompt or resource template