chore: add D212 lint rule to enforce Google-style docstrings (#1892)
This commit is contained in:
parent
df039bf97c
commit
d41d0c0128
31 changed files with 452 additions and 925 deletions
|
|
@ -1,6 +1,4 @@
|
|||
"""
|
||||
Tests for refactored OAuth client authentication implementation.
|
||||
"""
|
||||
"""Tests for refactored OAuth client authentication implementation."""
|
||||
|
||||
import base64
|
||||
import time
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@ from mcp.types import Tool
|
|||
|
||||
@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.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@ from mcp.types import ClientNotification, ClientRequest, ClientResult, EmptyResu
|
|||
|
||||
@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.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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 @@ class MockTokenStorage:
|
|||
|
||||
@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:
|
||||
|
|
|
|||
|
|
@ -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 @@ async def test_stdio_client_sigint_only_process(): # pragma: no cover
|
|||
|
||||
|
||||
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 @@ class TestChildProcessCleanup:
|
|||
@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 @@ class TestChildProcessCleanup:
|
|||
@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 @@ class TestChildProcessCleanup:
|
|||
|
||||
@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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 @@ from tests.shared.test_win32_utils import escape_path_for_python
|
|||
|
||||
@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 @@ async def test_lifespan_cleanup_executed():
|
|||
@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
|
||||
|
|
|
|||
|
|
@ -90,8 +90,7 @@ class ServerThread(threading.Thread):
|
|||
|
||||
|
||||
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.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@ from mcp.client.stdio import stdio_client
|
|||
@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.
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@ from mcp.types import (
|
|||
|
||||
@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)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
"""
|
||||
Tests for the AuthContext middleware components.
|
||||
"""
|
||||
"""Tests for the AuthContext middleware components."""
|
||||
|
||||
import time
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
"""
|
||||
Tests for the BearerAuth middleware components.
|
||||
"""
|
||||
"""Tests for the BearerAuth middleware components."""
|
||||
|
||||
import time
|
||||
from typing import Any, cast
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
"""
|
||||
Integration tests for MCP Oauth Protected Resource.
|
||||
"""
|
||||
"""Integration tests for MCP Oauth Protected Resource."""
|
||||
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue