A multi-user Model Context Protocol (MCP) server suite that exposes Google Calendar, Gmail, Meet, and Sheets as tools for AI agents like Claude. Deployed on AWS with per-user OAuth isolation, this lets any MCP-compatible AI assistant act on a user's Google Workspace data on their behalf — without ever hardcoding credentials.
Most MCP servers are built for a single user running everything locally. This project solves a harder problem: multiple users, one deployment. Each person authenticates once through a standard OAuth consent flow; their tokens are stored independently and refreshed automatically, so the same running server can safely serve many users at once.
┌─────────────────────────┐
│ AWS EC2 (Ubuntu) │
│ │
User Browser ───►│ nginx (port 80) │
│ ├── /auth → Flask │──► Google OAuth
│ ├── /calendar → :8001 │──► Google Calendar API
│ ├── /gmail → :8002 │──► Gmail API
│ ├── /meet → :8003 │──► Google Meet API
│ └── /sheets → :8004 │──► Google Sheets API
│ │
│ systemd manages all │
│ 5 processes, restarts │
│ on crash/reboot │
└───────────┬─────────────┘
│
┌───────────▼─────────────┐
│ AWS RDS (PostgreSQL) │
│ user_tokens table │
│ (per-user, per-service)│
└─────────────────────────┘
Each MCP server runs as an independent FastMCP process speaking streamable-HTTP, reverse-proxied through nginx under its own path. A lightweight Flask service handles the OAuth dance and persists refreshable tokens to Postgres, keyed by user_id and service name.
- Google Calendar — list, create, update, delete events; fetch by ID
- Gmail — search/list messages, fetch full message details, send mail with attachments
- Google Meet — create and retrieve Meet spaces
- Google Sheets — create spreadsheets, read/write ranges, append rows, clear ranges, find & replace
- Per-user OAuth 2.0 — no shared credentials; each user grants their own scopes and gets isolated, auto-refreshing tokens
- Production-style deployment — nginx reverse proxy, systemd-managed services, PostgreSQL-backed persistence
| Layer | Technology |
|---|---|
| MCP servers | Python, FastMCP |
| Auth server | Flask |
| Token storage | PostgreSQL (AWS RDS) |
| Reverse proxy | nginx |
| Process management | systemd |
| Hosting | AWS EC2 |
| Google APIs | Calendar v3, Gmail v1, Meet v2, Sheets v4 |
.
├── auth/
│ └── auth_server.py # OAuth flow: /auth/start, /auth/callback
├── models/
│ └── db.py # Token persistence (save_token / load_token)
├── google_auth/
│ └── services/ # Per-service credential + client builders
│ ├── google_calendar.py
│ ├── google_mail.py
│ ├── google_meet.py
│ └── google_sheets.py
├── mcp_server/
│ ├── run_calendar_mcp.py # FastMCP tool definitions, port 8001
│ ├── run_gmail_mcp.py # port 8002
│ ├── run_meet_mcp.py # port 8003
│ └── run_sheets_mcp.py # port 8004
├── server-configs/
│ ├── mcp # nginx site config
│ └── mcp-*.service # systemd unit files
├── .env.example
└── requirements.txt
Note on Docker files: This repo still contains
Dockerfile,docker_files/*.Dockerfile, anddocker-compose.ymlfrom an earlier iteration. The current deployment uses systemd + nginx directly on the host instead of containers (seeserver-configs/), which proved simpler to operate on a single free-tier EC2 instance. The Docker files are kept for reference but are not used in the deployment steps below.
- Create a project and enable the Calendar, Gmail, Meet, and Sheets APIs.
- Create an OAuth 2.0 Client ID of type Web application.
- Add an authorized redirect URI:
http://YOUR_HOST/auth/callback - Download the client secret JSON and save it as
credentials/credentials.json. - Add your Google account as a test user under OAuth consent screen → Audience while the app is in testing mode.
CREATE TABLE user_tokens (
user_id TEXT,
service TEXT,
token_json TEXT,
PRIMARY KEY (user_id, service)
);Copy .env.example to .env and fill in your values:
DATABASE_URL=postgresql://user:pass@your-rds-endpoint:5432/postgres?sslmode=require
REDIRECT_URI=http://YOUR_HOST/auth/callback
FLASK_SECRET=a_random_stringpython3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
export PYTHONPATH=$(pwd)
python auth/auth_server.py # OAuth server, port 5000
python mcp_server/run_calendar_mcp.py # repeat for gmail/meet/sheetsUnit files for each service live in server-configs/. Copy them to /etc/systemd/system/, then:
sudo systemctl daemon-reload
sudo systemctl enable --now mcp-auth mcp-calendar mcp-gmail mcp-meet mcp-sheetsCopy server-configs/mcp to /etc/nginx/sites-available/mcp, symlink it into sites-enabled/, and reload nginx.
Visit:
http://YOUR_HOST/auth/start?user_id=<any-identifier>
Complete the Google consent screen. The user's tokens are stored against that user_id and refreshed automatically on expiry.
Add each server as a remote MCP connector (via mcp-remote for clients that only support local commands):
{
"mcpServers": {
"GoogleCalendar": {
"command": "npx",
"args": ["mcp-remote", "http://YOUR_HOST/calendar/mcp", "--allow-http"]
}
}
}Repeat for /gmail/mcp, /meet/mcp, and /sheets/mcp.
credentials/,.env, and all token files are git-ignored — never commit secrets.- Tokens are isolated per
user_id; no user can access another's data. - For production use beyond a demo, front
user_idwith a signed JWT or session-based auth instead of a plain query parameter, and move to HTTPS with a real domain + Let's Encrypt certificate.
See LICENSE.md.