Multi-account Google Workspace MCP server — Gmail, Drive, Sheets, Calendar
Project description
Google Workspace MCP Server
A MCP server built with FastMCP for Google Workspace with multi-account support. Works with Claude Desktop, Claude Code, or any MCP client to interact with Gmail, Google Drive, Sheets, and Calendar across multiple Google accounts.
Features
- Multi-account profiles — Named profiles (e.g.
business,personal) to switch between accounts - Two transports — local STDIO (per-profile desktop OAuth) and remote HTTP + per-user OAuth, selected via
GW_MCP_TRANSPORT(see Transports) - Zero-friction setup — OAuth flows triggered from within Claude, no CLI needed
- Gmail — Search, read, send, threads, attachments, labels (create/list/update/delete), a full set of organizing tools (archive/unarchive, mark read/unread, star/unstar, trash/untrash, per-message & per-thread label modify, batch modify,
gmail_apply_to_search), and filters (rules): list/create/delete the auto-label/auto-archive rules — 25 tools - Google Drive — Upload, download, list, search, folders, move, trash (7 tools)
- Google Sheets — Read, write, append, create, metadata (5 tools)
- Google Calendar — Events, create/update/delete, free/busy, calendars (6 tools)
- 51 tools total including auth/setup + profile tools
- Clean architecture — Services layer (Google API) separated from tools layer (MCP)
Prerequisites
A Google Cloud Project with OAuth 2.0 credentials:
- Go to Google Cloud Console → Credentials
- Create an OAuth 2.0 Client ID (type: Desktop app)
- Enable these APIs in API Library: Gmail API, Google Drive API, Google Sheets API, Google Calendar API
That's it. No redirect URIs to configure — the server handles it.
Quick Start (Claude Desktop)
Add to your Claude Desktop config:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"google-workspace": {
"command": "uvx",
"args": ["--from", "/path/to/google-workspace-mcp-py", "google-workspace-mcp"]
}
}
}
Restart Claude Desktop. That's it — no pip install, no venv. uvx handles everything.
Requires uv — install with
curl -LsSf https://astral.sh/uv/install.sh | sh(Mac/Linux) orpowershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"(Windows)
Install from PyPI
Each release is published to PyPI as multi-gw-mcp, so you can also install/run it by name instead of a local path:
pip install multi-gw-mcp # or: uvx --from multi-gw-mcp google-workspace-mcp
Quick Start (Claude Code)
claude mcp add google-workspace -- uvx --from /path/to/google-workspace-mcp-py google-workspace-mcp
Setup Flow (happens inside Claude)
Once the server is running, Claude handles everything:
- Claude calls
check_setup_status→ sees "not configured" - You provide your Google Cloud Client ID and Secret
- Claude calls
setup_google_workspace→ config saved automatically - Claude calls
authenticate_account("business")→ your browser opens to Google sign-in → you click Allow → token saved - Claude calls
authenticate_account("personal")→ same for second account - Done. All tools work. Tokens auto-refresh.
No terminal commands. No config files to hand-edit. No copy-pasting tokens.
Transports
The server runs in one of two modes, chosen by the GW_MCP_TRANSPORT env var
(main() in server.py):
GW_MCP_TRANSPORT |
Mode | Auth |
|---|---|---|
stdio (default) |
Local STDIO | Per-profile desktop OAuth; tokens on disk under GW_MCP_CONFIG_DIR. Use the in-chat setup flow above. |
http (or sse) |
Remote HTTP server | Per-user OAuth via FastMCP's built-in GoogleProvider. Each connecting user signs in with their own Google account through the MCP protocol; additional accounts are added at runtime with add_gw_account. Requires GW_MCP_PUBLIC_URL, GW_MCP_WEB_CLIENT_ID, GW_MCP_WEB_CLIENT_SECRET. |
In HTTP mode the server binds GW_MCP_HOST/GW_MCP_PORT and exposes a /health
check plus an OAuth callback for adding profiles. Both transports share the same
51 tools and the same OAuth scopes.
If Published to PyPI
Once published, the config simplifies to:
{
"mcpServers": {
"google-workspace": {
"command": "uvx",
"args": ["google-workspace-mcp"]
}
}
}
Usage
Every tool accepts an optional profile parameter. If omitted, the first profile is used.
# Search business Gmail
gmail_search(query="from:aws subject:invoice after:2025/04/01", profile="business")
# Search personal Gmail
gmail_search(query="from:billing@example.com invoice", profile="personal")
# Organize: archive + mark read + label everything from a newsletter in one call
# (label args accept NAMES or IDs — names are resolved automatically)
gmail_apply_to_search(
query="from:newsletter@example.com older_than:30d",
add_label_ids=["Archive/Newsletters"],
archive=True, mark_read=True, max_results=200, profile="business"
)
# Star / archive a single message, or archive a whole conversation
gmail_star(message_id="...")
gmail_archive(message_id="...")
gmail_modify_thread(thread_id="...", remove_labels=["INBOX"], add_labels=["STARRED"])
# Trash is recoverable (not a permanent delete)
gmail_trash(message_id="...")
gmail_untrash(message_id="...")
# Filters (rules): auto-label + skip-inbox every future newsletter
# (label NAMES are resolved to IDs; the label must already exist)
gmail_create_filter(
from_address="newsletter@example.com",
add_labels=["Newsletters"], archive=True, mark_read=True, profile="business"
)
gmail_list_filters(profile="business")
gmail_delete_filter(filter_id="ANe1...", profile="business")
# Upload to Drive
drive_upload(local_path="/tmp/report.xlsx", folder_id="abc123", profile="business")
# Read a Google Sheet
sheets_read(spreadsheet_id="abc123", range="Sheet1!A1:D10")
# Check calendar
calendar_list_events(time_min="2025-04-01T00:00:00Z", time_max="2025-04-02T00:00:00Z")
All Tools (51)
| Category | Tool | Description |
|---|---|---|
| Setup | check_setup_status |
Check config & auth status (STDIO) |
| Setup | setup_google_workspace |
Save OAuth credentials + profiles (STDIO) |
| Setup | authenticate_account |
Open browser OAuth for a profile (STDIO) |
| Setup | add_google_profile |
Add another account to config (STDIO) |
| Profiles | list_google_profiles |
List accounts & auth status (STDIO) |
| Profiles | whoami |
Show the authenticated user (HTTP) |
| Profiles | add_gw_account |
Add another Google account at runtime (HTTP) |
| Profiles | list_gw_accounts |
List connected accounts (HTTP) |
| Gmail | gmail_search |
Search with Gmail query syntax |
| Gmail | gmail_read |
Read a message by ID |
| Gmail | gmail_read_thread |
Read entire thread |
| Gmail | gmail_send |
Send email or reply |
| Gmail | gmail_list_labels |
List system + user labels |
| Gmail | gmail_create_label |
Create a user label |
| Gmail | gmail_update_label |
Rename a user label |
| Gmail | gmail_delete_label |
Delete a user label |
| Gmail | gmail_modify_message |
Add/remove labels on a message |
| Gmail | gmail_batch_modify |
Add/remove labels on many messages |
| Gmail | gmail_modify_thread |
Add/remove labels on a whole thread |
| Gmail | gmail_archive / gmail_unarchive |
Remove/add INBOX |
| Gmail | gmail_mark_read / gmail_mark_unread |
Remove/add UNREAD |
| Gmail | gmail_star / gmail_unstar |
Add/remove STARRED |
| Gmail | gmail_trash / gmail_untrash |
Move to / restore from Trash (recoverable; not permanent delete) |
| Gmail | gmail_apply_to_search |
Run a search and bulk archive/label/mark-read all matches |
| Gmail | gmail_list_filters |
List all filters (auto-label/auto-archive rules) |
| Gmail | gmail_create_filter |
Create a filter — match by from/to/subject/query/etc., act via labels + flags (archive/mark_read/star/forward) |
| Gmail | gmail_delete_filter |
Delete a filter by ID |
| Gmail | gmail_get_attachment_data |
Get attachment as base64 (or save server-side) |
| Gmail | gmail_save_attachment_to_server |
Save attachment to the server filesystem |
| Drive | drive_list |
List/search files |
| Drive | drive_upload |
Upload local file |
| Drive | drive_upload_base64 |
Upload from base64 data |
| Drive | drive_download |
Download file |
| Drive | drive_create_folder |
Create folder |
| Drive | drive_move |
Move between folders |
| Drive | drive_trash |
Trash a file |
| Sheets | sheets_read |
Read data from range |
| Sheets | sheets_write |
Write data to range |
| Sheets | sheets_append |
Append rows |
| Sheets | sheets_create |
Create spreadsheet |
| Sheets | sheets_info |
Get metadata |
| Calendar | calendar_list_events |
List events |
| Calendar | calendar_create_event |
Create event |
| Calendar | calendar_update_event |
Update event |
| Calendar | calendar_delete_event |
Delete event |
| Calendar | calendar_free_busy |
Check availability |
| Calendar | calendar_list_calendars |
List calendars |
Architecture
src/google_workspace_mcp/
├── server.py # FastMCP composition + middleware
├── auth/
│ ├── oauth.py # OAuth2 + profiles + auto-setup + browser auth flow
│ └── setup.py # Optional CLI fallback (gw-mcp-auth)
├── services/ # Google API wrappers (reusable, testable)
│ ├── gmail.py
│ ├── drive.py
│ ├── sheets.py
│ └── calendar.py
├── tools/ # FastMCP @mcp.tool decorated functions
│ ├── profiles.py # Setup + auth + profile tools
│ ├── gmail.py
│ ├── drive.py
│ ├── sheets.py
│ └── calendar.py
└── middleware/
└── error_handler.py # Google API errors → friendly messages
Environment Variables
| Variable | Default | Description |
|---|---|---|
GW_MCP_TRANSPORT |
stdio |
stdio (local) or http/sse (remote server) |
GW_MCP_HOST |
127.0.0.1 |
Bind host (HTTP mode) |
GW_MCP_PORT |
8000 |
Bind port (HTTP mode) |
GW_MCP_PUBLIC_URL |
(unset) | Public base URL — set to enable HTTP per-user OAuth |
GW_MCP_WEB_CLIENT_ID / GW_MCP_WEB_CLIENT_SECRET |
(unset) | Web OAuth client for HTTP mode |
GW_MCP_CONFIG_DIR |
~/.config/google-workspace-mcp |
Config & token directory |
GW_MCP_OAUTH_PORT |
3456 |
Local port for OAuth callback (STDIO mode) |
GW_MCP_ALLOWED_CLIENT_REDIRECTS |
(built-in default) | Comma-separated allow-list of MCP-client redirect URI patterns. Defaults already cover Claude Code/Desktop loopback and Claude Web. Override only to extend. |
OAuth scopes
Both transports request the same scopes:
gmail.readonly,gmail.send,gmail.modify,gmail.settings.basicdrive.file(HTTP) /drive(STDIO),spreadsheets,calendar
gmail.modify covers label changes and messages.trash/untrash.
gmail.settings.basic is required for the filter (rule) tools
(gmail_list_filters / gmail_create_filter / gmail_delete_filter).
Permanent deletion is intentionally not supported (it would require the
broader full-mailbox scope).
⚠️ Re-consent required. Adding
gmail.settings.basicchanges the requested scope set, so every existing user must re-authenticate on their next use — existing tokens lack the new scope and the filter tools will return an insufficient-scope error until the account is re-authed. This is a one-time consent screen; no data is lost.
Remote / HTTP mode OAuth (Claude Code, Desktop & Web)
When GW_MCP_PUBLIC_URL is set the server becomes its own OAuth authorization
server and proxies upstream to Google. There are two distinct redirect
layers — do not conflate them:
-
MCP client → this server (handled in code). Claude clients register via Dynamic Client Registration with their own redirect URI:
- Claude Code (CLI) and Claude Desktop use an RFC 8252 loopback on a
dynamic port:
http://localhost:<port>/.../http://127.0.0.1:<port>/.... - Claude Web (claude.ai) uses the fixed callback
https://claude.ai/api/mcp/auth_callback.
All three are accepted by the built-in
GW_MCP_ALLOWED_CLIENT_REDIRECTSallow-list — no configuration needed. - Claude Code (CLI) and Claude Desktop use an RFC 8252 loopback on a
dynamic port:
-
This server → Google (one human step). The server always sends Google a single fixed redirect:
<GW_MCP_PUBLIC_URL>/auth/callback. This exact URI must be registered in the Google Cloud Console:APIs & Services → Credentials → your Web OAuth 2.0 Client (
GW_MCP_WEB_CLIENT_ID) → Authorized redirect URIs → addhttps://<your-public-host>/auth/callback(e.g.https://mcp.example.com/auth/callback).If this is missing, every client fails with Google
redirect_uri_mismatch— it is not a per-client problem.
Optional CLI
If you prefer terminal setup over in-chat setup:
uvx --from /path/to/project gw-mcp-auth --init # Create config interactively
uvx --from /path/to/project gw-mcp-auth --profile biz # Auth one profile
uvx --from /path/to/project gw-mcp-auth --status # Check status
Built With
- FastMCP — Python MCP framework
- Google API Python Client
- uv — Fast Python package management
Contributing
Contributions are welcome — see CONTRIBUTING.md for the dev setup and workflow. For security issues, please follow SECURITY.md rather than opening a public issue.
License
MIT © 3J Technologies
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file multi_gw_mcp-1.0.1.tar.gz.
File metadata
- Download URL: multi_gw_mcp-1.0.1.tar.gz
- Upload date:
- Size: 51.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
562870a0f07dd7dc1616771cc4427de5106e277a80d3b988fe2a248d37abd61a
|
|
| MD5 |
2d6981b0048eb79f65fe79d7a285d7f1
|
|
| BLAKE2b-256 |
ce3eacf57eb40819aa29f6e01bf2f05e6e18bbf67a41362ea6e48b49f0e21c97
|
File details
Details for the file multi_gw_mcp-1.0.1-py3-none-any.whl.
File metadata
- Download URL: multi_gw_mcp-1.0.1-py3-none-any.whl
- Upload date:
- Size: 45.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
97e5f5cca1e94ab174b4b3614dacd3b952a016a34e6893b6f73867b997713baa
|
|
| MD5 |
f05582f3c63814ff3912856fc8e1cc45
|
|
| BLAKE2b-256 |
ebd8cf333de815b2a683e742a398f15ff57fdc61f10742e4f9713121b14b4e6b
|