odysseus-dev/odysseus · error · HTTPException
OAuth keys file not found
Error message
OAuth keys file not found
What it means
On GET /oauth/authorize/{server_id}, the sanitized oauth_config's keys_file is empty or the file does not exist on disk (os.path.exists check), returning 400. The keys_file is a Google OAuth client-credentials JSON; the sanitize step may also have rewritten a relative path under the mcp_oauth base, so the file must actually live at that resolved location.
Source
Thrown at routes/mcp/mcp_routes.py:443
# ── OAuth flow for Google MCP servers ──────────────────────────
@router.get("/oauth/authorize/{server_id}")
def oauth_authorize(server_id: str, request: Request):
"""Show OAuth authorization page with Google sign-in link."""
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",View on GitHub (pinned to f9235ebbf1)
Solutions
- Copy the Google client-secret JSON into the mcp_oauth base directory and reference it by relative filename in oauth_config.
- Verify the resolved path exists (os.path.exists) before calling authorize.
- In containers, mount a persistent volume over the mcp_oauth base dir so credential files survive restarts.
Example fix
# before
oauth_config = {"keys_file": "/home/me/creds/secret.json"}
# after
base = _mcp_oauth_base_dir()
shutil.copy("/home/me/creds/secret.json", Path(base) / "secret.json")
oauth_config = {"keys_file": "secret.json"} Defensive patterns
Strategy: validation
Validate before calling
import os
def keys_file_ready(oauth_cfg: dict, base) -> bool:
kf = oauth_cfg.get("keys_file", "")
return bool(kf) and os.path.exists(os.path.join(base, kf)) Try / catch
On 400 'OAuth keys file not found', copy the credentials into the mcp_oauth base and re-register; do not retry the authorize URL.
Prevention
- Keep credential files inside the mcp_oauth directory.
- Mount persistent storage for mcp_oauth in containers.
- Check os.path.exists before starting any OAuth flow.
When it happens
Trigger: oauth_config references keys_file that was deleted, moved, or never copied into the mcp_oauth dir; container deployments where the base dir is an unmounted volume and files vanish on restart; path passed as absolute and rejected/rewritten by sanitization.
Common situations: creds file gitignored and missing after a fresh clone/CI deploy; Docker volume not mounted for mcp_oauth; keys file renamed after rotation.
Related errors
- Invalid OAuth {field_name}: path must stay under {base}
- Server has no OAuth config
- Invalid OAuth keys file format
- OAuth keys/token file not configured
- Unknown device-flow provider: ${provider}
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/60a8c9eb11859c8e.
Report an issue: GitHub.