Google Workspace MCP Server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Google Workspace MCP Serversearch my Gmail for unread messages from my boss"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Google Workspace MCP
A standalone MCP server exposing Google Workspace tools (Gmail, Drive, Docs, Sheets, Calendar, Slides, Tasks) via MCP OAuth 2.0 — no static bearer tokens, no per-user config.
How It Works
MCP Client (QwenPaw / Claude Desktop / Claude Code)
│
│ POST /mcp/ (no token)
├── 401 with WWW-Authenticate: Bearer resource_metadata="https://.../oauth-protected-resource/mcp"
│
│ GET /.well-known/oauth-protected-resource/mcp → discovers auth server URL
│ GET /.well-known/oauth-authorization-server → discovers endpoints (/authorize, /token, /register, /revoke)
│ POST /register → gets client_id
│ GET /authorize → browser: Google login + scope selection
│ POST /token → receives MCP JWT (access_token + refresh_token)
│
│ POST /mcp/ (Authorization: Bearer <JWT>)
│
│ JWT "sub" claim = Google email → GoogleClient looks up credentials in access_tokens.json → calls Google APIToken format: JWT signed with
MCP_JWT_SECRET.sub= Google email.Token store:
access_tokens.json(per-user Google refresh tokens + granted scopes)OAuth flow: Two-step — identify (openid/email) → scope selection UI → authorize
Token refresh: MCP access tokens expire in 8h; clients auto-refresh using the 30d refresh token
Scope changes: Re-authorize via the client's "re-connect" button (existing scopes pre-selected)
Related MCP server: Google Workspace MCP Server
Prerequisites
Docker + Docker Compose
A publicly reachable HTTPS URL (
EXTERNAL_URL) behind a TLS proxy (Caddy, nginx, Cloudflare Tunnel)Google Cloud OAuth 2.0 credentials (
client_secret.json) — instructions
Quick Start
1. Configure
cp .env.example .env
# Edit .env:
# EXTERNAL_URL=https://mcp.yourdomain.com # your public HTTPS domain
# MCP_JWT_SECRET=$(openssl rand -hex 32)
# SECRETS_VOLUME_PATH=/absolute/path/to/secrets # host dir for OAuth files2. Add Google credentials
cp your-downloaded-client_secret.json /absolute/path/to/secrets/client_secret.json3. Start
docker compose up -d --build4. Verify
curl http://localhost:8000/healthz
# Expected: {"status":"ok",...} (may show "degraded" until first user authorizes)5. Add to your MCP client (no bearer token needed)
The MCP client auto-discovers OAuth on first connect — no token in the config.
QwenPaw — place this JSON as a new driver config:
{
"key": "google-workspace-mcp",
"name": "Google Workspace MCP",
"description": "Gmail, Drive, Docs, Sheets, Calendar, Slides",
"enabled": true,
"transport": "streamable_http",
"url": "http://127.0.0.1:8000/mcp/"
}QwenPaw fields
oauth_status,access_summary,tools, andheadersare auto-populated after the first connection. Set onlykey,name,transport, andurl.
Claude Desktop — add to claude_desktop_config.json:
{
"mcpServers": {
"google-workspace-mcp": {
"url": "http://127.0.0.1:8000/mcp/"
}
}
}Claude Code:
claude mcp add google-workspace-mcp --transport http --url http://127.0.0.1:8000/mcp/6. Authorize
On first MCP tool call, your client will:
Auto-discover OAuth endpoints (via the 401 →
WWW-Authenticate→/.well-known/...)Register a dynamic client
Open a browser to Google login + scope selection
Store the MCP token internally
After authorization, oauth_status in the client shows "connected" and tools are available.
Getting Google OAuth Credentials
Go to Google Cloud Console
Create a new project (or use existing)
Enable APIs: Gmail API, Drive API, Docs API, Sheets API, Calendar API, Slides API
Go to APIs & Services → Credentials → Create Credentials → OAuth client ID
Application type: Web application
Name:
google-workspace-mcpAuthorized redirect URIs: Add
https://mcp.yourdomain.com/oauth/callbackDownload the JSON → save as
secrets/client_secret.jsonSet
EXTERNAL_URLin.envto match your domain (for OAuth callback + discovery)
Scope Selection
The /authorize flow shows a scope selector with all Google API scopes. Existing grants are shown pre-selected on re-authorization.
Service | Read scopes | Write scopes |
Gmail |
|
|
Drive |
|
|
Docs |
|
|
Sheets |
|
|
Calendar |
|
|
Slides |
|
|
Other |
|
|
Plus always included: openid, userinfo.email, userinfo.profile
Environment Variables
Variable | Default | Description |
|
| Public HTTPS URL (OAuth callbacks + metadata discovery) |
|
| Secret for signing MCP JWT access/refresh tokens |
|
| Container listen port |
|
| Path to Google OAuth credentials (mounted) |
|
| Per-user token registry (auto-populated) |
| (see .env) | Default Google API scopes if MCP client doesn't request any |
|
| MCP access token lifetime in seconds (8h) — client auto-refreshes |
|
| MCP refresh token lifetime in seconds (30d) |
|
| MCP authorization code lifetime in seconds (10 min) |
|
|
|
|
|
|
|
| Host directory for |
|
| Timezone |
|
| Log verbosity |
Token Lifetimes
Token | Lifetime | Auto-managed |
MCP access token (JWT) | 8 hours ( | Client refreshes via refresh token |
MCP refresh token (JWT) | 30 days ( | Rotated on each refresh |
MCP auth code | 10 minutes ( | Exchanged once for tokens |
Google refresh token | Unlimited (until revoked) | Auto-refreshed by server when access expires |
All MCP token lifetimes are configurable. Google's token refresh is transparent — GoogleClient.get_credentials() refreshes silently when the access token expires.
Docker Compose
# docker-compose.yml uses env_file: .env only (no environment: block).
# Secrets volume mounted at /secrets in container.
services:
google-workspace-mcp:
build: .
env_file: .env
volumes:
- ${SECRETS_VOLUME_PATH}:/secrets:rwCaddy Proxy (recommended)
mcp.yourdomain.com {
# Block /mcp from public internet (clients connect via localhost)
route /mcp/* {
respond 403
}
# Proxy OAuth + health endpoints
reverse_proxy google-workspace-mcp:8000
}Why block
/mcp/*with 403? The MCP bearer-token (JWT) is enforced application-side, but the 403 is defense-in-depth — a network-level block so the MCP interface is never reachable from the public internet. If you'd rather expose/mcp/*publicly and rely solely on JWT auth, switch toCaddyfile.example-full-proxyinstead.
Token Persistence
Once a Google account is authorized (entry in access_tokens.json), the user
does not need to re-authorize until that entry is removed or Google
revokes the refresh token. Google access tokens expire silently; the bridge
auto-refreshes them using the stored refresh token — no user interaction
required. Remove an account by deleting its entry from access_tokens.json.
Troubleshooting
Problem | Solution |
| MCP client should auto-refresh. If stuck, restart client or re-authorize |
"No Google credentials found for user@..." | User hasn't completed OAuth. Trigger OAuth via your MCP client (it discovers endpoints automatically) |
| Re-authorize with additional scopes via your MCP client (disconnect + reconnect) |
| Check |
Client can't discover OAuth | Verify |
| Mount it at |
| Google OAuth scope-mismatch Warning caught — token is recovered automatically; restart the flow (codes are single-use) |
Project Structure
google-workspace-mcp/
├── app/
│ ├── config.py # Settings (env-driven, no hardcoded values)
│ ├── main.py # FastAPI + MCP OAuth middleware + routes
│ ├── mcp_server.py # FastMCP server with all tools
│ ├── oauth.py # Two-step Google OAuth web UI (/oauth/*)
│ ├── oauth_provider.py # OAuthAuthorizationServerProvider implementation
│ ├── registry.py # JSON-backed per-user token registry
│ ├── google_client.py # Per-user Google credentials (via JWT subject)
│ ├── generate_config.py # Optional config generator (manual use only)
│ └── tools/ # Tool implementations (gmail, drive, docs, ...)
├── Caddyfile # Proxy: blocks /mcp, proxies OAuth + healthz
├── Caddyfile.example-full-proxy
├── docker-compose.yml # env_file only (no environment: block)
├── Dockerfile
├── .env / .env.example
└── README.mdWorks With
Any MCP client supporting OAuth 2.0 Dynamic Client Registration + PKCE:
QwenPaw
Claude Desktop
Claude Code
Any compliant implementation
This server cannot be deployed
Related MCP Connectors
MCP server connecting AI agents to 100+ apps (Gmail, Slack, Notion, GitHub) via one-click OAuth.
Zero-setup MCP gateway securely connecting AI to your tools with authentication and workflows
Hosted MCP server with managed OAuth for 15+ toolkits: Google Workspace, Fitbit, Oura, Kalshi, etc.
Multiple Gmail accounts, editable Google Sheets & Docs for AI agents. Deny-by-default access rules.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceAn MCP server that exposes 17 Google Workspace APIs (e.g., Gmail, Drive, Calendar) as auto-generated tools for AI assistants, enabling natural language control of Google services.-
- FlicenseBqualityCmaintenanceMCP server providing full Google Workspace access (Gmail, Drive, Calendar, Docs, Sheets, Slides, Forms, Tasks, Contacts) using your own OAuth credentials, enabling natural language management of Google services.100-
- AlicenseBqualityAmaintenanceMCP server that gives AI agents direct access to Google services including Gmail, Calendar, Drive, and more, with multi-account OAuth support. Enables reading and sending email, managing calendar events, and interacting with Google files through natural language.60231MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to send Gmail emails, create drafts, and append content to Google Docs through MCP tools. Provides secure OAuth-based integration with Google Workspace.208MIT