odysseus-dev/odysseus · error · ValueError

IMAP folder not found: {folder}

Error message

IMAP folder not found: {folder}

What it means

Raised when conn.select(folder, readonly=True) returns a status other than 'OK', meaning the IMAP server refused the folder name. Python's imaplib does not throw for a missing mailbox; it returns 'NO', so this ValueError converts that into an explicit error naming the folder.

Source

Thrown at mcp_servers/email_server.py:984

# ── Tool implementations ──


def _list_emails(folder="INBOX", max_results=20, unresponded_only=False,
                 unread_only=False, account=None):
    """List emails newest-first. By default returns the latest messages,
    including read mail, so it matches normal inbox UI expectations.
    Pass unread_only=True and/or unresponded_only=True for attention scans.
    account selects mailbox (None = default).
    """
    fixture = _fixture_list_emails(folder, max_results, unresponded_only, unread_only, account)
    if fixture is not None:
        return fixture
    conn = None
    try:
        conn = _imap_connect(account)
        select_status, _ = conn.select(_q(folder), readonly=True)
        if select_status != "OK":
            raise ValueError(f"IMAP folder not found: {folder}")

        if unread_only and unresponded_only:
            status, data = conn.uid("SEARCH", None, "(UNSEEN UNANSWERED)")
        elif unread_only:
            status, data = conn.uid("SEARCH", None, "(UNSEEN)")
        elif unresponded_only:
            # Was missing — unresponded_only=True (without unread_only) fell through
            # to "ALL" and returned answered mail too, despite the documented
            # "emails without replies" behaviour.
            status, data = conn.uid("SEARCH", None, "(UNANSWERED)")
        else:
            # Include read too — IMAP search "ALL" returns the entire folder
            status, data = conn.uid("SEARCH", None, "ALL")

        if status != "OK" or not data[0]:
            return []

        uid_list = list(reversed(data[0].split()))[:max_results]

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. List the server's actual folders first (imaplib list() or the MCP server's folder-list tool) and use the exact returned name
  2. Use 'INBOX', which exists on every IMAP server
  3. Match folder-name case and any delimiters (e.g. '[Gmail]/...') exactly

Example fix

# before
conn.select('Inbox', readonly=True)  # NO on case-sensitive servers
# after
status, folders = conn.list()
conn.select('INBOX', readonly=True)
Defensive patterns

Strategy: validation

Validate before calling

typ, data = conn.list()
names = [d.decode().rsplit('"',2)[-2] for d in data]
folder = folder if folder in names else 'INBOX'

Type guard

def folder_exists(conn, folder: str) -> bool:
    typ, data = conn.list()
    return any(folder.encode() in (d or b'') for d in data)

Try / catch

try:
    conn.select(_q(folder), readonly=True)
except ValueError:
    folder = 'INBOX'
    conn.select('INBOX', readonly=True)

Prevention

When it happens

Trigger: Calling list_emails with folder='Inbox' against a case-sensitive server that only has 'INBOX'; requesting an archived/nested folder like 'Archive/2024' that does not exist on the server; quoting issues in the folder name passed through _q().

Common situations: Folder naming differences between mail providers (Gmail '[Gmail]/Sent Mail' vs plain 'Sent'); hard-coded folder names ported from another provider; folders the IMAP account cannot see due to ACLs.

Related errors


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