{"record":{"id":"9f1dceb607cd7000","repo":"langchain-ai/deepagents","slug":"mcp-token-file-path-is-not-a-json-object-found","errorCode":null,"errorMessage":"MCP token file {path} is not a JSON object (found {type(data).__name__}). Delete it and run `/mcp login {self._server_name}` in the TUI (or `dcode mcp login {self._server_name}`).","messagePattern":"MCP token file (.+?) is not a JSON object \\(found (.+?)\\)\\. Delete it and run `/mcp login (.+?)` in the TUI \\(or `dcode mcp login (.+?)`\\)\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/mcp_auth.py","lineNumber":723,"sourceCode":"                f\"Failed to read MCP token file {path}: {exc}. \"\n                f\"Delete the file and run `/mcp login {self._server_name}` \"\n                f\"in the TUI (or `dcode mcp login {self._server_name}`).\"\n            )\n            raise RuntimeError(msg) from exc\n        # `json.loads` yields a `dict` only for object literals; `null`, a list,\n        # or a bare scalar would make the `.get` below raise `AttributeError`,\n        # which callers do not catch. Fail as a normal corrupt-file error.\n        if not isinstance(data, dict):\n            msg = (\n                f\"MCP token file {path} is not a JSON object (found \"\n                f\"{type(data).__name__}). Delete it and run \"\n                f\"`/mcp login {self._server_name}` in the TUI (or \"\n                f\"`dcode mcp login {self._server_name}`).\"\n            )\n            # Not `TypeError` (TRY004): this is a corrupt-file report, not a\n            # caller type error, and callers catch the same `RuntimeError` the\n            # other corruption modes raise. `TypeError` would escape them.\n            raise RuntimeError(msg)  # noqa: TRY004\n        if data.get(\"version\") != _STORAGE_VERSION:\n            # Render only the value's type, never the value itself: callers\n            # print this message verbatim (e.g. `mcp login` list on stderr),\n            # and the version field is attacker-controlled file content that\n            # could carry credential material planted by a malformed write.\n            msg = (\n                f\"MCP token file {path} has unsupported version \"\n                f\"({type(data.get('version')).__name__}; expected \"\n                f\"{_STORAGE_VERSION!r}). Delete it and run \"\n                f\"`/mcp login {self._server_name}` in the \"\n                f\"TUI (or `dcode mcp login {self._server_name}`).\"\n            )\n            raise RuntimeError(msg)\n        return data\n\n    def _write(self, data: dict) -> None:\n        path = self.path\n        path.parent.mkdir(parents=True, exist_ok=True)","sourceCodeStart":705,"sourceCodeEnd":741,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/mcp_auth.py#L705-L741","documentation":"After JSON parsing succeeds, _read verifies the document is a JSON object; null, arrays, or bare scalars would otherwise cause AttributeError later. The file exists and parses but has the wrong shape, so it is treated as corrupt and a RuntimeError is raised with re-login instructions. The message deliberately prints only the type, never the value, because the file content is attacker-controlled.","triggerScenarios":"Any of the _read-backed getters/setters encountering a token file whose top-level JSON value is not an object (e.g. `null`, `[]`, `\"str\"`, `123`).","commonSituations":"Someone ran `echo null > token.json` while debugging, a truncating editor emptied then saved `null`, or a script overwrote the file with a JSON array of tokens instead of the expected object schema.","solutions":["Delete the offending token file shown in the message.","Re-authenticate via `/mcp login <server>` (TUI) or `dcode mcp login <server>`.","If a tooling script writes this file, fix it to emit a JSON object with the expected schema/version fields."],"exampleFix":"// before\n$ cat github.json\nnull\n// after\n$ rm github.json && dcode mcp login github","handlingStrategy":"validation","validationCode":"import json\nfrom pathlib import Path\np = Path(token_store_dir()) / f\"{server_name}.json\"\nif p.exists():\n    data = json.loads(p.read_text(encoding=\"utf-8\"))\n    if not isinstance(data, dict):\n        p.unlink()\n        run_login(server_name)","typeGuard":"def is_token_dict(data: object) -> TypeGuard[dict]:\n    return isinstance(data, dict)","tryCatchPattern":"try:\n    tokens = store.get_tokens_sync()\nexcept RuntimeError as e:\n    if \"is not a JSON object\" in str(e):\n        store.path.unlink(missing_ok=True)\n        reauthenticate(server_name)\n    else:\n        raise","preventionTips":["Treat token files as opaque library-owned state - never rewrite them with ad-hoc scripts.","If scripting token provisioning, always emit a JSON object matching the library's schema.","Validate any externally provisioned token file with json.loads + isinstance(data, dict) first."],"tags":["mcp","json","schema-validation","corrupt-file"],"backgroundTag":"schema-validation-failed","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}