rohitg00/ai-engineering-from-scratch · error · RuntimeError
{peer.name}: RPC error {payload}
Error message
{peer.name}: RPC error {payload} What it means
The peer answered a tools/list or tools/call request with a JSON-RPC error object. The client surfaces the error payload verbatim in the message so the caller can see the server's code and message.
Source
Thrown at phases/13-tools-and-protocols/08-building-an-mcp-client/code/main.py:491
request_id = self._new_id()
if peer.era == "modern":
message = modern_request(
request_id,
method,
params,
peer.protocol_version or PROTOCOL_VERSION,
self.client_capabilities,
)
elif peer.era == "legacy":
message = legacy_request(request_id, method, params)
else:
raise RuntimeError(f"{peer.name}: protocol era not selected")
response = self._send(peer, message)
if not isinstance(response, dict):
raise RuntimeError(f"{peer.name}: missing response")
kind, payload = decode_rpc_response(response, request_id)
if kind != "result":
raise RuntimeError(f"{peer.name}: RPC error {payload}")
result = dict(payload)
if peer.era == "modern" and "resultType" not in result:
raise RuntimeError(f"{peer.name}: modern result omitted resultType")
if peer.era == "legacy":
result.setdefault("resultType", "complete")
return result
def discover_tools(self) -> None:
for peer_name in sorted(self.peers):
peer = self.peers[peer_name]
if peer.available:
result = self._request(peer, "tools/list", {})
peer.tools = sorted(result.get("tools", []), key=lambda tool: tool["name"])
def merge(self, policy: str = "prefix-on-collision") -> None:
if policy not in {"prefix-on-collision", "reject"}:
raise ValueError("policy must be prefix-on-collision or reject")
self.registry.clear()View on GitHub (pinned to 39ea8a1c6d)
Solutions
- Read the embedded payload for the server's error code and message
- Verify the tool name against the peer's discovered tools/list result
- Check argument shape before calling
- Fix the server-side cause if the error is a genuine execution failure
Example fix
// before
client.call("search", {"q": 123}) # q must be a string
// after
client.call("search", {"q": "hello"}) Defensive patterns
Strategy: try-catch
Validate before calling
names = {t['name'] for t in known_tools(peer)}
if tool_name not in names:
raise ValueError(f'unknown tool {tool_name} for {peer.name}') Type guard
null
Try / catch
try:
client.call(tool_name, args)
except RuntimeError as e:
if "RPC error" in str(e):
handle_server_error(parse_payload(str(e)), tool_name) Prevention
- Discover tools first and validate names/args against the schema
- Log the embedded error payload for diagnosis
When it happens
Trigger: decode_rpc_response(response, request_id) in _request returns kind='error', i.e. the matched response carries an error, not a result.
Common situations: Calling a tool that does not exist on the peer; invalid tool arguments; server-side execution failure or permission denial.
Related errors
- {peer.name}: legacy initialize returned an error
- {peer.name}: correct modern request error {code} before retr
- {peer.name}: proven-modern discovery retry returned an error
- -32600
- -32022
AI-assisted analysis of rohitg00/ai-engineering-from-scratch@39ea8a1c6d (2026-08-26).
Data as JSON: /api/errors/b1bae6f60663a56d.
Report an issue: GitHub.