Install
$ agentstack add mcp-ftaricano-mcp-outlook ✓ 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 No
- ✓ 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.
About
mcp-outlook
[](LICENSE) [](https://nodejs.org) [](https://github.com/ftaricano/mcp-outlook/actions/workflows/ci.yml) [](https://modelcontextprotocol.io)
MCP server for Microsoft Outlook / Exchange via the Microsoft Graph API. Exposes 40 tools over stdio — list, send, draft, search, organize, batch-operate, and handle attachments including large-file hybrid flows that bypass MCP token limits.
Works with any MCP-compatible client (Claude Desktop, Cursor, custom agents, etc.). Authenticates via Azure AD client-credentials (no user login required).
| Metric | Value | |---|---| | Tools | 40 | | Tests | 242 passing | | Node | ≥ 20 | | MCP SDK | ^1.29.0 | | License | MIT |
Requirements
- Node.js 20 or 22
- Azure AD app registration with Application permissions:
Mail.ReadWrite— required for all read/draft/folder operationsMail.Send— required only if you callsend_emailorreply_to_emailUser.Read.All— optional, only forlist_users- Admin consent granted in the Azure Portal
Install
git clone https://github.com/ftaricano/mcp-outlook.git
cd mcp-outlook
npm install
npm run build
Configure
Four required values feed both the server and the CLI:
| Variable | Required | Description | |---|---|---| | MICROSOFT_GRAPH_CLIENT_ID | yes | Azure AD application (client) UUID | | MICROSOFT_GRAPH_CLIENT_SECRET | yes | Client secret value | | MICROSOFT_GRAPH_TENANT_ID | yes | Azure AD tenant UUID | | TARGET_USER_EMAIL | yes* | Mailbox to operate on. Strongly recommended — omitting it causes runtime errors from Graph rather than a clean startup failure. | | LOG_LEVEL | no | error / warn / info (default) / debug | | OUTLOOK_KEYCHAIN_PREFIX | no | macOS Keychain service prefix. Default: mcp-outlook. | | DOWNLOAD_DIR | no | Absolute write root. All attachment downloads land here; everything else is rejected. Default: ~/Downloads/mcp-outlook-attachments. | | MCP_EMAIL_UPLOAD_DIRS | no | Colon-separated read allowlist for send_email_with_file / encode_file_for_attachment. Anything outside — including symlinks pointing out and files in ~/.ssh, ~/.aws, *.env, *.pem, etc. — is rejected. Defaults to DOWNLOAD_DIR. | | MAX_ATTACHMENT_MB | no | Attachment size cap (default: 25) |
Resolution order (first hit wins): process.env → /.env (if present) → macOS Keychain (security find-generic-password -s "::" -a "$USER"). On macOS, the default prefix is mcp-outlook; set OUTLOOK_KEYCHAIN_PREFIX if you want a different namespace.
To populate the Keychain:
security add-generic-password -U -s "mcp-outlook::MICROSOFT_GRAPH_CLIENT_ID" -a "$USER" -w ''
security add-generic-password -U -s "mcp-outlook::MICROSOFT_GRAPH_CLIENT_SECRET" -a "$USER" -w ''
security add-generic-password -U -s "mcp-outlook::MICROSOFT_GRAPH_TENANT_ID" -a "$USER" -w ''
security add-generic-password -U -s "mcp-outlook::TARGET_USER_EMAIL" -a "$USER" -w 'user@example.com'
For multi-account CLI setups, pass an alternative .env via --env-file or $OUTLOOK_ENV_FILE. Those explicit files override existing credential variables for the one-shot CLI process; the default /.env is only a missing-value fallback.
After setting permissions in Azure AD, click Grant admin consent — without this step every call returns 403.
Quickstart
Claude Desktop / Cursor
Add to your MCP client config:
{
"mcpServers": {
"outlook": {
"command": "node",
"args": ["/absolute/path/to/mcp-outlook/dist/index.js"],
"env": {
"MICROSOFT_GRAPH_CLIENT_ID": "your-client-id",
"MICROSOFT_GRAPH_CLIENT_SECRET": "your-client-secret",
"MICROSOFT_GRAPH_TENANT_ID": "your-tenant-id",
"TARGET_USER_EMAIL": "user@example.com"
}
}
}
}
Direct (stdio)
npm start
outlook CLI (one-shot calls)
The package ships with a standalone CLI — ./scripts/outlook.js, registered as the outlook bin — that spawns the server, runs a single MCP request, and exits. Useful for scripts, cron, smoke-testing a tool, or inspecting a schema without wiring up an MCP client.
# Discover
outlook list # all 40 tools with descriptions
outlook schema list_emails # input schema for a single tool
# Call with flags
outlook list_emails --limit=5 --folder=inbox
outlook create_draft --to='["a@b.com"]' --subject="Hi" --body="Hello"
# Call with raw JSON (useful for arrays/objects)
outlook batch_mark_as_read --json '{"emailIds":["id1","id2"]}'
# Flags: --env-file , --timeout , --compact, --help
CLI credentials resolve in this order: --env-file → $OUTLOOK_ENV_FILE → existing env vars → /.env for missing values → macOS Keychain. Explicit env files override existing credential variables for this one-shot process; the default repo .env does not.
Docker
docker build -t mcp-outlook .
docker run --rm -i --env-file .env mcp-outlook
Tools
40 tools across 6 categories:
| Category | Tools | |---|---| | Email | list_emails, send_email, create_draft, reply_to_email, mark_as_read, mark_as_unread, delete_email, summarize_email, summarize_emails_batch, list_users | | Attachments | list_attachments, download_attachment, download_attachment_to_file, download_all_attachments, list_downloaded_files, get_download_directory_info, cleanup_old_downloads, export_email_as_attachment, encode_file_for_attachment | | Hybrid (large-file) | send_email_from_attachment, send_email_with_file | | Folders | list_folders, create_folder, move_emails_to_folder, copy_emails_to_folder, delete_folder, get_folder_stats, organize_emails_by_rules | | Search | advanced_search, search_by_sender_domain, search_by_attachment_type, find_duplicate_emails, search_by_size, saved_searches | | Batch | batch_mark_as_read, batch_mark_as_unread, batch_delete_emails, batch_move_emails, batch_download_attachments, email_cleanup_wizard |
Hybrid functions
send_email_from_attachment and send_email_with_file solve a fundamental MCP limitation: large Base64 payloads overflow the protocol's token budget. These tools download/read the file directly on disk, then call the Graph API — no Base64 transfer through MCP at all.
createdraft vs sendemail
create_draft only requires Mail.ReadWrite. Use it when your tenant policy blocks Mail.Send (common in restrictive enterprise environments). The draft lands in the Drafts folder; open Outlook to review and send.
Architecture
src/
config/env.ts # zod-validated env — fails fast on bad credentials
auth/graphAuth.ts # MSAL client-credentials token provider (auto-refresh)
services/emailService.ts # Microsoft Graph wrapper with response caching + batch helpers
schemas/toolSchemas.ts # zod schemas for all 40 tool inputs
handlers/*.ts # one handler class per domain (email, folder, search…)
handlers/HandlerRegistry # zod validation + dispatch
logging/logger.ts # stderr JSON logger
templates/ # HTML email templates (4 themes)
utils/ # file manager, attachment validator, secret redaction
Runtime flow:
loadEnv()validates credentials via zod on startup — bad config exits immediately with a clear message.GraphAuthProviderlazily acquires tokens and refreshes 60 s before expiry.- MCP requests hit
HandlerRegistry.handleTool(name, args)→ zod validation → domain handler. - Handlers call
EmailService, which wraps Graph with response caching and batch helpers. Retry/backoff on 429 (honoringRetry-After) comes from the Graph SDK's default middleware, not a custom limiter.
Develop
| Command | Purpose | |---|---| | npm run build | TypeScript → dist/ | | npm run typecheck | tsc --noEmit | | npm run lint / npm run lint:fix | ESLint | | npm run format / npm run format:check | Prettier | | npm test | Vitest unit tests | | npm run test:coverage | Vitest with coverage thresholds | | npm run smoke | Protocol smoke — verify tools/list returns 40 entries | | npm run audit:prod | Audit runtime deps only |
CI runs lint + typecheck + tests + smoke on Node 20, 22, and 24.
Live integration smoke tests (require Graph credentials in env):
node scripts/live-readonly-smoke.js # 18 read-only + dry-run tools
node scripts/live-writes-smoke.js # 9 write-path tools (self-contained, safe)
Troubleshooting
403 / "Insufficient privileges" — Admin consent not granted. Go to Azure Portal → your app → API permissions → Grant admin consent.
send_email returns 403 but create_draft works — Your tenant policy blocks Mail.Send at the application level. Use create_draft instead.
Attachments arrive with 0 KB — Base64 payload too large for the MCP transport. Use send_email_from_attachment or send_email_with_file (hybrid functions).
delete_email returns 404 after move_emails_to_folder — Microsoft Graph issues a new message ID on move. The handler now returns the new ID in its output; re-read it before deleting.
Rate limiting (429) — Reduce maxConcurrent in batch operations. The Graph SDK's retry middleware backs off automatically (honoring Retry-After), but very high concurrency can still hit Graph throttle limits.
Security
This server handles Azure AD client secrets with broad mailbox access, and it is driven by an LLM that sees untrusted email bodies. Treat every tool call as potentially attacker-influenced.
Filesystem allowlist (pathGuard) — send_email_with_file, encode_file_for_attachment, and all attachment download paths go through a central allowlist (src/security/pathGuard.ts):
- Writes are confined to
DOWNLOAD_DIR. - Reads are confined to
MCP_EMAIL_UPLOAD_DIRS(defaults toDOWNLOAD_DIR). - Symlinks,
..traversal, NUL bytes, and files in secret-bearing locations (~/.ssh,~/.aws,~/.gnupg,.env*,*.pem,*.key,*.p12,*.pfx,credentials.json,id_rsa*, etc.) are rejected before the file is read.
Without this guard, an attacker who controls an email body could instruct the agent to attach ~/.ssh/id_rsa to an outbound email. The guard blocks that class of confused-deputy attack at the boundary.
Keep these practices:
- Never commit
.env— it is in.gitignore - Never commit
*.logor*.jsonlfiles — also in.gitignore - Store secrets in your OS keychain or a secrets manager, not in plaintext files
- Rotate the client secret in Azure AD immediately if it is ever exposed
- Set
MCP_EMAIL_UPLOAD_DIRSto the minimum set of directories the server actually needs to read. Do not set it to$HOMEor/. - Scope
Mail.Sendonly if you need outbound email —Mail.ReadWritealone is sufficient for drafts, search, and folder management - User-supplied HTML template fields are escaped before rendering. If you intentionally need trusted HTML, add an explicit sanitizer/allowlist instead of bypassing the template engine.
Report vulnerabilities privately through GitHub Security Advisories. See [SECURITY.md](SECURITY.md).
Known limitations
TARGET_USER_EMAILis optional in the schema for delegated/meexperiments, but client-credentials deployments should set it. Microsoft Graph application permissions do not infer a mailbox.- The
outlookCLI has a few tracked edge cases around numeric search flags, sender filtering, and download target paths. See issue #30; use--jsonfor numeric search terms until that issue is closed.
Contributing
npm run build && npm test && npm run smoke
Pre-PR checklist: build passes, lint clean, all tests green, smoke returns 40 tools.
Open an issue before submitting large changes.
Development workflow
For small fixes, keep the PR focused and include the command output from the pre-PR checklist. For larger changes, open an issue first and describe the affected tool contracts, Graph permissions, security impact, and manual smoke coverage.
Security-sensitive paths deserve extra review: src/security/, credential loading, Graph permission scopes, attachment handling, template rendering, and anything that reads from or writes to the local filesystem.
License
[MIT](LICENSE)
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: ftaricano
- Source: ftaricano/mcp-outlook
- 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.