Fastmail Mcp Remote
FreeNot checkedRemote MCP server for Fastmail on Cloudflare Workers with Cloudflare Access OAuth
About
Remote MCP server for Fastmail on Cloudflare Workers with Cloudflare Access OAuth
README
Fastmail has a powerful API (JMAP) and supports IMAP, but it doesn't offer a native MCP server. That means AI assistants like Claude, Copilot, and OpenClaw can't talk to your Fastmail account out of the box.
Fastmail Remote bridges this gap. It's a remote MCP server that runs on Cloudflare Workers, translating MCP tool calls into Fastmail JMAP API requests. Your Fastmail API token is stored as an encrypted Cloudflare secret, and all access is protected by Cloudflare Zero Trust authentication. You deploy it once, and any MCP client can connect via OAuth.
Client Interfaces
- Any MCP client (Claude.ai, Claude Code, GitHub Copilot) connects directly to the Worker via OAuth
- Fastmail CLI — a local command-line tool that calls the Worker and formats responses as compact text, saving 5-7x tokens
- Fastmail CLI for OpenClaw — an OpenClaw plugin (37 agent tools) published as fastmail-cli on npm
Architecture
┌─────────────┐ OAuth ┌──────────────────────┐ API Token ┌──────────────┐
│ Claude.ai │ ───────────► │ Cloudflare Worker │ ────────────────► │ Fastmail │
│ (MCP Client)│ (CF Access) │ (Remote MCP Server) │ (stored as secret) │ API │
└─────────────┘ └──────────────────────┘ └──────────────┘
┌─────────────┐ Bearer Token ┌──────────────────────┐ API Token ┌──────────────┐
│ fastmail CLI│ ───────────► │ Cloudflare Worker │ ────────────────► │ Fastmail │
│ (local) │ (PKCE OAuth) │ (Remote MCP Server) │ (stored as secret) │ API │
└─────────────┘ └──────────────────────┘ └──────────────┘
Fastmail CLI
A token-efficient CLI that calls the remote MCP server and formats responses as compact text, saving 5-7x tokens compared to raw MCP tool calls.
Quick Start
# Add alias to ~/.zshrc
alias fastmail="npx tsx ~/GitHub/fastmail-mcp-remote/cli/main.ts"
# Authenticate (one-time, tokens last 30 days)
fastmail auth --url https://your-worker.example.com --team yourteam
fastmail auth status # Shows authenticated user, token expiry
fastmail auth logout # Remove cached credentials
# Headless auth (SSH / no-browser environments)
fastmail auth --headless --url https://your-worker.example.com
# Prints a URL to open in any browser, then paste the token back
Commands
# Inbox & reading
fastmail inbox # 10 most recent inbox emails
fastmail inbox --limit 20 # More emails
fastmail email <id> # Read email (markdown format)
fastmail email thread <threadId> # Full conversation thread
# Searching
fastmail email search "query" # Text search
fastmail email search "invoice" --from [email protected]
# Composing
fastmail email send --to [email protected] --subject "Hi" --body "Hello!"
fastmail email draft --to [email protected] --subject "Draft" --body "..."
fastmail email reply <id> --body "Thanks!" --send
# Actions & bulk
fastmail email read|unread|flag|unflag|delete <id>
fastmail bulk read|delete|flag <id1> <id2> <id3>
# Mailboxes, contacts, calendar
fastmail mailboxes
fastmail contacts
fastmail calendars
fastmail events
# Memos (private notes)
fastmail memo <emailId>
fastmail memo create <emailId> --text "Note"
fastmail memo delete <emailId>
All commands support --json for raw JSON output.
CLI Structure
cli/
├── main.ts # Entry point, commander setup
├── mcp-client.ts # MCP SDK client (StreamableHTTPClientTransport)
├── auth.ts # PKCE OAuth flow + token caching
├── formatters.ts # Compact text output formatters
├── commands/
│ ├── email.ts # Email, bulk, mailbox, account commands
│ ├── contacts.ts # Contact commands
│ ├── calendar.ts # Calendar commands
│ └── memo.ts # Memo commands
└── skill.md # Claude Code skill documentation
Available Tools
list_mailboxes- List all mailboxeslist_emails- List emails from a mailboxget_email- Get a specific email by ID (includes threading: messageId, inReplyTo, references, threadId)send_email- Send an email (supports attachments and reply threading)create_draft- Create a draft email (supports attachments and reply threading)update_draft- Edit an existing draft's body (creates a replacement draft — the ID changes; re-derives the quoted original for reply drafts)reply_to_email- Reply to an email with automatic threading and quoting (like Fastmail's reply button)search_emails- Search emailsget_recent_emails- Get most recent emailsmark_email_read- Mark email as read/unreaddelete_email- Delete an email (move to trash)move_email- Move email to different mailboxget_email_attachments- Get attachment listdownload_attachment- Get attachment download URLadvanced_search- Advanced email search with filtersget_thread- Get all emails in a threadget_mailbox_stats- Get mailbox statisticsget_account_summary- Get account summarybulk_mark_read- Mark multiple emails read/unreadbulk_move- Move multiple emailsbulk_delete- Delete multiple emailsflag_email- Flag or unflag an emailbulk_flag- Flag or unflag multiple emailsget_inbox_updates- Get inbox changes since a previous state (incremental sync)create_memo- Add a private note (memo) to an email, rendered as a yellow inline annotation in Fastmailget_memo- Get the memo attached to an emaildelete_memo- Delete a memo from an emailgenerate_email_action_urls- Generate HMAC-signed URLs for email archive/delete actions (24h expiry, single-use)
Email Body Formats
The send_email, create_draft, and reply_to_email tools support three body formats:
| Parameter | Format | Description |
|---|---|---|
textBody |
Plain text | Simple text content |
htmlBody |
HTML | Rich HTML content |
markdownBody |
Markdown | GitHub-flavored Markdown (auto-converted to HTML) |
At least one body format is required. If markdownBody is provided, it takes precedence over htmlBody.
Example with Markdown:
{
"to": ["[email protected]"],
"subject": "Meeting notes",
"markdownBody": "# Meeting Summary\n\n- Point 1\n- Point 2\n\n**Action items:**\n1. Review proposal\n2. Send feedback"
}
Sending Emails with Attachments
Both send_email and create_draft support file attachments. Attachments are passed as an array of objects with base64-encoded content:
{
"to": ["[email protected]"],
"subject": "Document attached",
"textBody": "Please see the attached file.",
"attachments": [
{
"filename": "report.pdf",
"mimeType": "application/pdf",
"content": "<base64-encoded-file-content>"
}
]
}
Attachment limits:
- Maximum file size: 25MB per attachment
- Supported: Any file type (PDF, images, documents, etc.)
Validation:
- Filenames are validated to prevent path traversal attacks
- MIME types must be in valid
type/subtypeformat - Base64 content is validated before upload
Replying to Emails
The reply_to_email tool provides a convenient way to reply to emails with proper threading and quoting, just like Fastmail's reply button:
{
"emailId": "abc123",
"body": "Thanks for the information!",
"replyAll": false,
"sendImmediately": false
}
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
emailId |
string | required | ID of the email to reply to |
body |
string | required | Your reply message (plain text) |
htmlBody |
string | optional | Your reply message (HTML) |
markdownBody |
string | optional | Your reply message (Markdown, auto-converted to HTML) |
replyAll |
boolean | false |
Reply to all recipients (sender + CC) |
sendImmediately |
boolean | false |
Send immediately vs create draft |
excludeQuote |
boolean | false |
Skip quoting the original message |
What it handles automatically:
- Recipients: Uses
replyToorfromaddress;replyAllincludes CC recipients - Subject: Adds "Re:" prefix if not already present
- Threading: Sets
inReplyToandreferencesheaders for proper threading - Quoting: Formats quoted original in Fastmail style with attribution line
Email Threading (Low-Level)
For manual control over threading, send_email and create_draft support inReplyTo and references parameters, and get_email returns threading properties:
{
"to": ["[email protected]"],
"subject": "Re: Original subject",
"textBody": "My reply...",
"inReplyTo": ["<[email protected]>"],
"references": ["<[email protected]>", "<[email protected]>"]
}
Threading properties returned by get_email:
messageId- The email's Message-ID headerinReplyTo- Message-IDs this email replies toreferences- Full thread chain of Message-IDsthreadId- JMAP's internal thread identifier
Memos (Private Notes)
Add private notes to emails that render as yellow inline annotations in Fastmail's UI. Memos are personal — only visible to you.
{
"emailId": "abc123",
"text": "Follow up on this next week"
}
How it works: Memos are stored as special emails in a hidden Memos mailbox, linked to the target email via the In-Reply-To header. The $memo JMAP keyword triggers Fastmail's yellow annotation rendering.
| Tool | Description |
|---|---|
create_memo |
Add a memo to an email |
get_memo |
Read the memo on an email (returns text, date, memoId) |
delete_memo |
Remove the memo from an email |
Identity
list_identities- List sending identities
Contacts
list_contacts- List contactsget_contact- Get a specific contactsearch_contacts- Search contacts
Calendar
list_calendars- List all calendarslist_calendar_events- List calendar eventsget_calendar_event- Get a specific eventcreate_calendar_event- Create a new event
Utility
check_function_availability- Check available functionsgenerate_email_action_urls- Generate pre-signed action URLs for email operations
Setup Instructions
1. Create KV Namespace
npx wrangler login
npx wrangler kv namespace create "OAUTH_KV"
Copy the output ID and update wrangler.jsonc:
"kv_namespaces": [
{
"binding": "OAUTH_KV",
"id": "<YOUR_KV_NAMESPACE_ID>"
}
]
2. Configure Cloudflare Access
Create a Cloudflare Access SaaS application for OAuth:
- Go to Cloudflare Zero Trust Dashboard → Access → Applications
- Click Add an application → SaaS
- Fill in:
- Application name:
Fastmail MCP - Application type: Custom
- Application name:
- Configure OIDC settings:
- Auth Type: OIDC
- Redirect URI:
https://your-worker.example.com/mcp/callback
- Configure access policy to allow your users (e.g., email domain or specific emails)
- Copy the Client ID and generate a Client Secret
3. Configure Environment Variables
Add ACCESS_TEAM_NAME and ALLOWED_USERS as plaintext vars in your wrangler.jsonc (gitignored — PII stays local):
"vars": {
"ACCESS_TEAM_NAME": "yourteam",
"ALLOWED_USERS": "[email protected],[email protected]",
"SEND_APPROVAL_MODE": "required"
}
- ACCESS_TEAM_NAME: Your Cloudflare Zero Trust team name (the subdomain before
.cloudflareaccess.com) - ALLOWED_USERS: Comma-separated list of email addresses allowed to access the server
- SEND_APPROVAL_MODE: Outbound mail policy.
requiredis the default and server-enforced.clientrelies on the MCP client's tool approval UI.offsends directly.
Two optional vars tighten things further:
- ALLOWED_REDIRECT_HOSTS: Extra hostnames permitted as OAuth
redirect_uritargets. Loopback (native/CLI clients) and the worker's own origin are always allowed; everything else must be anhttpshost on this list. Defaults toclaude.ai,claude.com,anthropic.com. Setting it replaces the defaults rather than adding to them. - ACTION_ALLOWED_ORIGINS: Browser origins allowed to call the signed
/api/action/*endpoints. Leave unset (*) if you open the reading-digest page from a local file, which sendsOrigin: null.
For local development, also add these to .dev.vars (gitignored).
Warning: Deploying without the
varssection inwrangler.jsoncwill wipe all dashboard-set plaintext vars. Always keepvarsin your local config.
Outbound email approval
With SEND_APPROVAL_MODE=required, send_email, send_copy, and immediate replies never submit mail during the initial tool call. The server:
- Creates a Fastmail draft containing the exact recipients, subject, body, and attachments.
- Stores a ten-minute approval bound to the authenticated MCP user and a digest of that draft.
- Opens an authenticated review page through MCP URL elicitation when the client supports it, or returns the review URL in the tool result.
- Re-checks the draft digest and submits it once after approval. Declined and expired approvals leave the draft unsent.
The review URL works with regular MCP, Code Mode, the CLI, and OpenClaw. MCP 2026-07-28 clients use the stateless multi-round-trip elicitation flow. Older and headless clients receive the authenticated URL in the normal tool result. Tool annotations also mark outbound tools as write operations for clients with native approval policies. Code Mode cannot expose annotations for its inner tools, so it always applies the server gate unless approval mode is explicitly off.
4. Get Fastmail API Token
- Go to https://www.fastmail.com/settings/security/tokens
- Create a new API token with the scopes you need (Email, Contacts, Calendars)
- Copy the token
5. Configure Local Secrets
Create .dev.vars with your local development credentials:
ACCESS_CLIENT_ID="your-cloudflare-access-client-id"
ACCESS_CLIENT_SECRET="your-cloudflare-access-client-secret"
FASTMAIL_API_TOKEN="your-fastmail-api-token"
WORKER_URL="http://localhost:8788"
Note: ACCESS_TEAM_NAME and ALLOWED_USERS are configured in wrangler.jsonc vars, not in .dev.vars.
6. Test Locally
npm start
Server runs at http://localhost:8788/sse
Test with MCP Inspector:
npx @modelcontextprotocol/inspector@latest
# Open http://localhost:5173
# Enter http://localhost:8788/sse
# Click "Open OAuth Settings" → "Quick OAuth Flow"
# Authenticate via Cloudflare Access
# Click "Connect" → "List Tools"
7. Deploy to Cloudflare
npx wrangler deploy
8. Set Production Secrets
npx wrangler secret put ACCESS_CLIENT_ID
npx wrangler secret put ACCESS_CLIENT_SECRET
npx wrangler secret put FASTMAIL_API_TOKEN
npx wrangler secret put WORKER_URL
npx wrangler secret put ACTION_SIGNING_KEY
# Generate a 256-bit hex key: openssl rand -hex 32
9. Connect Clients
Fastmail CLI
# Add alias to ~/.zshrc
alias fastmail="npx tsx ~/GitHub/fastmail-mcp-remote/cli/main.ts"
# Authenticate (opens browser for CF Access login)
fastmail auth --url https://your-worker.example.com --team yourteam
# Or headless auth for SSH / no-browser environments
fastmail auth --headless --url https://your-worker.example.com
# Test
fastmail inbox
Claude.ai
- Go to https://claude.ai/settings/connectors
- Click Add custom connector
- Enter your MCP server URL:
https://your-worker.example.com/sse - Click Add
- Click Connect and authenticate via Cloudflare Access
Claude Code
claude mcp add --scope user --transport http fastmail "https://your-worker.example.com/mcp"
Then run /mcp in Claude Code to complete the OAuth flow.
GitHub Copilot CLI
GitHub Copilot CLI doesn't support automatic OAuth client registration, so you need to register a client first:
Register an OAuth client:
curl -X POST "https://your-worker.example.com/register" \ -H "Content-Type: application/json" \ -d '{"client_name": "github-copilot", "redirect_uris": ["http://localhost", "http://127.0.0.1"]}'Save the returned
client_id.Add the MCP server:
copilot mcp add fastmail --url "https://your-worker.example.com/mcp"When prompted for OAuth credentials:
- Client ID: Paste the
client_idfrom step 1 - Client Type: Select
[1] Public (no secret) - Press
Ctrl+Sto save and authenticate
- Client ID: Paste the
Security Notes
- Authentication via Cloudflare Access (supports GitHub, email OTP, and other identity providers)
- Fastmail API token stored encrypted in Cloudflare secrets
- OAuth tokens stored in Cloudflare KV with TTL expiration
- All traffic over HTTPS
- Email-based allowlist for access control
- Email action URLs use HMAC-SHA256 signatures with 24-hour expiry and single-use nonces
Delegate Access (Role-Based Permissions)
The server supports role-based access control with two layers:
- Role-based —
admin(full access) vsdelegate(read + inbox management + drafts) - Category-based — per-user disabled categories (e.g., hide contacts/calendar)
Roles
| Role | Can Do | Cannot Do |
|---|---|---|
| admin | Everything (unless categories are disabled) | — |
| delegate | Read email, manage inbox, create drafts, reply as draft | Send email, create calendar events |
Tool Categories
| Category | Tools | Admin | Delegate |
|---|---|---|---|
EMAIL_READ |
list_mailboxes, list_emails, get_email, search_emails, get_recent_emails, get_inbox_updates, get_email_attachments, download_attachment, advanced_search, get_thread, get_mailbox_stats, get_account_summary, list_identities, get_memo | Yes | Yes |
CONTACTS |
list_contacts, get_contact, search_contacts | Yes | Yes |
CALENDAR_READ |
list_calendars, list_calendar_events, get_calendar_event | Yes | Yes |
CALENDAR_WRITE |
create_calendar_event | Yes | No |
INBOX_MANAGE |
mark_email_read, flag_email, delete_email, move_email, bulk_mark_read, bulk_move, bulk_delete, bulk_flag, create_memo, delete_memo, generate_email_action_urls | Yes | Yes |
DRAFT |
create_draft, update_draft | Yes | Yes |
REPLY |
reply_to_email | Yes | Yes* |
SEND |
send_email | Yes | No |
META |
check_function_availability | Yes | Yes |
* Delegates can use reply_to_email to create draft replies, but sendImmediately: true is denied.
Configuration
Permissions are stored in Cloudflare KV under the key config:permissions as JSON:
{
"users": {
"[email protected]": {
"role": "admin",
"disabled_categories": []
},
"[email protected]": {
"role": "delegate",
"disabled_categories": ["CONTACTS", "CALENDAR_READ", "CALENDAR_WRITE"]
}
},
"default_role": "admin",
"default_disabled_categories": []
}
Set the config via wrangler:
# Write permissions config to KV
npx wrangler kv key put --binding=OAUTH_KV "config:permissions" '{
"users": {
"[email protected]": {
"role": "delegate",
"disabled_categories": ["CONTACTS", "CALENDAR_READ", "CALENDAR_WRITE"]
}
},
"default_role": "admin",
"default_disabled_categories": []
}'
How It Works
- tools/list filtering: Delegates only see tools they're allowed to use. Disabled categories are hidden for all roles.
- tools/call interception: If a delegate tries to call a denied tool, they receive a JSON-RPC error with an actionable hint (e.g., "Use 'create_draft' to compose emails as drafts instead").
- Config caching: Permissions config is cached for 5 minutes to minimize KV reads.
- Unknown users: Fall back to
default_roleanddefault_disabled_categories. - Case-insensitive: Email lookups are case-insensitive.
Troubleshooting
"OAuth error" when connecting
- Verify Cloudflare Access redirect URI matches your worker URL exactly
- Check that ACCESS_CLIENT_ID and ACCESS_CLIENT_SECRET are set correctly
- Ensure KV namespace is created and bound
- Verify
ACCESS_TEAM_NAMEinwrangler.jsoncvars matches your Zero Trust team name
Tools not appearing
- Check worker logs: Cloudflare Dashboard → Workers → Logs
- Verify tools are registered in the
init()method - Test with MCP Inspector first
"User not authorized" after login
- Verify your email is in
ALLOWED_USERSinwrangler.jsoncvars - Check the user email matches exactly (case-insensitive)
- Ensure
wrangler.jsonchas avarssection — deploying without it wipes plaintext vars
CLI "Not authenticated"
- Run
fastmail auth --url <url> --team <team>to authenticate - Run
fastmail auth --headlessfor SSH / no-browser environments - Run
fastmail auth statusto check token validity and authenticated user - Tokens expire after 30 days — re-run
fastmail authto refresh - Run
fastmail auth logoutto clear cached credentials
Fastmail API errors
- Verify FASTMAIL_API_TOKEN is set as a secret
- Check token has correct permissions in Fastmail settings
- Test token manually with curl first
Acknowledgments
This project is based on fastmail-mcp by MadLlama25. The original project provided the foundation for the Fastmail JMAP integration and MCP tool implementations.
Installing Fastmail Mcp Remote
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/omarshahine/fastmail-mcp-remoteFAQ
Is Fastmail Mcp Remote MCP free?
Yes, Fastmail Mcp Remote MCP is free — one-click install via Unyly at no cost.
Does Fastmail Mcp Remote need an API key?
No, Fastmail Mcp Remote runs without API keys or environment variables.
Is Fastmail Mcp Remote hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Fastmail Mcp Remote in Claude Desktop, Claude Code or Cursor?
Open Fastmail Mcp Remote on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
Gmail
Read, send and search emails from Claude
by GoogleSlack
Send, search and summarize Slack messages
by SlackRunbear
No-code MCP client for team chat platforms, such as Slack, Microsoft Teams, and Discord.
Discord Server
A community discord server dedicated to MCP by [Frank Fiegel](https://github.com/punkpeye)
Klavis AI
Open Source MCP Infra. Hosted MCP servers and MCP clients on Slack and Discord.
Work90210/APIFold
Turn any REST API into a hosted MCP server. 18 free public servers (GitHub, Stripe, Slack, OpenAI, Notion, and more) — no setup required, bring your own API key
by Work90210arikusi/deepseek-mcp-server
MCP server for DeepSeek AI with chat, reasoning, multi-turn sessions, function calling, thinking mode, and cost tracking.
by arikusihashgraph-online/hashnet-mcp-js
MCP server for the Registry Broker. Discover, register, and chat with AI agents on the Hashgraph network.
by hashgraph-onlineprofullstack/mcp-server
A comprehensive MCP server aggregating 20+ tools including SEO optimization, document conversion, domain lookup, email validation, QR generation, weather data,
by profullstackWayStation-ai/mcp
Seamlessly and securely connect Claude Desktop and other MCP hosts to your favorite apps (Notion, Slack, Monday, Airtable, etc.). Takes less than 90 secs.
by waystation-aiCompare Fastmail Mcp Remote with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All communication MCPs
