A tutorial/proof-of-concept demonstrating how to build an MCP (Model Context Protocol) server that integrates with Google Calendar, featuring enterprise-grade authentication patterns.
This project showcases several advanced integration patterns that are individually documented but rarely shown working together:
Deploy an MCP server as a Cloudflare Worker - lightweight, globally distributed, and serverless.
Exchange user access tokens for Google Calendar tokens without storing sensitive credentials yourself. Auth0's Token Vault securely manages the Google OAuth tokens.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Claude Code │────▶│ Worker │────▶│ Auth0 Token │────▶│ Google │
│ (MCP Client)│ │ (MCP Server)│ │ Vault │ │ Calendar │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ Auth0 JWT │ Token Exchange │ Google Token
└───────────────────┴───────────────────┘
Client-Initiated Backchannel Authentication (CIBA) for operations that require explicit user approval. When the agent tries to create or delete a calendar event, the user receives a push notification on their phone to approve or deny.
Agent: "Create meeting with Bob tomorrow at 2pm"
│
▼
┌─────────────────────────────────────────────────────┐
│ 🔔 Push Notification to User's Phone │
│ │
│ "Claude wants to create an event: │
│ Meeting with Bob - Tomorrow 2:00 PM" │
│ │
│ [Approve] [Deny] │
└─────────────────────────────────────────────────────┘
│
▼ (after approval)
Event Created
Claude Code can automatically register itself as an OAuth client with Auth0, enabling seamless authentication without pre-configuring client credentials.
Before the MCP server can access a user's Google Calendar, the user must explicitly consent to linking their Google account. The Connect SPA (Single Page Application, hosted on Cloudflare Pages) is a simple web application that handles this one-time consent flow:
┌─────────────────────────────────────────────────────────────────────────────┐
│ Connect SPA Flow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. User visits Connect SPA │
│ │ │
│ ▼ │
│ 2. User signs in with Auth0 (google-oauth2) │
│ │ │
│ ▼ │
│ 3. User selects permissions: │
│ ┌─────────────────────────────────────────┐ │
│ │ ☑ Read-only access │ │
│ │ View calendar events and settings │ │
│ │ │ │
│ │ ☑ Full access │ │
│ │ Create, edit, and delete events │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 4. User clicks "Connect Google Calendar" │
│ │ │
│ ▼ │
│ 5. Google OAuth consent screen appears │
│ │ │
│ ▼ │
│ 6. Auth0 stores Google tokens in Token Vault │
│ (linked to user's Auth0 identity) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
This separation is important: the MCP server never sees the Google OAuth flow directly. It only exchanges Auth0 tokens for Google tokens via the Token Vault, which already has the user's consent stored.
┌────────────────────────────────────────────────────────────────────────────┐
│ Auth0 Tenant │
├────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ google-oauth2 │ │ GoogleCal │ │ Claude Code (DCR) │ │
│ │ (User Login) │ │ (Token Vault) │ │ (Dynamic Client) │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────────────┘ │
│ │ │ │ │
│ │ authenticates │ stores Google │ registers │
│ │ users │ tokens │ automatically │
│ ▼ ▼ ▼ │
└────────────────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────────────┐
│ Cloudflare Worker (MCP Server) │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────────┐│
│ │ JWT Validation │ │ Token Exchange │ │ Calendar Tools ││
│ │ (Auth0 JWKS) │ │ (Token Vault) │ │ list/create/update/delete ││
│ └─────────────────┘ └─────────────────┘ └─────────────────────────────┘│
│ │ │ │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ CIBA (for sensitive operations) │ │
│ │ create_event, update_event, delete_event require │ │
│ │ user approval via push notification │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────┘
- Cloudflare account
- Auth0 account with:
- OIDC Dynamic Client Registration enabled
- Guardian push notifications configured (for CIBA)
- Google Cloud project with Calendar API enabled
- Node.js 18+
- Auth0 Deploy CLI (
npm install -g auth0-deploy-cli)
git clone https://github.com/dleen/google-calendar-mcp-cloudflare.git
cd google-calendar-mcp-cloudflare
cp .env.example .envEdit .env with your credentials (see comments in file for guidance).
./auth0/deploy importThis creates:
- Resource server (API) for the MCP endpoint
- SPA application for Google account linking
- Token Vault client for token exchange
- CIBA client for push notifications
- Google Calendar OAuth connection
See MANUAL_STEPS.md for steps that can't be automated:
- Enable My Account API for the SPA
- Configure Guardian for CIBA push notifications
After the first deploy, copy the generated client IDs and secrets from the Auth0 Dashboard:
- Google Calendar MCP Token Vault →
MCP_TOKEN_VAULT_CLIENT_ID/SECRET - Google Calendar MCP CIBA →
MCP_CIBA_CLIENT_ID/SECRET
Add these to your .env file.
./worker/deploy all # Sets secrets and deploys./connect-spa/deployVisit the Connect SPA URL and:
- Sign in with Auth0
- Click "Connect Google Calendar"
- Authorize the requested permissions
Once deployed, add the MCP server to Claude Code:
claude mcp add google-calendar https://your-worker.workers.dev/mcpClaude Code will:
- Discover Auth0 via
/.well-known/oauth-protected-resource - Register itself via DCR
- Authenticate you via Auth0
- Make authenticated MCP requests
"What's on my calendar today?"
"Schedule a meeting with alice@example.com tomorrow at 2pm"
"Delete my 3pm meeting" # Triggers CIBA approval
.
├── auth0/ # Auth0 configuration (IaC)
│ ├── clients/ # Application definitions
│ ├── connections/ # Identity providers
│ ├── grants/ # Client grants
│ ├── resource-servers/ # API definitions
│ └── deploy # Deploy script
├── connect-spa/ # Google account linking web app
│ ├── index.html # SPA with scope toggles
│ └── deploy # Deploy script
├── worker/ # Cloudflare Worker (MCP server)
│ ├── src/
│ │ ├── index.ts # Entry point, routing
│ │ ├── mcp.ts # MCP protocol handlers
│ │ ├── calendar.ts # Google Calendar API client
│ │ └── auth.ts # Auth0 JWT validation, CIBA, token exchange
│ ├── wrangler.toml # Worker configuration
│ └── deploy # Deploy script
├── .env.example # Environment template
└── MANUAL_STEPS.md # Non-automatable Auth0 setup
When the MCP server needs to call Google Calendar:
- Claude Code sends request with Auth0 JWT
- Worker validates JWT against Auth0 JWKS
- Worker calls Auth0 Token Vault with the JWT
- Token Vault returns the user's Google access token
- Worker calls Google Calendar API
- Results returned to Claude Code
For sensitive operations (create/update/delete events):
- Agent requests operation
- Worker initiates CIBA request to Auth0
- Auth0 sends push notification via Guardian
- User approves/denies on their phone
- Worker polls Auth0 for result
- On approval, operation proceeds
| Pattern | Problem Solved |
|---|---|
| Token Vault | Securely access third-party APIs without storing tokens |
| CIBA | Get user approval for sensitive agent actions |
| DCR | Zero-config client setup for MCP clients |
| Cloudflare Workers | Global, serverless, low-latency MCP hosting |
- MCP Protocol Specification
- Auth0 Token Vault
- Auth0 CIBA
- Auth0 Dynamic Client Registration
- Cloudflare Workers
MIT