chore: add D212 lint rule to enforce Google-style docstrings (#1892)
This commit is contained in:
parent
df039bf97c
commit
d41d0c0128
64 changed files with 452 additions and 925 deletions
|
|
@ -16,8 +16,7 @@ from mcp.shared.message import SessionMessage
|
|||
|
||||
|
||||
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 @@ class InMemoryTransport:
|
|||
*,
|
||||
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 @@ class InMemoryTransport:
|
|||
],
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
"""
|
||||
OAuth2 Authentication implementation for HTTPX.
|
||||
"""OAuth2 Authentication implementation for HTTPX.
|
||||
|
||||
Implements authorization code flow with PKCE and automatic token refresh.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 @@ class OAuthContext:
|
|||
|
||||
|
||||
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 @@ class OAuthClientProvider(httpx.Auth):
|
|||
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -67,8 +67,7 @@ class Client:
|
|||
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 @@ class Client:
|
|||
|
||||
@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 @@ class Client:
|
|||
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 @@ class Client:
|
|||
*,
|
||||
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 @@ class Client:
|
|||
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 @@ class Client:
|
|||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
"""
|
||||
Experimental client features.
|
||||
"""Experimental client features.
|
||||
|
||||
WARNING: These APIs are experimental and may change without notice.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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 @@ ResultT = TypeVar("ResultT", bound=types.Result)
|
|||
|
||||
|
||||
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 @@ class ExperimentalClientFeatures:
|
|||
)
|
||||
|
||||
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 @@ class ExperimentalClientFeatures:
|
|||
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 @@ class ExperimentalClientFeatures:
|
|||
self,
|
||||
cursor: str | None = None,
|
||||
) -> types.ListTasksResult:
|
||||
"""
|
||||
List all tasks.
|
||||
"""List all tasks.
|
||||
|
||||
Args:
|
||||
cursor: Optional pagination cursor
|
||||
|
|
@ -176,8 +171,7 @@ class ExperimentalClientFeatures:
|
|||
)
|
||||
|
||||
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 @@ class ExperimentalClientFeatures:
|
|||
)
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -49,8 +49,7 @@ PROCESS_TERMINATION_TIMEOUT = 2.0
|
|||
|
||||
|
||||
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 stdio_client(server: StdioServerParameters, errlog: TextIO = sys.stder
|
|||
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 websocket_client(
|
|||
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:
|
||||
|
|
|
|||
|
|
@ -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 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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 @@ JobHandle = int
|
|||
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -1,3 +1 @@
|
|||
"""
|
||||
MCP OAuth server authorization components.
|
||||
"""
|
||||
"""MCP OAuth server authorization components."""
|
||||
|
|
|
|||
|
|
@ -1,3 +1 @@
|
|||
"""
|
||||
Request handlers for MCP authorization endpoints.
|
||||
"""
|
||||
"""Request handlers for MCP authorization endpoints."""
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ from mcp.server.auth.provider import AccessToken, OAuthAuthorizationServerProvid
|
|||
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue