Skip to main content
Glama
YerayRodri

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).