diff --git a/src/fastmcp/prompts/prompt_manager.py b/src/fastmcp/prompts/prompt_manager.py index 8b19ded92..b2951da28 100644 --- a/src/fastmcp/prompts/prompt_manager.py +++ b/src/fastmcp/prompts/prompt_manager.py @@ -7,7 +7,7 @@ from typing import Any import mcp.types from fastmcp import settings -from fastmcp.exceptions import NotFoundError, PromptError +from fastmcp.exceptions import FastMCPError, NotFoundError, PromptError from fastmcp.prompts.prompt import ( FunctionPrompt, Prompt, @@ -121,7 +121,7 @@ class PromptManager: prompt = await self.get_prompt(name) try: return await prompt._render(arguments) - except PromptError: + except FastMCPError: raise except Exception as e: if self.mask_error_details: diff --git a/src/fastmcp/resources/resource_manager.py b/src/fastmcp/resources/resource_manager.py index ecd7b06a3..e021e5ac9 100644 --- a/src/fastmcp/resources/resource_manager.py +++ b/src/fastmcp/resources/resource_manager.py @@ -11,7 +11,7 @@ import mcp.types from pydantic import AnyUrl from fastmcp import settings -from fastmcp.exceptions import NotFoundError, ResourceError +from fastmcp.exceptions import FastMCPError, NotFoundError, ResourceError from fastmcp.resources.resource import Resource, ResourceContent from fastmcp.resources.template import ( ResourceTemplate, @@ -269,8 +269,8 @@ class ResourceManager: uri_str, params=params, ) - # Pass through ResourceErrors as-is - except ResourceError as e: + # Pass through FastMCPErrors as-is + except FastMCPError as e: logger.error(f"Error creating resource from template: {e}") raise e # Handle other exceptions @@ -307,7 +307,7 @@ class ResourceManager: resource = await self.get_resource(uri_str) try: return await resource._read() - except ResourceError: + except FastMCPError: raise except Exception as e: if self.mask_error_details: @@ -320,7 +320,7 @@ class ResourceManager: try: resource = await template.create_resource(uri_str, params=params) return await resource._read() - except ResourceError: + except FastMCPError: raise except Exception as e: if self.mask_error_details: diff --git a/src/fastmcp/tools/tool_manager.py b/src/fastmcp/tools/tool_manager.py index 18bb22722..1c1e5bba4 100644 --- a/src/fastmcp/tools/tool_manager.py +++ b/src/fastmcp/tools/tool_manager.py @@ -8,7 +8,7 @@ from mcp.types import ToolAnnotations from pydantic import ValidationError from fastmcp import settings -from fastmcp.exceptions import NotFoundError, ToolError +from fastmcp.exceptions import FastMCPError, NotFoundError, ToolError from fastmcp.settings import DuplicateBehavior from fastmcp.tools.tool import Tool, ToolResult from fastmcp.tools.tool_transform import ( @@ -160,9 +160,9 @@ class ToolManager: tool = await self.get_tool(key) try: return await tool.run(arguments) - except ValidationError: + except FastMCPError: raise - except ToolError: + except ValidationError: raise except Exception as e: if self.mask_error_details: diff --git a/tests/client/test_sse.py b/tests/client/test_sse.py index cdcd0cfca..cec868615 100644 --- a/tests/client/test_sse.py +++ b/tests/client/test_sse.py @@ -119,13 +119,15 @@ async def nested_sse_server(): ws="websockets-sansio", ) - server_task = asyncio.create_task(uvicorn.Server(config).serve()) + uvicorn_server = uvicorn.Server(config) + server_task = asyncio.create_task(uvicorn_server.serve()) await asyncio.sleep(0.1) try: yield f"http://127.0.0.1:{port}/nest-outer/nest-inner/mcp/sse/" finally: - server_task.cancel() + # Graceful shutdown - required for uvicorn 0.39+ due to context isolation + uvicorn_server.should_exit = True try: await server_task except asyncio.CancelledError: diff --git a/tests/server/test_logging.py b/tests/server/test_logging.py index cfc376c0b..e8810c4a9 100644 --- a/tests/server/test_logging.py +++ b/tests/server/test_logging.py @@ -49,9 +49,14 @@ async def test_uvicorn_logging_default_level( ) mock_server_instance.serve.assert_awaited_once() + # Signal the mock to finish and cancel with timeout + # Required for uvicorn 0.39+ due to context isolation + serve_finished_event.set() server_task.cancel() - with pytest.raises(asyncio.CancelledError): - await server_task + try: + await asyncio.wait_for(server_task, timeout=2.0) + except (asyncio.CancelledError, asyncio.TimeoutError): + pass @patch("fastmcp.server.server.uvicorn.Server") @@ -109,9 +114,14 @@ async def test_uvicorn_logging_with_custom_log_config( ) mock_server_instance.serve.assert_awaited_once() + # Signal the mock to finish and cancel with timeout + # Required for uvicorn 0.39+ due to context isolation + serve_finished_event.set() server_task.cancel() - with pytest.raises(asyncio.CancelledError): - await server_task + try: + await asyncio.wait_for(server_task, timeout=2.0) + except (asyncio.CancelledError, asyncio.TimeoutError): + pass @patch("fastmcp.server.server.uvicorn.Server") @@ -172,6 +182,11 @@ async def test_uvicorn_logging_custom_log_config_overrides_log_level_param( ) mock_server_instance.serve.assert_awaited_once() + # Signal the mock to finish and cancel with timeout + # Required for uvicorn 0.39+ due to context isolation + serve_finished_event.set() server_task.cancel() - with pytest.raises(asyncio.CancelledError): - await server_task + try: + await asyncio.wait_for(server_task, timeout=2.0) + except (asyncio.CancelledError, asyncio.TimeoutError): + pass