| name | google-workspace |
| description | Build integrations with Google Workspace APIs (Gmail, Calendar, Drive, Sheets, Docs, Chat, Meet, Forms, Tasks, Admin SDK). Covers OAuth 2.0, service accounts, rate limits, batch operations, and Cloudflare Workers patterns.
Use when building MCP servers, automation tools, or integrations with any Google Workspace API, or troubleshooting OAuth errors, rate limit 429 errors, scope issues, or API-specific gotchas.
|
| user-invocable | true |
Google Workspace APIs
Status: Production Ready
Last Updated: 2026-01-09
Dependencies: Cloudflare Workers (recommended), Google Cloud Project
Skill Version: 1.0.0
Quick Reference
Shared Authentication Patterns
All Google Workspace APIs use the same authentication mechanisms. Choose based on your use case.
Option 1: OAuth 2.0 (User Context)
Best for: Acting on behalf of a user, accessing user-specific data.
const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth')
authUrl.searchParams.set('client_id', env.GOOGLE_CLIENT_ID)
authUrl.searchParams.set('redirect_uri', `${env.BASE_URL}/callback`)
authUrl.searchParams.set('response_type', 'code')
authUrl.searchParams.set('scope', SCOPES.join(' '))
authUrl.searchParams.set('access_type', 'offline')
authUrl.searchParams.set('prompt', 'consent')
async function exchangeCode(code: string): Promise<TokenResponse> {
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { : },
: ({
code,
: env.,
: env.,
: ,
: ,
}),
})
response.()
}
(): <> {
response = (, {
: ,
: { : },
: ({
refresh_token,
: env.,
: env.,
: ,
}),
})
response.()
}
Critical:
- Always request
access_type=offline for refresh tokens
- Use
prompt=consent to ensure refresh token is returned
- Store refresh tokens securely (Cloudflare KV or D1)
- Access tokens expire in ~1 hour
Option 2: Service Account (Server-to-Server)
Best for: Backend automation, no user interaction, domain-wide delegation.
import { SignJWT } from 'jose'
async function getServiceAccountToken(
serviceAccount: ServiceAccountKey,
scopes: string[]
): Promise<string> {
const now = Math.floor(Date.now() / 1000)
const jwt = await new SignJWT({
iss: serviceAccount.client_email,
scope: scopes.join(' '),
aud: 'https://oauth2.googleapis.com/token',
iat: now,
exp: now + 3600,
})
.setProtectedHeader({ alg: 'RS256', typ: 'JWT' })
.sign(await importPKCS8(serviceAccount.private_key, 'RS256'))
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
: { : },
: ({
: ,
: jwt,
}),
})
data = response.()
data.
}
Domain-Wide Delegation (impersonate users):
const jwt = await new SignJWT({
iss: serviceAccount.client_email,
sub: 'user@domain.com',
scope: scopes.join(' '),
aud: 'https://oauth2.googleapis.com/token',
iat: now,
exp: now + 3600,
})
Setup Required:
- Create service account in Google Cloud Console
- Download JSON key file
- Enable domain-wide delegation in Admin Console (if impersonating)
- Store key as Cloudflare secret (JSON stringified)
Common Rate Limits
All Google Workspace APIs enforce quotas. These are approximate - check each API's specific limits.
Per-User Limits (OAuth)
| API | Reads | Writes | Notes |
|---|
| Gmail | 250/user/sec | 250/user/sec | Aggregate across all methods |
| Calendar | 500/user/100sec | 500/user/100sec | Per calendar |
| Drive | 1000/user/100sec | 1000/user/100sec | |
| Sheets | 100/user/100sec | 100/user/100sec | Lower than others |
Per-Project Limits
| API | Daily Quota | Per-Minute | Notes |
|---|
| Gmail | 1B units | Varies | Unit-based (send = 100 units) |
| Calendar | 1M queries | 500/sec | |
| Drive | 1B queries | 1000/sec | |
| Sheets | Unlimited | 500/user/100sec | |
Handling Rate Limits
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 5
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn()
} catch (error: any) {
const status = error.status || error.code
if (status === 429 || status === 503) {
const retryAfter = error.headers?.get('Retry-After') || Math.pow(2, i)
await new Promise(r => setTimeout(r, retryAfter * 1000))
continue
}
if (status === 403 && error.message?.includes('rateLimitExceeded')) {
await new Promise(r => setTimeout(r, .(, i) * ))
}
error
}
}
()
}
Batch Requests
Most Google APIs support batching multiple requests into one HTTP call.
async function batchRequest(
accessToken: string,
requests: BatchRequestItem[]
): Promise<BatchResponse[]> {
const boundary = 'batch_boundary'
let body = ''
requests.forEach((req, i) => {
body += `--${boundary}\r\n`
body += 'Content-Type: application/http\r\n'
body += `Content-ID: <item${i}>\r\n\r\n`
body += `${req.method} ${req.path} HTTP/1.1\r\n`
body += 'Content-Type: application/json\r\n\r\n'
if (req.body) body += JSON.stringify(req.body)
body += '\r\n'
})
body += `--${boundary}--`
const response = await fetch('https://www.googleapis.com/batch/v1', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': `multipart/mixed; boundary=${boundary}`,
},
body,
})
return ( response.())
}
Limits:
- Max 100 requests per batch (most APIs)
- Max 1000 requests per batch (some APIs like Drive)
- Each request in batch counts toward quota
Cloudflare Workers Configuration
{
"name": "google-workspace-mcp",
"main": "src/index.ts",
"compatibility_date": "2026-01-03",
"compatibility_flags": ["nodejs_compat"],
"kv_namespaces": [
{ "binding": "TOKENS", "id": "xxx" }
],
"d1_databases": [
{ "binding": "DB", "database_name": "workspace-mcp", "database_id": "xxx" }
]
}
Secrets to set:
echo "your-client-id" | npx wrangler secret put GOOGLE_CLIENT_ID
echo "your-client-secret" | npx wrangler secret put GOOGLE_CLIENT_SECRET
cat service-account.json | npx wrangler secret put GOOGLE_SERVICE_ACCOUNT
Common Errors
Error: "invalid_grant" on Token Refresh
Cause: Refresh token revoked or expired (6 months of inactivity)
Fix: Re-authenticate user, request new refresh token
Error: "access_denied" on OAuth
Cause: App not verified, or user not in test users list
Fix: Add user to OAuth consent screen test users, or complete app verification
Error: "insufficientPermissions" (403)
Cause: Missing required scope
Fix: Check scopes in authorization URL, re-authenticate with correct scopes
Error: "rateLimitExceeded" (403)
Cause: Quota exceeded
Fix: Implement exponential backoff, reduce request frequency, request quota increase
Error: "notFound" (404) on Known Resource
Cause: Using wrong API version, or resource in trash
Fix: Check API version in URL, check trash for deleted items
API-Specific Guides
Detailed patterns for each API are in the references/ directory. Load these when working with specific APIs.
Gmail API
See references/gmail-api.md
- Message CRUD, labels, threads
- MIME handling, attachments
- Push notifications (Pub/Sub)
Calendar API
See references/calendar-api.md
- Events CRUD, recurring events
- Free/busy queries
- Calendar sharing
Drive API
See references/drive-api.md
- File upload/download
- Permissions, sharing
- Search queries
Sheets API
See references/sheets-api.md
- Reading/writing cells
- A1 notation, ranges
- Batch updates
Chat API
See references/chat-api.md
- Bots, webhooks
- Cards v2, interactive forms
- Spaces, members, reactions
(Additional API references added as MCP servers are built)
Package Versions (Verified 2026-01-09)
{
"devDependencies": {
"@cloudflare/workers-types": "^4.20260109.0",
"wrangler": "^4.58.0",
"jose": "^6.1.3"
}
}
Official Documentation
Skill Roadmap
APIs documented as MCP servers are built: