From 44d272e0b4457fc4b55778c2d021586807d2ad51 Mon Sep 17 00:00:00 2001 From: go165 <196723798+go165@users.noreply.github.com> Date: Mon, 15 Jun 2026 03:06:46 +0800 Subject: [PATCH] fix(mcpserver): return resource not found code --- src/mcp/server/mcpserver/exceptions.py | 4 ++++ src/mcp/server/mcpserver/server.py | 10 ++++++++-- tests/interaction/_requirements.py | 6 ------ tests/interaction/mcpserver/test_resources.py | 4 ++-- tests/issues/test_141_resource_templates.py | 4 ++-- tests/server/mcpserver/test_server.py | 2 +- 6 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/mcp/server/mcpserver/exceptions.py b/src/mcp/server/mcpserver/exceptions.py index dd1b75e829..f1cc31df5c 100644 --- a/src/mcp/server/mcpserver/exceptions.py +++ b/src/mcp/server/mcpserver/exceptions.py @@ -12,6 +12,10 @@ class ValidationError(MCPServerError): class ResourceError(MCPServerError): """Error in resource operations.""" + def __init__(self, message: str, code: int | None = None): + super().__init__(message) + self.code = code + class ToolError(MCPServerError): """Error in tool operations.""" diff --git a/src/mcp/server/mcpserver/server.py b/src/mcp/server/mcpserver/server.py index fdb69571d8..907bc1714a 100644 --- a/src/mcp/server/mcpserver/server.py +++ b/src/mcp/server/mcpserver/server.py @@ -341,7 +341,13 @@ 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 ResourceError as exc: + if exc.code is not None: + message = f"Resource not found: {params.uri}" if exc.code == -32002 else str(exc) + raise MCPError(code=exc.code, message=message) from exc + raise contents: list[TextResourceContents | BlobResourceContents] = [] for item in results: if isinstance(item.content, bytes): @@ -448,7 +454,7 @@ async def read_resource( try: resource = await self._resource_manager.get_resource(uri, context) except ValueError as exc: - raise ResourceError(f"Unknown resource: {uri}") from exc + raise ResourceError(f"Resource not found: {uri}", code=-32002) from exc try: content = await resource.read() diff --git a/tests/interaction/_requirements.py b/tests/interaction/_requirements.py index caed8905d0..bf0bf23636 100644 --- a/tests/interaction/_requirements.py +++ b/tests/interaction/_requirements.py @@ -901,12 +901,6 @@ def __post_init__(self) -> None: "mcpserver:resource:unknown-uri": Requirement( source=f"{SPEC_BASE_URL}/server/resources#error-handling", behavior="resources/read for a URI matching no registered resource returns JSON-RPC error -32002.", - divergence=Divergence( - note=( - "The spec reserves -32002 for resource-not-found; MCPServer raises ResourceError, which " - "the low-level server converts to error code 0." - ), - ), ), # ═══════════════════════════════════════════════════════════════════════════ # Prompts diff --git a/tests/interaction/mcpserver/test_resources.py b/tests/interaction/mcpserver/test_resources.py index 57b0fdc86d..3d546bffa9 100644 --- a/tests/interaction/mcpserver/test_resources.py +++ b/tests/interaction/mcpserver/test_resources.py @@ -114,7 +114,7 @@ def user_profile(user_id: str) -> str: async def test_read_unknown_uri_is_error(connect: Connect) -> None: """Reading a URI that matches no registered resource fails with a JSON-RPC error. - The spec reserves -32002 for resource-not-found; see the divergence note on the requirement. + The spec reserves -32002 for resource-not-found. """ mcp = MCPServer("library") @@ -127,7 +127,7 @@ def app_config() -> str: with pytest.raises(MCPError) as exc_info: await client.read_resource("config://missing") - assert exc_info.value.error == snapshot(ErrorData(code=0, message="Unknown resource: config://missing")) + assert exc_info.value.error == snapshot(ErrorData(code=-32002, message="Resource not found: config://missing")) @requirement("mcpserver:resource:read-throws-surfaced") diff --git a/tests/issues/test_141_resource_templates.py b/tests/issues/test_141_resource_templates.py index f5c5081c3c..61d1a30b3a 100644 --- a/tests/issues/test_141_resource_templates.py +++ b/tests/issues/test_141_resource_templates.py @@ -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(ResourceError, match="Resource not found"): await mcp.read_resource("resource://users/123/posts") # Missing post_id - with pytest.raises(ResourceError, match="Unknown resource"): + with pytest.raises(ResourceError, match="Resource not found"): await mcp.read_resource("resource://users/123/posts/456/extra") # Extra path component diff --git a/tests/server/mcpserver/test_server.py b/tests/server/mcpserver/test_server.py index 21352b5f2f..00d4ec7772 100644 --- a/tests/server/mcpserver/test_server.py +++ b/tests/server/mcpserver/test_server.py @@ -730,7 +730,7 @@ async def test_read_unknown_resource(self): mcp = MCPServer() async with Client(mcp) as client: - with pytest.raises(MCPError, match="Unknown resource: unknown://missing"): + with pytest.raises(MCPError, match="Resource not found: unknown://missing"): await client.read_resource("unknown://missing") async def test_read_resource_error(self):