diff --git a/src/mcp/server/mcpserver/server.py b/src/mcp/server/mcpserver/server.py index 2a7a58117..02e929323 100644 --- a/src/mcp/server/mcpserver/server.py +++ b/src/mcp/server/mcpserver/server.py @@ -31,7 +31,6 @@ from mcp.server.lowlevel.server import LifespanResultT, Server from mcp.server.lowlevel.server import lifespan as default_lifespan from mcp.server.mcpserver.context import Context -from mcp.server.mcpserver.exceptions import ResourceError from mcp.server.mcpserver.prompts import Prompt, PromptManager from mcp.server.mcpserver.resources import FunctionResource, Resource, ResourceManager from mcp.server.mcpserver.tools import Tool, ToolManager @@ -44,6 +43,7 @@ from mcp.server.transport_security import TransportSecuritySettings from mcp.shared.exceptions import MCPError from mcp.types import ( + INVALID_PARAMS, Annotations, BlobResourceContents, CallToolRequestParams, @@ -439,7 +439,7 @@ async def read_resource( try: resource = await self._resource_manager.get_resource(uri, context) except ValueError: - raise ResourceError(f"Unknown resource: {uri}") + raise MCPError(code=INVALID_PARAMS, message=f"Unknown resource: {uri}") try: content = await resource.read() @@ -447,7 +447,7 @@ async def read_resource( except Exception as exc: logger.exception(f"Error getting resource {uri}") # If an exception happens when reading the resource, we should not leak the exception to the client. - raise ResourceError(f"Error reading resource {uri}") from exc + raise MCPError(code=INVALID_PARAMS, message=f"Error reading resource {uri}") from exc def add_tool( self, diff --git a/tests/issues/test_141_resource_templates.py b/tests/issues/test_141_resource_templates.py index f5c5081c3..f66f49725 100644 --- a/tests/issues/test_141_resource_templates.py +++ b/tests/issues/test_141_resource_templates.py @@ -2,7 +2,7 @@ from mcp import Client from mcp.server.mcpserver import MCPServer -from mcp.server.mcpserver.exceptions import ResourceError +from mcp.shared.exceptions import MCPError from mcp.types import ( ListResourceTemplatesResult, TextResourceContents, @@ -55,10 +55,10 @@ def get_user_profile_missing(user_id: str) -> str: # pragma: no cover assert result_list[0].mime_type == "text/plain" # Verify invalid parameters raise error - with pytest.raises(ResourceError, match="Unknown resource"): + with pytest.raises(MCPError, match="Unknown resource"): await mcp.read_resource("resource://users/123/posts") # Missing post_id - with pytest.raises(ResourceError, match="Unknown resource"): + with pytest.raises(MCPError, match="Unknown resource"): await mcp.read_resource("resource://users/123/posts/456/extra") # Extra path component diff --git a/tests/issues/test_1579_resource_error_code.py b/tests/issues/test_1579_resource_error_code.py new file mode 100644 index 000000000..bb534f362 --- /dev/null +++ b/tests/issues/test_1579_resource_error_code.py @@ -0,0 +1,47 @@ +"""Tests for issue #1579: FastMCP read_resource() returns incorrect error code. + +FastMCP previously returned error code 0 for resource-not-found because +ResourceError (an MCPServerError subclass) was caught by the generic +Exception handler in the low-level server, which defaults to code 0. + +The fix adds a dedicated handler for MCPServerError that maps it to +INVALID_PARAMS (-32602), consistent with the TypeScript SDK and the +emerging spec consensus. +""" + +import pytest + +from mcp.client import Client +from mcp.server.mcpserver import MCPServer +from mcp.shared.exceptions import MCPError +from mcp.types import INVALID_PARAMS + +pytestmark = pytest.mark.anyio + + +async def test_unknown_resource_returns_invalid_params_error_code(): + """Reading an unknown resource returns INVALID_PARAMS (-32602), not 0.""" + mcp = MCPServer() + + async with Client(mcp) as client: + with pytest.raises(MCPError) as exc_info: + await client.read_resource("resource://does-not-exist") + + assert exc_info.value.code == INVALID_PARAMS + assert "Unknown resource" in exc_info.value.message + + +async def test_resource_read_error_returns_invalid_params_error_code(): + """A resource that raises during read returns INVALID_PARAMS (-32602).""" + mcp = MCPServer() + + @mcp.resource("resource://failing") + def failing_resource(): + raise RuntimeError("something broke") + + async with Client(mcp) as client: + with pytest.raises(MCPError) as exc_info: + await client.read_resource("resource://failing") + + assert exc_info.value.code == INVALID_PARAMS + assert "Error reading resource" in exc_info.value.message