{"record":{"id":"5d710b433f124710","repo":"rohitg00/ai-engineering-from-scratch","slug":"peer-name-malformed-discovery-response","errorCode":null,"errorMessage":"{peer.name}: malformed discovery response","messagePattern":"(.+?): malformed discovery response","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"phases/13-tools-and-protocols/08-building-an-mcp-client/code/main.py","lineNumber":423,"sourceCode":"        request_id = self._new_id()\n        probe = modern_request(\n            request_id,\n            \"server/discover\",\n            {},\n            self.probe_version,\n            self.client_capabilities,\n        )\n        try:\n            response = self._send(peer, probe, self.discovery_timeout_ms)\n        except (TimeoutError, ConnectionError) as exc:\n            self._probe_legacy(peer, type(exc).__name__)\n            return\n\n        if response is None:\n            self._probe_legacy(peer, \"empty discovery response\")\n            return\n        if not isinstance(response, dict):\n            raise RuntimeError(f\"{peer.name}: malformed discovery response\")\n        kind, payload = decode_rpc_response(response, request_id)\n        if kind == \"result\":\n            advertised = payload.get(\"supportedVersions\", [])\n            if not isinstance(advertised, list) or not all(\n                isinstance(version, str) for version in advertised\n            ):\n                raise RuntimeError(f\"{peer.name}: malformed modern discovery result\")\n            selected = self._mutual_version(advertised)\n            if selected is None:\n                raise RuntimeError(f\"{peer.name}: no mutually supported modern version\")\n            self._activate_modern(peer, payload, selected)\n            return\n\n        code = payload[\"code\"]\n        if code in RECOGNIZED_MODERN_ERRORS:\n            if code != -32022:\n                raise RuntimeError(f\"{peer.name}: correct modern request error {code} before retrying\")\n            data = payload.get(\"data\")","sourceCodeStart":405,"sourceCodeEnd":441,"githubUrl":"https://github.com/rohitg00/ai-engineering-from-scratch/blob/39ea8a1c6d0b61f071226eff7ede4d4105fed820/phases/13-tools-and-protocols/08-building-an-mcp-client/code/main.py#L405-L441","documentation":"During modern discovery the peer returned a non-null response that is not a dict, so it cannot be a JSON-RPC message. The client distinguishes this from the empty case (which triggers a legacy probe) and treats a non-dict payload as a protocol violation.","triggerScenarios":"_connect_peer's discovery _send returns a truthy value where isinstance(response, dict) is False — e.g. a string, list, or number.","commonSituations":"Transport returning unparsed JSON text; a proxy or mock injecting a non-message payload; partial deserialization bugs in the framing layer.","solutions":["Inspect the raw discovery response the transport produced","Fix the transport to return parsed dict messages","Remove any intermediary that mangles the message shape","Add a regression test asserting discovery responses are dicts"],"exampleFix":"// before\ntransport.receive = lambda: raw_json_text  # str leaks through\n\n// after\ntransport.receive = lambda: json.loads(raw_json_text)  # dict returned","handlingStrategy":"type-guard","validationCode":"resp = transport.peek_discovery(peer)\nif resp is not None and not isinstance(resp, dict):\n    raise ValueError('transport returns non-dict frames; fix before connect')","typeGuard":"def is_rpc_message(value) -> bool:\n    return isinstance(value, dict) and \"jsonrpc\" in value","tryCatchPattern":"null","preventionTips":["Keep transports responsible for JSON parsing","Assert frame shape in transport unit tests"],"tags":["mcp","transport","response-shape","discovery"],"backgroundTag":"malformed-rpc-response","analyzedSha":"39ea8a1c6d0b61f071226eff7ede4d4105fed820","analyzedAt":"2026-08-26T03:13:46.626Z","schemaVersion":2},"datasetVersion":"2026-08-26T07:17:17.940Z"}