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
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.