From d065e9b4c060ff2f1ecd1d8b24f69264810bd26b Mon Sep 17 00:00:00 2001 From: Peter Alexander Date: Wed, 25 Mar 2026 15:07:29 +0000 Subject: [PATCH] fix(server): return -32602 for resource not found (SEP-2164) The SDK previously returned error code 0 for resource-not-found, making it impossible for clients to distinguish this from other errors. Per SEP-2164, servers should return -32602 (Invalid Params) with the uri in the error data field. Adds ResourceNotFoundError subclass of ResourceError so existing code catching ResourceError continues to work. The internal handler converts it to MCPError with INVALID_PARAMS before it reaches the JSON-RPC layer. Spec: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2164 --- src/mcp/server/mcpserver/exceptions.py | 4 ++++ src/mcp/server/mcpserver/server.py | 10 +++++++--- tests/server/mcpserver/test_server.py | 8 ++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/mcp/server/mcpserver/exceptions.py b/src/mcp/server/mcpserver/exceptions.py index dd1b75e82..0d47ac02e 100644 --- a/src/mcp/server/mcpserver/exceptions.py +++ b/src/mcp/server/mcpserver/exceptions.py @@ -13,6 +13,10 @@ class ResourceError(MCPServerError): """Error in resource operations.""" +class ResourceNotFoundError(ResourceError): + """Resource does not exist.""" + + class ToolError(MCPServerError): """Error in tool operations.""" diff --git a/src/mcp/server/mcpserver/server.py b/src/mcp/server/mcpserver/server.py index 2a7a58117..b517255ae 100644 --- a/src/mcp/server/mcpserver/server.py +++ b/src/mcp/server/mcpserver/server.py @@ -31,7 +31,7 @@ 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.exceptions import ResourceError, ResourceNotFoundError 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 +44,7 @@ from mcp.server.transport_security import TransportSecuritySettings from mcp.shared.exceptions import MCPError from mcp.types import ( + INVALID_PARAMS, Annotations, BlobResourceContents, CallToolRequestParams, @@ -332,7 +333,10 @@ async def _handle_read_resource( self, ctx: ServerRequestContext[LifespanResultT], params: ReadResourceRequestParams ) -> ReadResourceResult: context = Context(request_context=ctx, mcp_server=self) - results = await self.read_resource(params.uri, context) + try: + results = await self.read_resource(params.uri, context) + except ResourceNotFoundError as err: + raise MCPError(code=INVALID_PARAMS, message=str(err), data={"uri": str(params.uri)}) contents: list[TextResourceContents | BlobResourceContents] = [] for item in results: if isinstance(item.content, bytes): @@ -439,7 +443,7 @@ async def read_resource( try: resource = await self._resource_manager.get_resource(uri, context) except ValueError: - raise ResourceError(f"Unknown resource: {uri}") + raise ResourceNotFoundError(f"Unknown resource: {uri}") try: content = await resource.read() diff --git a/tests/server/mcpserver/test_server.py b/tests/server/mcpserver/test_server.py index 3ef06d038..6d6409eb4 100644 --- a/tests/server/mcpserver/test_server.py +++ b/tests/server/mcpserver/test_server.py @@ -20,6 +20,7 @@ from mcp.server.transport_security import TransportSecuritySettings from mcp.shared.exceptions import MCPError from mcp.types import ( + INVALID_PARAMS, AudioContent, BlobResourceContents, Completion, @@ -692,13 +693,16 @@ def get_text(): assert result.contents[0].text == "Hello, world!" async def test_read_unknown_resource(self): - """Test that reading an unknown resource raises MCPError.""" + """Test that reading an unknown resource returns -32602 with uri in data (SEP-2164).""" mcp = MCPServer() async with Client(mcp) as client: - with pytest.raises(MCPError, match="Unknown resource: unknown://missing"): + with pytest.raises(MCPError, match="Unknown resource: unknown://missing") as exc_info: await client.read_resource("unknown://missing") + assert exc_info.value.error.code == INVALID_PARAMS + assert exc_info.value.error.data == {"uri": "unknown://missing"} + async def test_read_resource_error(self): """Test that resource read errors are properly wrapped in MCPError.""" mcp = MCPServer()