odysseus-dev/odysseus · error · HTTPException
Invalid OAuth keys file format
Error message
Invalid OAuth keys file format
What it means
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.
Source
Thrown at routes/mcp/mcp_routes.py:449
require_admin(request)
db = SessionLocal()
try:
srv = db.query(McpServer).filter(McpServer.id == server_id).first()
if not srv:
raise HTTPException(404, "Server not found")
if not srv.oauth_config:
raise HTTPException(400, "Server has no OAuth config")
oauth_cfg = _sanitize_mcp_oauth_config(json.loads(srv.oauth_config))
keys_file = oauth_cfg.get("keys_file", "")
if not keys_file or not os.path.exists(keys_file):
raise HTTPException(400, "OAuth keys file not found")
with open(keys_file, encoding="utf-8") as f:
keys_data = json.load(f)
keys = keys_data.get("installed") or keys_data.get("web")
if not keys:
raise HTTPException(400, "Invalid OAuth keys file format")
client_id = keys["client_id"]
scopes = oauth_cfg.get("scopes", [])
# For Desktop App creds, default to localhost — the user will
# paste the resulting URL back if they're on a different device.
redirect_uri = _mcp_oauth_redirect_uri()
params = {
"client_id": client_id,
"redirect_uri": redirect_uri,
"response_type": "code",
"scope": " ".join(scopes),
"access_type": "offline",
"prompt": "consent",
"state": server_id,
}
auth_url = "https://accounts.google.com/o/oauth2/v2/auth?" + urllib.parse.urlencode(params)View on GitHub (pinned to f9235ebbf1)
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.
Example fix
# before (token file, wrong shape)
{"access_token": "...", "token_type": "Bearer"}
# after (client credentials file)
{"installed": {"client_id": "...", "client_secret": "...", "redirect_uris": ["http://localhost"]}} Defensive patterns
Strategy: type-guard
Validate before calling
import json
def keys_file_shape_ok(path: str) -> bool:
try:
with open(path, encoding="utf-8") as f:
d = json.load(f)
return isinstance(d, dict) and ("installed" in d or "web" in d)
except (OSError, json.JSONDecodeError):
return False Type guard
def is_google_client_creds(d: unknown): boolean {
return typeof d === "object" && d !== null &&
("installed" in d || "web" in d);
} Prevention
- Use the client-secret JSON downloaded from Google Cloud Console, exactly as downloaded.
- Never substitute token or service-account files for client credentials.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Server has no OAuth config
- OAuth keys file not found
- OAuth keys/token file not configured
- Unknown device-flow provider: ${provider}
- HTTP ${r.status}
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/b78be87fe14eaa31.
Report an issue: GitHub.