{"record":{"id":"b78be87fe14eaa31","repo":"odysseus-dev/odysseus","slug":"invalid-oauth-keys-file-format","errorCode":null,"errorMessage":"Invalid OAuth keys file format","messagePattern":"Invalid OAuth keys file format","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"routes/mcp/mcp_routes.py","lineNumber":449,"sourceCode":"        require_admin(request)\n        db = SessionLocal()\n        try:\n            srv = db.query(McpServer).filter(McpServer.id == server_id).first()\n            if not srv:\n                raise HTTPException(404, \"Server not found\")\n            if not srv.oauth_config:\n                raise HTTPException(400, \"Server has no OAuth config\")\n\n            oauth_cfg = _sanitize_mcp_oauth_config(json.loads(srv.oauth_config))\n            keys_file = oauth_cfg.get(\"keys_file\", \"\")\n            if not keys_file or not os.path.exists(keys_file):\n                raise HTTPException(400, \"OAuth keys file not found\")\n\n            with open(keys_file, encoding=\"utf-8\") as f:\n                keys_data = json.load(f)\n            keys = keys_data.get(\"installed\") or keys_data.get(\"web\")\n            if not keys:\n                raise HTTPException(400, \"Invalid OAuth keys file format\")\n\n            client_id = keys[\"client_id\"]\n            scopes = oauth_cfg.get(\"scopes\", [])\n\n            # For Desktop App creds, default to localhost — the user will\n            # paste the resulting URL back if they're on a different device.\n            redirect_uri = _mcp_oauth_redirect_uri()\n\n            params = {\n                \"client_id\": client_id,\n                \"redirect_uri\": redirect_uri,\n                \"response_type\": \"code\",\n                \"scope\": \" \".join(scopes),\n                \"access_type\": \"offline\",\n                \"prompt\": \"consent\",\n                \"state\": server_id,\n            }\n            auth_url = \"https://accounts.google.com/o/oauth2/v2/auth?\" + urllib.parse.urlencode(params)","sourceCodeStart":431,"sourceCodeEnd":467,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/mcp/mcp_routes.py#L431-L467","documentation":"On GET /oauth/authorize/{server_id}, the keys file exists and parses as JSON, but neither an \"installed\" nor a \"web\" top-level key is present, so the credentials shape is unrecognized and the route returns 400. Google client-secret files for desktop apps use \"installed\" and for web apps use \"web\"; anything else (a token file, a service-account key, or a hand-written file) fails here.","triggerScenarios":"Uploading an OAuth *token* JSON instead of the client-secret JSON; a service-account key file (type: service_account); a truncated or edited credentials file missing the wrapper object; wrong file downloaded from a different Google Cloud flow.","commonSituations":"Grabbing token.json written by a previous flow rather than client_secret_*.apps.googleusercontent.com.json; mixing up web vs desktop credential downloads with another format; copying only the inner {client_id,...} object without the \"installed\" wrapper.","solutions":["Download the OAuth client JSON from Google Cloud Console (Credentials → OAuth client ID) and use that file as keys_file.","Open the file and confirm it starts with {\"installed\": {...}} or {\"web\": {...}}.","If it is a token file or service-account key, get the correct client-credentials file — this flow only supports the installed/web shapes."],"exampleFix":"# before (token file, wrong shape)\n{\"access_token\": \"...\", \"token_type\": \"Bearer\"}\n\n# after (client credentials file)\n{\"installed\": {\"client_id\": \"...\", \"client_secret\": \"...\", \"redirect_uris\": [\"http://localhost\"]}}","handlingStrategy":"type-guard","validationCode":"import json\ndef keys_file_shape_ok(path: str) -> bool:\n    try:\n        with open(path, encoding=\"utf-8\") as f:\n            d = json.load(f)\n        return isinstance(d, dict) and (\"installed\" in d or \"web\" in d)\n    except (OSError, json.JSONDecodeError):\n        return False","typeGuard":"def is_google_client_creds(d: unknown): boolean {\n  return typeof d === \"object\" && d !== null &&\n    (\"installed\" in d || \"web\" in d);\n}","tryCatchPattern":null,"preventionTips":["Use the client-secret JSON downloaded from Google Cloud Console, exactly as downloaded.","Never substitute token or service-account files for client credentials."],"tags":["mcp","oauth","json","google","config"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}