Unified Cloud MCP
by dan-s-w
README.md
# Unified Cloud MCP
A remote MCP server for Cloudflare Workers that lets ChatGPT, Claude, and other MCP clients read and organize multiple independent Google and Microsoft cloud accounts without copying data between them.
> This repository is a sanitized portfolio snapshot. Deployment identifiers, account details, and organization-specific configuration have been replaced with examples. Write-capable features are disabled by default.
## Providers and tools
### Google
- Gmail: search, apply/remove labels, archive, mark read/unread, and move threads to Trash
- Gmail settings: read, schedule, update, enable, and disable vacation responders
- Drive: search, create folders, rename/move files, and move files to Trash
- Calendar: list, create, update, and delete primary-calendar events
- Contacts: search, create, update, and permanently delete contacts
- Photos: launch an interactive Picker session and read only the photos/videos the user explicitly selects
- Google Health: read all authorized Fitbit/Pixel health categories and perform explicitly confirmed writes without storing health values in the audit log
### Workspace Google Keep
- List, read, and create notes owned by the configured Workspace user
- Permanently delete notes only with exact confirmation
- Authenticate through a service account with domain-wide delegation, independently from normal Google OAuth
### Microsoft
- OneDrive: search, create folders, rename/move items, and delete items (normally recoverable through the OneDrive recycle bin)
### Obsidian
- List and read Markdown notes in one configured Google Drive vault
- Create and replace notes when separately enabled
- Reject file IDs outside the configured vault
### Personal finance API
- Read allowlisted routes exposed by your Cloudflare-hosted finance app
- Create, edit, and delete finance records when explicitly enabled
### Slack
- Group Slack workspaces under aliases such as `organization-a` and `organization-b`
- List conversations and users; search messages; read channels and threads
- Post, update, and delete messages and add/remove reactions when separately enabled
- Encrypt Slack user tokens before D1 storage
Google and Microsoft tools require an explicit account alias. Destructive operations require an exact action-specific confirmation phrase, and batch limits are enforced server-side.
## Security model
- OAuth refresh tokens are encrypted with AES-256-GCM before D1 storage.
- Provider tokens never leave the Worker.
- Setup endpoints require `ADMIN_SETUP_TOKEN`.
- MCP requests accept either OAuth 2.1 access tokens or the legacy `MCP_BEARER_TOKEN`.
- OAuth authorization requires the admin setup token and uses PKCE, dynamic client registration, and hashed token storage in Workers KV.
- Gmail label changes are capped at 50 threads; Trash operations at 10 threads.
- Permanent Gmail deletion and cross-account copying are not exposed.
- Email sending, vacation responder writes, Keep writes, Google Health writes, contact writes, file-content writes, and finance writes use independent server-side enable flags.
- Contact writes require exact confirmation and are audit logged.
- Finance requests are restricted to configured route prefixes; write operations are audit logged.
- Write confirmation is checked by the Worker, not merely requested in the prompt.
> Existing Google accounts must be reconnected after new Google scopes are deployed. Photos only exposes an interactive picker; it cannot silently search a user's full Photos library.
## Deploy
```bash
npm install
npx wrangler d1 create unified-cloud-mcp
# Put the database ID in wrangler.jsonc
npx wrangler kv namespace create OAUTH_KV
# Put the KV namespace ID in wrangler.jsonc
npm run db:migrate:remote
```
Create OAuth apps with these callbacks:
```text
https://YOUR-WORKER.workers.dev/oauth/google/callback
https://YOUR-WORKER.workers.dev/oauth/microsoft/callback
```
Enable Gmail, Google Drive, Google Calendar, People, Google Photos Picker, Google Photos Library, and Google Keep APIs. The normal Google OAuth app requests Gmail, Drive, Calendar, Contacts, Photos Picker, and app-created Photos Library scopes. Keep instead uses Workspace domain-wide delegation. The Microsoft app should accept personal and organizational accounts and grant delegated `User.Read` and `Files.ReadWrite` permissions plus `openid profile email offline_access`.
Configure secrets:
```bash
openssl rand -base64 32
npx wrangler secret put GOOGLE_CLIENT_ID
npx wrangler secret put GOOGLE_CLIENT_SECRET
npx wrangler secret put GOOGLE_KEEP_SERVICE_ACCOUNT_EMAIL
npx wrangler secret put GOOGLE_KEEP_PRIVATE_KEY
npx wrangler secret put MICROSOFT_CLIENT_ID
npx wrangler secret put MICROSOFT_CLIENT_SECRET
npx wrangler secret put TOKEN_ENCRYPTION_KEY_BASE64
npx wrangler secret put ADMIN_SETUP_TOKEN
npx wrangler secret put MCP_BEARER_TOKEN
npx wrangler secret put SLACK_ORG_A_CLIENT_ID
npx wrangler secret put SLACK_ORG_A_CLIENT_SECRET
npx wrangler secret put SLACK_ORG_B_CLIENT_ID
npx wrangler secret put SLACK_ORG_B_CLIENT_SECRET
npx wrangler secret put PUBLIC_BASE_URL
npx wrangler secret put FINANCE_APP_API_TOKEN
# Or use FINANCE_APP_API_KEY and FINANCE_APP_API_KEY_HEADER.
npm run deploy
```
Set non-secret Worker variables for optional autonomous operations:
```dotenv
ENABLE_AUTONOMOUS_EMAIL_SEND=false
EMAIL_ALLOWED_RECIPIENTS=
EMAIL_ALLOWED_DOMAINS=
ENABLE_FILE_CONTENT_WRITES=false
ENABLE_CONTACT_WRITES=false
ENABLE_VACATION_RESPONDER_WRITES=false
ENABLE_KEEP_WRITES=false
ENABLE_GOOGLE_HEALTH_WRITES=false
OBSIDIAN_VAULT_FOLDER_ID=
ENABLE_OBSIDIAN_WRITES=false
ENABLE_SLACK_WRITES=false
MAX_FILE_WRITE_BYTES=4000000
FINANCE_APP_BASE_URL=https://your-finance-app.example.com
FINANCE_ALLOWED_PATH_PREFIXES=/api/mcp
ENABLE_FINANCE_WRITES=false
FINANCE_MAX_BODY_BYTES=250000
FINANCE_REQUEST_TIMEOUT_MS=15000
```
Keep write-related `ENABLE_*` flags false until the corresponding accounts, route restrictions, and audit expectations have been tested.
Create one private Slack app per workspace from the manifests in `slack/`. Both use this OAuth redirect URL:
```text
https://YOUR-WORKER.workers.dev/oauth/slack/callback
```
After configuring the four Slack client credential secrets, authorize each organization:
```bash
curl -i -H "Authorization: Bearer $ADMIN_SETUP_TOKEN" \
"https://YOUR-WORKER.workers.dev/oauth/slack/start?organization=organization-a"
curl -i -H "Authorization: Bearer $ADMIN_SETUP_TOKEN" \
"https://YOUR-WORKER.workers.dev/oauth/slack/start?organization=organization-b"
```
Connect or reconnect each account:
Open `https://YOUR-WORKER.workers.dev/setup`, choose the provider and alias, and enter
`ADMIN_SETUP_TOKEN`. The token is submitted in the HTTPS request body and is not placed in the URL.
For command-line setup instead:
```bash
curl -i -H "Authorization: Bearer $ADMIN_SETUP_TOKEN" \
"https://YOUR-WORKER.workers.dev/oauth/google/start?alias=primary"
curl -i -H "Authorization: Bearer $ADMIN_SETUP_TOKEN" \
"https://YOUR-WORKER.workers.dev/oauth/microsoft/start?alias=primary"
```
Open the returned `location` URL in a browser. Repeat with each account alias.
## Connect ChatGPT
The Worker publishes OAuth authorization-server and protected-resource metadata automatically. In ChatGPT on the web:
1. Enable Developer Mode under **Settings → Security and login**.
2. Open **Plugins**, select **+**, and add:
`https://YOUR-WORKER.workers.dev/mcp`
3. When the Worker authorization page opens, enter `ADMIN_SETUP_TOKEN` and approve the connection.
4. Review the tools ChatGPT discovers before enabling the plugin.
Never enter the admin setup token into a page on a different hostname.
## Write confirmations
The tool error tells the client the exact phrase required. Examples:
```text
MODIFY 12 GMAIL THREADS IN primary
TRASH 3 GMAIL THREADS IN workspace
CREATE DRIVE FOLDER IN primary
UPDATE DRIVE FILE <file-id> IN workspace
CREATE CALENDAR EVENT IN primary
CREATE GOOGLE CONTACT IN primary
UPDATE GOOGLE CONTACT people/<contact-id> IN workspace
DELETE GOOGLE CONTACT people/<contact-id> IN primary
SET GMAIL VACATION RESPONDER IN primary
DISABLE GMAIL VACATION RESPONDER IN primary
CREATE WORKSPACE KEEP NOTE <title>
DELETE WORKSPACE KEEP NOTE notes/<note-id>
CREATE OBSIDIAN NOTE <note-name>
UPDATE OBSIDIAN NOTE <google-drive-file-id>
DELETE ONEDRIVE ITEM <item-id> IN primary
```
A recommended workflow is: search → present proposed changes → obtain user approval → call the write tool with the exact phrase.
## Development
```bash
cp .dev.vars.example .dev.vars
npm run db:migrate:local
npm run dev
npm run typecheck
```
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues