Gmail MCP Server
Full Gmail control for any MCP-compatible AI agent. Exposes ~40 tools covering reading, composing, labeling, filtering, threading, and account management — works with Claude, Cursor, Copilot, Goose, OpenCode, and any agent that speaks the MCP stdio protocol.
---
Requirements
- Python 3.10+
- A Google account
- Any MCP-compatible AI agent (Claude Code, Cursor, Copilot, Goose, OpenCode, etc.)
---
Quick Install (Linux)
Detects Arch or Ubuntu/Debian automatically, creates a virtualenv, installs deps, sets credential permissions, and prints the MCP config snippet ready to paste:
bash install.sh
Manual steps follow below if you prefer full control.
---
1. Python Dependencies
pip install -r requirements.txt
requirements.txt: `` mcp google-api-python-client google-auth-oauthlib google-auth-httplib2 ``
---
2. Google Cloud Console Setup
2.1 Create a Project
- Go to https://console.cloud.google.com/
- Click the project dropdown (top-left) → New Project
- Name it (e.g.
gmail-mcp) → Create - Make sure the new project is selected in the dropdown
2.2 Enable the Gmail API
- Go to APIs & Services → Library
- Search for
Gmail API→ click it → Enable
2.3 Configure the OAuth Consent Screen
- Go to APIs & Services → OAuth consent screen
- Choose External → Create
- Fill in:
- App name: anything (e.g.
Gmail MCP) - User support email: your Gmail address
- Developer contact email: your Gmail address
- Click Save and Continue through Scopes (skip adding scopes here)
- On the Test users page → Add users → add your Gmail address
- Click Save and Continue → Back to Dashboard
Important: While the app is in Testing mode only the users listed here can authenticate. The app does not need to be published.
2.4 Add Required OAuth Scopes
- Go to APIs & Services → OAuth consent screen → Edit App
- On the Scopes step → Add or Remove Scopes
- Search for and select each of the following:
| Scope | Why | |---|---| | https://mail.google.com/ | Full read/write/delete/send access | | https://www.googleapis.com/auth/gmail.settings.basic | Create/manage filters and labels |
- Click Update → Save and Continue
gmail.settings.basicis required specifically forfilters.create/filters.delete.mail.google.comalone is not sufficient for filter management despite appearing to be a superset scope.
2.5 Create OAuth Credentials
- Go to APIs & Services → Credentials
- + Create Credentials → OAuth client ID
- Application type: Desktop app
- Name: anything → Create
- Click Download JSON on the resulting dialog
- Save the downloaded file as
credentials.jsonin this project directory
---
3. First-Run Authentication
Run the server once manually to trigger the OAuth browser flow:
python server.py
A browser window opens. Log in with the Google account added as a test user and grant both requested permissions.
This writes token.pickle to the project directory. Subsequent runs use this token silently (auto-refreshed by Google's OAuth library).
If you change the requested scopes (e.g. add gmail.settings.basic later), the old token must be deleted:
rm token.pickle
python server.py
---
4. Register with AI Coding Assistants
All tools below use the same MCP stdio transport. The only difference between them is where the config file lives.
Common values to substitute throughout:
PYTHON→ full path to the Python binary with dependencies (e.g./home/user/.local/share/mamba/bin/python)SERVER→ full path toserver.py(e.g./home/user/gmail/server.py)
---
Claude Code
~/.claude.json: ``json { "mcpServers": { "gmail": { "type": "stdio", "command": "PYTHON", "args": ["SERVER"], "env": {} } } } ``
~/.claude/settings.json: ``json { "mcpServers": { "gmail": { "command": "PYTHON", "args": ["SERVER"] } } } ``
Restart Claude Code after editing.
---
Cursor
Global config ~/.cursor/mcp.json (or per-project .cursor/mcp.json): ``json { "mcpServers": { "gmail": { "command": "PYTHON", "args": ["SERVER"] } } } ``
Reload via Cursor Settings → MCP or restart Cursor.
---
Windsurf (Codeium)
~/.codeium/windsurf/mcp_config.json: ``json { "mcpServers": { "gmail": { "command": "PYTHON", "args": ["SERVER"] } } } ``
Reload via Windsurf Settings → MCP Servers → Refresh.
---
GitHub Copilot (VS Code)
VS Code 1.99+ supports MCP natively. Add to your User settings.json (Ctrl+Shift+P → Open User Settings JSON): ``json { "mcp": { "servers": { "gmail": { "type": "stdio", "command": "PYTHON", "args": ["SERVER"] } } } } ``
Or create a workspace-scoped .vscode/mcp.json: ``json { "servers": { "gmail": { "type": "stdio", "command": "PYTHON", "args": ["SERVER"] } } } ``
Copilot picks it up automatically. No restart needed.
---
Cline (VS Code extension)
Open the Cline sidebar → MCP Servers tab → Edit MCP Settings.
This opens cline_mcp_settings.json. Add: ``json { "mcpServers": { "gmail": { "command": "PYTHON", "args": ["SERVER"], "disabled": false, "autoApprove": [] } } } ``
Linux path: ~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json
---
Continue.dev
~/.continue/config.json: ``json { "mcpServers": [ { "name": "gmail", "command": "PYTHON", "args": ["SERVER"] } ] } ``
Or ~/.continue/config.yaml: ```yaml mcpServers:
- name: gmail
command: PYTHON args:
- SERVER
Reload with `Ctrl+Shift+P` → *Continue: Reload Config*.
---
### Zed
`~/.config/zed/settings.json`:
{ "context_servers": { "gmail": { "command": { "path": "PYTHON", "args": ["SERVER"], "env": {} }, "settings": {} } } } ```
Zed reloads context servers automatically on save.
---
Amazon Q Developer
~/.aws/amazonq/mcp.json: ``json { "mcpServers": { "gmail": { "command": "PYTHON", "args": ["SERVER"] } } } ``
Restart the Q Developer extension or CLI after editing.
---
Goose (Block)
~/.config/goose/config.yaml: ```yaml extensions: gmail: name: gmail type: stdio cmd: PYTHON args:
- SERVER
enabled: true ```
Run goose session — Goose loads extensions at startup.
---
OpenCode (SST)
~/.config/opencode/config.json: ``json { "mcp": { "gmail": { "command": "PYTHON", "args": ["SERVER"] } } } ``
---
5. Using Programmatically
Anthropic Python SDK
pip install anthropic
import anthropic
client = anthropic.Anthropic()
with client.beta.messages.stream(
model="claude-opus-4-8",
max_tokens=4096,
mcp_servers=[{
"type": "stdio",
"command": "PYTHON",
"args": ["SERVER"],
}],
messages=[{"role": "user", "content": "List my last 5 unread emails."}],
betas=["mcp-client-2025-04-04"],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
---
OpenAI Agents SDK
pip install openai-agents
import asyncio
from agents import Agent, Runner
from agents.mcp import MCPServerStdio
async def main():
async with MCPServerStdio(
params={"command": "PYTHON", "args": ["SERVER"]}
) as mcp:
agent = Agent(
name="gmail-agent",
instructions="You manage Gmail for the user.",
mcp_servers=[mcp],
)
result = await Runner.run(agent, "List my last 5 unread emails.")
print(result.final_output)
asyncio.run(main())
---
Claude Code CLI (subprocess / headless)
claude --mcp-config /path/to/mcp-config.json \
-p "Summarize my unread emails from today."
mcp-config.json: ``json { "mcpServers": { "gmail": { "type": "stdio", "command": "PYTHON", "args": ["SERVER"] } } } ``
---
Token handling in headless / CI environments
The OAuth flow requires a browser the first time. For agents running headlessly:
- Run the browser flow once on a machine with a display to produce
token.pickle - Copy
token.pickleandcredentials.jsonto the headless machine - The server auto-refreshes the token via the stored refresh token — no browser needed after that
chmod 600 credentials.json token.pickle
Do not pass secrets via environment variables.
---
6. Available Tools
Reading
| Tool | Description | |---|---| | list_messages | List/search messages (Gmail search syntax) | | get_message | Full plain-text content of a message | | get_message_html | HTML body of a message | | list_threads | List conversation threads | | get_thread | Full thread with all messages | | list_attachments | List attachments on a message | | get_attachment | Download attachment to local path |
Composing & Sending
| Tool | Description | |---|---| | send_email | Send a new email | | reply_email | Reply to a thread | | forward_email | Forward a message | | save_draft | Save a draft | | list_drafts | List drafts | | delete_draft | Delete a draft |
Organization
| Tool | Description | |---|---| | trash_message | Move to trash | | untrash_message | Restore from trash | | delete_permanently | Delete bypassing trash | | archive_message | Archive (remove from inbox) | | move_to_spam | Mark as spam | | mark_read | Mark as read | | mark_unread | Mark as unread | | star_message | Star a message | | unstar_message | Unstar a message | | apply_label | Apply label by ID | | remove_label | Remove label by ID | | batch_trash | Trash multiple messages | | batch_delete_permanently | Permanently delete multiple messages | | batch_archive | Archive multiple messages | | batch_mark_read | Mark multiple messages read | | trash_by_query | Trash all messages matching a search | | archive_by_query | Archive all messages matching a search |
Labels
| Tool | Description | |---|---| | list_labels | List all labels (system + user) | | create_label | Create a label (supports / for hierarchy) | | delete_label | Delete a label by ID | | rename_label | Rename a label |
Filters
| Tool | Description | |---|---| | list_filters | List all Gmail filters | | create_filter | Create a filter (from/to/subject/query + label actions) | | delete_filter | Delete a filter by ID |
Account & Export
| Tool | Description | |---|---| | get_profile | Email address, message count, storage quota | | download_all_messages | Export all messages to JSON file (paginated) | | get_download_status | Check progress of an ongoing export |
---
7. Troubleshooting
403 insufficientPermissions on filter operations
The token was created without gmail.settings.basic. Delete it and re-authenticate:
rm token.pickle
python server.py
Verify SCOPES in server.py contains both scopes:
SCOPES = [
"https://mail.google.com/",
"https://www.googleapis.com/auth/gmail.settings.basic",
]
Token has been expired or revoked
Delete token.pickle and re-authenticate. This happens if access is revoked in Google account settings or if the app has been inactive for 6+ months in testing mode.
redirect_uri_mismatch
The OAuth client must be type Desktop app, not Web application. Delete the existing client ID and create a new one with the correct type.
MCP tools not appearing in Claude Code
- Confirm
credentials.jsonandtoken.pickleexist in the project directory - Run
python server.pymanually to check for import errors - Restart Claude Code after editing
~/.claude.json - Check that the Python path in the MCP config points to the environment where dependencies are installed






