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
38 changed files with 253 additions and 326 deletions
|
|
@ -337,7 +337,7 @@ if __name__ == "__main__":
|
||||||
Handle task errors gracefully:
|
Handle task errors gracefully:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from mcp.shared.exceptions import McpError
|
from mcp.shared.exceptions import MCPError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = await session.experimental.call_tool_as_task("my_tool", args)
|
result = await session.experimental.call_tool_as_task("my_tool", args)
|
||||||
|
|
@ -349,8 +349,8 @@ try:
|
||||||
|
|
||||||
final = await session.experimental.get_task_result(task_id, CallToolResult)
|
final = await session.experimental.get_task_result(task_id, CallToolResult)
|
||||||
|
|
||||||
except McpError as e:
|
except MCPError as e:
|
||||||
print(f"MCP error: {e.error.message}")
|
print(f"MCP error: {e.message}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error: {e}")
|
print(f"Error: {e}")
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,38 @@ result = await session.list_resources(params=PaginatedRequestParams(cursor="next
|
||||||
result = await session.list_tools(params=PaginatedRequestParams(cursor="next_page_token"))
|
result = await session.list_tools(params=PaginatedRequestParams(cursor="next_page_token"))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `McpError` renamed to `MCPError`
|
||||||
|
|
||||||
|
The `McpError` exception class has been renamed to `MCPError` for consistent naming with the MCP acronym style used throughout the SDK.
|
||||||
|
|
||||||
|
**Before (v1):**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mcp.shared.exceptions import McpError
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = await session.call_tool("my_tool")
|
||||||
|
except McpError as e:
|
||||||
|
print(f"Error: {e.error.message}")
|
||||||
|
```
|
||||||
|
|
||||||
|
**After (v2):**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mcp.shared.exceptions import MCPError
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = await session.call_tool("my_tool")
|
||||||
|
except MCPError as e:
|
||||||
|
print(f"Error: {e.message}")
|
||||||
|
```
|
||||||
|
|
||||||
|
`MCPError` is also exported from the top-level `mcp` package:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mcp import MCPError
|
||||||
|
```
|
||||||
|
|
||||||
### `FastMCP` renamed to `MCPServer`
|
### `FastMCP` renamed to `MCPServer`
|
||||||
|
|
||||||
The `FastMCP` class has been renamed to `MCPServer` to better reflect its role as the main server class in the SDK. This is a simple rename with no functional changes to the class itself.
|
The `FastMCP` class has been renamed to `MCPServer` to better reflect its role as the main server class in the SDK. This is a simple rename with no functional changes to the class itself.
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ from urllib.parse import urlparse
|
||||||
from mcp import ClientSession, types
|
from mcp import ClientSession, types
|
||||||
from mcp.client.sse import sse_client
|
from mcp.client.sse import sse_client
|
||||||
from mcp.shared.context import RequestContext
|
from mcp.shared.context import RequestContext
|
||||||
from mcp.shared.exceptions import McpError, UrlElicitationRequiredError
|
from mcp.shared.exceptions import MCPError, UrlElicitationRequiredError
|
||||||
from mcp.types import URL_ELICITATION_REQUIRED
|
from mcp.types import URL_ELICITATION_REQUIRED
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -160,9 +160,9 @@ async def call_tool_with_error_handling(
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except McpError as e:
|
except MCPError as e:
|
||||||
# Check if this is a URL elicitation required error
|
# Check if this is a URL elicitation required error
|
||||||
if e.error.code == URL_ELICITATION_REQUIRED:
|
if e.code == URL_ELICITATION_REQUIRED:
|
||||||
print("\n[Tool requires URL elicitation to proceed]")
|
print("\n[Tool requires URL elicitation to proceed]")
|
||||||
|
|
||||||
# Convert to typed error to access elicitations
|
# Convert to typed error to access elicitations
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from .client.session_group import ClientSessionGroup
|
||||||
from .client.stdio import StdioServerParameters, stdio_client
|
from .client.stdio import StdioServerParameters, stdio_client
|
||||||
from .server.session import ServerSession
|
from .server.session import ServerSession
|
||||||
from .server.stdio import stdio_server
|
from .server.stdio import stdio_server
|
||||||
from .shared.exceptions import McpError, UrlElicitationRequiredError
|
from .shared.exceptions import MCPError, UrlElicitationRequiredError
|
||||||
from .types import (
|
from .types import (
|
||||||
CallToolRequest,
|
CallToolRequest,
|
||||||
ClientCapabilities,
|
ClientCapabilities,
|
||||||
|
|
@ -96,7 +96,7 @@ __all__ = [
|
||||||
"ListToolsResult",
|
"ListToolsResult",
|
||||||
"LoggingLevel",
|
"LoggingLevel",
|
||||||
"LoggingMessageNotification",
|
"LoggingMessageNotification",
|
||||||
"McpError",
|
"MCPError",
|
||||||
"Notification",
|
"Notification",
|
||||||
"PingRequest",
|
"PingRequest",
|
||||||
"ProgressNotification",
|
"ProgressNotification",
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ from mcp.client.sse import sse_client
|
||||||
from mcp.client.stdio import StdioServerParameters
|
from mcp.client.stdio import StdioServerParameters
|
||||||
from mcp.client.streamable_http import streamable_http_client
|
from mcp.client.streamable_http import streamable_http_client
|
||||||
from mcp.shared._httpx_utils import create_mcp_http_client
|
from mcp.shared._httpx_utils import create_mcp_http_client
|
||||||
from mcp.shared.exceptions import McpError
|
from mcp.shared.exceptions import MCPError
|
||||||
from mcp.shared.session import ProgressFnT
|
from mcp.shared.session import ProgressFnT
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -216,11 +216,9 @@ class ClientSessionGroup:
|
||||||
session_known_for_stack = session in self._session_exit_stacks
|
session_known_for_stack = session in self._session_exit_stacks
|
||||||
|
|
||||||
if not session_known_for_components and not session_known_for_stack:
|
if not session_known_for_components and not session_known_for_stack:
|
||||||
raise McpError(
|
raise MCPError(
|
||||||
types.ErrorData(
|
code=types.INVALID_PARAMS,
|
||||||
code=types.INVALID_PARAMS,
|
message="Provided session is not managed or already disconnected.",
|
||||||
message="Provided session is not managed or already disconnected.",
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if session_known_for_components: # pragma: no branch
|
if session_known_for_components: # pragma: no branch
|
||||||
|
|
@ -352,7 +350,7 @@ class ClientSessionGroup:
|
||||||
name = self._component_name(prompt.name, server_info)
|
name = self._component_name(prompt.name, server_info)
|
||||||
prompts_temp[name] = prompt
|
prompts_temp[name] = prompt
|
||||||
component_names.prompts.add(name)
|
component_names.prompts.add(name)
|
||||||
except McpError as err: # pragma: no cover
|
except MCPError as err: # pragma: no cover
|
||||||
logging.warning(f"Could not fetch prompts: {err}")
|
logging.warning(f"Could not fetch prompts: {err}")
|
||||||
|
|
||||||
# Query the server for its resources and aggregate to list.
|
# Query the server for its resources and aggregate to list.
|
||||||
|
|
@ -362,7 +360,7 @@ class ClientSessionGroup:
|
||||||
name = self._component_name(resource.name, server_info)
|
name = self._component_name(resource.name, server_info)
|
||||||
resources_temp[name] = resource
|
resources_temp[name] = resource
|
||||||
component_names.resources.add(name)
|
component_names.resources.add(name)
|
||||||
except McpError as err: # pragma: no cover
|
except MCPError as err: # pragma: no cover
|
||||||
logging.warning(f"Could not fetch resources: {err}")
|
logging.warning(f"Could not fetch resources: {err}")
|
||||||
|
|
||||||
# Query the server for its tools and aggregate to list.
|
# Query the server for its tools and aggregate to list.
|
||||||
|
|
@ -373,7 +371,7 @@ class ClientSessionGroup:
|
||||||
tools_temp[name] = tool
|
tools_temp[name] = tool
|
||||||
tool_to_session_temp[name] = session
|
tool_to_session_temp[name] = session
|
||||||
component_names.tools.add(name)
|
component_names.tools.add(name)
|
||||||
except McpError as err: # pragma: no cover
|
except MCPError as err: # pragma: no cover
|
||||||
logging.warning(f"Could not fetch tools: {err}")
|
logging.warning(f"Could not fetch tools: {err}")
|
||||||
|
|
||||||
# Clean up exit stack for session if we couldn't retrieve anything
|
# Clean up exit stack for session if we couldn't retrieve anything
|
||||||
|
|
@ -384,28 +382,19 @@ class ClientSessionGroup:
|
||||||
# Check for duplicates.
|
# Check for duplicates.
|
||||||
matching_prompts = prompts_temp.keys() & self._prompts.keys()
|
matching_prompts = prompts_temp.keys() & self._prompts.keys()
|
||||||
if matching_prompts:
|
if matching_prompts:
|
||||||
raise McpError( # pragma: no cover
|
raise MCPError( # pragma: no cover
|
||||||
types.ErrorData(
|
code=types.INVALID_PARAMS,
|
||||||
code=types.INVALID_PARAMS,
|
message=f"{matching_prompts} already exist in group prompts.",
|
||||||
message=f"{matching_prompts} already exist in group prompts.",
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
matching_resources = resources_temp.keys() & self._resources.keys()
|
matching_resources = resources_temp.keys() & self._resources.keys()
|
||||||
if matching_resources:
|
if matching_resources:
|
||||||
raise McpError( # pragma: no cover
|
raise MCPError( # pragma: no cover
|
||||||
types.ErrorData(
|
code=types.INVALID_PARAMS,
|
||||||
code=types.INVALID_PARAMS,
|
message=f"{matching_resources} already exist in group resources.",
|
||||||
message=f"{matching_resources} already exist in group resources.",
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
matching_tools = tools_temp.keys() & self._tools.keys()
|
matching_tools = tools_temp.keys() & self._tools.keys()
|
||||||
if matching_tools:
|
if matching_tools:
|
||||||
raise McpError(
|
raise MCPError(code=types.INVALID_PARAMS, message=f"{matching_tools} already exist in group tools.")
|
||||||
types.ErrorData(
|
|
||||||
code=types.INVALID_PARAMS,
|
|
||||||
message=f"{matching_tools} already exist in group tools.",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Aggregate components.
|
# Aggregate components.
|
||||||
self._sessions[session] = component_names
|
self._sessions[session] = component_names
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from typing import Any
|
||||||
from mcp.server.experimental.task_context import ServerTaskContext
|
from mcp.server.experimental.task_context import ServerTaskContext
|
||||||
from mcp.server.experimental.task_support import TaskSupport
|
from mcp.server.experimental.task_support import TaskSupport
|
||||||
from mcp.server.session import ServerSession
|
from mcp.server.session import ServerSession
|
||||||
from mcp.shared.exceptions import McpError
|
from mcp.shared.exceptions import MCPError
|
||||||
from mcp.shared.experimental.tasks.helpers import MODEL_IMMEDIATE_RESPONSE_KEY, is_terminal
|
from mcp.shared.experimental.tasks.helpers import MODEL_IMMEDIATE_RESPONSE_KEY, is_terminal
|
||||||
from mcp.types import (
|
from mcp.types import (
|
||||||
METHOD_NOT_FOUND,
|
METHOD_NOT_FOUND,
|
||||||
|
|
@ -72,13 +72,13 @@ class Experimental:
|
||||||
Args:
|
Args:
|
||||||
tool_task_mode: The tool's execution.taskSupport value
|
tool_task_mode: The tool's execution.taskSupport value
|
||||||
("forbidden", "optional", "required", or None)
|
("forbidden", "optional", "required", or None)
|
||||||
raise_error: If True, raises McpError on validation failure. If False, returns ErrorData.
|
raise_error: If True, raises MCPError on validation failure. If False, returns ErrorData.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None if valid, ErrorData if invalid and raise_error=False
|
None if valid, ErrorData if invalid and raise_error=False
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
McpError: If invalid and raise_error=True
|
MCPError: If invalid and raise_error=True
|
||||||
"""
|
"""
|
||||||
|
|
||||||
mode = tool_task_mode or TASK_FORBIDDEN
|
mode = tool_task_mode or TASK_FORBIDDEN
|
||||||
|
|
@ -86,18 +86,12 @@ class Experimental:
|
||||||
error: ErrorData | None = None
|
error: ErrorData | None = None
|
||||||
|
|
||||||
if mode == TASK_REQUIRED and not self.is_task:
|
if mode == TASK_REQUIRED and not self.is_task:
|
||||||
error = ErrorData(
|
error = ErrorData(code=METHOD_NOT_FOUND, message="This tool requires task-augmented invocation")
|
||||||
code=METHOD_NOT_FOUND,
|
|
||||||
message="This tool requires task-augmented invocation",
|
|
||||||
)
|
|
||||||
elif mode == TASK_FORBIDDEN and self.is_task:
|
elif mode == TASK_FORBIDDEN and self.is_task:
|
||||||
error = ErrorData(
|
error = ErrorData(code=METHOD_NOT_FOUND, message="This tool does not support task-augmented invocation")
|
||||||
code=METHOD_NOT_FOUND,
|
|
||||||
message="This tool does not support task-augmented invocation",
|
|
||||||
)
|
|
||||||
|
|
||||||
if error is not None and raise_error:
|
if error is not None and raise_error:
|
||||||
raise McpError(error)
|
raise MCPError(code=error.code, message=error.message)
|
||||||
|
|
||||||
return error
|
return error
|
||||||
|
|
||||||
|
|
@ -113,7 +107,7 @@ class Experimental:
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tool: The Tool definition
|
tool: The Tool definition
|
||||||
raise_error: If True, raises McpError on validation failure.
|
raise_error: If True, raises MCPError on validation failure.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None if valid, ErrorData if invalid and raise_error=False
|
None if valid, ErrorData if invalid and raise_error=False
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ class ExperimentalServerSessionFeatures:
|
||||||
The client's elicitation response
|
The client's elicitation response
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
McpError: If client doesn't support task-augmented elicitation
|
MCPError: If client doesn't support task-augmented elicitation
|
||||||
"""
|
"""
|
||||||
client_caps = self._session.client_params.capabilities if self._session.client_params else None
|
client_caps = self._session.client_params.capabilities if self._session.client_params else None
|
||||||
require_task_augmented_elicitation(client_caps)
|
require_task_augmented_elicitation(client_caps)
|
||||||
|
|
@ -174,7 +174,7 @@ class ExperimentalServerSessionFeatures:
|
||||||
The sampling result from the client
|
The sampling result from the client
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
McpError: If client doesn't support task-augmented sampling or tools
|
MCPError: If client doesn't support task-augmented sampling or tools
|
||||||
ValueError: If tool_use or tool_result message structure is invalid
|
ValueError: If tool_use or tool_result message structure is invalid
|
||||||
"""
|
"""
|
||||||
client_caps = self._session.client_params.capabilities if self._session.client_params else None
|
client_caps = self._session.client_params.capabilities if self._session.client_params else None
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import anyio
|
||||||
from mcp.server.experimental.task_result_handler import TaskResultHandler
|
from mcp.server.experimental.task_result_handler import TaskResultHandler
|
||||||
from mcp.server.session import ServerSession
|
from mcp.server.session import ServerSession
|
||||||
from mcp.server.validation import validate_sampling_tools, validate_tool_use_result_messages
|
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.shared.experimental.tasks.capabilities import (
|
from mcp.shared.experimental.tasks.capabilities import (
|
||||||
require_task_augmented_elicitation,
|
require_task_augmented_elicitation,
|
||||||
require_task_augmented_sampling,
|
require_task_augmented_sampling,
|
||||||
|
|
@ -32,7 +32,6 @@ from mcp.types import (
|
||||||
ElicitationCapability,
|
ElicitationCapability,
|
||||||
ElicitRequestedSchema,
|
ElicitRequestedSchema,
|
||||||
ElicitResult,
|
ElicitResult,
|
||||||
ErrorData,
|
|
||||||
IncludeContext,
|
IncludeContext,
|
||||||
ModelPreferences,
|
ModelPreferences,
|
||||||
RequestId,
|
RequestId,
|
||||||
|
|
@ -173,22 +172,12 @@ class ServerTaskContext:
|
||||||
def _check_elicitation_capability(self) -> None:
|
def _check_elicitation_capability(self) -> None:
|
||||||
"""Check if the client supports elicitation."""
|
"""Check if the client supports elicitation."""
|
||||||
if not self._session.check_client_capability(ClientCapabilities(elicitation=ElicitationCapability())):
|
if not self._session.check_client_capability(ClientCapabilities(elicitation=ElicitationCapability())):
|
||||||
raise McpError(
|
raise MCPError(code=INVALID_REQUEST, message="Client does not support elicitation capability")
|
||||||
ErrorData(
|
|
||||||
code=INVALID_REQUEST,
|
|
||||||
message="Client does not support elicitation capability",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _check_sampling_capability(self) -> None:
|
def _check_sampling_capability(self) -> None:
|
||||||
"""Check if the client supports sampling."""
|
"""Check if the client supports sampling."""
|
||||||
if not self._session.check_client_capability(ClientCapabilities(sampling=SamplingCapability())):
|
if not self._session.check_client_capability(ClientCapabilities(sampling=SamplingCapability())):
|
||||||
raise McpError(
|
raise MCPError(code=INVALID_REQUEST, message="Client does not support sampling capability")
|
||||||
ErrorData(
|
|
||||||
code=INVALID_REQUEST,
|
|
||||||
message="Client does not support sampling capability",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def elicit(
|
async def elicit(
|
||||||
self,
|
self,
|
||||||
|
|
@ -213,7 +202,7 @@ class ServerTaskContext:
|
||||||
The client's response
|
The client's response
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
McpError: If client doesn't support elicitation capability
|
MCPError: If client doesn't support elicitation capability
|
||||||
"""
|
"""
|
||||||
self._check_elicitation_capability()
|
self._check_elicitation_capability()
|
||||||
|
|
||||||
|
|
@ -281,7 +270,7 @@ class ServerTaskContext:
|
||||||
The client's response indicating acceptance, decline, or cancellation
|
The client's response indicating acceptance, decline, or cancellation
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
McpError: If client doesn't support elicitation capability
|
MCPError: If client doesn't support elicitation capability
|
||||||
RuntimeError: If handler is not configured
|
RuntimeError: If handler is not configured
|
||||||
"""
|
"""
|
||||||
self._check_elicitation_capability()
|
self._check_elicitation_capability()
|
||||||
|
|
@ -361,7 +350,7 @@ class ServerTaskContext:
|
||||||
The sampling result from the client
|
The sampling result from the client
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
McpError: If client doesn't support sampling capability or tools
|
MCPError: If client doesn't support sampling capability or tools
|
||||||
ValueError: If tool_use or tool_result message structure is invalid
|
ValueError: If tool_use or tool_result message structure is invalid
|
||||||
"""
|
"""
|
||||||
self._check_sampling_capability()
|
self._check_sampling_capability()
|
||||||
|
|
@ -436,7 +425,7 @@ class ServerTaskContext:
|
||||||
The client's elicitation response
|
The client's elicitation response
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
McpError: If client doesn't support task-augmented elicitation
|
MCPError: If client doesn't support task-augmented elicitation
|
||||||
RuntimeError: If handler is not configured
|
RuntimeError: If handler is not configured
|
||||||
"""
|
"""
|
||||||
client_caps = self._session.client_params.capabilities if self._session.client_params else None
|
client_caps = self._session.client_params.capabilities if self._session.client_params else None
|
||||||
|
|
@ -529,7 +518,7 @@ class ServerTaskContext:
|
||||||
The sampling result from the client
|
The sampling result from the client
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
McpError: If client doesn't support task-augmented sampling or tools
|
MCPError: If client doesn't support task-augmented sampling or tools
|
||||||
ValueError: If tool_use or tool_result message structure is invalid
|
ValueError: If tool_use or tool_result message structure is invalid
|
||||||
RuntimeError: If handler is not configured
|
RuntimeError: If handler is not configured
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from typing import Any
|
||||||
import anyio
|
import anyio
|
||||||
|
|
||||||
from mcp.server.session import ServerSession
|
from mcp.server.session import ServerSession
|
||||||
from mcp.shared.exceptions import McpError
|
from mcp.shared.exceptions import MCPError
|
||||||
from mcp.shared.experimental.tasks.helpers import RELATED_TASK_METADATA_KEY, is_terminal
|
from mcp.shared.experimental.tasks.helpers import RELATED_TASK_METADATA_KEY, is_terminal
|
||||||
from mcp.shared.experimental.tasks.message_queue import TaskMessageQueue
|
from mcp.shared.experimental.tasks.message_queue import TaskMessageQueue
|
||||||
from mcp.shared.experimental.tasks.resolver import Resolver
|
from mcp.shared.experimental.tasks.resolver import Resolver
|
||||||
|
|
@ -106,7 +106,7 @@ class TaskResultHandler:
|
||||||
while True:
|
while True:
|
||||||
task = await self._store.get_task(task_id)
|
task = await self._store.get_task(task_id)
|
||||||
if task is None:
|
if task is None:
|
||||||
raise McpError(ErrorData(code=INVALID_PARAMS, message=f"Task not found: {task_id}"))
|
raise MCPError(code=INVALID_PARAMS, message=f"Task not found: {task_id}")
|
||||||
|
|
||||||
await self._deliver_queued_messages(task_id, session, request_id)
|
await self._deliver_queued_messages(task_id, session, request_id)
|
||||||
|
|
||||||
|
|
@ -216,6 +216,6 @@ class TaskResultHandler:
|
||||||
"""
|
"""
|
||||||
resolver = self._pending_requests.pop(request_id, None)
|
resolver = self._pending_requests.pop(request_id, None)
|
||||||
if resolver is not None and not resolver.done():
|
if resolver is not None and not resolver.done():
|
||||||
resolver.set_exception(McpError(error))
|
resolver.set_exception(MCPError.from_error_data(error))
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from mcp.server.experimental.task_support import TaskSupport
|
from mcp.server.experimental.task_support import TaskSupport
|
||||||
from mcp.server.lowlevel.func_inspection import create_call_wrapper
|
from mcp.server.lowlevel.func_inspection import create_call_wrapper
|
||||||
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.helpers import cancel_task
|
||||||
from mcp.shared.experimental.tasks.in_memory_task_store import InMemoryTaskStore
|
from mcp.shared.experimental.tasks.in_memory_task_store import InMemoryTaskStore
|
||||||
from mcp.shared.experimental.tasks.message_queue import InMemoryTaskMessageQueue, TaskMessageQueue
|
from mcp.shared.experimental.tasks.message_queue import InMemoryTaskMessageQueue, TaskMessageQueue
|
||||||
|
|
@ -20,7 +20,6 @@ from mcp.types import (
|
||||||
INVALID_PARAMS,
|
INVALID_PARAMS,
|
||||||
CancelTaskRequest,
|
CancelTaskRequest,
|
||||||
CancelTaskResult,
|
CancelTaskResult,
|
||||||
ErrorData,
|
|
||||||
GetTaskPayloadRequest,
|
GetTaskPayloadRequest,
|
||||||
GetTaskPayloadResult,
|
GetTaskPayloadResult,
|
||||||
GetTaskRequest,
|
GetTaskRequest,
|
||||||
|
|
@ -135,12 +134,7 @@ class ExperimentalHandlers:
|
||||||
async def _default_get_task(req: GetTaskRequest) -> ServerResult:
|
async def _default_get_task(req: GetTaskRequest) -> ServerResult:
|
||||||
task = await support.store.get_task(req.params.task_id)
|
task = await support.store.get_task(req.params.task_id)
|
||||||
if task is None:
|
if task is None:
|
||||||
raise McpError(
|
raise MCPError(code=INVALID_PARAMS, message=f"Task not found: {req.params.task_id}")
|
||||||
ErrorData(
|
|
||||||
code=INVALID_PARAMS,
|
|
||||||
message=f"Task not found: {req.params.task_id}",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return GetTaskResult(
|
return GetTaskResult(
|
||||||
task_id=task.task_id,
|
task_id=task.task_id,
|
||||||
status=task.status,
|
status=task.status,
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ from mcp.server.streamable_http import EventStore
|
||||||
from mcp.server.streamable_http_manager import StreamableHTTPASGIApp, StreamableHTTPSessionManager
|
from mcp.server.streamable_http_manager import StreamableHTTPASGIApp, StreamableHTTPSessionManager
|
||||||
from mcp.server.transport_security import TransportSecuritySettings
|
from mcp.server.transport_security import TransportSecuritySettings
|
||||||
from mcp.shared.context import RequestContext
|
from mcp.shared.context import RequestContext
|
||||||
from mcp.shared.exceptions import McpError, UrlElicitationRequiredError
|
from mcp.shared.exceptions import MCPError, UrlElicitationRequiredError
|
||||||
from mcp.shared.message import ServerMessageMetadata, SessionMessage
|
from mcp.shared.message import ServerMessageMetadata, SessionMessage
|
||||||
from mcp.shared.session import RequestResponder
|
from mcp.shared.session import RequestResponder
|
||||||
from mcp.shared.tool_name_validation import validate_and_warn_tool_name
|
from mcp.shared.tool_name_validation import validate_and_warn_tool_name
|
||||||
|
|
@ -407,9 +407,7 @@ class Server(Generic[LifespanResultT, RequestT]):
|
||||||
case _: # pragma: no cover
|
case _: # pragma: no cover
|
||||||
raise ValueError(f"Unexpected return type from read_resource: {type(result)}")
|
raise ValueError(f"Unexpected return type from read_resource: {type(result)}")
|
||||||
|
|
||||||
return types.ReadResourceResult( # pragma: no cover
|
return types.ReadResourceResult(contents=[content]) # pragma: no cover
|
||||||
contents=[content],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.request_handlers[types.ReadResourceRequest] = handler
|
self.request_handlers[types.ReadResourceRequest] = handler
|
||||||
return func
|
return func
|
||||||
|
|
@ -781,13 +779,10 @@ class Server(Generic[LifespanResultT, RequestT]):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
response = await handler(req)
|
response = await handler(req)
|
||||||
except McpError as err:
|
except MCPError as err:
|
||||||
response = err.error
|
response = err.error
|
||||||
except anyio.get_cancelled_exc_class():
|
except anyio.get_cancelled_exc_class():
|
||||||
logger.info(
|
logger.info("Request %s cancelled - duplicate response suppressed", message.request_id)
|
||||||
"Request %s cancelled - duplicate response suppressed",
|
|
||||||
message.request_id,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
if raise_exceptions: # pragma: no cover
|
if raise_exceptions: # pragma: no cover
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,7 @@ class ServerSession(
|
||||||
The sampling result from the client.
|
The sampling result from the client.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
McpError: If tools are provided but client doesn't support them.
|
MCPError: If tools are provided but client doesn't support them.
|
||||||
ValueError: If tool_use or tool_result message structure is invalid.
|
ValueError: If tool_use or tool_result message structure is invalid.
|
||||||
StatelessModeNotSupported: If called in stateless HTTP mode.
|
StatelessModeNotSupported: If called in stateless HTTP mode.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,8 @@ This module provides validation logic for sampling and elicitation requests
|
||||||
that is shared across normal and task-augmented code paths.
|
that is shared across normal and task-augmented code paths.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from mcp.shared.exceptions import McpError
|
from mcp.shared.exceptions import MCPError
|
||||||
from mcp.types import (
|
from mcp.types import INVALID_PARAMS, ClientCapabilities, SamplingMessage, Tool, ToolChoice
|
||||||
INVALID_PARAMS,
|
|
||||||
ClientCapabilities,
|
|
||||||
ErrorData,
|
|
||||||
SamplingMessage,
|
|
||||||
Tool,
|
|
||||||
ToolChoice,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def check_sampling_tools_capability(client_caps: ClientCapabilities | None) -> bool:
|
def check_sampling_tools_capability(client_caps: ClientCapabilities | None) -> bool:
|
||||||
|
|
@ -46,11 +39,11 @@ def validate_sampling_tools(
|
||||||
tool_choice: The tool choice setting, if provided
|
tool_choice: The tool choice setting, if provided
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
McpError: If tools/tool_choice are provided but client doesn't support them
|
MCPError: If tools/tool_choice are provided but client doesn't support them
|
||||||
"""
|
"""
|
||||||
if tools is not None or tool_choice is not None:
|
if tools is not None or tool_choice is not None:
|
||||||
if not check_sampling_tools_capability(client_caps):
|
if not check_sampling_tools_capability(client_caps):
|
||||||
raise McpError(ErrorData(code=INVALID_PARAMS, message="Client does not support sampling tools capability"))
|
raise MCPError(code=INVALID_PARAMS, message="Client does not support sampling tools capability")
|
||||||
|
|
||||||
|
|
||||||
def validate_tool_use_result_messages(messages: list[SamplingMessage]) -> None:
|
def validate_tool_use_result_messages(messages: list[SamplingMessage]) -> None:
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,40 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from mcp.types import URL_ELICITATION_REQUIRED, ElicitRequestURLParams, ErrorData
|
from mcp.types import URL_ELICITATION_REQUIRED, ElicitRequestURLParams, ErrorData, JSONRPCError
|
||||||
|
|
||||||
|
|
||||||
class McpError(Exception):
|
class MCPError(Exception):
|
||||||
"""Exception type raised when an error arrives over an MCP connection."""
|
"""Exception type raised when an error arrives over an MCP connection."""
|
||||||
|
|
||||||
error: ErrorData
|
error: ErrorData
|
||||||
|
|
||||||
def __init__(self, error: ErrorData):
|
def __init__(self, code: int, message: str, data: Any = None):
|
||||||
"""Initialize McpError."""
|
super().__init__(code, message, data)
|
||||||
super().__init__(error.message)
|
self.error = ErrorData(code=code, message=message, data=data)
|
||||||
self.error = error
|
|
||||||
|
@property
|
||||||
|
def code(self) -> int:
|
||||||
|
return self.error.code
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self) -> str:
|
||||||
|
return self.error.message
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> Any:
|
||||||
|
return self.error.data # pragma: no cover
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_jsonrpc_error(cls, error: JSONRPCError) -> MCPError:
|
||||||
|
return cls.from_error_data(error.error)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_error_data(cls, error: ErrorData) -> MCPError:
|
||||||
|
return cls(code=error.code, message=error.message, data=error.data)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.message
|
||||||
|
|
||||||
|
|
||||||
class StatelessModeNotSupported(RuntimeError):
|
class StatelessModeNotSupported(RuntimeError):
|
||||||
|
|
@ -33,7 +55,7 @@ class StatelessModeNotSupported(RuntimeError):
|
||||||
self.method = method
|
self.method = method
|
||||||
|
|
||||||
|
|
||||||
class UrlElicitationRequiredError(McpError):
|
class UrlElicitationRequiredError(MCPError):
|
||||||
"""Specialized error for when a tool requires URL mode elicitation(s) before proceeding.
|
"""Specialized error for when a tool requires URL mode elicitation(s) before proceeding.
|
||||||
|
|
||||||
Servers can raise this error from tool handlers to indicate that the client
|
Servers can raise this error from tool handlers to indicate that the client
|
||||||
|
|
@ -42,7 +64,6 @@ class UrlElicitationRequiredError(McpError):
|
||||||
Example:
|
Example:
|
||||||
raise UrlElicitationRequiredError([
|
raise UrlElicitationRequiredError([
|
||||||
ElicitRequestURLParams(
|
ElicitRequestURLParams(
|
||||||
mode="url",
|
|
||||||
message="Authorization required for your files",
|
message="Authorization required for your files",
|
||||||
url="https://example.com/oauth/authorize",
|
url="https://example.com/oauth/authorize",
|
||||||
elicitation_id="auth-001"
|
elicitation_id="auth-001"
|
||||||
|
|
@ -50,23 +71,18 @@ class UrlElicitationRequiredError(McpError):
|
||||||
])
|
])
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, elicitations: list[ElicitRequestURLParams], message: str | None = None):
|
||||||
self,
|
|
||||||
elicitations: list[ElicitRequestURLParams],
|
|
||||||
message: str | None = None,
|
|
||||||
):
|
|
||||||
"""Initialize UrlElicitationRequiredError."""
|
"""Initialize UrlElicitationRequiredError."""
|
||||||
if message is None:
|
if message is None:
|
||||||
message = f"URL elicitation{'s' if len(elicitations) > 1 else ''} required"
|
message = f"URL elicitation{'s' if len(elicitations) > 1 else ''} required"
|
||||||
|
|
||||||
self._elicitations = elicitations
|
self._elicitations = elicitations
|
||||||
|
|
||||||
error = ErrorData(
|
super().__init__(
|
||||||
code=URL_ELICITATION_REQUIRED,
|
code=URL_ELICITATION_REQUIRED,
|
||||||
message=message,
|
message=message,
|
||||||
data={"elicitations": [e.model_dump(by_alias=True, exclude_none=True) for e in elicitations]},
|
data={"elicitations": [e.model_dump(by_alias=True, exclude_none=True) for e in elicitations]},
|
||||||
)
|
)
|
||||||
super().__init__(error)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def elicitations(self) -> list[ElicitRequestURLParams]:
|
def elicitations(self) -> list[ElicitRequestURLParams]:
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,8 @@ the main session code clean.
|
||||||
WARNING: These APIs are experimental and may change without notice.
|
WARNING: These APIs are experimental and may change without notice.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from mcp.shared.exceptions import McpError
|
from mcp.shared.exceptions import MCPError
|
||||||
from mcp.types import (
|
from mcp.types import INVALID_REQUEST, ClientCapabilities, ClientTasksCapability
|
||||||
INVALID_REQUEST,
|
|
||||||
ClientCapabilities,
|
|
||||||
ClientTasksCapability,
|
|
||||||
ErrorData,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def check_tasks_capability(
|
def check_tasks_capability(
|
||||||
|
|
@ -76,36 +71,26 @@ def has_task_augmented_sampling(caps: ClientCapabilities) -> bool:
|
||||||
|
|
||||||
|
|
||||||
def require_task_augmented_elicitation(client_caps: ClientCapabilities | None) -> None:
|
def require_task_augmented_elicitation(client_caps: ClientCapabilities | None) -> None:
|
||||||
"""Raise McpError if client doesn't support task-augmented elicitation.
|
"""Raise MCPError if client doesn't support task-augmented elicitation.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
client_caps: The client's declared capabilities, or None if not initialized
|
client_caps: The client's declared capabilities, or None if not initialized
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
McpError: If client doesn't support task-augmented elicitation
|
MCPError: If client doesn't support task-augmented elicitation
|
||||||
"""
|
"""
|
||||||
if client_caps is None or not has_task_augmented_elicitation(client_caps):
|
if client_caps is None or not has_task_augmented_elicitation(client_caps):
|
||||||
raise McpError(
|
raise MCPError(code=INVALID_REQUEST, message="Client does not support task-augmented elicitation")
|
||||||
ErrorData(
|
|
||||||
code=INVALID_REQUEST,
|
|
||||||
message="Client does not support task-augmented elicitation",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def require_task_augmented_sampling(client_caps: ClientCapabilities | None) -> None:
|
def require_task_augmented_sampling(client_caps: ClientCapabilities | None) -> None:
|
||||||
"""Raise McpError if client doesn't support task-augmented sampling.
|
"""Raise MCPError if client doesn't support task-augmented sampling.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
client_caps: The client's declared capabilities, or None if not initialized
|
client_caps: The client's declared capabilities, or None if not initialized
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
McpError: If client doesn't support task-augmented sampling
|
MCPError: If client doesn't support task-augmented sampling
|
||||||
"""
|
"""
|
||||||
if client_caps is None or not has_task_augmented_sampling(client_caps):
|
if client_caps is None or not has_task_augmented_sampling(client_caps):
|
||||||
raise McpError(
|
raise MCPError(code=INVALID_REQUEST, message="Client does not support task-augmented sampling")
|
||||||
ErrorData(
|
|
||||||
code=INVALID_REQUEST,
|
|
||||||
message="Client does not support task-augmented sampling",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from contextlib import asynccontextmanager
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from mcp.shared.exceptions import McpError
|
from mcp.shared.exceptions import MCPError
|
||||||
from mcp.shared.experimental.tasks.context import TaskContext
|
from mcp.shared.experimental.tasks.context import TaskContext
|
||||||
from mcp.shared.experimental.tasks.store import TaskStore
|
from mcp.shared.experimental.tasks.store import TaskStore
|
||||||
from mcp.types import (
|
from mcp.types import (
|
||||||
|
|
@ -19,7 +19,6 @@ from mcp.types import (
|
||||||
TASK_STATUS_FAILED,
|
TASK_STATUS_FAILED,
|
||||||
TASK_STATUS_WORKING,
|
TASK_STATUS_WORKING,
|
||||||
CancelTaskResult,
|
CancelTaskResult,
|
||||||
ErrorData,
|
|
||||||
Task,
|
Task,
|
||||||
TaskMetadata,
|
TaskMetadata,
|
||||||
TaskStatus,
|
TaskStatus,
|
||||||
|
|
@ -68,7 +67,7 @@ async def cancel_task(
|
||||||
CancelTaskResult with the cancelled task state
|
CancelTaskResult with the cancelled task state
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
McpError: With INVALID_PARAMS (-32602) if:
|
MCPError: With INVALID_PARAMS (-32602) if:
|
||||||
- Task does not exist
|
- Task does not exist
|
||||||
- Task is already in a terminal state (completed, failed, cancelled)
|
- Task is already in a terminal state (completed, failed, cancelled)
|
||||||
|
|
||||||
|
|
@ -79,20 +78,10 @@ async def cancel_task(
|
||||||
"""
|
"""
|
||||||
task = await store.get_task(task_id)
|
task = await store.get_task(task_id)
|
||||||
if task is None:
|
if task is None:
|
||||||
raise McpError(
|
raise MCPError(code=INVALID_PARAMS, message=f"Task not found: {task_id}")
|
||||||
ErrorData(
|
|
||||||
code=INVALID_PARAMS,
|
|
||||||
message=f"Task not found: {task_id}",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_terminal(task.status):
|
if is_terminal(task.status):
|
||||||
raise McpError(
|
raise MCPError(code=INVALID_PARAMS, message=f"Cannot cancel task in terminal state '{task.status}'")
|
||||||
ErrorData(
|
|
||||||
code=INVALID_PARAMS,
|
|
||||||
message=f"Cannot cancel task in terminal state '{task.status}'",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Update task to cancelled status
|
# Update task to cancelled status
|
||||||
cancelled_task = await store.update_task(task_id, status=TASK_STATUS_CANCELLED)
|
cancelled_task = await store.update_task(task_id, status=TASK_STATUS_CANCELLED)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStre
|
||||||
from pydantic import BaseModel, TypeAdapter
|
from pydantic import BaseModel, TypeAdapter
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
from mcp.shared.exceptions import McpError
|
from mcp.shared.exceptions import MCPError
|
||||||
from mcp.shared.message import MessageMetadata, ServerMessageMetadata, SessionMessage
|
from mcp.shared.message import MessageMetadata, ServerMessageMetadata, SessionMessage
|
||||||
from mcp.shared.response_router import ResponseRouter
|
from mcp.shared.response_router import ResponseRouter
|
||||||
from mcp.types import (
|
from mcp.types import (
|
||||||
|
|
@ -237,7 +237,7 @@ class BaseSession(
|
||||||
) -> ReceiveResultT:
|
) -> ReceiveResultT:
|
||||||
"""Sends a request and wait for a response.
|
"""Sends a request and wait for a response.
|
||||||
|
|
||||||
Raises an McpError if the response contains an error. If a request read timeout is provided, it will take
|
Raises an MCPError if the response contains an error. If a request read timeout is provided, it will take
|
||||||
precedence over the session read timeout.
|
precedence over the session read timeout.
|
||||||
|
|
||||||
Do not use this method to emit notifications! Use send_notification() instead.
|
Do not use this method to emit notifications! Use send_notification() instead.
|
||||||
|
|
@ -271,18 +271,12 @@ class BaseSession(
|
||||||
with anyio.fail_after(timeout):
|
with anyio.fail_after(timeout):
|
||||||
response_or_error = await response_stream_reader.receive()
|
response_or_error = await response_stream_reader.receive()
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
raise McpError(
|
class_name = request.__class__.__name__
|
||||||
ErrorData(
|
message = f"Timed out while waiting for response to {class_name}. Waited {timeout} seconds."
|
||||||
code=REQUEST_TIMEOUT,
|
raise MCPError(code=REQUEST_TIMEOUT, message=message)
|
||||||
message=(
|
|
||||||
f"Timed out while waiting for response to {request.__class__.__name__}. "
|
|
||||||
f"Waited {timeout} seconds."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(response_or_error, JSONRPCError):
|
if isinstance(response_or_error, JSONRPCError):
|
||||||
raise McpError(response_or_error.error)
|
raise MCPError.from_jsonrpc_error(response_or_error)
|
||||||
else:
|
else:
|
||||||
return result_type.model_validate(response_or_error.result, by_name=False)
|
return result_type.model_validate(response_or_error.result, by_name=False)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1654,8 +1654,8 @@ class ElicitRequestURLParams(RequestParams):
|
||||||
"""The URL that the user should navigate to."""
|
"""The URL that the user should navigate to."""
|
||||||
|
|
||||||
elicitation_id: str
|
elicitation_id: str
|
||||||
"""
|
"""The ID of the elicitation, which must be unique within the context of the server.
|
||||||
The ID of the elicitation, which must be unique within the context of the server.
|
|
||||||
The client MUST treat this ID as an opaque value.
|
The client MUST treat this ID as an opaque value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,15 +59,15 @@ class ErrorData(BaseModel):
|
||||||
"""The error type that occurred."""
|
"""The error type that occurred."""
|
||||||
|
|
||||||
message: str
|
message: str
|
||||||
"""
|
"""A short description of the error.
|
||||||
A short description of the error. The message SHOULD be limited to a concise single
|
|
||||||
sentence.
|
The message SHOULD be limited to a concise single sentence.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data: Any = None
|
data: Any = None
|
||||||
"""
|
"""Additional information about the error.
|
||||||
Additional information about the error. The value of this member is defined by the
|
|
||||||
sender (e.g. detailed error information, nested errors etc.).
|
The value of this member is defined by the sender (e.g. detailed error information, nested errors, etc.).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from mcp.client.session_group import (
|
||||||
StreamableHttpParameters,
|
StreamableHttpParameters,
|
||||||
)
|
)
|
||||||
from mcp.client.stdio import StdioServerParameters
|
from mcp.client.stdio import StdioServerParameters
|
||||||
from mcp.shared.exceptions import McpError
|
from mcp.shared.exceptions import MCPError
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@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(
|
async def test_client_session_group_connect_to_server_duplicate_tool_raises_error(
|
||||||
mock_exit_stack: contextlib.AsyncExitStack,
|
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 ---
|
# --- Setup Pre-existing State ---
|
||||||
group = ClientSessionGroup(exit_stack=mock_exit_stack)
|
group = ClientSessionGroup(exit_stack=mock_exit_stack)
|
||||||
existing_tool_name = "shared_tool"
|
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=[])
|
mock_session_new.list_prompts.return_value = mock.AsyncMock(prompts=[])
|
||||||
|
|
||||||
# --- Test Execution and Assertion ---
|
# --- Test Execution and Assertion ---
|
||||||
with pytest.raises(McpError) as excinfo:
|
with pytest.raises(MCPError) as excinfo:
|
||||||
with mock.patch.object(
|
with mock.patch.object(
|
||||||
group,
|
group,
|
||||||
"_establish_session",
|
"_establish_session",
|
||||||
|
|
@ -274,7 +274,7 @@ async def test_client_session_group_disconnect_non_existent_server():
|
||||||
"""Test disconnecting a server that isn't connected."""
|
"""Test disconnecting a server that isn't connected."""
|
||||||
session = mock.Mock(spec=mcp.ClientSession)
|
session = mock.Mock(spec=mcp.ClientSession)
|
||||||
group = ClientSessionGroup()
|
group = ClientSessionGroup()
|
||||||
with pytest.raises(McpError):
|
with pytest.raises(MCPError):
|
||||||
await group.disconnect_from_server(session)
|
await group.disconnect_from_server(session)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue