Minimal MCP server that bridges an MCP-capable agent to an IMAP4 mailbox. It uses
fastmcp with imap-tools to expose mailbox helpers so an agent can explore
folders, search, fetch message content, inspect and change message keywords,
decrypt PGP/MIME message bodies, and create draft replies without speaking IMAP
directly.
whoami(): Return the configured login/email address.list_mailboxes(directory, pattern): Enumerate folders using IMAP globbing.mailboxes_status(directory): ReturnMESSAGES,RECENT, andUNSEENcounts.search(directory="INBOX", criteria="ALL"): Run an IMAP search and return matching UIDs.get_header(directory, uids): Fetch raw headers (dict of header name to list of values) for each UID.get_header_field(directory, uids, field): Fetch one header field for each UID.get_text(directory, uids): Fetch the plain text body for each UID, decrypting PGP/MIME messages when possible.get_html(directory, uids): Fetch the HTML body for each UID, decrypting PGP/MIME messages when possible.get_size(directory, uids): Fetch RFC822 message sizes in bytes.get_keywords(directory, uids): Fetch IMAP flags/keywords for each UID.change_keywords(directory, uids, keywords, set): Add or remove IMAP flags/keywords.create_message(content): Append a raw RFC 822 message to theDraftsmailbox.
Notes:
- UIDs are relative to the folder you query; use the same
directoryforsearchand subsequentget_*calls. - Message fetches are read-only and avoid marking messages as seen.
get_text()andget_html()use the localgpgkeyring formultipart/encryptedmessages withapplication/pgp-encrypted.change_keywords()andcreate_message()modify the mailbox state.
- Python 3.10+
- An IMAP account with host, username, and either:
- Password/app password (traditional authentication), or
- OAuth2 token (for Gmail and other OAuth-enabled providers)
gpginstalled on the host when you want to read PGP/MIME-encrypted messages
- (Recommended)
python3 -m venv .venv && source .venv/bin/activate - Install dependencies:
python3 -m pip install -r requirements.txt
The server reads credentials from environment variables; a .env file is supported via python-dotenv.
For standard IMAP servers:
IMAP_HOST=imap.example.com
IMAP_LOGIN=user@example.com
IMAP_PASSWORD=app-specific-password
For Gmail using OAuth2:
IMAP_HOST=imap.gmail.com
IMAP_LOGIN=user@gmail.com
IMAP_TOKEN=your-oauth2-access-token
To decrypt encrypted message bodies through get_text() and get_html():
GNUPGHOME=/path/to/gpg-home
If GNUPGHOME is unset, the server uses the default GnuPG home directory for
the account running mcp-server.py. The required secret keys must already be
available in that keyring.
To obtain a Gmail OAuth2 token:
- Create OAuth2 credentials in Google Cloud Console
- Enable Gmail API for your project
- Create OAuth 2.0 Client ID (Desktop application)
- Note your client ID and client secret
- Run the token helper script:
python3 gmail_auth.py --client-id YOUR_CLIENT_ID --client-secret YOUR_CLIENT_SECRET - Follow the browser prompt to authorize access
- Copy the
access_tokento your.envfile asIMAP_TOKEN
Keep credentials out of version control and prefer app passwords or OAuth2 tokens when possible.
Direct execution: python mcp-server.py
Register with a client: clause mcp add mailbox ./mcp-server.py or codex mcp add mailbox ./mcp-server.py
You can also run the MCP server using http transport:
fastmcp run mcp-server.py -t http --host 192.168.100.1
And then add it to codex:
codex mcp add email --url http://192.168.100.1:8000/mcp
Ensure you run the command in a shell where the environment variables above are set or a .env file is present.
> Review message-id 20260424091024.23495-1-deller@kernel.org from INBOX/Qemu/linux-user
-
High:
[PATCH 4/4]mis-encodes successful_NEWgetsockopt()results when userspace passes a buffer larger thansizeof(struct target__kernel_sock_timeval). The new branch chooses the 64-bit copy path only on exact equality; any largerlenfalls back tocopy_to_user_timeval(), which writes the old layout instead. The decision needs to be based onoptname, withlenclamped viaMIN(...), not onlen == sizeof(...). Relevant code is inlinux-user/syscall.c:2604. Source for the patch hunk: https://www.mail-archive.com/qemu-devel%40nongnu.org/msg1186189.html -
Medium:
[PATCH 1/4]should not replaceabi_llongwith plainint64_tinstruct target__kernel_sock_timeval.abi_llongcarries target ABI alignment requirements;int64_tdoes not, so this can change guest-visible layout/alignment on some targets. The current struct is atlinux-user/syscall_defs.h:240. Richard Henderson already NACKed this point in-thread: https://www.mail-archive.com/qemu-devel%40nongnu.org/msg1186340.html
I did not find another separate functional issue in patches 2/4 or 3/4 beyond those. If you want, I can turn this into a reply mail in the repo's preferred quoted format.
> Write replies in Draft folder
Two draft replies are in Drafts:
Re: [PATCH 1/4] linux-user: Use int64_t in target__kernel_sock_timevalRe: [PATCH 4/4] linux-user: Add getsockopt() for SO_RCVTIMEO_NEW and SO_SNDTIMEO_NEW
The first calls out the target ABI alignment issue with replacing abi_llong,
and the second points out the _NEW getsockopt bug when userspace provides a
buffer larger than the 64-bit timeval struct.
Distributed under the GNU General Public License v2.0. See LICENSE for details.