rohitg00/ai-engineering-from-scratch · error · RuntimeError
{peer.name}: missing response
Error message
{peer.name}: missing response What it means
A request completed at the transport level but _send returned a non-dict, so the client cannot decode a JSON-RPC response. This is the steady-state request path (tools/list, tools/call) rather than the handshake paths.
Source
Thrown at phases/13-tools-and-protocols/08-building-an-mcp-client/code/main.py:488
self._connect_peer(self.peers[peer_name])
def _request(self, peer: Peer, method: str, params: dict[str, Any]) -> dict[str, Any]:
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:View on GitHub (pinned to 39ea8a1c6d)
Solutions
- Check peer liveness before issuing requests
- Fix the transport read path to parse frames into dicts
- Reconnect the peer (re-run connect_all) if the socket dropped
- Correct test doubles to return dict responses
Defensive patterns
Strategy: type-guard
Validate before calling
if not peer_still_connected(peer):
client.connect_all() # re-handshake before requesting Type guard
def is_rpc_message(value) -> bool:
return isinstance(value, dict) and ("result" in value or "error" in value) Try / catch
try:
client.discover_tools()
except RuntimeError as e:
if "missing response" in str(e):
client.connect_all(); client.discover_tools() Prevention
- Check peer liveness before long-lived sessions
- Make transports return None-safe parsed dicts only
When it happens
Trigger: self._send(peer, message) inside _request returns something for which isinstance(response, dict) is False.
Common situations: Peer disconnecting mid-request so the reader yields None; transport leaking raw text; test stubs returning the wrong type.
Related errors
- {peer.name}: bounded legacy probe returned no result
- {peer.name}: malformed discovery response
- {peer.name}: proven-modern discovery retry returned no resul
- -32600
- -32022
AI-assisted analysis of rohitg00/ai-engineering-from-scratch@39ea8a1c6d (2026-08-26).
Data as JSON: /api/errors/26eded0ebda9b08f.
Report an issue: GitHub.