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

  1. Copy the Google client-secret JSON into the mcp_oauth base directory and reference it by relative filename in oauth_config.
  2. Verify the resolved path exists (os.path.exists) before calling authorize.
  3. 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

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


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/60a8c9eb11859c8e. Report an issue: GitHub.