odysseus-dev/odysseus · error · ValueError

Error: email MCP requires an authenticated owner or ODYSSEUS

Error message

Error: email MCP requires an authenticated owner or ODYSSEUS_MCP_EMAIL_OWNER when owner-scoped email accounts are configured.

What it means

ValueError (_OWNER_SCOPE_ERROR) raised while resolving the email MCP server's account config. When accounts in the database are owner-scoped (_mcp_owner_required true) but no authenticated owner is present and ODYSSEUS_MCP_EMAIL_OWNER is unset, the server refuses to start because it cannot tell which owner's mailboxes it may access.

Source

Thrown at mcp_servers/email_server.py:314

        "smtp_security": os.environ.get("SMTP_SECURITY", ""),
        "smtp_user": os.environ.get("SMTP_USER", ""),
        "smtp_password": os.environ.get("SMTP_PASSWORD", ""),
        "smtp_starttls": os.environ.get("SMTP_STARTTLS", "false").lower() == "true",
        "smtp_ssl": os.environ.get("SMTP_SSL", "true").lower() == "true",
        "from_address": os.environ.get("EMAIL_FROM", ""),
        "archive_folder": os.environ.get("ARCHIVE_FOLDER", "Archive"),
        "trash_folder": os.environ.get("TRASH_FOLDER", "Trash"),
        "cache_db": os.environ.get(
            "EMAIL_CACHE_DB",
            EMAIL_CACHE_DB,
        ),
        "account_id": None,
        "account_name": None,
    }

    raw_rows = _read_accounts_from_db()
    if _mcp_owner_required(raw_rows):
        raise ValueError(_OWNER_SCOPE_ERROR)
    rows = _filter_accounts_for_owner(raw_rows)
    row = _resolve_account_from_rows(rows, account)
    if _current_owner() and raw_rows and not rows:
        raise ValueError("No email account is configured for the authenticated owner")
    if account and rows and not row:
        available = ", ".join(
            f"{r.get('name') or r.get('imap_user')} <{r.get('imap_user') or r.get('from_address') or '?'}>"
            for r in rows
        )
        raise ValueError(f"Email account not found for selector {account!r}. Available accounts: {available}")
    if row:
        cfg["account_id"] = row["id"]
        cfg["account_name"] = row["name"]
        cfg["imap_host"] = row["imap_host"] or cfg["imap_host"]
        cfg["imap_port"] = int(row["imap_port"] or cfg["imap_port"])
        cfg["imap_user"] = row["imap_user"] or cfg["imap_user"]
        # Passwords in email_accounts are stored encrypted via
        # src.secret_storage.encrypt — decrypt before handing to IMAP

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Set ODYSSEUS_MCP_EMAIL_OWNER to the username whose email accounts should be used
  2. Or run the server within the authenticated app context so _current_owner() resolves
  3. If owner scoping is unwanted, remove owner values from the account rows so _mcp_owner_required returns false

Example fix

# before
$ python mcp_servers/email_server.py  # owner-scoped rows, no owner → ValueError
# after
$ ODYSSEUS_MCP_EMAIL_OWNER=alice python mcp_servers/email_server.py
Defensive patterns

Strategy: validation

Validate before calling

import os
owner = _current_owner() or os.environ.get('ODYSSEUS_MCP_EMAIL_OWNER')
rows = _read_accounts_from_db()
if _mcp_owner_required(rows) and not owner:
    raise RuntimeError('Set ODYSSEY_MCP_EMAIL_OWNER before starting the email MCP server')

Try / catch

try:
    cfg = build_email_config()
except ValueError as e:
    if 'authenticated owner' in str(e):
        sys.exit('Set ODYSSEUS_MCP_EMAIL_OWNER=<username> and restart the email MCP server')
    raise

Prevention

When it happens

Trigger: Starting/invoking the email MCP server (build of cfg) with owner-scoped account rows in the accounts DB while the request/context carries no owner and the ODYSSEUS_MCP_EMAIL_OWNER environment variable is empty.

Common situations: Running the MCP server as a standalone process without the app's auth context; env var lost in a new deployment or service file; accounts migrated to per-owner mode without updating the launcher.

Understand the failure class

Related errors


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