4e0fc9ebcb
A custom argument validator that raises something other than ValidationError escaped Tool.run unwrapped, losing the "Error executing tool" prefix and the UnexpectedToolError type. It is now wrapped as a crash, and an MCPError raised there still passes through. A ResourceError (usually ResourceNotFoundError from ctx.read_resource) that escapes a tool body is now classified like a ToolError, since it is the same anticipated outcome resources/read logs at INFO. An UnexpectedResourceError escaping a tool stays a crash. MCPServer.read_resource is now the single place a resource crash is wrapped (plus create_resource for templates), so the built-in Resource types let the original exception propagate to direct callers. Also: trimmed raise-site comments in favour of the exception docstrings, reworded the ToolError and ResourceError docstrings, documented the FunctionResource/FileResource.read change in migration.md, corrected the uri-templates tip and example, and pinned the new cases in tests (including a wire test for ResourceNotFoundError from a static resource).
19 lines
540 B
Python
19 lines
540 B
Python
from pathlib import Path
|
|
|
|
from mcp.server import MCPServer
|
|
from mcp.server.mcpserver.exceptions import ResourceNotFoundError
|
|
from mcp.shared.path_security import safe_join
|
|
|
|
mcp = MCPServer("Bookshop")
|
|
|
|
DOCS_ROOT = Path("./manuals")
|
|
|
|
|
|
@mcp.resource("manuals://{+path}")
|
|
def read_manual(path: str) -> str:
|
|
"""A staff manual page, served from a directory on disk."""
|
|
file = safe_join(DOCS_ROOT, path)
|
|
if not file.is_file():
|
|
raise ResourceNotFoundError(f"No manual at {path!r}.")
|
|
return file.read_text(encoding="utf-8")
|