MailOKF MCP
Provides tools for interacting with Gmail mailboxes, including synchronization, search, drafting, sending, replying, forwarding, moving, deleting, labeling, and managing attachments.
Provides tools for interacting with iCloud Mail mailboxes via IMAP/SMTP, including synchronization, search, drafting, sending, replying, forwarding, moving, deleting, labeling, and managing attachments.
Uses SQLite with FTS5 for normalized storage and full-text search of synchronized email data, serving as the operational database and search layer.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@MailOKF MCP Search my Gmail for unread invoices from the last month"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
MailOKF MCP
MailOKF is a local-first Python MCP server that turns Gmail, Microsoft Outlook/Microsoft 365, iCloud Mail, and generic IMAP/SMTP mailboxes into a synchronized, searchable knowledge layer—without making the local copy authoritative.
It exposes 38 tools and four resources for mailbox actions, synchronization, OKF retrieval, attachments, and optional semantic search. The provider mailbox remains authoritative; SQLite and OKF are local projections.
MailOKF combines full email operations with durable Open Knowledge Format (OKF) projections in one local-first MCP:
full initial mailbox import;
provider-specific incremental synchronization;
normalized SQLite storage and FTS5 search;
one Open Knowledge Format (OKF) source concept per email;
local, content-addressed attachment retention;
raw MIME deletion after successful conversion;
optional CocoIndex + sqlite-vec semantic indexing;
draft, send, reply, forward, move, delete, label/category, and batch tools;
explicit confirmation and idempotency controls for outbound email.
tamper-evident managed outbound attachments for new sends and drafts.
Implementation status: MailOKF 0.3 provides 25 stable core email operations with camelCase calling shapes plus thirteen MailOKF extensions. Live Gmail, Microsoft, and iCloud interoperability still requires testing with your own provider apps, credentials, tenants, mailbox sizes, throttling conditions, and compliance requirements before production use.
Production notice: This project is local-first software, not a live-provider certification. Test it with isolated accounts and validate provider, tenant, scale, privacy, and compliance requirements before production use.
Keep private: Never commit OAuth JSON, tokens,
MAILOKF_ROOT_DIR, runtime mailbox data, attachments, quarantine, or backups.
Documentation
Guide | Use it for |
Choose the recommended path for users, operators, or contributors. | |
Install, configure, sync, run MCP, back up, and troubleshoot. | |
Create a Gmail OAuth desktop client. | |
Register an Outlook public client. | |
Understand authority, storage, synchronization, and failures. | |
Review supported versions, reporting, secrets, and trust boundaries. | |
Set up development and prepare a pull request. |
Related MCP server: MailMesh MCP
Outbound attachments
email_send and email_draft_create accept an optional strict attachments array. Both require a nonblank idempotencyKey before provider access; terminal send/draft retries replay durable results and ambiguous provider outcomes remain blocked. Each item must
select exactly one source: {stagedId, filename?}, {messageId, attachmentId, filename?}, or
{localPath, filename?, confirmed: true}. Reply and forward do not accept attachment inputs.
Use mail_attachment_list(account_id, message_id) to inspect retained message attachments without
paths or bytes. Its typed results include messageId and attachmentId, so they can be copied into
the retained selector above. mail_attachment_stage(local_path, filename?, confirmed?) returns a
typed result whose stagedId can be copied into the staged selector. mail_attachment_create
accepts filename, optional content_type, and exactly one tagged content shape:
{kind: "text", contentText: "..."} or {kind: "base64", contentBase64: "..."}. Its typed result
also exposes stagedId. Configured workspace/import roots are allowed; arbitrary paths require both
MAILOKF_DANGEROUSLY_ALLOW_ARBITRARY_ATTACHMENT_PATHS=true and confirmed=true; the former
MAILOKF_ALLOW_ARBITRARY_ATTACHMENT_PATHS name is ignored. MAILOKF_ATTACHMENT_IMPORT_ROOTS uses
the platform PATH separator.
Outbound requests accept up to 20 files and 157,286,400 bytes combined; the per-file staging limit
is Settings.max_attachment_bytes, configured by MAILOKF_MAX_ATTACHMENT_BYTES. Inline UTF-8 text
is size-preflighted in bounded chunks before encoding, and strict base64 is size-preflighted then
decoded incrementally without exceeding that per-file cap. Bound filenames and parameter-free MIME
types are limited to 255 UTF-8 bytes each; managed metadata sidecars are limited to 4,096 bytes.
Count, per-file, total, and metadata limit failures use fixed public messages rather than embedding
configuration values.
Staging writes one physical outbound/sha256/<digest> blob per SHA-256 and an opaque,
restart-stable sidecar reference that persists no source path or source-path hash. Its validated safe
filename persists and defaults to the source basename when no filename override is supplied. An
unbound staged or created reference atomically binds to the first account that uses it for
email_send or email_draft_create. Content and metadata are reverified before each use. Reusing an
already-bound batch creates no empty transaction marker; stale markers with no referencing binding
are reclaimed under the store lock. Duplicate output names receive deterministic numeric suffixes.
Gmail and IMAP preflight a conservative complete-MIME bound before reading managed descriptors, then build MIME with the preserved filename and type. Gmail rejects a fully serialized RFC
2822 MIME message above its provider-specific 36,700,160-byte cap before creating a send/draft
operation; the global attachment contract is unchanged. Outlook uses Graph fileAttachment below
3,000,000 bytes and sequential upload sessions from 3,000,000 through 150,000,000 bytes.
Managed publication is atomic and cross-process serialized. Every configured root ancestor is opened from the filesystem anchor without following symlinks. Platforms without the required descriptor-relative filesystem operations or cross-process file locking fail closed rather than falling back to process-local attachment safety.
Outbound attachments specifically require secure POSIX descriptor operations and fcntl locks.
They are intentionally unsupported on Windows; ordinary attachment-free mail, synchronization,
search, and the rest of MailOKF's Windows surface remain supported. Windows CI treats this feature
as an expected fail-closed capability rather than advertising attachment delivery. Tests that
exercise POSIX-only no-follow OAuth configuration reads or descriptor-secure storage migration are
also skipped on Windows instead of weakening those filesystem guarantees.
Why this is an MCP server rather than only a converter
The provider mailbox remains authoritative. MailOKF keeps a synchronized local projection and exposes both:
resources and retrieval tools for local OKF knowledge;
action tools for provider operations such as draft, send, reply, move, and label.
This keeps read-heavy agent queries local and fast, while provider APIs remain available for fresh synchronization and controlled writes.
Architecture
flowchart LR
G[Gmail API] --> P[Provider adapters]
O[Microsoft Graph] --> P
I[iCloud / IMAP + SMTP] --> P
P --> S[Full + incremental sync engine]
S --> T[Private raw MIME staging]
T --> M[MIME parser]
M --> A[SHA-256 attachment blob store]
M --> D[SQLite operational DB + FTS5]
D --> K[Deterministic OKF renderer]
K --> V[CocoIndex incremental vectors]
D --> X[MCP tools/resources]
K --> X
P --> XPer-message commit boundary
stage raw MIME privately
-> parse text and metadata
-> write/verify attachment blobs
-> upsert SQLite + FTS
-> atomically write OKF message and thread files
-> delete temporary raw MIME unless retention is enabledIf parsing, attachment persistence, database commit, or OKF rendering fails, MailOKF retains the raw message rather than discarding it. It normally moves the file to quarantine; if that move fails, it leaves the bytes in staging and records the fallback path and error.
Storage layout
${MAILOKF_ROOT_DIR:-./lcl_data}/
├── mailokf.json # non-secret local layout manifest
├── mailokf.sqlite # sync ledger, normalized records, FTS5
├── bundles/
│ └── <account-id>/
│ ├── index.md # OKF-reserved navigation file
│ ├── log.md # OKF-reserved date-grouped change log
│ ├── bundle.md # normal email_knowledge_bundle concept
│ ├── messages/YYYY/MM/*.md # one OKF source concept per message
│ └── threads/*.md # deterministic thread manifests
├── attachments/
│ └── sha256/ab/cd/<digest> # deduplicated attachment bytes
├── staging/ # temporary raw MIME only
├── quarantine/ # failed raw MIME conversions
├── vectors/
│ ├── okf-vectors.sqlite # sqlite-vec index
│ └── cocoindex-state/ # CocoIndex incremental state
├── workspace/ # only authorized outbound attachment import source
├── outbound/ # private immutable managed outbound attachments
└── config/
├── accounts.enc
├── master.key
├── gateway.token
└── oauth/When run from a source checkout without MAILOKF_ROOT_DIR, MailOKF stores all local state in the
repository's ignored lcl_data/ directory. Set MAILOKF_ROOT_DIR to use a different portable root;
this is required when the application is installed outside a checkout or when a CLI, daemon, gateway,
and MCP client must share a separately managed location. Paired MAILOKF_DATA_DIR and
MAILOKF_CONFIG_DIR overrides remain available as an explicit legacy split mode. Use mailokf storage show to inspect resolution and mailokf storage consolidate --root <path> after stopping all MailOKF processes to migrate split state safely. To move an already-unified root, keep every MailOKF process stopped and run mailokf storage relocate --source-root <old-root> --root <new-root>; add --move only when verified source removal is intended. Both workflows reject WAL/SHM, links, special files, and overlapping roots, use a private restart journal, verify every destination byte, and publish a newly root-bound mailokf.json only after the destination is complete. Relocation targets one local or cloud-mounted filesystem root and does not implement direct object-storage semantics.
This layout is local-filesystem preparation, not an Azure Blob or S3 implementation. SQLite and its WAL/SHM/lock semantics require a local filesystem and are not object-storage compatible.
Local quick start
Install Python 3.11+ and uv, then use the locked environment:
git clone https://github.com/pat229988/OKF_MAIL.git
cd OKF_MAIL
uv sync --locked
uv run mailokf init
uv run mailokf doctor
uv run mailokf account listOptional dependencies are explicit:
# Outlook/Microsoft 365 device-code authorization
uv sync --locked --extra outlook
# CocoIndex + sqlite-vec semantic and hybrid search
uv sync --locked --extra vectors
# Both
uv sync --locked --extra outlook --extra vectorsThen follow the Gmail, Outlook, or local IMAP/iCloud setup; test the account; and run an initial sync. CLI, daemon, gateway, and MCP clients must use the same MAILOKF_ROOT_DIR.
Docker Compose local modes
Compose deliberately separates the continuously running synchronization daemon from the client-attached MCP stdio process. Build the image and initialize the unified named root volume:
docker compose build
docker compose run --rm -T mailokf-stdio initRun the supported long-running polling mode in the background:
docker compose up -d mailokf-daemon
docker compose logs -f mailokf-daemonDo not run the stdio service detached. Configure an MCP client to create an attached, non-TTY container for each client session (replace the Compose path with the checkout's absolute path):
{
"mcpServers": {
"mailokf-docker": {
"command": "docker",
"args": [
"compose",
"-f",
"/absolute/path/to/OKF_MAIL/docker-compose.yml",
"run",
"--rm",
"-T",
"mailokf-stdio"
]
}
}
}Compose injects literal policy defaults from .env.example, followed by an optional ignored
.env file for local overrides. The container storage root is /mailokf, and both services share
one named volume mounted on a directory owned by nonroot UID 10001. The
Compose configuration neither publishes an HTTP port nor permits unauthenticated HTTP, even if
.env requests it. docker compose down preserves the volumes; adding --volumes permanently
removes the local database, retained mail, encrypted account vault, and generated master key.
MCP tool surface
The current contract defines 38 tools (25 compatibility tools plus 13 extensions) and four resource templates. Test sources are maintained under tests/; RELEASE_MANIFEST.json inventories them by path and hash instead of freezing a brittle function or module count here.
Original compatible tools
email_list_accounts email_add_account email_remove_account
email_test_account email_list_folders email_search
email_get email_get_thread email_get_attachment
email_send email_reply email_forward
email_draft_create email_draft_list email_move
email_transfer email_delete email_mark
email_label email_folder_create email_get_labels
email_get_categories email_batch_delete email_batch_move
email_batch_markMailOKF extensions
mail_sync_initial mail_sync_now mail_sync_all
mail_sync_status okf_validate_bundle okf_rebuild_bundle
knowledge_search knowledge_get vector_index_update
vector_search mail_attachment_list mail_attachment_stage
mail_attachment_createResources
okf://accounts/{account_id}/index
okf://accounts/{account_id}/messages/{message_id}
okf://accounts/{account_id}/threads/{thread_id}
mailokf://accounts/{account_id}/sync-statusProvider synchronization design
Provider | Initial synchronization | Incremental cursor | Outbound path |
Gmail |
|
| MIME attachments through Gmail drafts/send API |
Outlook | folder enumeration + message delta + raw MIME | one opaque | Graph small attachment POST or large upload session on drafts |
iCloud / IMAP | folder UID scan + |
| MIME attachments through authenticated SMTP and IMAP Drafts |
Raw mail and attachments
The default policy is intentionally asymmetric:
raw
.emlfiles are temporary and deleted only after a successful local commit;attachment bytes are retained in a SHA-256 blob store;
OKF files contain attachment metadata and
attachment://sha256/<digest>references;normalized text and HTML bodies are stored in SQLite; OKF remains the portable text projection;
quarantine contains messages that could not be safely converted.
Set MAILOKF_RETAIN_RAW_MIME=true only for debugging or legal-retention requirements and protect that directory accordingly.
Search model
FTS5 provides exact and explainable sender, subject, body, and attachment-name retrieval.
CocoIndex watches the OKF directory and incrementally reprocesses only changed Markdown files.
Sentence Transformers creates local embeddings.
sqlite-vec stores and queries vectors in a
vec0virtual table.Hybrid search merges keyword and semantic ranks using reciprocal-rank fusion.
Security boundaries
Provider credentials are encrypted locally with AES-256-GCM.
Outbound send/reply/forward tools require
confirmed=trueand a nonblank idempotency key.Pending or unknown provider outcomes block automatic retry with the same key.
Email contents are untrusted data and never authorize tool calls.
Attachment paths supplied for outgoing email are validated as local files.
Raw MIME staging uses private file permissions and quarantine-on-failure semantics.
Message failures are durably retried and bounded permanent failures are dead-lettered.
Bare numeric IMAP UIDs require explicit source-folder context.
Permanent Gmail deletion requires stored
https://mail.google.com/scope; the default setup flow does not request it.Separate tenant/account roots must be maintained in multi-user deployments.
Do not expose streamable HTTP outside localhost without MCP authorization, TLS, and tenant-aware access control.
Development
Use the locked Python 3.11+ environment:
uv sync --locked --extra dev --extra outlook
uv run python -m compileall -q src tests scripts
uv run python scripts/snapshot_mcp_contract.py --check
uv run pytest -q
uv run mailokf --helpSee docs/MCP_SURFACE.md for the stable interface map and docs/MCP_CONTRACT.md for contract guarantees.
The test suite covers full and incremental synchronization, remote deletion reconciliation, MIME cleanup, attachment persistence, OKF rendering and validation, FTS5, encrypted credentials, and thread references.
Important production work
Before production rollout, complete provider-specific and operational validation, particularly:
live provider contract tests;
OAuth verification/consent and app publishing;
Graph throttling and large-folder pagination;
IMAP server compatibility and UID edge cases;
push notifications/webhooks;
multi-tenant authorization;
backup, retention, erasure, and audit policies;
semantic retrieval evaluation.
Attribution
MailOKF is released under the MIT License. Its stable MCP interface, local-first storage model, and OKF knowledge layer are documented in docs/MCP_SURFACE.md and docs/MCP_CONTRACT.md.
Available Tools
38 toolsemail_add_accountC
Add a new IMAP or iCloud email account (Gmail and Outlook require the setup wizard for OAuth)
Inputs: name (required), email (required), provider (required), tls, +5 more.
email_batch_deleteC
Delete multiple emails at once. Much faster than individual deletes.
Inputs: emailIds (required), accountId (required), permanent, sourceFolder.
email_batch_markC
Mark multiple emails at once (read/unread, starred, flagged). Much faster than individual marks.
Inputs: emailIds (required), accountId (required), read, flagged, +2 more.
email_batch_moveC
Move multiple emails to a folder at once. Much faster than individual moves.
Inputs: emailIds (required), accountId (required), targetFolder (required), sourceFolder.
email_deleteC
Delete an email (moves to trash by default, or permanently deletes)
Inputs: emailId (required), accountId (required), permanent, sourceFolder.
email_draft_createA
Create exactly one provider draft with optional retained, staged, or confirmed local attachments. A nonblank idempotencyKey is required; reuse it only for the identical draft so retries return the durable prior result without creating a duplicate.
Inputs: to (required), body (required), subject (required), accountId (required), +2 more.
email_draft_listC
List email drafts
Inputs: accountId (required), limit, offset.
email_folder_createC
Create a new email folder
Inputs: name (required), accountId (required), parentPath.
email_forwardC
Forward an email to new recipients
Inputs: to (required), emailId (required), accountId (required), body, +2 more.
email_getC
Get a single email by ID
Inputs: emailId (required), accountId (required).
email_get_attachmentC
Get an email attachment by ID, returns base64 encoded data
Inputs: emailId (required), accountId (required), attachmentId (required).
email_get_categoriesB
List all categories for an email account (Outlook only)
Inputs: accountId (required).
email_get_labelsC
List all labels for an email account (Gmail only)
Inputs: accountId (required).
email_get_threadC
Get an email thread by thread ID
Inputs: threadId (required), accountId (required).
email_labelC
Add or remove labels on an email (Gmail only)
Inputs: emailId (required), accountId (required), addLabels, removeLabels.
email_list_accountsB
List all configured email accounts and their connection status
Inputs: no named parameters.
email_list_foldersC
List all email folders for an account
Inputs: accountId (required).
email_markC
Mark an email as read/unread, starred, or flagged
Inputs: emailId (required), accountId (required), read, flagged, +2 more.
email_moveC
Move an email to a different folder
Inputs: emailId (required), accountId (required), targetFolder (required), sourceFolder.
email_remove_accountC
Remove an email account
Inputs: accountId (required).
email_replyC
Reply to an existing email
Inputs: body (required), emailId (required), accountId (required), replyAll, +2 more.
email_searchB
Search emails with filters. By default returns compact results without full body content to save context. Set returnBody=true to include full email bodies.
Inputs: accountId (required), to, body, from, +10 more.
email_sendA
Send exactly one email after explicit confirmation. confirmed=true and a nonblank idempotencyKey are required safety inputs. Reuse synchronized mail with {messageId, attachmentId} from mail_attachment_list; use {stagedId} from mail_attachment_stage/mail_attachment_create for local or agent-created files; or use {localPath, confirmed:true} for an authorized workspace/import file.
Inputs: to (required), body (required), subject (required), accountId (required), +5 more.
email_test_accountC
Test an email account connection
Inputs: accountId (required).
email_transferB
Move or copy emails between accounts while preserving raw MIME
Inputs: emailIds (required), sourceAccountId (required), targetAccountId (required), markRead, +3 more.
knowledge_getC
Retrieve a source-backed knowledge record for a message.
Inputs: account_id (required), message_id (required). Returns: object
knowledge_searchC
Search the local knowledge corpus with keyword, semantic, or hybrid retrieval.
Inputs: query (required), account_id (required), mode, limit, +1 more. Returns: result
mail_attachment_createA
Create an agent-generated text/base64 file for later send or draft attachment.
Inputs: content (required), filename (required), content_type. Returns: sha256, filename, mimeType, stagedId, +3 more
mail_attachment_listC
List retained metadata reusable as {messageId, attachmentId, filename?} selectors.
Inputs: account_id (required), message_id (required). Returns: result
mail_attachment_stageC
Stage an authorized file and return {stagedId, filename?} for send or draft.
Inputs: local_path (required), filename, confirmed. Returns: sha256, filename, mimeType, stagedId, +3 more
mail_sync_allC
Synchronize every enabled account.
Inputs: full. Returns: result
mail_sync_initialC
Perform a full initial mailbox synchronization and reconciliation.
Inputs: account_id (required). Returns: object
mail_sync_nowA
Run an incremental sync, or force a full reconciliation.
Inputs: account_id (required), full. Returns: object
mail_sync_statusB
Return freshness, cursor status, and local object counts.
Inputs: account_id (required). Returns: object
okf_rebuild_bundleC
Regenerate the OKF projection from the canonical SQLite records.
Inputs: account_id (required). Returns: object
okf_validate_bundleA
Validate OKF frontmatter and attachment references for one account.
Inputs: account_id (required). Returns: object
vector_index_updateC
Apply CocoIndex incremental changes from OKF files into sqlite-vec.
Inputs: full_reprocess. Returns: object
vector_searchB
Run account-isolated semantic similarity search over the OKF vector index.
Inputs: query (required), account_id (required), limit. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 38 tools
Most mail_* and email_* tools have clear purposes, but there is notable overlap in the sync tools (mail_sync_initial, mail_sync_now, mail_sync_all) which could confuse an agent choosing which to invoke. The 'okf_' and 'vector_' prefixed tools are also conceptually related to mail syncing/indexing, creating some boundary ambiguity between mail_sync_* and okf_rebuild_bundle/vector_index_update.
The naming is mixed: email_* tools use verb_noun (email_add_account, email_get, email_send), but there are inconsistencies like email_add_account vs email_list_accounts (verb prefix on some, noun prefix on others), and mail_* tools use a different prefix entirely (mail_sync_*, mail_attachment_*). The verb placement varies (email_get before email_get_thread, email_send vs email_reply). Also tool names like 'email_add_account' have a noun in the middle rather than a resource at the end.
38 tools is well beyond the ideal 3-15 range and leans heavy. While email operations naturally need many actions, the apparent scope includes network/auth (add_account, remove_account, test_account), lifecycle (get/get_thread/delete/batch_delete), organization (move/batch_move/label/folder), drafts/send (send/reply/forward/draft_create), attachments (three mail_attachment_* tools), plus a parallel OKF/vector knowledge system. This feels like 2-3 servers' worth of functionality compressed into one.
The surface is fairly complete for the email domain: CRUD on accounts, full email lifecycle (get/search/send/reply/forward/delete), batch operations, folders, labels, drafts, and attachments are all covered. There are minor gaps like missing undo/save-draft operations and no unread-count summary, but these are workable. The addition of OKF and vector indexing extends completeness beyond basic email into knowledge management, though it arguably over-scopes the server.
Maintenance
Related MCP Connectors
Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.
Your IMAP mailbox as an MCP server: read, search and (if you allow it) organize mail. Open source.
Email inboxes for AI agents: send, receive, reply, search, and manage threaded email over MCP.
Hosted email MCP for your own Gmail, Outlook.com, Microsoft 365, iCloud or IMAP inbox: read, search, draft, reply in thread, forward and file mail. It moves or flags up to 500 messages in one call, and a send leaves exactly one copy in Sent. A calendar is a separate connection, and connecting one adds diary and scheduling tools.
Related MCP Servers
- AlicenseBqualityDmaintenanceA local MCP server that provides LLM clients with read/write access to email and calendar data from Gmail, iCloud, and generic IMAP providers. It runs entirely on your machine, keeping data private while enabling email management, calendar operations, and task handling through natural language.39MIT
- FlicenseNot gradedqualityCmaintenanceA private, single-user MCP server that unifies Gmail, Microsoft 365/Outlook, and IMAP mailboxes for LLMs to search and read emails live, without storing or caching mailbox contents.-
- AlicenseNot gradedqualityDmaintenanceA self-hosted, multi-mailbox MCP server that exposes IMAP/SMTP operations as tools for AI agents, enabling email management like listing folders, reading, searching, sending, and moving messages via natural language.MIT
- AlicenseNot gradedqualityCmaintenanceMCP server that gives an AI assistant access to IMAP/SMTP mailboxes to read, search, organize, draft, and send email, with local mirroring and full-text search, no third-party data path, and credentials stored only in the macOS Keychain.7 npmMIT