• v0.29.0 e15f4332db

    0.29.0 — G1, a fourth tenant reading Gmail
    Some checks failed
    CI / gate (push) Failing after 1m22s
    CI / image (push) Has been skipped
    Stable

    joeyr released this 2026-08-21 21:29:11 -05:00 | 21 commits to main since this release

    G1: a fourth tenant, and it reads Gmail. Seven read tools on their own hostname, their
    own connector and their own credential, built against what nine live probes found rather
    than against the RFCs. The code is not live-verified and G1 is not done until it is
    docs/gmail-plan.md carries the pass to run, and a green gate has cleared nothing on this
    project since M2 shipped 799 passing tests around a move_message that had never worked.

    Added

    • TENANT=gmail, with mcp_gateway/gmail/: list_folders, list_unread_counts,
      list_messages, search_messages, get_message, get_message_security and
      get_attachment. Read-only, and not by a switch — there is no write path in the
      package at all, asserted against the built server with MAIL_WRITABLE_FOLDERS and
      MAIL_SEND_ENABLED both set to values that would enable one next door.

    • GOOGLE_MAIL_ADDRESS and GOOGLE_APP_PASSWORD, required only by this tenant, which
      reads neither Apple value. Provider-prefixed rather than generic because these arrive as
      per-service Portainer variables: two service blocks each holding a MAIL_ADDRESS, with
      different mailboxes in them, is the paste hazard Credentials already names for
      MCP_OAUTH_SIGNING_KEY.

    • deploy/nginx/gmail.conf, and Config.mail_address / Config.is_mail_tenant so
      nothing downstream has to know which tenant it is in to ask for the mailbox.

    Changed

    • list_unread_counts and search_messages exclude All Mail from the all-labels
      answer
      , because Gmail files every message there as well as under its label — probe 5
      found every sampled INBOX message in both. Summing across labels without this reports
      roughly double the real unread total, and reports it confidently. Excluded by role,
      never by name
      : the [Gmail]/ folders are localized, so matching the English string
      would silently stop matching and put the double count straight back with no error.

    • Both mail tenants now name their account in every colliding tool description. Seven
      names exist on both connectors, and CLAUDE.md mandates verb-first names with a test
      pinning it — so the names cannot disambiguate and the descriptions have to. A rule
      applied to one of two colliding tenants is not a rule, so iCloud's seven changed in this
      commit too, and a test fails if either side ever drops it.

    Notes

    • Gmail advertises SPECIAL-USE, UIDPLUS, MOVE and X-GM-EXT-1. iCloud has
      neither SPECIAL-USE nor MOVE, so its role-guessing and its COPYUID workaround are
      iCloud's problems and are deliberately not carried across. Copying them would have
      added mystery rather than safety.

    • get_message_security needed no new parsing. mail/authresults.py reads Gmail's
      header unmodified. The tool does add one thing probe 9 found by accident: a message the
      user sent themselves carries no Authentication-Results, because it never left
      Google — so an absent block is reported as not applicable rather than rendered as a
      failure. Everything in Sent looks like that, and it is exactly what a naive
      implementation would flag as suspicious.

    • Two of the new tests were rewritten after they passed. The first fake overrode
      unread_counts and re-implemented the All Mail rule inside itself, which asserts only
      that the test agrees with the test. It now intercepts the connection, so the real method
      runs — and both it and the naming rule were confirmed by breaking the code and watching
      them fail.

    • 1100 tests green (16 new).

    Downloads