Skip to content

Commit 36e5268

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 bcbb7ac commit 36e5268

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
@@ -487,6 +487,10 @@ await ctx.log(level="info", data="hello")
487487

488488
Positional calls (`await ctx.info("hello")`) are unaffected.
489489

490+
### `ResourceManager.get_resource()` and `ResourceTemplate.create_resource()` raise typed exceptions
491+
492+
`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.
493+
490494
### Replace `RootModel` by union types with `TypeAdapter` validation
491495

492496
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
@@ -130,5 +130,7 @@ async def create_resource(
130130
meta=self.meta,
131131
fn=lambda: result, # Capture result in closure
132132
)
133+
except ResourceError:
134+
raise
133135
except Exception as e:
134136
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
@@ -766,6 +766,21 @@ def get_item(item_id: str) -> str:
766766
assert exc_info.value.error.code == INTERNAL_ERROR
767767
assert exc_info.value.error.code != INVALID_PARAMS
768768

769+
async def test_read_resource_template_not_found(self):
770+
"""A template handler raising ResourceNotFoundError must surface as INVALID_PARAMS per SEP-2164."""
771+
mcp = MCPServer()
772+
773+
@mcp.resource("resource://users/{user_id}")
774+
def get_user(user_id: str) -> str:
775+
raise ResourceNotFoundError(f"no user {user_id}")
776+
777+
async with Client(mcp) as client:
778+
with pytest.raises(MCPError, match="no user 999") as exc_info:
779+
await client.read_resource("resource://users/999")
780+
781+
assert exc_info.value.error.code == INVALID_PARAMS
782+
assert exc_info.value.error.data == {"uri": "resource://users/999"}
783+
769784
async def test_binary_resource(self):
770785
mcp = MCPServer()
771786

0 commit comments

Comments
 (0)