gmail-drafts-mcp
# gmail-drafts-mcp
An MCP server that lets an AI agent **search, read and draft** in your Gmail, and **never send**.
The agent prepares replies as Gmail drafts, and you review and send them yourself. There is no
tool to send, delete, archive or relabel mail, so a confused or prompt-injected agent cannot email
anyone on your behalf through this server.
- One Python file, standard library only (Python 3.11+), no dependencies to audit.
- IMAP with a Google app password: no Google Cloud project and no OAuth consent screen.
- Reading never marks mail as read (`EXAMINE`, `BODY.PEEK`).
- Works with any MCP client over stdio (Claude Code, Claude Desktop, Cursor, and others).
## Tools
| Tool | What it does |
|---|---|
| `gmail_search` | Searches the whole mailbox with Gmail's own syntax (`from:`, `newer_than:7d`, `has:attachment`, `label:`), newest first. |
| `gmail_read` | Reads one message: headers, body (HTML turned into text, links kept) and the list of attachments. |
| `gmail_draft` | Saves a draft, optionally as a reply in an existing thread. Never sends. |
| `gmail_labels` | Lists folders and labels. |
Set `GMAIL_READ_ONLY=1` to remove `gmail_draft` too.
## Setup
1. Turn on 2-Step Verification for your Google account, then create an app password at
<https://myaccount.google.com/apppasswords>.
2. Add the server to your MCP client.
**Claude Code**
```sh
claude mcp add gmail-drafts \
-e GMAIL_USER=you@gmail.com -e GMAIL_APP_PASSWORD=xxxxxxxxxxxxxxxx \
-- uvx --from git+https://github.com/studenciak00owl/gmail-drafts-mcp gmail-drafts-mcp
```
**Claude Desktop and other clients** (`mcpServers` in the client's JSON config)
```json
{
"mcpServers": {
"gmail-drafts": {
"command": "uvx",
"args": ["--from", "git+https://github.com/studenciak00owl/gmail-drafts-mcp", "gmail-drafts-mcp"],
"env": {"GMAIL_USER": "you@gmail.com", "GMAIL_APP_PASSWORD": "xxxxxxxxxxxxxxxx"}
}
}
}
```
If you don't use `uv`, download `gmail_drafts_mcp.py` and run it with `python3 -I gmail_drafts_mcp.py`
as the command.
## Security notes
- **"Never sends" is enforced by this server, not by Google.** An app password grants full IMAP
and SMTP access to the mailbox. Keep it out of any place the agent can read (the agent's working
directory, the repository, the chat). Store it in your client's secret store or environment.
- If the agent runs as the same OS user as the server, a determined agent with shell access could
read the password from the process environment. For a hard boundary, run the server as another
user, or use the Gmail API with only the `gmail.compose` and `gmail.readonly` scopes.
- Email is untrusted input. Every message the server returns is labelled as data, not instructions,
but your agent's own rules still matter.
- Revoking the app password at <https://myaccount.google.com/apppasswords> cuts the server off
immediately.
## Development
```sh
python3 -m unittest discover -s tests
```
## License
MIT
TDQS
Scored across 4 tools
Each tool maps to a distinct action-resource pair: search the mailbox, read one message by uid, create a draft, list labels. There is minor conceptual proximity between gmail_search and gmail_labels, but the descriptions make the boundary clear (label enumeration vs. message querying).
All four tools follow a uniform snake_case gmail_<verb>|noun pattern (gmail_search, gmail_read, gmail_draft, gmail_labels). No mixed conventions or vague verbs.
Four tools is lean but defensible for a narrowly scoped draft-oriented Gmail server: search, read, draft, labels cover the core loop. It edges toward thin since there is no tool to inspect or manage existing drafts.
The search → read → draft workflow is closed and send is deliberately omitted, but draft lifecycle is incomplete: no listing, updating, or deleting of existing drafts, so an agent can create duplicate drafts with no way to check. gmail_read surfaces attachment metadata but there is no way to fetch attachment content.