Return -32602 for resource not found (SEP-2164) (#2920)
This commit is contained in:
parent
0fa03ec69f
commit
f253682393
5 changed files with 113 additions and 43 deletions
|
|
@ -113,6 +113,10 @@ class Context(BaseModel, Generic[LifespanContextT, RequestT]):
|
|||
|
||||
Returns:
|
||||
The resource content as either text or bytes
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundError: If no resource or template matches the URI.
|
||||
ResourceError: If template creation or resource reading fails.
|
||||
"""
|
||||
assert self._mcp_server is not None, "Context is not available outside of a request"
|
||||
return await self._mcp_server.read_resource(uri, self)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,15 @@ class ResourceError(MCPServerError):
|
|||
"""Error in resource operations."""
|
||||
|
||||
|
||||
class ResourceNotFoundError(ResourceError):
|
||||
"""Resource does not exist.
|
||||
|
||||
Raise this from a resource template handler to signal that the requested instance does not exist;
|
||||
clients receive `-32602` (invalid params) per
|
||||
[SEP-2164](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2164).
|
||||
"""
|
||||
|
||||
|
||||
class ToolError(MCPServerError):
|
||||
"""Error in tool operations."""
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Any
|
|||
|
||||
from pydantic import AnyUrl
|
||||
|
||||
from mcp.server.mcpserver.exceptions import ResourceNotFoundError
|
||||
from mcp.server.mcpserver.resources.base import Resource
|
||||
from mcp.server.mcpserver.resources.templates import ResourceTemplate
|
||||
from mcp.server.mcpserver.utilities.logging import get_logger
|
||||
|
|
@ -79,7 +80,12 @@ class ResourceManager:
|
|||
return template
|
||||
|
||||
async def get_resource(self, uri: AnyUrl | str, context: Context[LifespanContextT, RequestT]) -> Resource:
|
||||
"""Get resource by URI, checking concrete resources first, then templates."""
|
||||
"""Get resource by URI, checking concrete resources first, then templates.
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundError: If no resource or template matches the URI.
|
||||
ResourceError: If a matching template fails to create the resource.
|
||||
"""
|
||||
uri_str = str(uri)
|
||||
logger.debug("Getting resource", extra={"uri": uri_str})
|
||||
|
||||
|
|
@ -90,12 +96,9 @@ class ResourceManager:
|
|||
# Then check templates
|
||||
for template in self._templates.values():
|
||||
if params := template.matches(uri_str):
|
||||
try:
|
||||
return await template.create_resource(uri_str, params, context=context)
|
||||
except Exception as e: # pragma: no cover
|
||||
raise ValueError(f"Error creating resource from template: {e}")
|
||||
return await template.create_resource(uri_str, params, context=context)
|
||||
|
||||
raise ValueError(f"Unknown resource: {uri}")
|
||||
raise ResourceNotFoundError(f"Unknown resource: {uri}")
|
||||
|
||||
def list_resources(self) -> list[Resource]:
|
||||
"""List all registered resources."""
|
||||
|
|
|
|||
|
|
@ -11,12 +11,16 @@ from urllib.parse import unquote
|
|||
import anyio.to_thread
|
||||
from pydantic import BaseModel, Field, validate_call
|
||||
|
||||
from mcp.server.mcpserver.exceptions import ResourceError
|
||||
from mcp.server.mcpserver.resources.types import FunctionResource, Resource
|
||||
from mcp.server.mcpserver.utilities.context_injection import find_context_parameter, inject_context
|
||||
from mcp.server.mcpserver.utilities.func_metadata import func_metadata
|
||||
from mcp.server.mcpserver.utilities.logging import get_logger
|
||||
from mcp.shared._callable_inspection import is_async_callable
|
||||
from mcp.types import Annotations, Icon
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mcp.server.context import LifespanContextT, RequestT
|
||||
from mcp.server.mcpserver.context import Context
|
||||
|
|
@ -106,7 +110,7 @@ class ResourceTemplate(BaseModel):
|
|||
"""Create a resource from the template with the given parameters.
|
||||
|
||||
Raises:
|
||||
ValueError: If creating the resource fails.
|
||||
ResourceError: If creating the resource fails.
|
||||
"""
|
||||
try:
|
||||
# Add context to params if needed
|
||||
|
|
@ -129,5 +133,8 @@ class ResourceTemplate(BaseModel):
|
|||
meta=self.meta,
|
||||
fn=lambda: result, # Capture result in closure
|
||||
)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error creating resource from template: {e}")
|
||||
except ResourceError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
logger.exception(f"Error creating resource from template {uri}")
|
||||
raise ResourceError(f"Error creating resource from template {uri}") from exc
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ from mcp.server.lowlevel.helper_types import ReadResourceContents
|
|||
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,8 @@ from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
|
|||
from mcp.server.transport_security import TransportSecuritySettings
|
||||
from mcp.shared.exceptions import MCPError
|
||||
from mcp.types import (
|
||||
INTERNAL_ERROR,
|
||||
INVALID_PARAMS,
|
||||
Annotations,
|
||||
BlobResourceContents,
|
||||
CallToolRequestParams,
|
||||
|
|
@ -341,7 +343,12 @@ class MCPServer(Generic[LifespanResultT]):
|
|||
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)})
|
||||
except ResourceError as err:
|
||||
raise MCPError(code=INTERNAL_ERROR, message=str(err), data={"uri": str(params.uri)})
|
||||
contents: list[TextResourceContents | BlobResourceContents] = []
|
||||
for item in results:
|
||||
if isinstance(item.content, bytes):
|
||||
|
|
@ -442,13 +449,15 @@ class MCPServer(Generic[LifespanResultT]):
|
|||
async def read_resource(
|
||||
self, uri: AnyUrl | str, context: Context[LifespanResultT, Any] | None = None
|
||||
) -> Iterable[ReadResourceContents]:
|
||||
"""Read a resource by URI."""
|
||||
"""Read a resource by URI.
|
||||
|
||||
Raises:
|
||||
ResourceNotFoundError: If no resource or template matches the URI.
|
||||
ResourceError: If template creation or resource reading fails.
|
||||
"""
|
||||
if context is None:
|
||||
context = Context(mcp_server=self)
|
||||
try:
|
||||
resource = await self._resource_manager.get_resource(uri, context)
|
||||
except ValueError as exc:
|
||||
raise ResourceError(f"Unknown resource: {uri}") from exc
|
||||
resource = await self._resource_manager.get_resource(uri, context)
|
||||
|
||||
try:
|
||||
content = await resource.read()
|
||||
|
|
|
|||
Loading…
Reference in a new issue