Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/experimental/tasks-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ if __name__ == "__main__":
Handle task errors gracefully:

```python
from mcp.shared.exceptions import McpError
from mcp.shared.exceptions import MCPError

try:
result = await session.experimental.call_tool_as_task("my_tool", args)
Expand All @@ -349,8 +349,8 @@ try:

final = await session.experimental.get_task_result(task_id, CallToolResult)

except McpError as e:
print(f"MCP error: {e.error.message}")
except MCPError as e:
print(f"MCP error: {e.message}")
except Exception as e:
print(f"Error: {e}")
```
Expand Down
32 changes: 32 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,38 @@ result = await session.list_resources(params=PaginatedRequestParams(cursor="next
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.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`

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.
Expand Down
6 changes: 3 additions & 3 deletions examples/snippets/clients/url_elicitation_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from mcp import ClientSession, types
from mcp.client.sse import sse_client
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


Expand Down Expand Up @@ -160,9 +160,9 @@ async def call_tool_with_error_handling(

return result

except McpError as e:
except MCPError as e:
# 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]")

# Convert to typed error to access elicitations
Expand Down
4 changes: 2 additions & 2 deletions src/mcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from .client.stdio import StdioServerParameters, stdio_client
from .server.session import ServerSession
from .server.stdio import stdio_server
from .shared.exceptions import McpError, UrlElicitationRequiredError
from .shared.exceptions import MCPError, UrlElicitationRequiredError
from .types import (
CallToolRequest,
ClientCapabilities,
Expand Down Expand Up @@ -96,7 +96,7 @@
"ListToolsResult",
"LoggingLevel",
"LoggingMessageNotification",
"McpError",
"MCPError",
"Notification",
"PingRequest",
"ProgressNotification",
Expand Down
39 changes: 14 additions & 25 deletions src/mcp/client/session_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from mcp.client.stdio import StdioServerParameters
from mcp.client.streamable_http import streamable_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


Expand Down Expand Up @@ -216,11 +216,9 @@ async def disconnect_from_server(self, session: mcp.ClientSession) -> None:
session_known_for_stack = session in self._session_exit_stacks

if not session_known_for_components and not session_known_for_stack:
raise McpError(
types.ErrorData(
code=types.INVALID_PARAMS,
message="Provided session is not managed or already disconnected.",
)
raise MCPError(
code=types.INVALID_PARAMS,
message="Provided session is not managed or already disconnected.",
)

if session_known_for_components: # pragma: no branch
Expand Down Expand Up @@ -352,7 +350,7 @@ async def _aggregate_components(self, server_info: types.Implementation, session
name = self._component_name(prompt.name, server_info)
prompts_temp[name] = prompt
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}")

# Query the server for its resources and aggregate to list.
Expand All @@ -362,7 +360,7 @@ async def _aggregate_components(self, server_info: types.Implementation, session
name = self._component_name(resource.name, server_info)
resources_temp[name] = resource
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}")

# Query the server for its tools and aggregate to list.
Expand All @@ -373,7 +371,7 @@ async def _aggregate_components(self, server_info: types.Implementation, session
tools_temp[name] = tool
tool_to_session_temp[name] = session
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}")

# Clean up exit stack for session if we couldn't retrieve anything
Expand All @@ -384,28 +382,19 @@ async def _aggregate_components(self, server_info: types.Implementation, session
# Check for duplicates.
matching_prompts = prompts_temp.keys() & self._prompts.keys()
if matching_prompts:
raise McpError( # pragma: no cover
types.ErrorData(
code=types.INVALID_PARAMS,
message=f"{matching_prompts} already exist in group prompts.",
)
raise MCPError( # pragma: no cover
code=types.INVALID_PARAMS,
message=f"{matching_prompts} already exist in group prompts.",
)
matching_resources = resources_temp.keys() & self._resources.keys()
if matching_resources:
raise McpError( # pragma: no cover
types.ErrorData(
code=types.INVALID_PARAMS,
message=f"{matching_resources} already exist in group resources.",
)
raise MCPError( # pragma: no cover
code=types.INVALID_PARAMS,
message=f"{matching_resources} already exist in group resources.",
)
matching_tools = tools_temp.keys() & self._tools.keys()
if matching_tools:
raise McpError(
types.ErrorData(
code=types.INVALID_PARAMS,
message=f"{matching_tools} already exist in group tools.",
)
)
raise MCPError(code=types.INVALID_PARAMS, message=f"{matching_tools} already exist in group tools.")

# Aggregate components.
self._sessions[session] = component_names
Expand Down
20 changes: 7 additions & 13 deletions src/mcp/server/experimental/request_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from mcp.server.experimental.task_context import ServerTaskContext
from mcp.server.experimental.task_support import TaskSupport
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.types import (
METHOD_NOT_FOUND,
Expand Down Expand Up @@ -72,32 +72,26 @@ def validate_task_mode(
Args:
tool_task_mode: The tool's execution.taskSupport value
("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:
None if valid, ErrorData if invalid and raise_error=False

Raises:
McpError: If invalid and raise_error=True
MCPError: If invalid and raise_error=True
"""

mode = tool_task_mode or TASK_FORBIDDEN

error: ErrorData | None = None

if mode == TASK_REQUIRED and not self.is_task:
error = ErrorData(
code=METHOD_NOT_FOUND,
message="This tool requires task-augmented invocation",
)
error = ErrorData(code=METHOD_NOT_FOUND, message="This tool requires task-augmented invocation")
elif mode == TASK_FORBIDDEN and self.is_task:
error = ErrorData(
code=METHOD_NOT_FOUND,
message="This tool does not support task-augmented invocation",
)
error = ErrorData(code=METHOD_NOT_FOUND, message="This tool does not support task-augmented invocation")

if error is not None and raise_error:
raise McpError(error)
raise MCPError(code=METHOD_NOT_FOUND, message=error.message)

return error

Expand All @@ -113,7 +107,7 @@ def validate_for_tool(

Args:
tool: The Tool definition
raise_error: If True, raises McpError on validation failure.
raise_error: If True, raises MCPError on validation failure.

Returns:
None if valid, ErrorData if invalid and raise_error=False
Expand Down
4 changes: 2 additions & 2 deletions src/mcp/server/experimental/session_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ async def elicit_as_task(
The client's elicitation response

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
require_task_augmented_elicitation(client_caps)
Expand Down Expand Up @@ -174,7 +174,7 @@ async def create_message_as_task(
The sampling result from the client

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
"""
client_caps = self._session.client_params.capabilities if self._session.client_params else None
Expand Down
27 changes: 8 additions & 19 deletions src/mcp/server/experimental/task_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from mcp.server.experimental.task_result_handler import TaskResultHandler
from mcp.server.session import ServerSession
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 (
require_task_augmented_elicitation,
require_task_augmented_sampling,
Expand All @@ -32,7 +32,6 @@
ElicitationCapability,
ElicitRequestedSchema,
ElicitResult,
ErrorData,
IncludeContext,
ModelPreferences,
RequestId,
Expand Down Expand Up @@ -173,22 +172,12 @@ async def _send_notification(self) -> None:
def _check_elicitation_capability(self) -> None:
"""Check if the client supports elicitation."""
if not self._session.check_client_capability(ClientCapabilities(elicitation=ElicitationCapability())):
raise McpError(
ErrorData(
code=INVALID_REQUEST,
message="Client does not support elicitation capability",
)
)
raise MCPError(code=INVALID_REQUEST, message="Client does not support elicitation capability")

def _check_sampling_capability(self) -> None:
"""Check if the client supports sampling."""
if not self._session.check_client_capability(ClientCapabilities(sampling=SamplingCapability())):
raise McpError(
ErrorData(
code=INVALID_REQUEST,
message="Client does not support sampling capability",
)
)
raise MCPError(code=INVALID_REQUEST, message="Client does not support sampling capability")

async def elicit(
self,
Expand All @@ -213,7 +202,7 @@ async def elicit(
The client's response

Raises:
McpError: If client doesn't support elicitation capability
MCPError: If client doesn't support elicitation capability
"""
self._check_elicitation_capability()

Expand Down Expand Up @@ -281,7 +270,7 @@ async def elicit_url(
The client's response indicating acceptance, decline, or cancellation

Raises:
McpError: If client doesn't support elicitation capability
MCPError: If client doesn't support elicitation capability
RuntimeError: If handler is not configured
"""
self._check_elicitation_capability()
Expand Down Expand Up @@ -361,7 +350,7 @@ async def create_message(
The sampling result from the client

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
"""
self._check_sampling_capability()
Expand Down Expand Up @@ -436,7 +425,7 @@ async def elicit_as_task(
The client's elicitation response

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
"""
client_caps = self._session.client_params.capabilities if self._session.client_params else None
Expand Down Expand Up @@ -529,7 +518,7 @@ async def create_message_as_task(
The sampling result from the client

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
RuntimeError: If handler is not configured
"""
Expand Down
6 changes: 3 additions & 3 deletions src/mcp/server/experimental/task_result_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import anyio

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.message_queue import TaskMessageQueue
from mcp.shared.experimental.tasks.resolver import Resolver
Expand Down Expand Up @@ -106,7 +106,7 @@ async def handle(
while True:
task = await self._store.get_task(task_id)
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)

Expand Down Expand Up @@ -216,6 +216,6 @@ def route_error(self, request_id: RequestId, error: ErrorData) -> bool:
"""
resolver = self._pending_requests.pop(request_id, None)
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 False
Loading