Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/mcp/server/mcpserver/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down
10 changes: 7 additions & 3 deletions src/mcp/server/mcpserver/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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()
Expand Down
8 changes: 6 additions & 2 deletions tests/server/mcpserver/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand Down
Loading