mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-21 13:04:18 +02:00
Rename McpError to MCPError and convert construction sites
This commit is contained in:
parent
2f49f9f7c0
commit
fdac9f9f2e
39 changed files with 217 additions and 281 deletions
|
|
@ -11,7 +11,7 @@ from urllib.parse import urlparse
|
|||
import cyclopts
|
||||
import mcp_types
|
||||
import pydantic_core
|
||||
from mcp import McpError
|
||||
from mcp import MCPError
|
||||
from rich.console import Console
|
||||
|
||||
from fastmcp.cli.client import _build_client, resolve_server_spec
|
||||
|
|
@ -754,7 +754,7 @@ async def generate_cli_command(
|
|||
f"[dim]Discovered {len(tools)} tool(s) from {server_spec}[/dim]"
|
||||
)
|
||||
|
||||
except (RuntimeError, TimeoutError, McpError, OSError) as exc:
|
||||
except (RuntimeError, TimeoutError, MCPError, OSError) as exc:
|
||||
console.print(f"[bold red]Error:[/bold red] Could not connect: {exc}")
|
||||
sys.exit(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import anyio
|
|||
import httpx
|
||||
import mcp_types
|
||||
from exceptiongroup import catch
|
||||
from mcp import ClientSession, McpError
|
||||
from mcp import ClientSession, MCPError
|
||||
from mcp_types import GetTaskResult, TaskStatusNotification
|
||||
from pydantic import AnyUrl
|
||||
|
||||
|
|
@ -617,7 +617,7 @@ class Client(
|
|||
"Session task completed without exception but connection failed"
|
||||
)
|
||||
# Preserve specific exception types that clients may want to handle
|
||||
if isinstance(exception, httpx.HTTPStatusError | McpError):
|
||||
if isinstance(exception, httpx.HTTPStatusError | MCPError):
|
||||
raise exception
|
||||
raise RuntimeError(
|
||||
f"Client failed to connect: {exception}"
|
||||
|
|
@ -865,7 +865,7 @@ class Client(
|
|||
|
||||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
logger.debug(f"[{self.name}] called complete: {ref}")
|
||||
|
||||
|
|
@ -895,7 +895,7 @@ class Client(
|
|||
|
||||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
result = await self.complete_mcp(
|
||||
ref=ref, argument=argument, context_arguments=context_arguments
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class ClientPromptsMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
with client_span(
|
||||
"prompts/list",
|
||||
|
|
@ -80,7 +80,7 @@ class ClientPromptsMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If the page limit is reached before pagination completes.
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
all_prompts: list[mcp_types.Prompt] = []
|
||||
cursor: str | None = None
|
||||
|
|
@ -129,7 +129,7 @@ class ClientPromptsMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
with client_span(
|
||||
f"prompts/get {name}",
|
||||
|
|
@ -232,7 +232,7 @@ class ClientPromptsMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
# Merge version into request-level meta (not arguments)
|
||||
request_meta = dict(meta) if meta else {}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class ClientResourcesMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
with client_span(
|
||||
"resources/list",
|
||||
|
|
@ -79,7 +79,7 @@ class ClientResourcesMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If the page limit is reached before pagination completes.
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
all_resources: list[mcp_types.Resource] = []
|
||||
cursor: str | None = None
|
||||
|
|
@ -122,7 +122,7 @@ class ClientResourcesMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
with client_span(
|
||||
"resources/templates/list",
|
||||
|
|
@ -156,7 +156,7 @@ class ClientResourcesMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If the page limit is reached before pagination completes.
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
all_templates: list[mcp_types.ResourceTemplate] = []
|
||||
cursor: str | None = None
|
||||
|
|
@ -201,7 +201,7 @@ class ClientResourcesMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
uri_str = str(uri)
|
||||
with client_span(
|
||||
|
|
@ -293,7 +293,7 @@ class ClientResourcesMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
# Merge version into request-level meta (not arguments)
|
||||
request_meta = dict(meta) if meta else {}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
|||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import mcp_types
|
||||
from mcp import McpError
|
||||
from mcp import MCPError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fastmcp.client.client import Client
|
||||
|
|
@ -43,7 +43,7 @@ class ClientTaskManagementMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If client not connected
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
request = GetTaskRequest(params=GetTaskRequestParams(taskId=task_id))
|
||||
return await self._await_with_session_monitoring(
|
||||
|
|
@ -67,7 +67,7 @@ class ClientTaskManagementMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If client not connected, task not found, or task failed
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
request = GetTaskPayloadRequest(
|
||||
params=GetTaskPayloadRequestParams(taskId=task_id)
|
||||
|
|
@ -104,7 +104,7 @@ class ClientTaskManagementMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If client not connected
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
# Send protocol request
|
||||
params = PaginatedRequestParams(cursor=cursor, limit=limit) # type: ignore[call-arg] # Optional field in MCP SDK # ty:ignore[unknown-argument]
|
||||
|
|
@ -126,7 +126,7 @@ class ClientTaskManagementMixin:
|
|||
try:
|
||||
status = await self.get_task_status(task_id)
|
||||
tasks.append(status.model_dump(by_alias=True))
|
||||
except McpError:
|
||||
except MCPError:
|
||||
# Task may have expired or been deleted, skip it
|
||||
continue
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ class ClientTaskManagementMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If task doesn't exist
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
request = CancelTaskRequest(params=CancelTaskRequestParams(taskId=task_id))
|
||||
return await self._await_with_session_monitoring(
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class ClientToolsMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
with client_span(
|
||||
"tools/list",
|
||||
|
|
@ -84,7 +84,7 @@ class ClientToolsMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If the page limit is reached before pagination completes.
|
||||
McpError: If the request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
all_tools: list[mcp_types.Tool] = []
|
||||
cursor: str | None = None
|
||||
|
|
@ -144,7 +144,7 @@ class ClientToolsMixin:
|
|||
|
||||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
McpError: If the tool call requests results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the tool call requests results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
with client_span(
|
||||
f"tools/call {name}",
|
||||
|
|
@ -281,7 +281,7 @@ class ClientToolsMixin:
|
|||
|
||||
Raises:
|
||||
ToolError: If the tool call results in an error.
|
||||
McpError: If the tool call request results in a TimeoutError | JSONRPCError
|
||||
MCPError: If the tool call request results in a TimeoutError | JSONRPCError
|
||||
RuntimeError: If called while the client is not connected.
|
||||
"""
|
||||
# Merge version into request-level meta (not arguments)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
import logging
|
||||
|
||||
try:
|
||||
from mcp import McpError
|
||||
from mcp import MCPError
|
||||
except ImportError:
|
||||
|
||||
class McpError(Exception): # type: ignore[no-redef]
|
||||
class MCPError(Exception): # type: ignore[no-redef]
|
||||
"""Fallback used when MCP dependencies are not installed."""
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any, cast
|
|||
import anyio
|
||||
import mcp_types
|
||||
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
||||
from mcp import LoggingLevel, McpError
|
||||
from mcp import LoggingLevel, MCPError
|
||||
from mcp.server.lowlevel.server import (
|
||||
LifespanResultT,
|
||||
NotificationOptions,
|
||||
|
|
@ -135,8 +135,8 @@ class MiddlewareServerSession(ServerSession):
|
|||
mw_context,
|
||||
cast("CallNext[Any, Any]", call_original_handler),
|
||||
)
|
||||
except McpError as e:
|
||||
# McpError can be thrown from middleware in `on_initialize`
|
||||
except MCPError as e:
|
||||
# MCPError can be thrown from middleware in `on_initialize`
|
||||
# send the error to responder.
|
||||
if not responder._completed:
|
||||
with responder:
|
||||
|
|
@ -144,7 +144,7 @@ class MiddlewareServerSession(ServerSession):
|
|||
else:
|
||||
# Don't re-raise: prevents responding to initialize request twice
|
||||
logger.warning(
|
||||
"Received McpError but responder is already completed. "
|
||||
"Received MCPError but responder is already completed. "
|
||||
"Cannot send error response as response was already sent.",
|
||||
exc_info=e,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ from collections.abc import Callable
|
|||
from typing import Any
|
||||
|
||||
import anyio
|
||||
from mcp import McpError
|
||||
from mcp_types import ErrorData
|
||||
from mcp import MCPError
|
||||
|
||||
from fastmcp.exceptions import NotFoundError
|
||||
|
||||
|
|
@ -47,7 +46,7 @@ class ErrorHandlingMiddleware(Middleware):
|
|||
logger: Logger instance for error logging. If None, uses 'fastmcp.errors'
|
||||
include_traceback: Whether to include full traceback in error logs
|
||||
error_callback: Optional callback function called for each error
|
||||
transform_errors: Whether to transform non-MCP errors to McpError
|
||||
transform_errors: Whether to transform non-MCP errors to MCPError
|
||||
"""
|
||||
self.logger = logger or logging.getLogger("fastmcp.errors")
|
||||
self.include_traceback = include_traceback
|
||||
|
|
@ -82,7 +81,7 @@ class ErrorHandlingMiddleware(Middleware):
|
|||
self, error: Exception, context: MiddlewareContext
|
||||
) -> Exception:
|
||||
"""Transform non-MCP errors to proper MCP errors."""
|
||||
if isinstance(error, McpError):
|
||||
if isinstance(error, MCPError):
|
||||
return error
|
||||
|
||||
if not self.transform_errors:
|
||||
|
|
@ -92,30 +91,20 @@ class ErrorHandlingMiddleware(Middleware):
|
|||
error_type = type(error.__cause__) if error.__cause__ else type(error)
|
||||
|
||||
if error_type in (ValueError, TypeError):
|
||||
return McpError(
|
||||
ErrorData(code=-32602, message=f"Invalid params: {error!s}")
|
||||
)
|
||||
return MCPError(code=-32602, message=f"Invalid params: {error!s}")
|
||||
elif error_type in (FileNotFoundError, KeyError, NotFoundError):
|
||||
# MCP spec defines -32002 specifically for resource not found
|
||||
method = context.method or ""
|
||||
if method.startswith("resources/"):
|
||||
return McpError(
|
||||
ErrorData(code=-32002, message=f"Resource not found: {error!s}")
|
||||
)
|
||||
return McpError(ErrorData(code=-32001, message=f"Not found: {error!s}"))
|
||||
return MCPError(code=-32002, message=f"Resource not found: {error!s}")
|
||||
return MCPError(code=-32001, message=f"Not found: {error!s}")
|
||||
elif error_type is PermissionError:
|
||||
return McpError(
|
||||
ErrorData(code=-32000, message=f"Permission denied: {error!s}")
|
||||
)
|
||||
return MCPError(code=-32000, message=f"Permission denied: {error!s}")
|
||||
# asyncio.TimeoutError is a subclass of TimeoutError in Python 3.10, alias in 3.11+
|
||||
elif error_type in (TimeoutError, asyncio.TimeoutError):
|
||||
return McpError(
|
||||
ErrorData(code=-32000, message=f"Request timeout: {error!s}")
|
||||
)
|
||||
return MCPError(code=-32000, message=f"Request timeout: {error!s}")
|
||||
else:
|
||||
return McpError(
|
||||
ErrorData(code=-32603, message=f"Internal error: {error!s}")
|
||||
)
|
||||
return MCPError(code=-32603, message=f"Internal error: {error!s}")
|
||||
|
||||
async def on_message(self, context: MiddlewareContext, call_next: CallNext) -> Any:
|
||||
"""Handle errors for all messages."""
|
||||
|
|
|
|||
|
|
@ -7,17 +7,16 @@ from collections.abc import Awaitable, Callable
|
|||
from typing import Any, cast
|
||||
|
||||
import anyio
|
||||
from mcp import McpError
|
||||
from mcp_types import ErrorData
|
||||
from mcp import MCPError
|
||||
|
||||
from .middleware import CallNext, Middleware, MiddlewareContext
|
||||
|
||||
|
||||
class RateLimitError(McpError):
|
||||
class RateLimitError(MCPError):
|
||||
"""Error raised when rate limit is exceeded."""
|
||||
|
||||
def __init__(self, message: str = "Rate limit exceeded"):
|
||||
super().__init__(ErrorData(code=-32000, message=message))
|
||||
super().__init__(code=-32000, message=message)
|
||||
|
||||
|
||||
class TokenBucketRateLimiter:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from collections.abc import Awaitable, Callable, Sequence
|
|||
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
||||
|
||||
import mcp_types
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp_types import ContentBlock
|
||||
from pydantic import AnyUrl
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ def _apply_pagination(
|
|||
cursor: str | None,
|
||||
page_size: int | None,
|
||||
) -> tuple[list[PaginateT], str | None]:
|
||||
"""Apply pagination to items, raising McpError for invalid cursors.
|
||||
"""Apply pagination to items, raising MCPError for invalid cursors.
|
||||
|
||||
If page_size is None, returns all items without pagination.
|
||||
"""
|
||||
|
|
@ -38,7 +38,7 @@ def _apply_pagination(
|
|||
try:
|
||||
return paginate_sequence(items, cursor, page_size)
|
||||
except ValueError as e:
|
||||
raise McpError(mcp_types.ErrorData(code=-32602, message=str(e))) from e
|
||||
raise MCPError(code=-32602, message=str(e)) from e
|
||||
|
||||
|
||||
class MCPOperationsMixin:
|
||||
|
|
@ -291,15 +291,11 @@ class MCPOperationsMixin:
|
|||
return result
|
||||
return result.to_mcp_result(uri)
|
||||
except DisabledError as e:
|
||||
raise McpError(
|
||||
mcp_types.ErrorData(
|
||||
code=-32002, message=f"Resource not found: {str(uri)!r}"
|
||||
)
|
||||
raise MCPError(
|
||||
code=-32002, message=f"Resource not found: {str(uri)!r}"
|
||||
) from e
|
||||
except NotFoundError as e:
|
||||
raise McpError(
|
||||
mcp_types.ErrorData(code=-32002, message=f"Resource not found: {e}")
|
||||
) from e
|
||||
raise MCPError(code=-32002, message=f"Resource not found: {e}") from e
|
||||
|
||||
async def _get_prompt_mcp(
|
||||
self, name: str, arguments: dict[str, Any] | None
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from mcp import ServerSession
|
|||
from mcp.client.session import ClientSession
|
||||
from mcp.server.lowlevel.server import request_ctx
|
||||
from mcp.shared.context import LifespanContextT, RequestContext
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp_types import (
|
||||
METHOD_NOT_FOUND,
|
||||
BlobResourceContents,
|
||||
|
|
@ -66,12 +66,10 @@ logger = get_logger(__name__)
|
|||
ClientFactoryT = Callable[[], Client] | Callable[[], Awaitable[Client]]
|
||||
|
||||
|
||||
def _proxy_upstream_error(error: Exception) -> McpError:
|
||||
return McpError(
|
||||
mcp_types.ErrorData(
|
||||
code=mcp_types.INTERNAL_ERROR,
|
||||
message=str(error),
|
||||
)
|
||||
def _proxy_upstream_error(error: Exception) -> MCPError:
|
||||
return MCPError(
|
||||
code=mcp_types.INTERNAL_ERROR,
|
||||
message=str(error),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -98,7 +96,7 @@ class ProxyInitializeMiddleware(Middleware):
|
|||
)
|
||||
async with client:
|
||||
await client.initialize()
|
||||
except McpError:
|
||||
except MCPError:
|
||||
raise
|
||||
except (
|
||||
RuntimeError,
|
||||
|
|
@ -635,7 +633,7 @@ class ProxyProvider(Provider):
|
|||
tools = [
|
||||
ProxyTool.from_mcp_tool(self.client_factory, t) for t in mcp_tools
|
||||
]
|
||||
except McpError as e:
|
||||
except MCPError as e:
|
||||
if e.error.code == METHOD_NOT_FOUND:
|
||||
tools = []
|
||||
else:
|
||||
|
|
@ -672,7 +670,7 @@ class ProxyProvider(Provider):
|
|||
ProxyResource.from_mcp_resource(self.client_factory, r)
|
||||
for r in mcp_resources
|
||||
]
|
||||
except McpError as e:
|
||||
except MCPError as e:
|
||||
if e.error.code == METHOD_NOT_FOUND:
|
||||
resources = []
|
||||
else:
|
||||
|
|
@ -709,7 +707,7 @@ class ProxyProvider(Provider):
|
|||
ProxyTemplate.from_mcp_template(self.client_factory, t)
|
||||
for t in mcp_templates
|
||||
]
|
||||
except McpError as e:
|
||||
except MCPError as e:
|
||||
if e.error.code == METHOD_NOT_FOUND:
|
||||
templates = []
|
||||
else:
|
||||
|
|
@ -746,7 +744,7 @@ class ProxyProvider(Provider):
|
|||
ProxyPrompt.from_mcp_prompt(self.client_factory, p)
|
||||
for p in mcp_prompts
|
||||
]
|
||||
except McpError as e:
|
||||
except MCPError as e:
|
||||
if e.error.code == METHOD_NOT_FOUND:
|
||||
prompts = []
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ from key_value.aio.adapters.pydantic import PydanticAdapter
|
|||
from key_value.aio.protocols import AsyncKeyValue
|
||||
from key_value.aio.stores.memory import MemoryStore
|
||||
from mcp.server.lowlevel.server import LifespanResultT
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp_types import (
|
||||
Annotations,
|
||||
AnyFunction,
|
||||
|
|
@ -1473,7 +1473,7 @@ class FastMCP(
|
|||
exc_info=True,
|
||||
)
|
||||
raise
|
||||
except McpError:
|
||||
except MCPError:
|
||||
logger.exception(f"Error reading resource {uri!r}")
|
||||
raise
|
||||
except Exception as e:
|
||||
|
|
@ -1517,7 +1517,7 @@ class FastMCP(
|
|||
e.log_level, f"Error reading resource {uri!r}", exc_info=True
|
||||
)
|
||||
raise
|
||||
except McpError:
|
||||
except MCPError:
|
||||
logger.exception(f"Error reading resource {uri!r}")
|
||||
raise
|
||||
except Exception as e:
|
||||
|
|
@ -1638,7 +1638,7 @@ class FastMCP(
|
|||
e.log_level, f"Error rendering prompt {name!r}", exc_info=True
|
||||
)
|
||||
raise
|
||||
except McpError:
|
||||
except MCPError:
|
||||
logger.exception(f"Error rendering prompt {name!r}")
|
||||
raise
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ async def elicit_for_task(
|
|||
|
||||
Raises:
|
||||
RuntimeError: If Docket is not available
|
||||
McpError: If the elicitation request fails
|
||||
MCPError: If the elicitation request fails
|
||||
"""
|
||||
docket = fastmcp._docket
|
||||
if docket is None:
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ from datetime import datetime, timezone
|
|||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
import mcp_types
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp_types import INTERNAL_ERROR, ErrorData
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp_types import INTERNAL_ERROR
|
||||
|
||||
from fastmcp.server.dependencies import (
|
||||
_current_docket,
|
||||
|
|
@ -95,11 +95,9 @@ async def submit_to_docket(
|
|||
# mounted children (whose parent server owns the Docket instance).
|
||||
docket = ctx.fastmcp._docket or _current_docket.get()
|
||||
if docket is None:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=INTERNAL_ERROR,
|
||||
message="Background tasks require a running FastMCP server context",
|
||||
)
|
||||
raise MCPError(
|
||||
code=INTERNAL_ERROR,
|
||||
message="Background tasks require a running FastMCP server context",
|
||||
)
|
||||
|
||||
# Register the current server so background workers resolve
|
||||
|
|
|
|||
|
|
@ -13,12 +13,11 @@ from typing import TYPE_CHECKING, Any, Literal
|
|||
|
||||
import mcp_types
|
||||
from docket.execution import ExecutionState
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp_types import (
|
||||
INTERNAL_ERROR,
|
||||
INVALID_PARAMS,
|
||||
CancelTaskResult,
|
||||
ErrorData,
|
||||
GetTaskResult,
|
||||
ListTasksResult,
|
||||
)
|
||||
|
|
@ -88,7 +87,7 @@ async def _lookup_task_execution(
|
|||
Tuple of (execution, created_at, poll_interval_ms)
|
||||
|
||||
Raises:
|
||||
McpError: If task not found or execution not found
|
||||
MCPError: If task not found or execution not found
|
||||
"""
|
||||
prefix = task_redis_prefix(task_scope)
|
||||
task_meta_key = docket.key(f"{prefix}:{client_task_id}")
|
||||
|
|
@ -104,18 +103,14 @@ async def _lookup_task_execution(
|
|||
# Decode and validate task_key
|
||||
task_key = task_key_bytes.decode("utf-8") if task_key_bytes else None
|
||||
if not task_key:
|
||||
raise McpError(
|
||||
ErrorData(code=INVALID_PARAMS, message=f"Task {client_task_id} not found")
|
||||
)
|
||||
raise MCPError(code=INVALID_PARAMS, message=f"Task {client_task_id} not found")
|
||||
|
||||
# Get execution
|
||||
execution = await docket.get_execution(task_key)
|
||||
if not execution:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=INVALID_PARAMS,
|
||||
message=f"Task {client_task_id} execution not found",
|
||||
)
|
||||
raise MCPError(
|
||||
code=INVALID_PARAMS,
|
||||
message=f"Task {client_task_id} execution not found",
|
||||
)
|
||||
|
||||
# Parse metadata with defaults
|
||||
|
|
@ -145,10 +140,8 @@ async def tasks_get_handler(server: FastMCP, params: dict[str, Any]) -> GetTaskR
|
|||
async with fastmcp.server.context.Context(fastmcp=server):
|
||||
client_task_id = params.get("taskId")
|
||||
if not client_task_id:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=INVALID_PARAMS, message="Missing required parameter: taskId"
|
||||
)
|
||||
raise MCPError(
|
||||
code=INVALID_PARAMS, message="Missing required parameter: taskId"
|
||||
)
|
||||
|
||||
# Get authorization scope for task lookup
|
||||
|
|
@ -157,11 +150,9 @@ async def tasks_get_handler(server: FastMCP, params: dict[str, Any]) -> GetTaskR
|
|||
# Get Docket instance
|
||||
docket = server._docket
|
||||
if docket is None:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=INTERNAL_ERROR,
|
||||
message="Background tasks require Docket",
|
||||
)
|
||||
raise MCPError(
|
||||
code=INTERNAL_ERROR,
|
||||
message="Background tasks require Docket",
|
||||
)
|
||||
|
||||
# Look up task execution and metadata
|
||||
|
|
@ -232,10 +223,8 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
|
|||
async with fastmcp.server.context.Context(fastmcp=server):
|
||||
client_task_id = params.get("taskId")
|
||||
if not client_task_id:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=INVALID_PARAMS, message="Missing required parameter: taskId"
|
||||
)
|
||||
raise MCPError(
|
||||
code=INVALID_PARAMS, message="Missing required parameter: taskId"
|
||||
)
|
||||
|
||||
# Get authorization scope for task lookup
|
||||
|
|
@ -244,11 +233,9 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
|
|||
# Get execution from Docket (use instance attribute for cross-task access)
|
||||
docket = server._docket
|
||||
if docket is None:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=INTERNAL_ERROR,
|
||||
message="Background tasks require Docket",
|
||||
)
|
||||
raise MCPError(
|
||||
code=INTERNAL_ERROR,
|
||||
message="Background tasks require Docket",
|
||||
)
|
||||
|
||||
# Look up full task key from Redis
|
||||
|
|
@ -259,20 +246,16 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
|
|||
task_key = None if task_key_bytes is None else task_key_bytes.decode("utf-8")
|
||||
|
||||
if task_key is None:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=INVALID_PARAMS,
|
||||
message=f"Invalid taskId: {client_task_id} not found",
|
||||
)
|
||||
raise MCPError(
|
||||
code=INVALID_PARAMS,
|
||||
message=f"Invalid taskId: {client_task_id} not found",
|
||||
)
|
||||
|
||||
execution = await docket.get_execution(task_key)
|
||||
if execution is None:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=INVALID_PARAMS,
|
||||
message=f"Invalid taskId: {client_task_id} not found",
|
||||
)
|
||||
raise MCPError(
|
||||
code=INVALID_PARAMS,
|
||||
message=f"Invalid taskId: {client_task_id} not found",
|
||||
)
|
||||
|
||||
# Sync state from Redis
|
||||
|
|
@ -282,11 +265,9 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
|
|||
state_map = DOCKET_TO_MCP_STATE
|
||||
if execution.state not in (ExecutionState.COMPLETED, ExecutionState.FAILED):
|
||||
mcp_state = state_map.get(execution.state, "failed")
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=INVALID_PARAMS,
|
||||
message=f"Task not completed yet (current state: {mcp_state})",
|
||||
)
|
||||
raise MCPError(
|
||||
code=INVALID_PARAMS,
|
||||
message=f"Task not completed yet (current state: {mcp_state})",
|
||||
)
|
||||
|
||||
# Get result from Docket
|
||||
|
|
@ -331,11 +312,9 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
|
|||
component = None
|
||||
|
||||
if component is None:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=INTERNAL_ERROR,
|
||||
message=f"Component not found for task: {component_key}",
|
||||
)
|
||||
raise MCPError(
|
||||
code=INTERNAL_ERROR,
|
||||
message=f"Component not found for task: {component_key}",
|
||||
)
|
||||
|
||||
# Build related-task metadata
|
||||
|
|
@ -390,11 +369,9 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
|
|||
return mcp_result
|
||||
|
||||
else:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=INTERNAL_ERROR,
|
||||
message=f"Internal error: Unknown component type: {type(component).__name__}",
|
||||
)
|
||||
raise MCPError(
|
||||
code=INTERNAL_ERROR,
|
||||
message=f"Internal error: Unknown component type: {type(component).__name__}",
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -433,10 +410,8 @@ async def tasks_cancel_handler(
|
|||
async with fastmcp.server.context.Context(fastmcp=server):
|
||||
client_task_id = params.get("taskId")
|
||||
if not client_task_id:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=INVALID_PARAMS, message="Missing required parameter: taskId"
|
||||
)
|
||||
raise MCPError(
|
||||
code=INVALID_PARAMS, message="Missing required parameter: taskId"
|
||||
)
|
||||
|
||||
# Get authorization scope for task lookup
|
||||
|
|
@ -445,11 +420,9 @@ async def tasks_cancel_handler(
|
|||
# Get Docket instance
|
||||
docket = server._docket
|
||||
if docket is None:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=INTERNAL_ERROR,
|
||||
message="Background tasks require Docket",
|
||||
)
|
||||
raise MCPError(
|
||||
code=INTERNAL_ERROR,
|
||||
message="Background tasks require Docket",
|
||||
)
|
||||
|
||||
# Look up task execution and metadata
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ from __future__ import annotations
|
|||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
import mcp_types
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp_types import METHOD_NOT_FOUND, ErrorData
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp_types import METHOD_NOT_FOUND
|
||||
|
||||
from fastmcp.server.tasks.config import TaskMeta
|
||||
from fastmcp.server.tasks.handlers import submit_to_docket
|
||||
|
|
@ -41,7 +41,7 @@ async def check_background_task(
|
|||
CreateTaskResult if submitted to docket, None for sync execution
|
||||
|
||||
Raises:
|
||||
McpError: If mode="required" but no task metadata, or mode="forbidden"
|
||||
MCPError: If mode="required" but no task metadata, or mode="forbidden"
|
||||
but task metadata is present
|
||||
"""
|
||||
task_config = component.task_config
|
||||
|
|
@ -51,20 +51,16 @@ async def check_background_task(
|
|||
|
||||
# Enforce mode="required" - must have task metadata
|
||||
if task_config.mode == "required" and not task_meta:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=METHOD_NOT_FOUND,
|
||||
message=f"{entity_label} requires task-augmented execution",
|
||||
)
|
||||
raise MCPError(
|
||||
code=METHOD_NOT_FOUND,
|
||||
message=f"{entity_label} requires task-augmented execution",
|
||||
)
|
||||
|
||||
# Enforce mode="forbidden" - cannot be called with task metadata
|
||||
if not task_config.supports_tasks() and task_meta:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=METHOD_NOT_FOUND,
|
||||
message=f"{entity_label} does not support task-augmented execution",
|
||||
)
|
||||
raise MCPError(
|
||||
code=METHOD_NOT_FOUND,
|
||||
message=f"{entity_label} does not support task-augmented execution",
|
||||
)
|
||||
|
||||
# No task metadata - synchronous execution
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ from typing import (
|
|||
)
|
||||
|
||||
import anyio
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp_types import ErrorData, Icon, ToolAnnotations
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp_types import Icon, ToolAnnotations
|
||||
from pydantic import Field, TypeAdapter
|
||||
from pydantic import ValidationError as PydanticValidationError
|
||||
from pydantic.json_schema import SkipJsonSchema
|
||||
|
|
@ -408,11 +408,9 @@ class FunctionTool(Tool):
|
|||
f"Consider using task=True for long-running operations. "
|
||||
f"See https://gofastmcp.com/servers/tasks"
|
||||
)
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=-32000,
|
||||
message=f"Tool '{self.name}' execution timed out after {self.timeout}s",
|
||||
)
|
||||
raise MCPError(
|
||||
code=-32000,
|
||||
message=f"Tool '{self.name}' execution timed out after {self.timeout}s",
|
||||
) from None
|
||||
else:
|
||||
result = await self._execute(type_adapter, exec_is_async, arguments)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ from collections.abc import Callable, Iterable, Mapping
|
|||
from typing import Any
|
||||
|
||||
import httpx
|
||||
import mcp_types
|
||||
from exceptiongroup import BaseExceptionGroup
|
||||
from mcp import McpError
|
||||
from mcp import MCPError
|
||||
|
||||
import fastmcp
|
||||
|
||||
|
|
@ -20,11 +19,9 @@ def iter_exc(group: BaseExceptionGroup):
|
|||
def _exception_handler(group: BaseExceptionGroup):
|
||||
for leaf in iter_exc(group):
|
||||
if isinstance(leaf, httpx.ConnectTimeout):
|
||||
raise McpError(
|
||||
error=mcp_types.ErrorData(
|
||||
code=httpx.codes.REQUEST_TIMEOUT,
|
||||
message="Timed out while waiting for response.",
|
||||
)
|
||||
raise MCPError(
|
||||
code=httpx.codes.REQUEST_TIMEOUT,
|
||||
message="Timed out while waiting for response.",
|
||||
)
|
||||
raise leaf
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from typing import Any, cast
|
|||
|
||||
import anyio
|
||||
import pytest
|
||||
from mcp import ClientSession, McpError
|
||||
from mcp import ClientSession, MCPError
|
||||
from mcp_types import TextContent
|
||||
from pydantic import AnyUrl
|
||||
|
||||
|
|
@ -284,7 +284,7 @@ async def test_server_deserialization_error():
|
|||
client = Client(transport=FastMCPTransport(server))
|
||||
|
||||
async with client:
|
||||
with pytest.raises(McpError, match="Could not convert argument"):
|
||||
with pytest.raises(MCPError, match="Could not convert argument"):
|
||||
await client.get_prompt(
|
||||
"strict_typed_prompt",
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""Client timeout tests."""
|
||||
|
||||
import pytest
|
||||
from mcp import McpError
|
||||
from mcp import MCPError
|
||||
|
||||
from fastmcp.client import Client
|
||||
from fastmcp.client.transports import FastMCPTransport
|
||||
|
|
@ -14,14 +14,14 @@ class TestTimeout:
|
|||
transport=FastMCPTransport(fastmcp_server), timeout=0.05
|
||||
) as client:
|
||||
with pytest.raises(
|
||||
McpError,
|
||||
MCPError,
|
||||
match="Timed out while waiting for response to ClientRequest. Waited 0.05 seconds",
|
||||
):
|
||||
await client.call_tool("sleep", {"seconds": 0.1})
|
||||
|
||||
async def test_timeout_tool_call(self, fastmcp_server: FastMCP):
|
||||
async with Client(transport=FastMCPTransport(fastmcp_server)) as client:
|
||||
with pytest.raises(McpError):
|
||||
with pytest.raises(MCPError):
|
||||
await client.call_tool("sleep", {"seconds": 0.1}, timeout=0.01)
|
||||
|
||||
async def test_timeout_tool_call_overrides_client_timeout(
|
||||
|
|
@ -31,7 +31,7 @@ class TestTimeout:
|
|||
transport=FastMCPTransport(fastmcp_server),
|
||||
timeout=2,
|
||||
) as client:
|
||||
with pytest.raises(McpError):
|
||||
with pytest.raises(MCPError):
|
||||
await client.call_tool("sleep", {"seconds": 0.1}, timeout=0.01)
|
||||
|
||||
async def test_timeout_tool_call_overrides_client_timeout_even_if_lower(
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ async def test_forbidden_mode_tool_caches_error_result():
|
|||
async def test_forbidden_mode_prompt_raises_error():
|
||||
"""Prompts with task=False (mode=forbidden) raise error."""
|
||||
import pytest
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
|
||||
mcp = FastMCP("test")
|
||||
|
||||
|
|
@ -171,15 +171,15 @@ async def test_forbidden_mode_prompt_raises_error():
|
|||
return "Immediate"
|
||||
|
||||
async with Client(mcp) as client:
|
||||
# Prompts with mode="forbidden" raise McpError when called with task=True
|
||||
with pytest.raises(McpError):
|
||||
# Prompts with mode="forbidden" raise MCPError when called with task=True
|
||||
with pytest.raises(MCPError):
|
||||
await client.get_prompt("non_task_prompt", task=True)
|
||||
|
||||
|
||||
async def test_forbidden_mode_resource_raises_error():
|
||||
"""Resources with task=False (mode=forbidden) raise error."""
|
||||
import pytest
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
|
||||
mcp = FastMCP("test")
|
||||
|
||||
|
|
@ -188,8 +188,8 @@ async def test_forbidden_mode_resource_raises_error():
|
|||
return "Immediate"
|
||||
|
||||
async with Client(mcp) as client:
|
||||
# Resources with mode="forbidden" raise McpError when called with task=True
|
||||
with pytest.raises(McpError):
|
||||
# Resources with mode="forbidden" raise MCPError when called with task=True
|
||||
with pytest.raises(MCPError):
|
||||
await client.read_resource("file://immediate.txt", task=True)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import json
|
|||
import sys
|
||||
|
||||
import pytest
|
||||
from mcp import McpError
|
||||
from mcp import MCPError
|
||||
from mcp_types import TextResourceContents
|
||||
|
||||
from fastmcp.client import Client
|
||||
|
|
@ -161,7 +161,7 @@ async def test_nested_sse_server_resolves_correctly(nested_sse_server: str):
|
|||
class TestTimeout:
|
||||
async def test_timeout(self, sse_server: str):
|
||||
with pytest.raises(
|
||||
McpError,
|
||||
MCPError,
|
||||
match="Timed out while waiting for response to ClientRequest. Waited 0.03 seconds",
|
||||
):
|
||||
async with Client(
|
||||
|
|
@ -172,7 +172,7 @@ class TestTimeout:
|
|||
|
||||
async def test_timeout_tool_call(self, sse_server: str):
|
||||
async with Client(transport=SSETransport(sse_server)) as client:
|
||||
with pytest.raises(McpError, match="Timed out"):
|
||||
with pytest.raises(MCPError, match="Timed out"):
|
||||
await client.call_tool("sleep", {"seconds": 0.1}, timeout=0.03)
|
||||
|
||||
async def test_timeout_tool_call_overrides_client_timeout_if_lower(
|
||||
|
|
@ -182,7 +182,7 @@ class TestTimeout:
|
|||
transport=SSETransport(sse_server),
|
||||
timeout=2,
|
||||
) as client:
|
||||
with pytest.raises(McpError, match="Timed out"):
|
||||
with pytest.raises(MCPError, match="Timed out"):
|
||||
await client.call_tool("sleep", {"seconds": 0.1}, timeout=0.03)
|
||||
|
||||
async def test_timeout_client_timeout_does_not_override_tool_call_timeout_if_lower(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from contextlib import suppress
|
|||
from unittest.mock import AsyncMock, call
|
||||
|
||||
import pytest
|
||||
from mcp import McpError
|
||||
from mcp import MCPError
|
||||
from mcp_types import TextResourceContents
|
||||
|
||||
from fastmcp import Context
|
||||
|
|
@ -265,8 +265,8 @@ async def test_nested_streamable_http_server_resolves_correctly(nested_server: s
|
|||
class TestTimeout:
|
||||
async def test_timeout(self, streamable_http_server: str):
|
||||
# note this transport behaves differently than others and raises
|
||||
# McpError from the *client* context
|
||||
with pytest.raises(McpError, match="Timed out"):
|
||||
# MCPError from the *client* context
|
||||
with pytest.raises(MCPError, match="Timed out"):
|
||||
async with Client(
|
||||
transport=StreamableHttpTransport(streamable_http_server),
|
||||
timeout=0.02,
|
||||
|
|
@ -277,7 +277,7 @@ class TestTimeout:
|
|||
async with Client(
|
||||
transport=StreamableHttpTransport(streamable_http_server),
|
||||
) as client:
|
||||
with pytest.raises(McpError):
|
||||
with pytest.raises(MCPError):
|
||||
await client.call_tool("sleep", {"seconds": 0.2}, timeout=0.1)
|
||||
|
||||
async def test_timeout_tool_call_overrides_client_timeout(
|
||||
|
|
@ -287,5 +287,5 @@ class TestTimeout:
|
|||
transport=StreamableHttpTransport(streamable_http_server),
|
||||
timeout=2,
|
||||
) as client:
|
||||
with pytest.raises(McpError):
|
||||
with pytest.raises(MCPError):
|
||||
await client.call_tool("sleep", {"seconds": 0.2}, timeout=0.1)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import json
|
|||
import os
|
||||
|
||||
import pytest
|
||||
from mcp import McpError
|
||||
from mcp import MCPError
|
||||
from mcp_types import Resource, TextContent, Tool
|
||||
|
||||
from fastmcp import Client
|
||||
|
|
@ -96,7 +96,7 @@ class TestGithubMCPRemote:
|
|||
"""Test calling a non-existing tool"""
|
||||
async with streamable_http_client:
|
||||
assert streamable_http_client.is_connected()
|
||||
with pytest.raises(McpError, match=r"unknown tool|tool not found"):
|
||||
with pytest.raises(MCPError, match=r"unknown tool|tool not found"):
|
||||
await streamable_http_client.call_tool("foo")
|
||||
|
||||
async def test_call_tool_list_commits(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import logging
|
|||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
from mcp import McpError
|
||||
from mcp import MCPError
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client import Client
|
||||
|
|
@ -106,9 +106,8 @@ class TestErrorHandlingMiddleware:
|
|||
def test_transform_error_mcp_error(self, mock_context):
|
||||
"""Test that MCP errors are not transformed."""
|
||||
middleware = ErrorHandlingMiddleware()
|
||||
from mcp_types import ErrorData
|
||||
|
||||
error = McpError(ErrorData(code=-32001, message="test error"))
|
||||
error = MCPError(code=-32001, message="test error")
|
||||
|
||||
result = middleware._transform_error(error, mock_context)
|
||||
|
||||
|
|
@ -130,7 +129,7 @@ class TestErrorHandlingMiddleware:
|
|||
|
||||
result = middleware._transform_error(error, mock_context)
|
||||
|
||||
assert isinstance(result, McpError)
|
||||
assert isinstance(result, MCPError)
|
||||
assert result.error.code == -32602
|
||||
assert "Invalid params: test error" in result.error.message
|
||||
|
||||
|
|
@ -146,7 +145,7 @@ class TestErrorHandlingMiddleware:
|
|||
]:
|
||||
result = middleware._transform_error(error, resource_context)
|
||||
|
||||
assert isinstance(result, McpError)
|
||||
assert isinstance(result, MCPError)
|
||||
assert result.error.code == -32002
|
||||
assert "Resource not found: test error" in result.error.message
|
||||
|
||||
|
|
@ -160,7 +159,7 @@ class TestErrorHandlingMiddleware:
|
|||
]:
|
||||
result = middleware._transform_error(error, mock_context)
|
||||
|
||||
assert isinstance(result, McpError)
|
||||
assert isinstance(result, MCPError)
|
||||
assert result.error.code == -32001
|
||||
assert "Not found: test error" in result.error.message
|
||||
|
||||
|
|
@ -171,7 +170,7 @@ class TestErrorHandlingMiddleware:
|
|||
|
||||
result = middleware._transform_error(error, mock_context)
|
||||
|
||||
assert isinstance(result, McpError)
|
||||
assert isinstance(result, MCPError)
|
||||
assert result.error.code == -32000
|
||||
assert "Permission denied: test error" in result.error.message
|
||||
|
||||
|
|
@ -182,7 +181,7 @@ class TestErrorHandlingMiddleware:
|
|||
|
||||
result = middleware._transform_error(error, mock_context)
|
||||
|
||||
assert isinstance(result, McpError)
|
||||
assert isinstance(result, MCPError)
|
||||
assert result.error.code == -32000
|
||||
assert "Request timeout: test error" in result.error.message
|
||||
|
||||
|
|
@ -193,7 +192,7 @@ class TestErrorHandlingMiddleware:
|
|||
|
||||
result = middleware._transform_error(error, mock_context)
|
||||
|
||||
assert isinstance(result, McpError)
|
||||
assert isinstance(result, MCPError)
|
||||
assert result.error.code == -32603
|
||||
assert "Internal error: test error" in result.error.message
|
||||
|
||||
|
|
@ -212,10 +211,10 @@ class TestErrorHandlingMiddleware:
|
|||
mock_call_next = AsyncMock(side_effect=ValueError("test error"))
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await middleware.on_message(mock_context, mock_call_next)
|
||||
|
||||
assert isinstance(exc_info.value, McpError)
|
||||
assert isinstance(exc_info.value, MCPError)
|
||||
assert exc_info.value.error.code == -32602
|
||||
assert "Invalid params: test error" in exc_info.value.error.message
|
||||
assert "Error in test_method: ValueError: test error" in caplog.text
|
||||
|
|
@ -228,10 +227,10 @@ class TestErrorHandlingMiddleware:
|
|||
mock_call_next = AsyncMock(side_effect=tool_error)
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await middleware.on_message(mock_context, mock_call_next)
|
||||
|
||||
assert isinstance(exc_info.value, McpError)
|
||||
assert isinstance(exc_info.value, MCPError)
|
||||
assert exc_info.value.error.code == -32602
|
||||
assert "Invalid params: test error" in exc_info.value.error.message
|
||||
assert "Error in test_method: ToolError: test error" in caplog.text
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ from typing import Any
|
|||
|
||||
import mcp_types as mt
|
||||
import pytest
|
||||
from mcp import McpError
|
||||
from mcp_types import ErrorData, TextContent
|
||||
from mcp import MCPError
|
||||
from mcp_types import TextContent
|
||||
|
||||
from fastmcp import Client, FastMCP
|
||||
from fastmcp.server.middleware import CallNext, Middleware, MiddlewareContext
|
||||
|
|
@ -299,7 +299,7 @@ async def test_middleware_can_access_initialize_result():
|
|||
|
||||
|
||||
async def test_middleware_mcp_error_during_initialization():
|
||||
"""Test that McpError raised in middleware during initialization is sent to client."""
|
||||
"""Test that MCPError raised in middleware during initialization is sent to client."""
|
||||
server = FastMCP("TestServer")
|
||||
|
||||
class ErrorThrowingMiddleware(Middleware):
|
||||
|
|
@ -308,15 +308,13 @@ async def test_middleware_mcp_error_during_initialization():
|
|||
context: MiddlewareContext[mt.InitializeRequest],
|
||||
call_next: CallNext[mt.InitializeRequest, mt.InitializeResult | None],
|
||||
) -> mt.InitializeResult | None:
|
||||
raise McpError(
|
||||
ErrorData(
|
||||
code=mt.INVALID_PARAMS, message="Invalid initialization parameters"
|
||||
)
|
||||
raise MCPError(
|
||||
code=mt.INVALID_PARAMS, message="Invalid initialization parameters"
|
||||
)
|
||||
|
||||
server.add_middleware(ErrorThrowingMiddleware())
|
||||
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
async with Client(server):
|
||||
pass
|
||||
|
||||
|
|
@ -325,7 +323,7 @@ async def test_middleware_mcp_error_during_initialization():
|
|||
|
||||
|
||||
async def test_middleware_mcp_error_before_call_next():
|
||||
"""Test McpError raised before calling next middleware."""
|
||||
"""Test MCPError raised before calling next middleware."""
|
||||
server = FastMCP("TestServer")
|
||||
|
||||
class EarlyErrorMiddleware(Middleware):
|
||||
|
|
@ -334,13 +332,11 @@ async def test_middleware_mcp_error_before_call_next():
|
|||
context: MiddlewareContext[mt.InitializeRequest],
|
||||
call_next: CallNext[mt.InitializeRequest, mt.InitializeResult | None],
|
||||
) -> mt.InitializeResult | None:
|
||||
raise McpError(
|
||||
ErrorData(code=mt.INVALID_REQUEST, message="Request validation failed")
|
||||
)
|
||||
raise MCPError(code=mt.INVALID_REQUEST, message="Request validation failed")
|
||||
|
||||
server.add_middleware(EarlyErrorMiddleware())
|
||||
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
async with Client(server):
|
||||
pass
|
||||
|
||||
|
|
@ -349,7 +345,7 @@ async def test_middleware_mcp_error_before_call_next():
|
|||
|
||||
|
||||
async def test_middleware_mcp_error_after_call_next():
|
||||
"""Test that McpError raised after call_next doesn't break the connection.
|
||||
"""Test that MCPError raised after call_next doesn't break the connection.
|
||||
|
||||
When an error is raised after call_next, the responder has already completed,
|
||||
so the error is caught but not sent to the responder (checked via _completed flag).
|
||||
|
|
@ -368,9 +364,7 @@ async def test_middleware_mcp_error_after_call_next():
|
|||
) -> mt.InitializeResult | None:
|
||||
await call_next(context)
|
||||
self.error_raised = True
|
||||
raise McpError(
|
||||
ErrorData(code=mt.INTERNAL_ERROR, message="Post-processing failed")
|
||||
)
|
||||
raise MCPError(code=mt.INTERNAL_ERROR, message="Post-processing failed")
|
||||
|
||||
middleware = PostProcessingErrorMiddleware()
|
||||
server.add_middleware(middleware)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from collections.abc import Generator
|
|||
from typing import Any, Literal, TypeVar
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import mcp
|
||||
import mcp_types
|
||||
import pytest
|
||||
from inline_snapshot import snapshot
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import mcp_types
|
|||
import pytest
|
||||
from anyio import create_task_group
|
||||
from dirty_equals import Contains
|
||||
from mcp import McpError
|
||||
from mcp import MCPError
|
||||
from mcp_types import Icon, TextContent, TextResourceContents
|
||||
from pydantic import AnyUrl
|
||||
|
||||
|
|
@ -259,7 +259,7 @@ async def test_proxy_ping_surfaces_wrong_remote_path():
|
|||
async with run_server_async(remote, transport="http") as url:
|
||||
proxy = create_proxy(StreamableHttpTransport(url.removesuffix("/mcp")))
|
||||
|
||||
with pytest.raises(McpError, match="Session terminated"):
|
||||
with pytest.raises(MCPError, match="Session terminated"):
|
||||
async with Client(proxy):
|
||||
pass
|
||||
|
||||
|
|
@ -271,7 +271,7 @@ async def test_proxy_initialize_forwards_remote_connection_error():
|
|||
provider_error_strategy="raise",
|
||||
)
|
||||
|
||||
with pytest.raises(McpError, match="Client failed to connect"):
|
||||
with pytest.raises(MCPError, match="Client failed to connect"):
|
||||
async with Client(proxy):
|
||||
pass
|
||||
|
||||
|
|
@ -294,7 +294,7 @@ async def test_proxy_list_tools_client_surfaces_remote_connection_error():
|
|||
provider_error_strategy="raise",
|
||||
)
|
||||
|
||||
with pytest.raises(McpError, match="Client failed to connect"):
|
||||
with pytest.raises(MCPError, match="Client failed to connect"):
|
||||
async with Client(proxy) as client:
|
||||
await client.list_tools()
|
||||
|
||||
|
|
@ -561,7 +561,7 @@ class TestResources:
|
|||
|
||||
async def test_read_resource_returns_none_if_not_found(self, proxy_server):
|
||||
with pytest.raises(
|
||||
McpError, match="Unknown resource: 'resource://nonexistent'"
|
||||
MCPError, match="Unknown resource: 'resource://nonexistent'"
|
||||
):
|
||||
async with Client(proxy_server) as client:
|
||||
await client.read_resource("resource://nonexistent")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ over sync vs task execution for resources and resource templates.
|
|||
"""
|
||||
|
||||
import pytest
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client import Client
|
||||
|
|
@ -44,14 +44,14 @@ class TestResourceTaskMetaParameter:
|
|||
assert result.contents[0].content == "hello world"
|
||||
|
||||
async def test_task_meta_on_forbidden_resource_raises_error(self):
|
||||
"""Providing task_meta to a task=False resource raises McpError."""
|
||||
"""Providing task_meta to a task=False resource raises MCPError."""
|
||||
server = FastMCP("test")
|
||||
|
||||
@server.resource("data://test", task=False)
|
||||
async def sync_only_resource() -> str:
|
||||
return "hello"
|
||||
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await server.read_resource("data://test", task_meta=TaskMeta())
|
||||
|
||||
assert "does not support task-augmented execution" in str(exc_info.value)
|
||||
|
|
@ -100,14 +100,14 @@ class TestResourceTemplateTaslMeta:
|
|||
assert result.contents[0].content == "Item 42"
|
||||
|
||||
async def test_template_task_meta_on_forbidden_template_raises_error(self):
|
||||
"""Providing task_meta to a task=False template raises McpError."""
|
||||
"""Providing task_meta to a task=False template raises MCPError."""
|
||||
server = FastMCP("test")
|
||||
|
||||
@server.resource("item://{id}", task=False)
|
||||
async def sync_only_template(id: str) -> str:
|
||||
return f"Item {id}"
|
||||
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await server.read_resource("item://42", task_meta=TaskMeta())
|
||||
|
||||
assert "does not support task-augmented execution" in str(exc_info.value)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ async def test_server_tasks_true_defaults_all_components():
|
|||
async def test_server_tasks_false_defaults_all_components():
|
||||
"""Server with tasks=False makes all components default to mode=forbidden."""
|
||||
import pytest
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
|
||||
mcp = FastMCP("test", tasks=False)
|
||||
|
||||
|
|
@ -78,12 +78,12 @@ async def test_server_tasks_false_defaults_all_components():
|
|||
assert result.is_error
|
||||
assert "does not support task-augmented execution" in str(result)
|
||||
|
||||
# Prompt with mode="forbidden" raises McpError when called with task=True
|
||||
with pytest.raises(McpError):
|
||||
# Prompt with mode="forbidden" raises MCPError when called with task=True
|
||||
with pytest.raises(MCPError):
|
||||
await client.get_prompt("my_prompt", task=True)
|
||||
|
||||
# Resource with mode="forbidden" raises McpError when called with task=True
|
||||
with pytest.raises(McpError):
|
||||
# Resource with mode="forbidden" raises MCPError when called with task=True
|
||||
with pytest.raises(MCPError):
|
||||
await client.read_resource("test://resource", task=True)
|
||||
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ async def test_component_explicit_true_overrides_server_false():
|
|||
async def test_mixed_explicit_and_inherited():
|
||||
"""Mix of explicit True/False/None on different components."""
|
||||
import pytest
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
|
||||
mcp = FastMCP("test", tasks=True) # Server default is True
|
||||
|
||||
|
|
@ -240,8 +240,8 @@ async def test_mixed_explicit_and_inherited():
|
|||
inherited_prompt_task = await client.get_prompt("inherited_prompt", task=True)
|
||||
assert not inherited_prompt_task.returned_immediately
|
||||
|
||||
# Explicit False prompt (mode="forbidden") raises McpError
|
||||
with pytest.raises(McpError):
|
||||
# Explicit False prompt (mode="forbidden") raises MCPError
|
||||
with pytest.raises(MCPError):
|
||||
await client.get_prompt("explicit_false_prompt", task=True)
|
||||
|
||||
# Resources
|
||||
|
|
@ -250,8 +250,8 @@ async def test_mixed_explicit_and_inherited():
|
|||
)
|
||||
assert not inherited_resource_task.returned_immediately
|
||||
|
||||
# Explicit False resource (mode="forbidden") raises McpError
|
||||
with pytest.raises(McpError):
|
||||
# Explicit False resource (mode="forbidden") raises MCPError
|
||||
with pytest.raises(MCPError):
|
||||
await client.read_resource("test://explicit_false", task=True)
|
||||
|
||||
|
||||
|
|
@ -303,7 +303,7 @@ async def test_resource_template_inherits_server_tasks_default():
|
|||
async def test_multiple_components_same_name_different_tasks():
|
||||
"""Different component types with same name can have different task settings."""
|
||||
import pytest
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
|
||||
mcp = FastMCP("test", tasks=False)
|
||||
|
||||
|
|
@ -320,8 +320,8 @@ async def test_multiple_components_same_name_different_tasks():
|
|||
tool_task = await client.call_tool("shared_name", task=True)
|
||||
assert not tool_task.returned_immediately
|
||||
|
||||
# Prompt inheriting False (mode="forbidden") raises McpError
|
||||
with pytest.raises(McpError):
|
||||
# Prompt inheriting False (mode="forbidden") raises MCPError
|
||||
with pytest.raises(MCPError):
|
||||
await client.get_prompt("shared_name_prompt", task=True)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Tests for TaskConfig:
|
|||
from datetime import timedelta
|
||||
|
||||
import pytest
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp_types import TextContent, ToolExecution
|
||||
from mcp_types import Tool as MCPTool
|
||||
|
||||
|
|
@ -189,7 +189,7 @@ class TestResourceModeEnforcement:
|
|||
from mcp_types import METHOD_NOT_FOUND
|
||||
|
||||
async with Client(server) as client:
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await client.read_resource("resource://required")
|
||||
|
||||
assert exc_info.value.error.code == METHOD_NOT_FOUND
|
||||
|
|
@ -241,7 +241,7 @@ class TestPromptModeEnforcement:
|
|||
from mcp_types import METHOD_NOT_FOUND
|
||||
|
||||
async with Client(server) as client:
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await client.get_prompt("required_prompt")
|
||||
|
||||
assert exc_info.value.error.code == METHOD_NOT_FOUND
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class TestTaskMetaParameter:
|
|||
async def sync_only_tool(x: int) -> int:
|
||||
return x * 2
|
||||
|
||||
# Error is raised before docket is needed (McpError wrapped as ToolError)
|
||||
# Error is raised before docket is needed (MCPError wrapped as ToolError)
|
||||
with pytest.raises(ToolError) as exc_info:
|
||||
await server.call_tool("sync_only_tool", {"x": 5}, task_meta=TaskMeta())
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Tests the tasks/get, tasks/result, and tasks/list JSON-RPC protocol methods.
|
|||
import asyncio
|
||||
|
||||
import pytest
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client import Client
|
||||
|
|
@ -150,7 +150,7 @@ async def test_get_status_nonexistent_task_raises_error(endpoint_server):
|
|||
async with Client(endpoint_server) as client:
|
||||
# Try to get status for task that was never created
|
||||
# Per SDK implementation: raises ValueError which becomes JSON-RPC error
|
||||
with pytest.raises(McpError, match="Task nonexistent-task-id not found"):
|
||||
with pytest.raises(MCPError, match="Task nonexistent-task-id not found"):
|
||||
await client.get_task_status("nonexistent-task-id")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ async def test_prompt_task_executes_in_background(prompt_server):
|
|||
|
||||
async def test_forbidden_mode_prompt_rejects_task_calls(prompt_server):
|
||||
"""Prompts with task=False (mode=forbidden) reject task-augmented calls."""
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp_types import METHOD_NOT_FOUND
|
||||
|
||||
@prompt_server.prompt(task=False) # Explicitly disable task support
|
||||
|
|
@ -78,10 +78,10 @@ async def test_forbidden_mode_prompt_rejects_task_calls(prompt_server):
|
|||
return f"Sync prompt: {topic}"
|
||||
|
||||
async with Client(prompt_server) as client:
|
||||
# Calling with task=True when task=False should raise McpError
|
||||
# Calling with task=True when task=False should raise MCPError
|
||||
import pytest
|
||||
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await client.get_prompt("sync_only_prompt", {"topic": "test"}, task=True)
|
||||
|
||||
# New behavior: mode="forbidden" returns METHOD_NOT_FOUND error
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ Proxy servers explicitly forbid task-augmented execution. All proxy components
|
|||
Clients connecting through proxies can:
|
||||
- Execute tools/prompts/resources normally (sync execution)
|
||||
- NOT use task-augmented execution (task=True fails gracefully for tools,
|
||||
raises McpError for prompts/resources)
|
||||
raises MCPError for prompts/resources)
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp_types import TextContent, TextResourceContents
|
||||
|
||||
from fastmcp import FastMCP
|
||||
|
|
@ -128,9 +128,9 @@ class TestProxyPromptsTaskForbidden:
|
|||
"""Test that prompts with task=True are forbidden through proxy."""
|
||||
|
||||
async def test_prompt_task_raises_mcp_error(self, proxy_server: FastMCP):
|
||||
"""Prompt called with task=True through proxy raises McpError."""
|
||||
"""Prompt called with task=True through proxy raises MCPError."""
|
||||
async with Client(proxy_server) as client:
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await client.get_prompt("greeting_prompt", {"name": "Alice"}, task=True)
|
||||
|
||||
assert "does not support task-augmented execution" in str(exc_info.value)
|
||||
|
|
@ -158,17 +158,17 @@ class TestProxyResourcesTaskForbidden:
|
|||
"""Test that resources with task=True are forbidden through proxy."""
|
||||
|
||||
async def test_resource_task_raises_mcp_error(self, proxy_server: FastMCP):
|
||||
"""Resource read with task=True through proxy raises McpError."""
|
||||
"""Resource read with task=True through proxy raises MCPError."""
|
||||
async with Client(proxy_server) as client:
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await client.read_resource("data://info.txt", task=True)
|
||||
|
||||
assert "does not support task-augmented execution" in str(exc_info.value)
|
||||
|
||||
async def test_resource_template_task_raises_mcp_error(self, proxy_server: FastMCP):
|
||||
"""Resource template with task=True through proxy raises McpError."""
|
||||
"""Resource template with task=True through proxy raises MCPError."""
|
||||
async with Client(proxy_server) as client:
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await client.read_resource("data://user/42.json", task=True)
|
||||
|
||||
assert "does not support task-augmented execution" in str(exc_info.value)
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ async def test_resource_template_with_task(resource_server):
|
|||
async def test_forbidden_mode_resource_rejects_task_calls(resource_server):
|
||||
"""Resources with task=False (mode=forbidden) reject task-augmented calls."""
|
||||
import pytest
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp_types import METHOD_NOT_FOUND
|
||||
|
||||
@resource_server.resource(
|
||||
|
|
@ -96,8 +96,8 @@ async def test_forbidden_mode_resource_rejects_task_calls(resource_server):
|
|||
return "Sync content"
|
||||
|
||||
async with Client(resource_server) as client:
|
||||
# Calling with task=True when task=False should raise McpError
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
# Calling with task=True when task=False should raise MCPError
|
||||
with pytest.raises(MCPError) as exc_info:
|
||||
await client.read_resource("file://sync.txt", task=True)
|
||||
|
||||
# New behavior: mode="forbidden" returns METHOD_NOT_FOUND error
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from unittest.mock import patch
|
|||
|
||||
import mcp_types
|
||||
import pytest
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
|
||||
from fastmcp import Client, FastMCP
|
||||
from fastmcp.utilities.pagination import CursorState, paginate_sequence
|
||||
|
|
@ -187,7 +187,7 @@ class TestServerPagination:
|
|||
return "ok"
|
||||
|
||||
async with Client(server) as client:
|
||||
with pytest.raises(McpError) as exc:
|
||||
with pytest.raises(MCPError) as exc:
|
||||
await client.list_tools_mcp(cursor="invalid!")
|
||||
assert exc.value.error.code == -32602
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import time
|
|||
|
||||
import anyio
|
||||
import pytest
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp_types import TextContent
|
||||
|
||||
from fastmcp import FastMCP
|
||||
|
|
@ -188,5 +188,5 @@ class TestToolTimeout:
|
|||
return "never"
|
||||
|
||||
# TimeoutError should be caught and converted to ToolError
|
||||
with pytest.raises((ToolError, McpError)):
|
||||
with pytest.raises((ToolError, MCPError)):
|
||||
await mcp.call_tool("times_out")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue