Fix uvicorn 0.39+ test timeouts and FastMCPError propagation (#2699)

This commit is contained in:
Jeremiah Lowin 2025-12-23 19:02:47 -05:00 committed by GitHub
commit 9147518c01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 35 additions and 18 deletions

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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