Skip to main content
Glama
EdamAme-x

mail-mcp

by EdamAme-x

mail-mcp

Read and search mail across multiple Gmail, Outlook / Microsoft 365, and IMAP accounts through one local MCP server. Built with TypeScript, Hono and @hono/mcp. Requires Node.js 22 or newer.

Install

npm install -g mail-mcp

Upgrading from the former scoped package:

npm uninstall -g @ame-x/mail-mcp
npm install -g mail-mcp

The command and ~/.mail-mcp account storage stay the same; existing accounts do not need to be registered again.

Each login gets a local account name, such as personal, work, or icloud. You can register multiple accounts from the same provider. Names use lowercase letters, digits, hyphens and underscores (1–64 characters).

Related MCP server: mcp-gmail

Register accounts

Gmail

Enable the Gmail API in a Google Cloud project, configure the OAuth consent screen, and create a Desktop app OAuth client. Download its JSON file. If the app is in testing, add your Google account as a test user.

mail-mcp login --provider gmail --account personal --credentials ./google-client.json
mail-mcp login --provider gmail --account second --credentials ./google-client.json

Open the printed URL and sign in to the intended Google account. Login uses PKCE and gmail.readonly; access tokens refresh automatically. Google can expire refresh tokens after seven days for external OAuth apps in testing.

Outlook / Microsoft 365

Register an application in Microsoft Entra. Select supported account types that include the accounts you want to use (including personal Microsoft accounts for Outlook.com). Enable Allow public client flows in Authentication and add the Microsoft Graph delegated Mail.Read permission. Your organization may require administrator consent or prohibit device code login.

mail-mcp login --provider outlook --account work --client-id YOUR_APPLICATION_ID
mail-mcp login --provider outlook --account private --client-id YOUR_APPLICATION_ID --tenant consumers

Open the displayed Microsoft URL and enter the code. The default tenant is common; you can also specify organizations, consumers, or a tenant ID. Requests use Mail.Read and offline_access. Refresh tokens are updated locally. This release uses the global Microsoft cloud endpoints.

IMAP

Use your provider's TLS IMAP endpoint and an app password when required. This covers providers that permit password/app-password IMAP access, including many hosted and custom-domain mail services. It does not bypass a provider's login restrictions. Providers that require OAuth should use a supported OAuth adapter.

Prepare a local JSON file (keep it outside your repository):

{
  "host": "imap.example.com",
  "port": 993,
  "user": "you@example.com",
  "password": "YOUR_APP_PASSWORD",
  "mailbox": "INBOX"
}
mail-mcp login --provider imap --account other --credentials ./imap-account.json

Login verifies the TLS connection and opens the mailbox read-only before saving. Port defaults to 993; mailbox defaults to INBOX. IMAP searches cover the configured mailbox, while Gmail and Outlook search their accounts. To read another IMAP mailbox, register it under another account name. Implicit TLS with certificate verification is required; STARTTLS and plaintext connections are not supported. The source JSON is not deleted automatically and still contains a password.

Start and connect

mail-mcp accounts
mail-mcp serve

Connect your MCP client to http://127.0.0.1:3000/mcp using Streamable HTTP. For clients using a mcpServers URL configuration:

{
  "mcpServers": {
    "mail": { "url": "http://127.0.0.1:3000/mcp" }
  }
}

Keep serve running separately. Choose another port with --port 3001. The transport is HTTP, not stdio.

Tools

Tool

Purpose

list_accounts

List local account names and providers; never returns credentials

list_messages

List/search all or selected accounts; return summaries with source account, provider and message ID

get_message

Read one message using its account and ID

get_messages

Read up to 20 messages across accounts, retaining successful results when another read fails

Cross-account keyword search:

{ "text": "invoice", "limit": 10 }

Restrict the search to two accounts:

{ "accounts": ["personal", "work"], "text": "invoice", "limit": 10 }

text is passed to each provider's text-search facility. Tokenization, indexed fields and matching differ: Gmail searches its mail index, Outlook searches its message index, and IMAP uses TEXT. This is not a shared exact-match search engine. Use Gmail's native operators for a single Gmail account:

{ "accounts": ["personal"], "query": "is:unread from:billing@example.com" }

query cannot be combined with text and requires exactly one Gmail account. For a single registered Gmail account, old query calls still work.

limit is per account, from 1 to 100 (default 20). Results are combined and sorted by date within each returned page; this is not a globally paginated timeline. IMAP selects messages in descending UID order. Outlook keyword search is limited by Microsoft's search-result cap (up to 1,000 messages).

The result contains messages, nextPageTokens, and per-account errors. To continue, pass nextPageTokens back as pageTokens, with the same search inputs:

{
  "text": "invoice",
  "limit": 10,
  "pageTokens": { "personal": "TOKEN_FROM_RESULT" }
}

Only accounts present in pageTokens are queried on continuation, so completed accounts are not restarted. An empty map ends the search. If accounts is also specified, every selected account must have a token. A single-account call can also pass pageToken. Tokens are provider-specific and must not be edited.

Read the returned message reference:

{ "account": "work", "id": "ID_FROM_LIST" }

Batch read:

{
  "messages": [
    { "account": "personal", "id": "ID_1" },
    { "account": "work", "id": "ID_2" }
  ]
}

