Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this makes sense to be consistent. Although it does go against the google style guide: https://google.github.io/styleguide/pyguide.html#3162-naming-conventions

but since we're not strictly following that then it's not really an issue :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are you seeing the lack of consistency? MCP is an acronymn, so that's why it would be uppercased.

Copy link
Contributor

@maxisbey maxisbey Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh no, I was agreeing with you that it makes sense for consistency, as you wrote "The McpError exception class has been renamed to MCPError for consistent naming".

I was just linking the google style guide saying to use CapWords which I thiiink also apply to acronyms? but I don't think we should follow that anyway


**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`

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=error.code, 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