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:
Marcelo Trylesinski 2026-01-26 14:37:44 +01:00 • committed by GitHub
commit acba5478a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 253 additions and 326 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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