MCP server providing 124 tools across 12 Google Workspace services.
A single Docker deployment serves 5 authenticated Gmail accounts via
FastMCP over Streamable HTTP transport.
An nginx auth portal handles OAuth flows and provides a management UI.
All traffic is routed through Traefik with dual-domain access (LAN .home and public .sanchezfamily.ca).
Upstream project: Fork of
taylorwilsdon/google_workspace_mcp
(workspace-mcp v1.9.0, MIT license). Customized with multi-account support, Docker deployment,
nginx auth portal, and CLI wrapper (gw).
MCP Tools
124
Google Services
12
Authenticated Accounts
5
Containers
2
Transport
Streamable HTTP
Runtime
Python 3.11 + FastMCP
2. Runtime Architecture
Diagram A: Runtime Request Flow
Request Flow
MCP client (Claude Code, Gemini, etc.) sends a tool invocation via POST /mcp over Streamable HTTP.
Traefik terminates TLS (Let's Encrypt for .sanchezfamily.ca, local CA for .home) and forwards to gws-portal on port 80.
gws-portal (nginx) proxies /mcp, /health, and /oauth2callback to the gws-mcp container on port 8000. Static auth portal UI is served directly.
gws-mcp (FastMCP + FastAPI) resolves the target Google account from tool parameters, loads OAuth credentials from the gws_creds Docker volume, and calls the appropriate Google Workspace API.
Google API responses are marshalled back through the MCP protocol to the calling agent.
Component Classification
Component
Classification
Role
gws-mcp
Primary
FastMCP server, 124 tools across 12 Google services
Warning: Running docker compose down -v deletes the credentials volume. All 5 accounts would need re-authentication via the portal. Use docker compose down (without -v) to preserve credentials.
5. Public Interfaces (MCP Tools)
HTTP Endpoints
Method
Path
Description
POST
/mcp
MCP Streamable HTTP endpoint (tool invocation)
GET
/health
Server health + list of authenticated accounts
GET
/oauth2callback
OAuth redirect handler
GET
/
Auth portal UI (glassmorphism setup page)
MCP Client Configuration
// Claude Code
{
"gws-mcp": {
"type": "sse",
"url": "http://gws-mcp.home/mcp"
}
}
// Other agents (Gemini, Codex, Qwen) via supergateway
{
"command": "npx",
"args": ["supergateway", "--streamableHttp", "http://gws-mcp.home/mcp"]
}
Both containers run on hosting_web (external Docker bridge shared with Traefik and all hosting services).
Volumes
Volume
Mount
Purpose
gws_creds
/app/store_creds:rw
OAuth token storage (5 account JSON files)
./portal
/usr/share/nginx/html:ro
Auth portal static files
./nginx.conf
/etc/nginx/conf.d/default.conf:ro
nginx reverse proxy config
Traefik Routing
Domain
Target
TLS
gws-mcp.sanchezfamily.ca
gws-portal :80
Let's Encrypt (via Cloudflare DNS challenge)
gws-mcp.home
gws-portal :80
Local CA wildcard cert
Environment Variables
Variable
Default
Description
GOOGLE_CLIENT_ID
(from OAuth project)
Google OAuth 2.0 client ID
GOOGLE_CLIENT_SECRET
(from OAuth project)
Google OAuth 2.0 client secret
GOOGLE_OAUTH_REDIRECT_URI
https://gws-mcp.sanchezfamily.ca/oauth2callback
OAuth callback URL
WORKSPACE_MCP_PORT
8000
MCP server bind port
WORKSPACE_MCP_HOST
0.0.0.0
MCP server bind address
WORKSPACE_MCP_CREDENTIALS_DIR
/app/store_creds
Credential storage directory
TOOL_TIER
complete
Tool tier (complete or basic)
OAUTHLIB_INSECURE_TRANSPORT
1
Allow OAuth over HTTP (behind TLS proxy)
Secrets are stored in .env.production at the deploy directory. Google Cloud project ID: 718777906559.
Operations
# Deploy / update
cd /home/rod/_rod/_gws-mcp
docker compose up -d
# Rebuild after code changes
docker compose up -d --build
# View logs
docker compose logs -f gws-mcp
docker compose logs -f gws-portal
# Health check
curl -sk https://gws-mcp.home/health
# Stop (preserves credentials)
docker compose down
Healthcheck
The gws-mcp container has a built-in Docker healthcheck that polls http://localhost:8000/health every 30 seconds (10s timeout, 30s start period, 3 retries). Monitored by Portainer.
8. Dependencies
Python Dependencies
Package
Version
Purpose
fastmcp
>=2.14.4
MCP server framework (Streamable HTTP transport)
fastapi
>=0.115.12
HTTP API framework (health, OAuth callback)
google-api-python-client
>=2.168.0
Google Workspace API client
google-auth-oauthlib
>=1.2.2
Google OAuth 2.0 authentication
google-auth-httplib2
>=0.2.0
HTTP transport for Google Auth
httpx
>=0.28.1
Async HTTP client
pyjwt
>=2.10.1
JWT token handling
cryptography
>=45.0.0
Cryptographic operations for auth
python-dotenv
>=1.1.0
Environment variable loading
pyyaml
>=6.0.2
Tool tier config parsing
py-key-value-aio
>=0.3.0
Async key-value store
Infrastructure Dependencies
Dependency
Role
Required
Docker + Docker Compose v2
Container runtime
Yes
Traefik (shared)
TLS termination, routing
Yes
Pi-hole DNS (shared)
.home domain resolution
For LAN access
Cloudflare (shared)
Public DNS + tunnel
For public access
Google Cloud Console
OAuth app credentials
Yes
uv
Python package manager (in container)
Yes (build-time)
Upstream
Forked from taylorwilsdon/google_workspace_mcp
(MIT license, v1.9.0). Author: Taylor Wilsdon.
Customizations: multi-account support, Docker deployment with nginx portal, CLI wrapper, rod-server integration.
9. Constraints & Risks
Item
Severity
Description
Google OAuth token expiry
Medium
Tokens auto-refresh, but a revoked token requires manual re-auth via the portal. No alerting for expired tokens.
Credential volume deletion
High
docker compose down -v destroys all 5 account credentials. No backup mechanism configured.
Google API quotas
Medium
Google Workspace APIs have per-user and per-project rate limits. No client-side rate limiting or retry backoff beyond what the Google client library provides.
OAuth app in "Testing" mode
Low
Google OAuth consent screen is in testing mode, which limits to explicitly listed test users. Production publishing requires Google review.
Single-point proxy
Low
nginx portal is the sole entry point. If it goes down, the MCP server is unreachable even though it may still be healthy.
OAUTHLIB_INSECURE_TRANSPORT=1
Low
Required because Traefik terminates TLS and the container receives HTTP internally. Acceptable in this architecture but would be a concern if the container were directly exposed.