Install
$ agentstack add mcp-rumbitopi-mail-cal-drive-mcp ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
mail-cal-drive-mcp
[](LICENSE) [](https://nodejs.org) [](https://docs.docker.com/compose/) [](https://modelcontextprotocol.io)
A self-hosted Model Context Protocol (MCP) server for multi-account email, calendar, and cloud storage. Connect Microsoft 365, Google Workspace, and IMAP accounts to any MCP client — Claude Desktop, Claude Code, VS Code, Cursor, and more.
Runs locally in Docker with Postgres. Credentials encrypted at rest. Survives reboots without re-authorization.
| Provider | Email | Calendar | Drive | |----------|-------|----------|-------| | Microsoft 365 | ✅ | ✅ | ✅ OneDrive | | Google Workspace | ✅ | ✅ | ✅ Google Drive | | IMAP | ✅ | ❌ | ❌ |
Prerequisites
- Docker and Docker Compose
- Node.js 20+ (for local development only)
Quick Start
# 1. Clone
git clone https://github.com/rumbitopi/mail-cal-drive-mcp.git
cd mail-cal-drive-mcp
# 2. Configure
cp .env.example .env
# 3. Generate keys (run 3 times — one for each)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Paste into .env as POSTGRES_PASSWORD, CREDENTIAL_ENCRYPTION_KEY, and API_KEY
# Update DATABASE_URL to use the same POSTGRES_PASSWORD
# 4. Start (server runs with zero providers — configure them later)
docker compose up --build -d
# 5. Verify health
curl http://localhost:3100/health
# 6. Verify MCP
curl -X POST http://localhost:3100/mcp \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}},"id":1}'
Architecture
┌──────────────────────────────────────────────┐
│ Docker Compose │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ workspace │◄──►│ postgres:5432│ │
│ │ -mcp :3100 │ │ (persistent) │ │
│ │ Streamable │ └──────────────┘ │
│ │ HTTP + Auth │ │
│ └─────────────┘ │
└──────────────────────────────────────────────┘
Protocol: MCP over Streamable HTTP (2024-11-05), MCP SDK 1.25.3
What persists across reboots (in Postgres):
- Credentials (AES-256-GCM encrypted)
- MSAL token cache (silent Microsoft refresh — no re-auth)
- Pending auth flows (survive restarts mid-auth)
What's ephemeral (in memory):
- MCP sessions (clients auto-reconnect)
Client Configuration
Claude Desktop
Claude Desktop speaks stdio, not HTTP. supergateway bridges the two.
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"workspace": {
"command": "npx",
"args": [
"-y", "supergateway",
"--streamableHttp", "http://localhost:3100/mcp",
"--header", "Authorization: Bearer YOUR_API_KEY"
]
}
}
}
Claude Code
Add to .claude/settings.local.json in your project:
{
"mcpServers": {
"workspace": {
"type": "streamable-http",
"url": "http://localhost:3100/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
VS Code (GitHub Copilot)
Add to .vscode/settings.json:
{
"github.copilot.chat.mcpServers": {
"workspace": {
"type": "streamable-http",
"url": "http://localhost:3100/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
Cursor
Add to .cursor/mcp.json:
{
"mcpServers": {
"workspace": {
"url": "http://localhost:3100/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
Provider Setup
Microsoft 365
Uses device code flow — no redirect URI needed.
Azure App Registration
- Go to Azure Portal → Azure Active Directory → App registrations → New registration
- Name:
Workspace MCP(or anything) - Supported account types: "Accounts in any organizational directory and personal Microsoft accounts"
- Redirect URI: Leave blank
- Click Register
Enable Public Client Flow
- Go to your app → Authentication
- Under Advanced settings, set "Allow public client flows" to Yes
- Click Save
Add API Permissions
- Go to API permissions → Add a permission → Microsoft Graph → Delegated permissions
- Add:
Mail.ReadWrite,Mail.SendCalendars.ReadWrite,Calendars.Read.SharedFiles.ReadWrite,Files.ReadWrite.AllUser.Read,offline_access
- Click "Grant admin consent" if you're an admin
Configure .env
MS_ENABLED=true
MS_CLIENT_ID=
MS_TENANT_ID=
Connect
Call auth_start with provider: "microsoft". You'll get a URL and code. Visit the URL, enter the code, sign in. Then call auth_complete. One-time setup — refresh tokens persist across reboots.
Google Workspace
Uses OAuth callback flow — requires a redirect URI.
Google Cloud Setup
- Go to Google Cloud Console → create or select a project
- Enable APIs: Gmail API, Google Calendar API, Google Drive API
- OAuth consent screen:
- User Type: External (or Internal for Workspace)
- Add scopes:
gmail.modify,gmail.labels,calendar,calendar.events,drive,drive.file - Add yourself as a test user
- Credentials → Create OAuth client ID:
- Type: Web application
- Redirect URI:
http://localhost:3100/auth/google/callback
Configure .env
GOOGLE_ENABLED=true
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=http://localhost:3100/auth/google/callback
Connect
Call auth_start with provider: "google". Open the URL, sign in, grant permissions. The callback saves your tokens. Then call auth_complete. One-time setup.
IMAP
No external setup needed — provide credentials directly.
Connect
Call auth_start with:
provider: "imap"
accountName: "Personal Email"
email: "user@example.com"
imapHost: "imap.example.com"
imapPort: 993
imapUsername: "user@example.com"
imapPassword: "your-app-password"
imapTls: true
No auth_complete needed — IMAP accounts are ready immediately.
Common IMAP Servers
| Provider | Host | Port | Notes | |----------|------|------|-------| | Gmail | imap.gmail.com | 993 | Requires App Password | | Outlook/Hotmail | outlook.office365.com | 993 | Requires App Password | | Yahoo | imap.mail.yahoo.com | 993 | Requires App Password | | Fastmail | imap.fastmail.com | 993 | Requires App Password |
MCP Tools (36 total)
Authentication (4)
| Tool | Description | |------|-------------| | auth_status | List all accounts with connection status | | auth_start | Start OAuth/device code flow or IMAP setup | | auth_complete | Complete pending authentication | | auth_revoke | Remove an account |
Email (10)
| Tool | Description | |------|-------------| | list_accounts | List all configured email accounts | | list_folders | Get folders/labels for an account | | list_messages | Messages with pagination | | get_message | Full message with body and attachments | | get_attachment | Download attachment (text decoded, binary as blob) | | search_messages | Search with filters (see parameters below) | | move_message | Move to folder | | delete_message | Trash or permanent delete | | mark_read | Mark read/unread | | bulk_mail_action | Batch operations with dry-run support |
Calendar (8)
| Tool | Description | |------|-------------| | list_calendars | All calendars for an account | | list_events | Events in a date range | | get_event | Full event details | | create_event | Create with attendees, recurrence, conferencing | | update_event | Modify existing event | | delete_event | Delete event | | find_free_time | Find available time slots across accounts | | check_conflicts | Detect scheduling conflicts |
Drive (14)
| Tool | Description | |------|-------------| | list_files | List files and folders | | get_file | File metadata | | get_file_content | Download file content | | search_files | Search by name, content, or type | | upload_file | Upload new file (text or base64) | | create_folder | Create folder | | move_file | Move file/folder | | copy_file | Copy file | | rename_file | Rename file/folder | | delete_file | Delete (trash or permanent) | | get_sharing | View sharing permissions | | share_file | Share with user or create link | | unshare_file | Remove sharing | | get_storage_quota | Storage usage info |
Tool Parameter Reference
get_attachment
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | accountId | string | Yes | Account ID | | messageId | string | Yes | Message ID | | attachmentId | string | Yes | Attachment ID from get_message |
Returns text content for text-based files, or an MCP EmbeddedResource with blob for binary files (PDF, images, etc.). Max 5MB.
search_messages
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | accountIds | string[] | Yes | Account IDs to search | | from | string | No | Filter by sender email | | to | string | No | Filter by recipient email | | subject | string | No | Filter by subject (partial match) | | body | string | No | Search in message body | | folder | string | No | Limit to specific folder | | hasAttachment | boolean | No | Filter by attachment presence | | isRead | boolean | No | Filter by read status | | isStarred | boolean | No | Filter by starred status | | after | string | No | Messages after date (ISO 8601) | | before | string | No | Messages before date (ISO 8601) | | labels | string[] | No | Filter by labels (Gmail) | | limit | number | No | Max results per account (default: 50) |
create_event
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | accountId | string | Yes | Account ID | | title | string | Yes | Event title | | startTime | string | Yes | Start time (ISO 8601) | | endTime | string | Yes | End time (ISO 8601) | | calendarId | string | No | Calendar ID (default: primary) | | description | string | No | Event description | | location | string | No | Event location | | isAllDay | boolean | No | All-day event | | timeZone | string | No | Time zone (default: UTC) | | attendees | array | No | [{email, optional?}] | | visibility | string | No | "public" or "private" | | addConference | boolean | No | Add video conference link |
bulkmailaction
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | accountId | string | Yes | Account ID | | action | string | Yes | "move", "delete", "markRead", "markUnread", "star", "unstar", "archive" | | dryRun | boolean | No | Preview without executing (default: false) | | targetFolder | string | No | Destination folder (required for "move") | | from | string | No | Filter by sender | | subject | string | No | Filter by subject | | folder | string | No | Limit to folder | | isRead | boolean | No | Filter by read status | | after | string | No | After date (ISO 8601) | | before | string | No | Before date (ISO 8601) | | limit | number | No | Max messages to process |
findfreetime
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | accountIds | string[] | Yes | Account IDs to check | | startDate | string | Yes | Start of range (ISO 8601) | | endDate | string | Yes | End of range (ISO 8601) | | duration | number | Yes | Required duration in minutes | | calendarIds | string[] | No | Calendars to check (default: primary) |
upload_file
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | accountId | string | Yes | Account ID | | name | string | Yes | File name | | content | string | Yes | File content (text or base64) | | folderId | string | No | Parent folder ID (default: root) | | mimeType | string | No | MIME type | | isBase64 | boolean | No | Content is base64 encoded |
share_file
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | accountId | string | Yes | Account ID | | fileId | string | Yes | File ID | | type | string | Yes | "user", "group", or "anyone" | | role | string | Yes | "reader", "writer", or "commenter" | | email | string | No | Email (required for user/group) | | sendNotification | boolean | No | Send email notification | | message | string | No | Notification message |
Environment Variables
| Variable | Required | Default | Description | |----------|----------|---------|-------------| | DATABASE_URL | Yes | — | Postgres connection string | | POSTGRES_PASSWORD | Yes | — | Postgres password (used by Docker Compose) | | CREDENTIAL_ENCRYPTION_KEY | Yes | — | 64 hex chars (32 bytes) for AES-256-GCM | | API_KEY | Yes | — | Min 32 chars, used as Bearer token | | NODE_ENV | No | development | development or production | | MCP_PORT | No | 3100 | Server port | | LOG_LEVEL | No | info | error, warn, info, debug | | MS_ENABLED | No | false | Enable Microsoft 365 | | MS_CLIENT_ID | If MS | — | Azure App client ID | | MS_TENANT_ID | If MS | common | Azure tenant ID | | GOOGLE_ENABLED | No | false | Enable Google Workspace | | GOOGLE_CLIENT_ID | If Google | — | OAuth client ID | | GOOGLE_CLIENT_SECRET | If Google | — | OAuth client secret | | GOOGLE_REDIRECT_URI | If Google | — | OAuth callback URL | | IMAP_ENABLED | No | true | Enable IMAP provider |
Endpoints
| Endpoint | Method | Auth | Description | |----------|--------|------|-------------| | /health | GET | No | Health check | | /mcp | POST | Bearer | MCP JSON-RPC endpoint | | /mcp | GET | Bearer | SSE stream for notifications | | /mcp | DELETE | Bearer | Session cleanup | | /auth/google/callback | GET | No | Google OAuth redirect handler |
Security
This server is designed for localhost use only. Do not expose it to the internet without additional hardening.
- Encryption at rest: All credentials, MSAL token cache, and pending auth flows encrypted with AES-256-GCM before storage in Postgres
- Bearer auth: Timing-safe token comparison (
crypto.timingSafeEqual) on all protected endpoints - Session limits: Max 100 concurrent sessions, 30-minute TTL with automatic eviction
- Network: Binds to
127.0.0.1only — not accessible from other machines - Body size: 10MB request limit to prevent memory exhaustion
- IMAP note: IMAP credentials are passwords (not OAuth tokens) — encrypted at rest, but inherently less secure than OAuth. Use app passwords where possible.
- Logging: All logs to stderr (MCP requirement). No tokens, passwords, or keys logged.
- Token rotation: To rotate the encryption key, generate a new one, re-run
auth_startfor each account, and update.env. There is no in-place re-encryption (accounts must be re-authorized).
See [SECURITY.md](SECURITY.md) for vulnerability reporting.
Troubleshooting
Container won't start
- Check
docker compose logs workspace-mcpfor errors - Verify
DATABASE_URLmatches the Postgres container credentials - Ensure Postgres is healthy:
docker compose logs postgres
Microsoft device code expired
- Device codes expire after 15 minutes. C
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: rumbitopi
- Source: rumbitopi/mail-cal-drive-mcp
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.