{"record":{"id":"1a34913839ea8a4c","repo":"rohitg00/ai-engineering-from-scratch","slug":"meta-must-be-an-object","errorCode":null,"errorMessage":"_meta must be an object","messagePattern":"_meta must be an object","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"certifications/claude/lessons/11-mcp-server-design-and-integration/code/main.py","lineNumber":207,"sourceCode":"            raise ValueError(\"unsupported or malformed notification\")\n        request_id = params.get(\"requestId\")\n        if type(request_id) not in (str, int):\n            raise ValueError(\"notifications/cancelled requires a requestId\")\n        reason = params.get(\"reason\")\n        if reason is not None and not isinstance(reason, str):\n            raise ValueError(\"notifications/cancelled reason must be a string\")\n        metadata = params.get(\"_meta\")\n        if metadata is not None and not isinstance(metadata, dict):\n            raise ValueError(\"notifications/cancelled _meta must be an object\")\n        if set(params) - {\"requestId\", \"reason\", \"_meta\"}:\n            raise ValueError(\"notifications/cancelled contains unexpected fields\")\n\n    def _validate_request_metadata(self, params: Any) -> dict[str, Any]:\n        if not isinstance(params, dict):\n            raise ValueError(\"params must be an object\")\n        metadata = params.get(\"_meta\")\n        if not isinstance(metadata, dict):\n            raise ValueError(\"_meta must be an object\")\n        version = metadata.get(PROTOCOL_VERSION_KEY)\n        capabilities = metadata.get(CLIENT_CAPABILITIES_KEY)\n        if not isinstance(version, str):\n            raise ValueError(f\"_meta.{PROTOCOL_VERSION_KEY} is required\")\n        if not isinstance(capabilities, dict):\n            raise ValueError(f\"_meta.{CLIENT_CAPABILITIES_KEY} is required\")\n        client_info = metadata.get(CLIENT_INFO_KEY)\n        if client_info is not None and (\n            not isinstance(client_info, dict)\n            or not isinstance(client_info.get(\"name\"), str)\n            or not isinstance(client_info.get(\"version\"), str)\n        ):\n            raise ValueError(f\"_meta.{CLIENT_INFO_KEY} must include name and version\")\n        if version != CURRENT_PROTOCOL_VERSION:\n            raise ProtocolError(\n                -32022,\n                \"Unsupported protocol version\",\n                {\"supported\": [CURRENT_PROTOCOL_VERSION], \"requested\": version},","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/rohitg00/ai-engineering-from-scratch/blob/39ea8a1c6d0b61f071226eff7ede4d4105fed820/certifications/claude/lessons/11-mcp-server-design-and-integration/code/main.py#L189-L225","documentation":"The MCP simulator's _validate_request_metadata requires every request params object to carry a `_meta` key that is a dict (JSON object). If params has no `_meta` at all, or `_meta` is a string/number/list/None, the exchange rejects the request before dispatch. This mirrors the spec's rule that stateless MCP requests embed protocol negotiation metadata in `_meta`.","triggerScenarios":"Calling exchange() (or server/discover, resources/read, prompts/get, tools/call through it) with params like {}, {\"uri\": ...} with no _meta, or {\"_meta\": \"2026-07-28\"} (a string instead of an object).","commonSituations":"Client code built for a stateful MCP transport (initialize handshake once, then plain params) ported to this stateless design; hand-written JSON payloads where _meta is omitted or accidentally double-encoded as a JSON string; test fixtures missing the _meta block.","solutions":["Add a _meta object to every request params: {\"_meta\": {\"io.modelcontextprotocol/protocolVersion\": \"2026-07-28\", \"io.modelcontextprotocol/clientCapabilities\": {}}}","If _meta is present but a string, json.loads it or wrap it as an object before sending","Centralize _meta construction in one client helper so no call site can forget it"],"exampleFix":"# before\nparams = {\"uri\": \"study://catalog\"}\nserver.exchange(method, params)\n# after\nparams = {\n  \"uri\": \"study://catalog\",\n  \"_meta\": {\n    \"io.modelcontextprotocol/protocolVersion\": \"2026-07-28\",\n    \"io.modelcontextprotocol/clientCapabilities\": {},\n  },\n}\nserver.exchange(method, params)","handlingStrategy":"validation","validationCode":"def build_params(payload: dict) -> dict:\n    payload[\"_meta\"] = payload.get(\"_meta\") or {}\n    assert isinstance(payload[\"_meta\"], dict), \"_meta must be an object\"\n    return payload","typeGuard":"def has_valid_meta(params: dict) -> bool:\n    return isinstance(params.get(\"_meta\"), dict)","tryCatchPattern":"try:\n    server.exchange(method, params)\nexcept ValueError as e:\n    if \"_meta\" in str(e):\n        params = {**params, \"_meta\": default_meta()}\n        server.exchange(method, params)","preventionTips":["Build every request through one helper that always injects a _meta object","Never hand-write params dicts at call sites; assemble them from a typed client class","Add a unit test asserting all outbound payloads contain a dict _meta"],"tags":["mcp","metadata","request-validation","json-rpc"],"backgroundTag":"missing-required-field","analyzedSha":"39ea8a1c6d0b61f071226eff7ede4d4105fed820","analyzedAt":"2026-08-26T03:13:46.626Z","schemaVersion":2},"datasetVersion":"2026-08-26T07:17:17.940Z"}