mail-cal-drive-mcp
A self-hosted MCP server for managing email, calendar, and cloud storage across Microsoft 365, Google Workspace, and IMAP accounts, enabling natural language interaction through any MCP client.
README
mail-cal-drive-mcp
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 | 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=<Application (client) ID from Overview page>
MS_TENANT_ID=<Directory (tenant) ID, or "common" for multi-tenant>
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=<Client ID>
GOOGLE_CLIENT_SECRET=<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 |
bulk_mail_action
| 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 |
find_free_time
| 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 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. Call
auth_startagain for a fresh code.
Google OAuth: "Access blocked" or consent screen issues
- Make sure your Google Cloud app is in "Testing" mode with your email added as a test user
- Verify the redirect URI in Google Cloud Console matches
GOOGLE_REDIRECT_URIexactly
IMAP connection refused
- Verify host, port, and TLS settings for your provider
- Most providers require an App Password when 2FA is enabled (not your regular password)
- Check if your provider blocks third-party IMAP access (Gmail: enable "Less secure apps" or use App Password)
"Invalid or expired session" on tool calls
- MCP sessions expire after 30 minutes of inactivity and are lost on container restart. Your client should auto-reconnect. If not, restart the client.
Development
# Install dependencies
npm install
# Start with hot reload (requires local Postgres on DATABASE_URL)
npm run dev
# Build TypeScript
npm run build
# Production start
npm start
# Run tests
npm test
# Type check
npm run typecheck
# Migrate credentials from old file-based storage
DATABASE_URL=... CREDENTIAL_ENCRYPTION_KEY=... npm run migrate
Project Structure
mail-cal-drive-mcp/
├── src/
│ ├── index.ts # Entry point
│ ├── server.ts # Express server
│ ├── config.ts # Environment config
│ ├── logger.ts # Structured console logger
│ ├── auth/
│ │ ├── types.ts # Credential interfaces
│ │ ├── storage.ts # Postgres credential storage
│ │ └── bearer.ts # Bearer token middleware
│ ├── storage/
│ │ ├── postgres.ts # Postgres client + encrypted CRUD
│ │ ├── msal-cache.ts # MSAL cache plugin (Postgres)
│ │ └── pending-flows.ts # Pending auth flow storage
│ ├── providers/
│ │ ├── base.ts # Abstract interfaces
│ │ ├── types.ts # Shared types
│ │ ├── microsoft/ # Graph API (mail, calendar, drive)
│ │ ├── google/ # Google APIs (Gmail, Calendar, Drive)
│ │ └── imap/ # ImapFlow (mail only)
│ ├── mcp/
│ │ ├── handler.ts # MCP request routing
│ │ ├── session.ts # Session management
│ │ └── tools/ # 35 MCP tools
│ └── utils/ # Timezone, recurrence, MIME helpers
├── db/migrations/ # Postgres schema
├── docker-compose.yml
├── Dockerfile
└── .env # Configuration (gitignored)
License
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。