"""Two error channels: ToolError -> is_error result; MCPError -> JSON-RPC protocol error.""" from mcp_types import INVALID_PARAMS from mcp.server.mcpserver import MCPServer from mcp.server.mcpserver.exceptions import ToolError from mcp.shared.exceptions import MCPError from stories._hosting import run_server_from_args def build_server() -> MCPServer: mcp = MCPServer("error-handling-example") @mcp.tool() def divide(a: float, b: float) -> float: """Divide a by b. Division by zero is an execution error the LLM should see.""" if b == 0: # ToolError is caught by the tool wrapper and returned as # CallToolResult(is_error=True) — the LLM reads the message and can # self-correct. raise ToolError("cannot divide by zero") return a / b @mcp.tool() def restricted() -> str: """A tool that always rejects the caller at the protocol level.""" # MCPError escapes the tool wrapper and becomes a JSON-RPC error # response — the *host* sees code/message/data, not the LLM. raise MCPError(code=INVALID_PARAMS, message="this tool is gated", data={"reason": "demo"}) return mcp if __name__ == "__main__": run_server_from_args(build_server)