Skip to content

Commit cb93651

Browse files
committed
fix(server): let template handlers signal not-found per SEP-2164
ResourceTemplate.create_resource() now re-raises ResourceError (and its ResourceNotFoundError subclass) instead of wrapping them, so a template handler can raise ResourceNotFoundError to produce -32602 INVALID_PARAMS on the wire. Other exceptions are still wrapped as ResourceError. Also exports ResourceError and ResourceNotFoundError from mcp.server.mcpserver, and documents the ValueError->ResourceError change in docs/migration.md.
1 parent d4d62fb commit cb93651

4 files changed

Lines changed: 24 additions & 2 deletions

File tree

docs/migration.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,10 @@ async def my_tool(x: int, ctx: Context) -> str:
343343

344344
The internal layers (`ToolManager.call_tool`, `Tool.run`, `Prompt.render`, `ResourceTemplate.create_resource`, etc.) now require `context` as a positional argument.
345345

346+
### `ResourceManager.get_resource()` and `ResourceTemplate.create_resource()` raise typed exceptions
347+
348+
`ResourceManager.get_resource()` now raises `ResourceNotFoundError` (instead of `ValueError`) when no resource or template matches the URI. `ResourceTemplate.create_resource()` now raises `ResourceError` (instead of `ValueError`) when the template function fails. Neither subclasses `ValueError`, so callers catching `ValueError` should switch to `ResourceNotFoundError` / `ResourceError` (both importable from `mcp.server.mcpserver`). `MCPServer.read_resource()` continues to raise `ResourceError` and is unaffected.
349+
346350
### Replace `RootModel` by union types with `TypeAdapter` validation
347351

348352
The following union types are no longer `RootModel` subclasses:

src/mcp/server/mcpserver/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from mcp.types import Icon
44

55
from .context import Context
6+
from .exceptions import ResourceError, ResourceNotFoundError
67
from .server import MCPServer
78
from .utilities.types import Audio, Image
89

9-
__all__ = ["MCPServer", "Context", "Image", "Audio", "Icon"]
10+
__all__ = ["MCPServer", "Context", "Image", "Audio", "Icon", "ResourceError", "ResourceNotFoundError"]

src/mcp/server/mcpserver/resources/templates.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,5 +127,7 @@ async def create_resource(
127127
meta=self.meta,
128128
fn=lambda: result, # Capture result in closure
129129
)
130+
except ResourceError:
131+
raise
130132
except Exception as e:
131133
raise ResourceError(f"Error creating resource from template: {e}")

tests/server/mcpserver/test_server.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from mcp.client import Client
1313
from mcp.server.context import ServerRequestContext
1414
from mcp.server.experimental.request_context import Experimental
15-
from mcp.server.mcpserver import Context, MCPServer
15+
from mcp.server.mcpserver import Context, MCPServer, ResourceNotFoundError
1616
from mcp.server.mcpserver.exceptions import ToolError
1717
from mcp.server.mcpserver.prompts.base import Message, UserMessage
1818
from mcp.server.mcpserver.resources import FileResource, FunctionResource
@@ -731,6 +731,21 @@ def get_item(item_id: str) -> str:
731731
assert exc_info.value.error.code == INTERNAL_ERROR
732732
assert exc_info.value.error.code != INVALID_PARAMS
733733

734+
async def test_read_resource_template_not_found(self):
735+
"""A template handler raising ResourceNotFoundError must surface as INVALID_PARAMS per SEP-2164."""
736+
mcp = MCPServer()
737+
738+
@mcp.resource("resource://users/{user_id}")
739+
def get_user(user_id: str) -> str:
740+
raise ResourceNotFoundError(f"no user {user_id}")
741+
742+
async with Client(mcp) as client:
743+
with pytest.raises(MCPError, match="no user 999") as exc_info:
744+
await client.read_resource("resource://users/999")
745+
746+
assert exc_info.value.error.code == INVALID_PARAMS
747+
assert exc_info.value.error.data == {"uri": "resource://users/999"}
748+
734749
async def test_binary_resource(self):
735750
mcp = MCPServer()
736751

0 commit comments

Comments
 (0)