gmail-mcp
by YerayRodri
README.md
# gmail-mcp
MCP server for Gmail with full read/write coverage: search, send, reply,
drafts, labels, filters, vacation responder, auto-forwarding, signature, and
attachment handling (download + render to PDF).
Built because the commonly available Gmail MCP integrations are read/label-only
(no sending, no attachment export, no settings management). This one closes
that gap using the standard Gmail API with your own OAuth client.
## Why it's safe by design
The OAuth scope used is `gmail.modify`, not the full `https://mail.google.com/`
scope. This means the Gmail API itself **rejects** permanent delete
(`messages.delete`, `batchDelete`, `threads.delete`) — the only "removal" tool
is trash/untrash, which is reversible.
## Tools (41)
### Read / search
| Tool | What it does |
|---|---|
| `get_profile` | Account email and message/thread totals |
| `list_messages` | Search with Gmail query syntax (`query`, `label_ids`, pagination) |
| `get_message` | Parsed message: from/to/cc/subject/date/body/labels/attachments |
| `get_thread` | Full thread with all messages parsed |
| `list_attachments` | Real attachment metadata for a message (with `attachment_id`) |
| `download_attachment` | Download an attachment to disk |
| `export_message_to_pdf` | Render a message (HTML + inline images resolved) to PDF via WeasyPrint |
| `export_thread_to_pdf` | Same, for a full thread (one message per page) |
### Send / reply (require `confirmed=True`)
| Tool | What it does |
|---|---|
| `send_message` | Send a new email (text/HTML, attachments) |
| `reply_message` | Reply within a thread, auto-generating `In-Reply-To`/`References` |
### Drafts
| Tool | What it does |
|---|---|
| `list_drafts` / `get_draft` | List and read drafts |
| `create_draft` / `update_draft` / `delete_draft` | Manage drafts |
| `send_draft` | Send an existing draft (`confirmed=True`) |
### Labels
| Tool | What it does |
|---|---|
| `list_labels` / `create_label` / `update_label` | Label management |
| `delete_label` | Delete a label (`confirmed=True`) |
| `modify_message_labels` / `modify_thread_labels` | Add/remove labels on a message or full thread |
### Organization (trash only, reversible)
| Tool | What it does |
|---|---|
| `trash_message` / `untrash_message` | Move to trash / restore |
| `batch_modify_messages` | Change labels on several messages at once |
### Incremental sync
| Tool | What it does |
|---|---|
| `get_history_changes` | Delta of changes since a `history_id` — added/removed messages and label changes, without re-listing everything |
### Mailbox delegation
| Tool | What it does |
|---|---|
| `list_delegates` | Accounts with delegated access to this mailbox |
| `add_delegate` | Grants full delegated access (read/send/manage) to another Google account. `confirmed=True` — more sensitive than forwarding: persistent access, not just a copy |
| `remove_delegate` | Revokes delegated access (`confirmed=True`) |
⚠️ **Requires domain-wide delegation.** Verified live: on a regular Gmail/Workspace account without
that admin-level setup, these 3 tools return `403: Access restricted to service accounts that have
been delegated domain-wide authority`. Left implemented for accounts where it does apply.
### Authorized forwarding addresses
Different from `get_auto_forwarding`/`update_auto_forwarding` below (which toggle forwarding to an
**already-authorized** address): these manage which addresses are allowed as a target in the first
place.
| Tool | What it does |
|---|---|
| `list_forwarding_addresses` | Authorized addresses (verified or pending) |
| `add_forwarding_address` | Authorizes a new address — Google sends it a verification email. `confirmed=True`, same risk category as enabling forwarding |
| `remove_forwarding_address` | Revokes an address's authorization (`confirmed=True`) |
### Settings
| Tool | What it does |
|---|---|
| `get_vacation` / `update_vacation` | Vacation auto-responder |
| `get_auto_forwarding` / `update_auto_forwarding` | Auto-forwarding (`confirmed=True` when enabling — this is a data-exfiltration vector, double check the destination address) |
| `list_filters` / `create_filter` / `delete_filter` | Filters (`delete_filter` requires `confirmed=True`) |
| `list_send_as` / `update_signature` | Send-as aliases and HTML signature (`confirmed=True`) |
## Confirmation pattern
Tools that send mail, delete something, or change externally-visible settings
take `confirmed: bool = False`. Called without it, they return
`{"requires_confirmation": true, ...}` with a preview of what would happen —
the calling agent must show that preview to the user and only repeat the call
with `confirmed=True` after explicit approval. `confirmed=True` is a technical
flag, not a substitute for actually asking.
## Security
- Every tool ships with [MCP Tool Annotations](https://modelcontextprotocol.io/specification)
(`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`), so MCP clients can use
them to decide whether to prompt for confirmation before running a tool.
- Execution errors propagate as real MCP protocol errors (`isError: true`), not as a JSON payload
that looks like a success with an `"error"` key buried inside — so the calling model actually
sees the failure and can self-correct instead of silently treating it as a success.
## Setup
1. Create a Google Cloud project (or reuse one) and enable the **Gmail API**.
2. Create an OAuth 2.0 Client ID of type "Desktop app" and download it as
`client_secret.json`.
3. On the OAuth consent screen, add these scopes:
`gmail.modify`, `gmail.settings.basic`, `gmail.settings.sharing`.
4. If the app is in "Testing" mode, add your own Google account as a test user.
5. Install dependencies:
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# weasyprint also needs a system package for PDF rendering:
# macOS: brew install pango
# Debian/Ubuntu: apt install libpango-1.0-0 libpangocairo-1.0-0
```
6. Run the OAuth flow once per account:
```bash
CLIENT_SECRET_PATH=~/.config/gmail-mcp/client_secret.json \
TOKEN_OUT=~/.config/gmail-mcp/token.json \
python3 setup_auth.py
```
This opens a browser — log in and grant access. Run it again with a
different `TOKEN_OUT` for each additional Gmail account you want to expose
as a separate MCP server instance.
## MCP client configuration
```json
{
"mcpServers": {
"gmail": {
"command": "/path/to/.venv/bin/python3",
"args": ["/path/to/gmail-mcp/server.py"],
"env": {
"GMAIL_TOKEN_PATH": "~/.config/gmail-mcp/token.json"
}
}
}
}
```
To expose a second account, add another entry (e.g. `gmail-work`) pointing at
the same `server.py` with a different `GMAIL_TOKEN_PATH`.
| Env var | Default | Purpose |
|---|---|---|
| `GMAIL_TOKEN_PATH` | `~/.config/gmail-mcp/token.json` | Path to the OAuth token for this account |
## Notes on usage (for the calling agent)
- **API quota:** 6,000 units/user/minute. `messages.get` costs 20 units,
`messages.list` costs 5. Filter with Gmail's `query` syntax instead of
fetching everything and filtering client-side.
- **Use `get_thread` instead of N `get_message` calls** when you need a whole
conversation.
- **`attachment_id` is not stable across calls** — always use the one from the
same response you're about to call `download_attachment` with.
- **Prefer `body_html` over `body_text`** for anything with lists, bold text
or links — Gmail does not reflow plain text, so `\n` becomes a hard line
break.
- **No automatic retry on 429** — back off manually if you hit a rate limit.
## License
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues