refactor: McpError renamed to MCPError and flatten parameters (#1956)
Co-authored-by: Max Isbey <224885523+maxisbey@users.noreply.github.com>
This commit is contained in:
parent
65c614e48e
commit
acba5478a9
19 changed files with 253 additions and 326 deletions
|
|
@ -13,7 +13,7 @@ from mcp.client.session_group import (
|
|||
StreamableHttpParameters,
|
||||
)
|
||||
from mcp.client.stdio import StdioServerParameters
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -225,7 +225,7 @@ async def test_client_session_group_disconnect_from_server():
|
|||
async def test_client_session_group_connect_to_server_duplicate_tool_raises_error(
|
||||
mock_exit_stack: contextlib.AsyncExitStack,
|
||||
):
|
||||
"""Test McpError raised when connecting a server with a dup name."""
|
||||
"""Test MCPError raised when connecting a server with a dup name."""
|
||||
# --- Setup Pre-existing State ---
|
||||
group = ClientSessionGroup(exit_stack=mock_exit_stack)
|
||||
existing_tool_name = "shared_tool"
|
||||
|
|
@ -251,7 +251,7 @@ async def test_client_session_group_connect_to_server_duplicate_tool_raises_erro
|
|||
mock_session_new.list_prompts.return_value = mock.AsyncMock(prompts=[])
|
||||
|
||||
# --- Test Execution and Assertion ---
|
||||
with pytest.raises(McpError) as excinfo:
|
||||
with pytest.raises(MCPError) as excinfo:
|
||||
with mock.patch.object(
|
||||
group,
|
||||
"_establish_session",
|
||||
|
|
@ -274,7 +274,7 @@ async def test_client_session_group_disconnect_non_existent_server():
|
|||
"""Test disconnecting a server that isn't connected."""
|
||||
session = mock.Mock(spec=mcp.ClientSession)
|
||||
group = ClientSessionGroup()
|
||||
with pytest.raises(McpError):
|
||||
with pytest.raises(MCPError):
|
||||
await group.disconnect_from_server(session)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from mcp.client.stdio import (
|
|||
_terminate_process_tree,
|
||||
stdio_client,
|
||||
)
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.shared.message import SessionMessage
|
||||
from mcp.types import CONNECTION_CLOSED, JSONRPCMessage, JSONRPCRequest, JSONRPCResponse
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ async def test_stdio_client_bad_path():
|
|||
async with stdio_client(server_params) as (read_stream, write_stream):
|
||||
async with ClientSession(read_stream, write_stream) as session:
|
||||
# The session should raise an error when the connection closes
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await session.initialize()
|
||||
|
||||
# Check that we got a connection closed error
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from mcp.server import Server
|
|||
from mcp.server.lowlevel import NotificationOptions
|
||||
from mcp.server.models import InitializationOptions
|
||||
from mcp.server.session import ServerSession
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.shared.message import ServerMessageMetadata, SessionMessage
|
||||
from mcp.shared.response_router import ResponseRouter
|
||||
from mcp.shared.session import RequestResponder
|
||||
|
|
@ -506,7 +506,7 @@ async def test_default_task_handlers_via_enable_tasks() -> None:
|
|||
assert get_result.status == "working"
|
||||
|
||||
# Test get_task (default handler - not found path)
|
||||
with pytest.raises(McpError, match="not found"):
|
||||
with pytest.raises(MCPError, match="not found"):
|
||||
await client_session.send_request(
|
||||
GetTaskRequest(params=GetTaskRequestParams(task_id="nonexistent-task")),
|
||||
GetTaskResult,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import pytest
|
|||
|
||||
from mcp.server.experimental.task_context import ServerTaskContext
|
||||
from mcp.server.experimental.task_result_handler import TaskResultHandler
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.shared.experimental.tasks.in_memory_task_store import InMemoryTaskStore
|
||||
from mcp.shared.experimental.tasks.message_queue import InMemoryTaskMessageQueue
|
||||
from mcp.types import (
|
||||
|
|
@ -164,7 +164,7 @@ async def test_server_task_context_fail_with_notify() -> None:
|
|||
|
||||
@pytest.mark.anyio
|
||||
async def test_elicit_raises_when_client_lacks_capability() -> None:
|
||||
"""Test that elicit() raises McpError when client doesn't support elicitation."""
|
||||
"""Test that elicit() raises MCPError when client doesn't support elicitation."""
|
||||
store = InMemoryTaskStore()
|
||||
mock_session = Mock()
|
||||
mock_session.check_client_capability = Mock(return_value=False)
|
||||
|
|
@ -180,7 +180,7 @@ async def test_elicit_raises_when_client_lacks_capability() -> None:
|
|||
handler=handler,
|
||||
)
|
||||
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await ctx.elicit(message="Test?", requested_schema={"type": "object"})
|
||||
|
||||
assert "elicitation capability" in exc_info.value.error.message
|
||||
|
|
@ -190,7 +190,7 @@ async def test_elicit_raises_when_client_lacks_capability() -> None:
|
|||
|
||||
@pytest.mark.anyio
|
||||
async def test_create_message_raises_when_client_lacks_capability() -> None:
|
||||
"""Test that create_message() raises McpError when client doesn't support sampling."""
|
||||
"""Test that create_message() raises MCPError when client doesn't support sampling."""
|
||||
store = InMemoryTaskStore()
|
||||
mock_session = Mock()
|
||||
mock_session.check_client_capability = Mock(return_value=False)
|
||||
|
|
@ -206,7 +206,7 @@ async def test_create_message_raises_when_client_lacks_capability() -> None:
|
|||
handler=handler,
|
||||
)
|
||||
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await ctx.create_message(messages=[], max_tokens=100)
|
||||
|
||||
assert "sampling capability" in exc_info.value.error.message
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from datetime import datetime, timedelta, timezone
|
|||
|
||||
import pytest
|
||||
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.shared.experimental.tasks.helpers import cancel_task
|
||||
from mcp.shared.experimental.tasks.in_memory_task_store import InMemoryTaskStore
|
||||
from mcp.types import INVALID_PARAMS, CallToolResult, TaskMetadata, TextContent
|
||||
|
|
@ -347,8 +347,8 @@ async def test_cancel_task_succeeds_for_working_task(store: InMemoryTaskStore) -
|
|||
|
||||
@pytest.mark.anyio
|
||||
async def test_cancel_task_rejects_nonexistent_task(store: InMemoryTaskStore) -> None:
|
||||
"""Test cancel_task raises McpError with INVALID_PARAMS for nonexistent task."""
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
"""Test cancel_task raises MCPError with INVALID_PARAMS for nonexistent task."""
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await cancel_task(store, "nonexistent-task-id")
|
||||
|
||||
assert exc_info.value.error.code == INVALID_PARAMS
|
||||
|
|
@ -357,11 +357,11 @@ async def test_cancel_task_rejects_nonexistent_task(store: InMemoryTaskStore) ->
|
|||
|
||||
@pytest.mark.anyio
|
||||
async def test_cancel_task_rejects_completed_task(store: InMemoryTaskStore) -> None:
|
||||
"""Test cancel_task raises McpError with INVALID_PARAMS for completed task."""
|
||||
"""Test cancel_task raises MCPError with INVALID_PARAMS for completed task."""
|
||||
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
|
||||
await store.update_task(task.task_id, status="completed")
|
||||
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await cancel_task(store, task.task_id)
|
||||
|
||||
assert exc_info.value.error.code == INVALID_PARAMS
|
||||
|
|
@ -370,11 +370,11 @@ async def test_cancel_task_rejects_completed_task(store: InMemoryTaskStore) -> N
|
|||
|
||||
@pytest.mark.anyio
|
||||
async def test_cancel_task_rejects_failed_task(store: InMemoryTaskStore) -> None:
|
||||
"""Test cancel_task raises McpError with INVALID_PARAMS for failed task."""
|
||||
"""Test cancel_task raises MCPError with INVALID_PARAMS for failed task."""
|
||||
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
|
||||
await store.update_task(task.task_id, status="failed")
|
||||
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await cancel_task(store, task.task_id)
|
||||
|
||||
assert exc_info.value.error.code == INVALID_PARAMS
|
||||
|
|
@ -383,11 +383,11 @@ async def test_cancel_task_rejects_failed_task(store: InMemoryTaskStore) -> None
|
|||
|
||||
@pytest.mark.anyio
|
||||
async def test_cancel_task_rejects_already_cancelled_task(store: InMemoryTaskStore) -> None:
|
||||
"""Test cancel_task raises McpError with INVALID_PARAMS for already cancelled task."""
|
||||
"""Test cancel_task raises MCPError with INVALID_PARAMS for already cancelled task."""
|
||||
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
|
||||
await store.update_task(task.task_id, status="cancelled")
|
||||
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await cancel_task(store, task.task_id)
|
||||
|
||||
assert exc_info.value.error.code == INVALID_PARAMS
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import anyio
|
|||
import pytest
|
||||
|
||||
from mcp.server.experimental.task_result_handler import TaskResultHandler
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.shared.experimental.tasks.in_memory_task_store import InMemoryTaskStore
|
||||
from mcp.shared.experimental.tasks.message_queue import InMemoryTaskMessageQueue, QueuedMessage
|
||||
from mcp.shared.experimental.tasks.resolver import Resolver
|
||||
|
|
@ -71,11 +71,11 @@ async def test_handle_returns_result_for_completed_task(
|
|||
async def test_handle_raises_for_nonexistent_task(
|
||||
store: InMemoryTaskStore, queue: InMemoryTaskMessageQueue, handler: TaskResultHandler
|
||||
) -> None:
|
||||
"""Test that handle() raises McpError for nonexistent task."""
|
||||
"""Test that handle() raises MCPError for nonexistent task."""
|
||||
mock_session = Mock()
|
||||
request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id="nonexistent"))
|
||||
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await handler.handle(request, mock_session, "req-1")
|
||||
|
||||
assert "not found" in exc_info.value.error.message
|
||||
|
|
@ -214,7 +214,7 @@ async def test_route_error_resolves_pending_request_with_exception(
|
|||
assert result is True
|
||||
assert resolver.done()
|
||||
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await resolver.wait()
|
||||
assert exc_info.value.error.message == "Something went wrong"
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp import MCPError
|
||||
from mcp.shared.experimental.tasks.capabilities import (
|
||||
check_tasks_capability,
|
||||
has_task_augmented_elicitation,
|
||||
|
|
@ -231,15 +231,15 @@ class TestRequireTaskAugmentedElicitation:
|
|||
"""Tests for require_task_augmented_elicitation function."""
|
||||
|
||||
def test_raises_when_none(self) -> None:
|
||||
"""Raises McpError when client_caps is None."""
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
"""Raises MCPError when client_caps is None."""
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
require_task_augmented_elicitation(None)
|
||||
assert "task-augmented elicitation" in str(exc_info.value)
|
||||
|
||||
def test_raises_when_missing(self) -> None:
|
||||
"""Raises McpError when capability is missing."""
|
||||
"""Raises MCPError when capability is missing."""
|
||||
caps = ClientCapabilities()
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
require_task_augmented_elicitation(caps)
|
||||
assert "task-augmented elicitation" in str(exc_info.value)
|
||||
|
||||
|
|
@ -259,15 +259,15 @@ class TestRequireTaskAugmentedSampling:
|
|||
"""Tests for require_task_augmented_sampling function."""
|
||||
|
||||
def test_raises_when_none(self) -> None:
|
||||
"""Raises McpError when client_caps is None."""
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
"""Raises MCPError when client_caps is None."""
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
require_task_augmented_sampling(None)
|
||||
assert "task-augmented sampling" in str(exc_info.value)
|
||||
|
||||
def test_raises_when_missing(self) -> None:
|
||||
"""Raises McpError when capability is missing."""
|
||||
"""Raises MCPError when capability is missing."""
|
||||
caps = ClientCapabilities()
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
require_task_augmented_sampling(caps)
|
||||
assert "task-augmented sampling" in str(exc_info.value)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import pytest
|
||||
|
||||
from mcp.server.experimental.request_context import Experimental
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.types import (
|
||||
METHOD_NOT_FOUND,
|
||||
TASK_FORBIDDEN,
|
||||
|
|
@ -58,7 +58,7 @@ def test_validate_task_mode_required_without_task_returns_error() -> None:
|
|||
|
||||
def test_validate_task_mode_required_without_task_raises_by_default() -> None:
|
||||
exp = Experimental(task_metadata=None)
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
exp.validate_task_mode(TASK_REQUIRED)
|
||||
assert exc_info.value.error.code == METHOD_NOT_FOUND
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ def test_validate_task_mode_forbidden_with_task_returns_error() -> None:
|
|||
|
||||
def test_validate_task_mode_forbidden_with_task_raises_by_default() -> None:
|
||||
exp = Experimental(task_metadata=TaskMetadata(ttl=60000))
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
exp.validate_task_mode(TASK_FORBIDDEN)
|
||||
assert exc_info.value.error.code == METHOD_NOT_FOUND
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStre
|
|||
from mcp import types
|
||||
from mcp.client.session import ClientSession
|
||||
from mcp.server.lowlevel import Server
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.shared.message import SessionMessage
|
||||
from mcp.types import ContentBlock, TextContent
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ async def test_notification_validation_error(tmp_path: Path):
|
|||
|
||||
# Second call should timeout (slow operation with minimal timeout)
|
||||
# Use very small timeout to trigger quickly without waiting
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await session.call_tool("slow", read_timeout_seconds=0.000001) # artificial timeout that always fails
|
||||
assert "Timed out while waiting" in str(exc_info.value)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from mcp.server.mcpserver.resources import FileResource, FunctionResource
|
|||
from mcp.server.mcpserver.utilities.types import Audio, Image
|
||||
from mcp.server.session import ServerSession
|
||||
from mcp.server.transport_security import TransportSecuritySettings
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.types import (
|
||||
AudioContent,
|
||||
BlobResourceContents,
|
||||
|
|
@ -1469,7 +1469,7 @@ class TestServerPrompts:
|
|||
"""Test error when getting unknown prompt."""
|
||||
mcp = MCPServer()
|
||||
async with Client(mcp) as client:
|
||||
with pytest.raises(McpError, match="Unknown prompt"):
|
||||
with pytest.raises(MCPError, match="Unknown prompt"):
|
||||
await client.get_prompt("unknown")
|
||||
|
||||
@pytest.mark.anyio
|
||||
|
|
@ -1482,7 +1482,7 @@ class TestServerPrompts:
|
|||
return f"Hello, {name}!"
|
||||
|
||||
async with Client(mcp) as client:
|
||||
with pytest.raises(McpError, match="Missing required arguments"):
|
||||
with pytest.raises(MCPError, match="Missing required arguments"):
|
||||
await client.get_prompt("prompt_fn")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
"""Test that UrlElicitationRequiredError is properly propagated as MCP error."""
|
||||
|
||||
import pytest
|
||||
from inline_snapshot import snapshot
|
||||
|
||||
from mcp import Client, types
|
||||
from mcp import Client, ErrorData, types
|
||||
from mcp.server.mcpserver import Context, MCPServer
|
||||
from mcp.server.session import ServerSession
|
||||
from mcp.shared.exceptions import McpError, UrlElicitationRequiredError
|
||||
from mcp.shared.exceptions import MCPError, UrlElicitationRequiredError
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_url_elicitation_error_thrown_from_tool():
|
||||
"""Test that UrlElicitationRequiredError raised from a tool is received as McpError by client."""
|
||||
"""Test that UrlElicitationRequiredError raised from a tool is received as MCPError by client."""
|
||||
mcp = MCPServer(name="UrlElicitationErrorServer")
|
||||
|
||||
@mcp.tool(description="A tool that raises UrlElicitationRequiredError")
|
||||
|
|
@ -28,28 +29,30 @@ async def test_url_elicitation_error_thrown_from_tool():
|
|||
)
|
||||
|
||||
async with Client(mcp) as client:
|
||||
# Call the tool - it should raise McpError with URL_ELICITATION_REQUIRED code
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await client.call_tool("connect_service", {"service_name": "github"})
|
||||
|
||||
# Verify the error details
|
||||
error = exc_info.value.error
|
||||
assert error.code == types.URL_ELICITATION_REQUIRED
|
||||
assert error.message == "URL elicitation required"
|
||||
|
||||
# Verify the error data contains elicitations
|
||||
assert error.data is not None
|
||||
assert "elicitations" in error.data
|
||||
elicitations = error.data["elicitations"]
|
||||
assert len(elicitations) == 1
|
||||
assert elicitations[0]["mode"] == "url"
|
||||
assert elicitations[0]["url"] == "https://github.example.com/oauth/authorize"
|
||||
assert elicitations[0]["elicitationId"] == "github-auth-001"
|
||||
assert exc_info.value.error == snapshot(
|
||||
ErrorData(
|
||||
code=types.URL_ELICITATION_REQUIRED,
|
||||
message="URL elicitation required",
|
||||
data={
|
||||
"elicitations": [
|
||||
{
|
||||
"mode": "url",
|
||||
"message": "Authorization required to connect to github",
|
||||
"url": "https://github.example.com/oauth/authorize",
|
||||
"elicitationId": "github-auth-001",
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_url_elicitation_error_from_error():
|
||||
"""Test that client can reconstruct UrlElicitationRequiredError from McpError."""
|
||||
"""Test that client can reconstruct UrlElicitationRequiredError from MCPError."""
|
||||
mcp = MCPServer(name="UrlElicitationErrorServer")
|
||||
|
||||
@mcp.tool(description="A tool that raises UrlElicitationRequiredError with multiple elicitations")
|
||||
|
|
@ -73,12 +76,12 @@ async def test_url_elicitation_error_from_error():
|
|||
|
||||
async with Client(mcp) as client:
|
||||
# Call the tool and catch the error
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await client.call_tool("multi_auth", {})
|
||||
|
||||
# Reconstruct the typed error
|
||||
mcp_error = exc_info.value
|
||||
assert mcp_error.error.code == types.URL_ELICITATION_REQUIRED
|
||||
assert mcp_error.code == types.URL_ELICITATION_REQUIRED
|
||||
|
||||
url_error = UrlElicitationRequiredError.from_error(mcp_error.error)
|
||||
|
||||
|
|
@ -98,7 +101,7 @@ async def test_normal_exceptions_still_return_error_result():
|
|||
raise ValueError("Something went wrong")
|
||||
|
||||
async with Client(mcp) as client:
|
||||
# Normal exceptions should be returned as error results, not McpError
|
||||
# Normal exceptions should be returned as error results, not MCPError
|
||||
result = await client.call_tool("failing_tool", {})
|
||||
assert result.is_error is True
|
||||
assert len(result.content) == 1
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import pytest
|
|||
import mcp.types as types
|
||||
from mcp import Client
|
||||
from mcp.server.lowlevel.server import Server
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.types import (
|
||||
CallToolRequest,
|
||||
CallToolRequestParams,
|
||||
|
|
@ -61,7 +61,7 @@ async def test_server_remains_functional_after_cancel():
|
|||
CallToolResult,
|
||||
)
|
||||
pytest.fail("First request should have been cancelled") # pragma: no cover
|
||||
except McpError:
|
||||
except MCPError:
|
||||
pass # Expected
|
||||
|
||||
# Start first request
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from mcp.server import Server
|
|||
from mcp.server.lowlevel import NotificationOptions
|
||||
from mcp.server.models import InitializationOptions
|
||||
from mcp.server.session import ServerSession
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.shared.message import SessionMessage
|
||||
from mcp.shared.session import RequestResponder
|
||||
from mcp.types import (
|
||||
|
|
@ -397,7 +397,7 @@ async def test_create_message_tool_result_validation():
|
|||
|
||||
@pytest.mark.anyio
|
||||
async def test_create_message_without_tools_capability():
|
||||
"""Test that create_message raises McpError when tools are provided without capability."""
|
||||
"""Test that create_message raises MCPError when tools are provided without capability."""
|
||||
server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[SessionMessage](1)
|
||||
client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[SessionMessage | Exception](1)
|
||||
|
||||
|
|
@ -426,8 +426,8 @@ async def test_create_message_without_tools_capability():
|
|||
tool = types.Tool(name="test_tool", input_schema={"type": "object"})
|
||||
text = types.TextContent(type="text", text="hello")
|
||||
|
||||
# Should raise McpError when tools are provided but client lacks capability
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
# Should raise MCPError when tools are provided but client lacks capability
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await session.create_message(
|
||||
messages=[types.SamplingMessage(role="user", content=text)],
|
||||
max_tokens=100,
|
||||
|
|
@ -435,8 +435,8 @@ async def test_create_message_without_tools_capability():
|
|||
)
|
||||
assert "does not support sampling tools capability" in exc_info.value.error.message
|
||||
|
||||
# Should also raise McpError when tool_choice is provided
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
# Should also raise MCPError when tool_choice is provided
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await session.create_message(
|
||||
messages=[types.SamplingMessage(role="user", content=text)],
|
||||
max_tokens=100,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from mcp.server.validation import (
|
|||
validate_sampling_tools,
|
||||
validate_tool_use_result_messages,
|
||||
)
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.types import (
|
||||
ClientCapabilities,
|
||||
SamplingCapability,
|
||||
|
|
@ -55,16 +55,16 @@ def test_validate_sampling_tools_no_error_when_tools_none() -> None:
|
|||
|
||||
|
||||
def test_validate_sampling_tools_raises_when_tools_provided_but_no_capability() -> None:
|
||||
"""Raises McpError when tools provided but client doesn't support."""
|
||||
"""Raises MCPError when tools provided but client doesn't support."""
|
||||
tool = Tool(name="test", input_schema={"type": "object"})
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
validate_sampling_tools(None, [tool], None)
|
||||
assert "sampling tools capability" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_validate_sampling_tools_raises_when_tool_choice_provided_but_no_capability() -> None:
|
||||
"""Raises McpError when tool_choice provided but client doesn't support."""
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
"""Raises MCPError when tool_choice provided but client doesn't support."""
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
validate_sampling_tools(None, None, ToolChoice(mode="auto"))
|
||||
assert "sampling tools capability" in str(exc_info.value)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from mcp.shared.exceptions import McpError, UrlElicitationRequiredError
|
||||
from mcp.shared.exceptions import MCPError, UrlElicitationRequiredError
|
||||
from mcp.types import URL_ELICITATION_REQUIRED, ElicitRequestURLParams, ErrorData
|
||||
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ def test_url_elicitation_required_error_data_contains_elicitations() -> None:
|
|||
|
||||
|
||||
def test_url_elicitation_required_error_inherits_from_mcp_error() -> None:
|
||||
"""Test that UrlElicitationRequiredError inherits from McpError."""
|
||||
"""Test that UrlElicitationRequiredError inherits from MCPError."""
|
||||
elicitation = ElicitRequestURLParams(
|
||||
mode="url",
|
||||
message="Auth required",
|
||||
|
|
@ -146,7 +146,7 @@ def test_url_elicitation_required_error_inherits_from_mcp_error() -> None:
|
|||
)
|
||||
error = UrlElicitationRequiredError([elicitation])
|
||||
|
||||
assert isinstance(error, McpError)
|
||||
assert isinstance(error, MCPError)
|
||||
assert isinstance(error, Exception)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import mcp.types as types
|
|||
from mcp import Client
|
||||
from mcp.client.session import ClientSession
|
||||
from mcp.server.lowlevel.server import Server
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.shared.memory import create_client_server_memory_streams
|
||||
from mcp.shared.message import SessionMessage
|
||||
from mcp.types import (
|
||||
|
|
@ -77,7 +77,7 @@ async def test_request_cancellation():
|
|||
types.CallToolResult,
|
||||
)
|
||||
pytest.fail("Request should have been cancelled") # pragma: no cover
|
||||
except McpError as e:
|
||||
except MCPError as e:
|
||||
# Expected - request was cancelled
|
||||
assert "Request cancelled" in str(e)
|
||||
ev_cancelled.set()
|
||||
|
|
@ -164,7 +164,7 @@ async def test_error_response_id_type_mismatch_string_to_int():
|
|||
but the client sent "id": 0 (integer).
|
||||
"""
|
||||
ev_error_received = anyio.Event()
|
||||
error_holder: list[McpError] = []
|
||||
error_holder: list[MCPError | Exception] = []
|
||||
|
||||
async with create_client_server_memory_streams() as (client_streams, server_streams):
|
||||
client_read, client_write = client_streams
|
||||
|
|
@ -191,8 +191,8 @@ async def test_error_response_id_type_mismatch_string_to_int():
|
|||
nonlocal error_holder
|
||||
try:
|
||||
await client_session.send_ping()
|
||||
pytest.fail("Expected McpError to be raised") # pragma: no cover
|
||||
except McpError as e:
|
||||
pytest.fail("Expected MCPError to be raised") # pragma: no cover
|
||||
except MCPError as e:
|
||||
error_holder.append(e)
|
||||
ev_error_received.set()
|
||||
|
||||
|
|
@ -246,7 +246,7 @@ async def test_response_id_non_numeric_string_no_match():
|
|||
request_read_timeout_seconds=0.5,
|
||||
)
|
||||
pytest.fail("Expected timeout") # pragma: no cover
|
||||
except McpError as e:
|
||||
except MCPError as e:
|
||||
assert "Timed out" in str(e)
|
||||
ev_timeout.set()
|
||||
|
||||
|
|
@ -279,7 +279,7 @@ async def test_connection_closed():
|
|||
# any request will do
|
||||
await client_session.initialize()
|
||||
pytest.fail("Request should have errored") # pragma: no cover
|
||||
except McpError as e:
|
||||
except MCPError as e:
|
||||
# Expected - request errored
|
||||
assert "Connection closed" in str(e)
|
||||
ev_response.set()
|
||||
|
|
|
|||
|
|
@ -25,10 +25,9 @@ from mcp.client.sse import _extract_session_id_from_endpoint, sse_client
|
|||
from mcp.server import Server
|
||||
from mcp.server.sse import SseServerTransport
|
||||
from mcp.server.transport_security import TransportSecuritySettings
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.types import (
|
||||
EmptyResult,
|
||||
ErrorData,
|
||||
Implementation,
|
||||
InitializeResult,
|
||||
JSONRPCResponse,
|
||||
|
|
@ -70,7 +69,7 @@ class ServerTest(Server): # pragma: no cover
|
|||
await anyio.sleep(2.0)
|
||||
return f"Slow response from {parsed.netloc}"
|
||||
|
||||
raise McpError(error=ErrorData(code=404, message="OOPS! no resource with that URI was found"))
|
||||
raise MCPError(code=404, message="OOPS! no resource with that URI was found")
|
||||
|
||||
@self.list_tools()
|
||||
async def handle_list_tools() -> list[Tool]:
|
||||
|
|
@ -266,7 +265,7 @@ async def test_sse_client_exception_handling(
|
|||
initialized_sse_client_session: ClientSession,
|
||||
) -> None:
|
||||
session = initialized_sse_client_session
|
||||
with pytest.raises(McpError, match="OOPS! no resource with that URI was found"):
|
||||
with pytest.raises(MCPError, match="OOPS! no resource with that URI was found"):
|
||||
await session.read_resource(uri="xxx://will-not-work")
|
||||
|
||||
|
||||
|
|
@ -282,7 +281,7 @@ async def test_sse_client_timeout( # pragma: no cover
|
|||
assert isinstance(response, ReadResourceResult)
|
||||
|
||||
with anyio.move_on_after(3):
|
||||
with pytest.raises(McpError, match="Read timed out"):
|
||||
with pytest.raises(MCPError, match="Read timed out"):
|
||||
response = await session.read_resource(uri="slow://2")
|
||||
# we should receive an error here
|
||||
return
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ from starlette.requests import Request
|
|||
from starlette.routing import Mount
|
||||
|
||||
import mcp.types as types
|
||||
from mcp import MCPError
|
||||
from mcp.client.session import ClientSession
|
||||
from mcp.client.streamable_http import StreamableHTTPTransport, streamable_http_client
|
||||
from mcp.server import Server
|
||||
|
|
@ -44,16 +45,9 @@ from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
|
|||
from mcp.server.transport_security import TransportSecuritySettings
|
||||
from mcp.shared._httpx_utils import create_mcp_http_client
|
||||
from mcp.shared.context import RequestContext
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.message import ClientMessageMetadata, ServerMessageMetadata, SessionMessage
|
||||
from mcp.shared.session import RequestResponder
|
||||
from mcp.types import (
|
||||
InitializeResult,
|
||||
JSONRPCRequest,
|
||||
TextContent,
|
||||
TextResourceContents,
|
||||
Tool,
|
||||
)
|
||||
from mcp.types import InitializeResult, JSONRPCRequest, TextContent, TextResourceContents, Tool
|
||||
from tests.test_helpers import wait_for_server
|
||||
|
||||
# Test constants
|
||||
|
|
@ -987,11 +981,7 @@ async def initialized_client_session(basic_server: None, basic_server_url: str):
|
|||
@pytest.mark.anyio
|
||||
async def test_streamable_http_client_basic_connection(basic_server: None, basic_server_url: str):
|
||||
"""Test basic client connection with initialization."""
|
||||
async with streamable_http_client(f"{basic_server_url}/mcp") as (
|
||||
read_stream,
|
||||
write_stream,
|
||||
_,
|
||||
):
|
||||
async with streamable_http_client(f"{basic_server_url}/mcp") as (read_stream, write_stream, _):
|
||||
async with ClientSession(
|
||||
read_stream,
|
||||
write_stream,
|
||||
|
|
@ -1030,7 +1020,7 @@ async def test_streamable_http_client_tool_invocation(initialized_client_session
|
|||
@pytest.mark.anyio
|
||||
async def test_streamable_http_client_error_handling(initialized_client_session: ClientSession):
|
||||
"""Test error handling in client."""
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await initialized_client_session.read_resource(uri="unknown://test-error")
|
||||
assert exc_info.value.error.code == 0
|
||||
assert "Unknown resource: unknown://test-error" in exc_info.value.error.message
|
||||
|
|
@ -1067,15 +1057,8 @@ async def test_streamable_http_client_session_persistence(basic_server: None, ba
|
|||
@pytest.mark.anyio
|
||||
async def test_streamable_http_client_json_response(json_response_server: None, json_server_url: str):
|
||||
"""Test client with JSON response mode."""
|
||||
async with streamable_http_client(f"{json_server_url}/mcp") as (
|
||||
read_stream,
|
||||
write_stream,
|
||||
_,
|
||||
):
|
||||
async with ClientSession(
|
||||
read_stream,
|
||||
write_stream,
|
||||
) as session:
|
||||
async with streamable_http_client(f"{json_server_url}/mcp") as (read_stream, write_stream, _):
|
||||
async with ClientSession(read_stream, write_stream) as session:
|
||||
# Initialize the session
|
||||
result = await session.initialize()
|
||||
assert isinstance(result, InitializeResult)
|
||||
|
|
@ -1104,11 +1087,7 @@ async def test_streamable_http_client_get_stream(basic_server: None, basic_serve
|
|||
if isinstance(message, types.ServerNotification): # pragma: no branch
|
||||
notifications_received.append(message)
|
||||
|
||||
async with streamable_http_client(f"{basic_server_url}/mcp") as (
|
||||
read_stream,
|
||||
write_stream,
|
||||
_,
|
||||
):
|
||||
async with streamable_http_client(f"{basic_server_url}/mcp") as (read_stream, write_stream, _):
|
||||
async with ClientSession(read_stream, write_stream, message_handler=message_handler) as session:
|
||||
# Initialize the session - this triggers the GET stream setup
|
||||
result = await session.initialize()
|
||||
|
|
@ -1137,11 +1116,7 @@ async def test_streamable_http_client_session_termination(basic_server: None, ba
|
|||
captured_session_id = None
|
||||
|
||||
# Create the streamable_http_client with a custom httpx client to capture headers
|
||||
async with streamable_http_client(f"{basic_server_url}/mcp") as (
|
||||
read_stream,
|
||||
write_stream,
|
||||
get_session_id,
|
||||
):
|
||||
async with streamable_http_client(f"{basic_server_url}/mcp") as (read_stream, write_stream, get_session_id):
|
||||
async with ClientSession(read_stream, write_stream) as session:
|
||||
# Initialize the session
|
||||
result = await session.initialize()
|
||||
|
|
@ -1165,7 +1140,7 @@ async def test_streamable_http_client_session_termination(basic_server: None, ba
|
|||
):
|
||||
async with ClientSession(read_stream, write_stream) as session: # pragma: no branch
|
||||
# Attempt to make a request after termination
|
||||
with pytest.raises(McpError, match="Session terminated"): # pragma: no branch
|
||||
with pytest.raises(MCPError, match="Session terminated"): # pragma: no branch
|
||||
await session.list_tools()
|
||||
|
||||
|
||||
|
|
@ -1201,11 +1176,7 @@ async def test_streamable_http_client_session_termination_204(
|
|||
captured_session_id = None
|
||||
|
||||
# Create the streamable_http_client with a custom httpx client to capture headers
|
||||
async with streamable_http_client(f"{basic_server_url}/mcp") as (
|
||||
read_stream,
|
||||
write_stream,
|
||||
get_session_id,
|
||||
):
|
||||
async with streamable_http_client(f"{basic_server_url}/mcp") as (read_stream, write_stream, get_session_id):
|
||||
async with ClientSession(read_stream, write_stream) as session:
|
||||
# Initialize the session
|
||||
result = await session.initialize()
|
||||
|
|
@ -1229,10 +1200,7 @@ async def test_streamable_http_client_session_termination_204(
|
|||
):
|
||||
async with ClientSession(read_stream, write_stream) as session: # pragma: no branch
|
||||
# Attempt to make a request after termination
|
||||
with pytest.raises( # pragma: no branch
|
||||
McpError,
|
||||
match="Session terminated",
|
||||
):
|
||||
with pytest.raises(MCPError, match="Session terminated"): # pragma: no branch
|
||||
await session.list_tools()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,20 +12,12 @@ from starlette.applications import Starlette
|
|||
from starlette.routing import WebSocketRoute
|
||||
from starlette.websockets import WebSocket
|
||||
|
||||
from mcp import MCPError
|
||||
from mcp.client.session import ClientSession
|
||||
from mcp.client.websocket import websocket_client
|
||||
from mcp.server import Server
|
||||
from mcp.server.websocket import websocket_server
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.types import (
|
||||
EmptyResult,
|
||||
ErrorData,
|
||||
InitializeResult,
|
||||
ReadResourceResult,
|
||||
TextContent,
|
||||
TextResourceContents,
|
||||
Tool,
|
||||
)
|
||||
from mcp.types import EmptyResult, InitializeResult, ReadResourceResult, TextContent, TextResourceContents, Tool
|
||||
from tests.test_helpers import wait_for_server
|
||||
|
||||
SERVER_NAME = "test_server_for_WS"
|
||||
|
|
@ -58,7 +50,7 @@ class ServerTest(Server): # pragma: no cover
|
|||
await anyio.sleep(2.0)
|
||||
return f"Slow response from {parsed.netloc}"
|
||||
|
||||
raise McpError(error=ErrorData(code=404, message="OOPS! no resource with that URI was found"))
|
||||
raise MCPError(code=404, message="OOPS! no resource with that URI was found")
|
||||
|
||||
@self.list_tools()
|
||||
async def handle_list_tools() -> list[Tool]:
|
||||
|
|
@ -84,12 +76,7 @@ def make_server_app() -> Starlette: # pragma: no cover
|
|||
async with websocket_server(websocket.scope, websocket.receive, websocket.send) as streams:
|
||||
await server.run(streams[0], streams[1], server.create_initialization_options())
|
||||
|
||||
app = Starlette(
|
||||
routes=[
|
||||
WebSocketRoute("/ws", endpoint=handle_ws),
|
||||
]
|
||||
)
|
||||
|
||||
app = Starlette(routes=[WebSocketRoute("/ws", endpoint=handle_ws)])
|
||||
return app
|
||||
|
||||
|
||||
|
|
@ -176,7 +163,7 @@ async def test_ws_client_exception_handling(
|
|||
initialized_ws_client_session: ClientSession,
|
||||
) -> None:
|
||||
"""Test exception handling in WebSocket communication"""
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await initialized_ws_client_session.read_resource("unknown://example")
|
||||
assert exc_info.value.error.code == 404
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue