chore: add D212 lint rule to enforce Google-style docstrings (#1892)

This commit is contained in:
Felix Weinberger 2026-01-16 16:10:52 +00:00 • committed by GitHub
commit d41d0c0128
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 452 additions and 925 deletions

View file

@ -4,9 +4,7 @@ from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, field_validator
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 @@ class InvalidRedirectUriError(Exception):
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 @@ class OAuthClientMetadata(BaseModel):
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
"""

View file

@ -1,6 +1,4 @@
"""
Request context for MCP handlers.
"""
"""Request context for MCP handlers."""
from dataclasses import dataclass, field
from typing import Any, Generic

View file

@ -6,9 +6,7 @@ from mcp.types import URL_ELICITATION_REQUIRED, ElicitRequestURLParams, ErrorDat
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 @@ class McpError(Exception):
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 @@ class StatelessModeNotSupported(RuntimeError):
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.

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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 @@ from mcp.types import TASK_STATUS_COMPLETED, TASK_STATUS_FAILED, Result, Task
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 @@ class TaskContext:
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 @@ class TaskContext:
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 @@ class TaskContext:
)
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 @@ class TaskContext:
)
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

View file

@ -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 @@ RELATED_TASK_METADATA_KEY = "io.modelcontextprotocol/related-task"
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

View file

@ -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)

View file

@ -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 @@ from mcp.types import JSONRPCNotification, JSONRPCRequest, RequestId
@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 @@ class TaskMessageQueue(ABC):
@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 @@ class TaskMessageQueue(ABC):
@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 @@ class TaskMessageQueue(ABC):
@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 @@ class TaskMessageQueue(ABC):
@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 @@ class TaskMessageQueue(ABC):
@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 @@ class TaskMessageQueue(ABC):
@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 @@ class TaskMessageQueue(ABC):
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 @@ class InMemoryTaskMessageQueue(TaskMessageQueue):
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.

View file

@ -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.

View file

@ -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 @@ T = TypeVar("T")
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.

View file

@ -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 @@ from mcp.types import Result, Task, TaskMetadata, TaskStatus
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 @@ class TaskStore(ABC):
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 @@ class TaskStore(ABC):
@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 @@ class TaskStore(ABC):
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 @@ class TaskStore(ABC):
@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 @@ class TaskStore(ABC):
@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 @@ class TaskStore(ABC):
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 @@ class TaskStore(ABC):
@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 @@ class TaskStore(ABC):
@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 @@ class TaskStore(ABC):
@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().

View file

@ -1,6 +1,4 @@
"""
In-memory transports
"""
"""In-memory transports"""
from __future__ import annotations
@ -17,8 +15,7 @@ MessageStream = tuple[MemoryObjectReceiveStream[SessionMessage | Exception], Mem
@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

View file

@ -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.

View file

@ -8,8 +8,7 @@ from mcp.types import Implementation, Prompt, Resource, ResourceTemplate, Tool
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'

View file

@ -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 @@ from mcp.types import ErrorData, RequestId
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 @@ class ResponseRouter(Protocol):
"""
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 @@ class ResponseRouter(Protocol):
... # 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

View file

@ -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 @@ class BaseSession(
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 @@ class BaseSession(
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 @@ class BaseSession(
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 @@ class BaseSession(
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 @@ class BaseSession(
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 @@ class BaseSession(
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 @@ class BaseSession(
"""
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 @@ class BaseSession(
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.
"""