IDs belong to their source account. account may be omitted for get_message only when exactly one account is registered. IMAP IDs include UIDVALIDITY to reject stale references after a mailbox reset. The message is returned under message, alongside its account and provider. Text, HTML, dates and attachment metadata depend on what the provider supplies. Treat email content as untrusted input.

No tool sends, deletes, moves, or marks messages as read. File attachment contents are not returned. For IMAP, reading parses the complete raw message, including attachments, with a 10 MiB download limit; larger messages return an error. Gmail separately stored text bodies are fetched automatically.

Errors (v0.3)

Tool failures set MCP isError: true and return JSON under error:

{
  "error": {
    "code": "AUTH_REQUIRED",
    "message": "Authentication expired or was rejected. Log in to this account again.",
    "retryable": false
  }
}

Errors have stable code, safe message, and retryable fields. Rate limits and temporary HTTP failures may include retryAfterMs from Retry-After. No automatic retries run except a single OAuth refresh/retry after a 401.

Code

Action

INVALID_INPUT

Check arguments, account selection, and page tokens.

ACCOUNT_NOT_FOUND, CREDENTIALS_INVALID, AUTH_REQUIRED

Register or log in to the affected account again.

PROVIDER_CHANGED

Restart the server after changing an account's provider.

STORAGE

Check credential file access and available disk space.

FORBIDDEN

Check the provider's granted mail permissions.

NOT_FOUND, STALE_CURSOR

Search again to obtain current message IDs.

RATE_LIMITED, UNAVAILABLE, TIMEOUT, NETWORK

Retry later, respecting retryAfterMs when present.

INVALID_RESPONSE, TOO_LARGE, INTERNAL

The response could not be processed; retrying unchanged input may not help.

ALL_FAILED

Inspect failures for each account/message's error and retry advice.

Cross-account searches and batch reads preserve partial results. Their per-item error fields now contain these objects instead of v0.2 strings. If every item fails, the tool returns ALL_FAILED with a failures array containing account, optional id, and error. An empty successful search remains a success, including when other selected accounts fail. This behavior also applies to list_accounts.

Provider JSON is validated before use and limited to 16 MiB per response. Outlook attachment pagination rejects loops and stops after 100 pages. OAuth refreshes are shared within each account's client; a late 401 reuses the refreshed token. If saving a rotated token fails, the client retains it in memory and retries saving on the next request. Restarting before that save succeeds loses the in-memory token and may require logging in again.

Stored credentials and upgrading

Credentials are stored as local JSON in ~/.mail-mcp/accounts/<name>.json. Each account has its own file, so refreshing one does not overwrite another. Files are written atomically, with directory/file modes 0700 / 0600 on POSIX; Windows uses your user profile's ACLs. Credentials are not encrypted.

The old ~/.mail-mcp/tokens.json from v0.1 is recognized as Gmail account default. It moves to accounts/default.json on the next successful token save. Existing credentials need no manual editing. Login without --account still targets default.

mail-mcp logout --account work

Logout removes only that account's local credentials, not the provider's grant. Stop the server before changing accounts or running another instance against the same credential directory. Revoke grants in the provider's settings when needed. Malformed credentials are reported per account without exposing their contents.

The server binds to 127.0.0.1 and rejects foreign Host and Origin headers. It has no remote authentication; do not expose it through a public proxy. Local processes can access the endpoint.

Development

See CONTRIBUTING.md for local setup and pull requests, SECURITY.md for private vulnerability reporting, and CHANGELOG.md for release notes.

Linting and formatting use Ultracite with Oxlint and Oxfmt. npm run check runs lint, formatting, and TypeScript checks. Use npm run fix to apply lint fixes and formatting, and review the resulting diff. Development tooling requires Node.js 22.12 or newer.

Keep documentation, code comments, CLI output, and setup pages in English. Encoding tests should use Unicode escapes for non-English text fixtures.

MailService returns neverthrow ResultAsync<T, MailError>. Use andThen for dependent operations, map for successful values, orElse for recovery, and match at the MCP boundary. Expected failures are values; they do not depend on matching exception messages. Promise-based storage, HTTP, and IMAP adapters are converted at I/O boundaries with attempt. guard also captures unexpected exceptions in composition callbacks. valueOrThrow is reserved for imperative protocol adapters and the CLI, outside domain composition.

npm ci
npm run check
npm test
npm run build
node dist/cli.js --help
npm pack --dry-run
npm publish --access public

Tests use fake credentials and mocked provider responses; they do not log in to real mailboxes. CI runs on Node 22/24 on Windows/Linux. prepack builds the CLI; only compiled JavaScript, package metadata, README and license are published.

References: Hono MCP, Google desktop OAuth, Microsoft device login, Graph messages, ImapFlow.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Provides access to Gmail functionalities including listing unread emails, searching via query syntax, and managing messages through archiving or marking as read. It enables MCP clients to securely interact with and organize email data using the Gmail API.
    5
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables Gmail access through MCP, allowing LLMs to read, compose, and send emails.
    10
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    Enables Gmail management via MCP, including email search, retrieval, labeling, sending, and forwarding through IMAP and SMTP.
    15
    11
    BSD 3-Clause
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables Gmail operations such as reading, sending, searching, and managing emails, threads, labels, and drafts via MCP tools.
    1
    MIT