chore: add D212 lint rule to enforce Google-style docstrings (#1892)
This commit is contained in:
parent
df039bf97c
commit
d41d0c0128
5 changed files with 452 additions and 925 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 @@ from mcp.types import (
|
|||
|
||||
@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 test_run_task_basic_flow() -> 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 test_run_task_auto_fails_on_exception() -> 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
|
||||
|
|
|
|||
|
|
@ -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 @@ def create_sampling_task_handlers(
|
|||
|
||||
@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 test_scenario1_normal_tool_normal_elicitation() -> 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 test_scenario2_normal_tool_task_augmented_elicitation() -> 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 test_scenario3_task_augmented_tool_normal_elicitation() -> 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 test_scenario4_task_augmented_tool_task_augmented_elicitation() -> Non
|
|||
|
||||
@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 test_scenario2_sampling_normal_tool_task_augmented_sampling() -> 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,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
"""
|
||||
Tests for TaskMessageQueue and InMemoryTaskMessageQueue.
|
||||
"""
|
||||
"""Tests for TaskMessageQueue and InMemoryTaskMessageQueue."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
|
|
|||
|
|
@ -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 @@ def test_server_with_cancel_task_handler_declares_cancel_capability() -> None:
|
|||
|
||||
|
||||
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 @@ def test_server_with_all_task_handlers_has_full_capability() -> None:
|
|||
|
||||
|
||||
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 @@ class TestClientCapabilities:
|
|||
|
||||
|
||||
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 @@ class TestToolLevelNegotiation:
|
|||
|
||||
|
||||
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 @@ class TestTaskStatusLifecycle:
|
|||
|
||||
|
||||
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 @@ class TestInputRequiredStatus:
|
|||
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 @@ class TestCreatingTask:
|
|||
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 @@ class TestCreatingTask:
|
|||
|
||||
|
||||
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 @@ class TestGettingTaskStatus:
|
|||
|
||||
|
||||
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 @@ class TestRetrievingResults:
|
|||
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 @@ class TestRetrievingResults:
|
|||
|
||||
|
||||
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 @@ class TestListingTasks:
|
|||
|
||||
|
||||
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 @@ class TestCancellingTasks:
|
|||
|
||||
|
||||
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 @@ class TestStatusNotifications:
|
|||
|
||||
|
||||
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 @@ class TestTaskManagement:
|
|||
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 @@ class TestTaskManagement:
|
|||
|
||||
|
||||
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 @@ class TestResultHandling:
|
|||
|
||||
|
||||
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 @@ class TestProgressTracking:
|
|||
|
||||
|
||||
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 @@ class TestProtocolErrors:
|
|||
|
||||
|
||||
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 @@ class TestTaskExecutionErrors:
|
|||
|
||||
|
||||
class TestTaskObject:
|
||||
"""
|
||||
Task Object fields:
|
||||
"""Task Object fields:
|
||||
- taskId: String identifier
|
||||
- status: Current execution state
|
||||
- statusMessage: Optional human-readable description
|
||||
|
|
@ -684,8 +659,7 @@ class TestTaskObject:
|
|||
|
||||
|
||||
class TestRelatedTaskMetadata:
|
||||
"""
|
||||
Related Task Metadata structure:
|
||||
"""Related Task Metadata structure:
|
||||
{"_meta": {"io.modelcontextprotocol/related-task": {"taskId": "..."}}}
|
||||
"""
|
||||
|
||||
|
|
@ -699,42 +673,34 @@ class TestRelatedTaskMetadata:
|
|||
|
||||
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue