outlook-mcp
by dbfournier33
README.md
# outlook-mcp
An [MCP](https://modelcontextprotocol.io) server that gives AI agents structured access to a Microsoft Outlook mailbox through the Microsoft Graph API — with one deliberate constraint at its core:
> **Agents read and draft. Only humans send.**
There is no send tool in this server, and the OAuth token it holds does not include the `Mail.Send` scope. Even a fully compromised or badly prompted agent cannot send email through it — the capability doesn't exist at the token level. Everything outbound lands in your Drafts folder for human review.
I built this to power an autonomous operations pipeline: scheduled headless agent sessions sweep the mailbox twice a day, reconcile every open commitment and follow-up into a dashboard, and queue reply drafts for review. It runs identically under **Claude Code** and **OpenAI Codex** — one integration layer, two vendors' agents.
## Permission model
| Capability | Agent | Notes |
|---|---|---|
| List/search/read mail | ✅ | Full mailbox visibility |
| Organize (folders, move, batch triage) | ✅ | Reversible operations only |
| Create drafts / reply drafts | ✅ | Lands in Drafts, never sent |
| **Send email** | ❌ | No tool, and no `Mail.Send` scope on the token |
| Delete email | ❌ | Not implemented |
Auth is MSAL Authorization Code Flow with PKCE through a localhost-only callback. Tokens cache to `~/.outlook-mcp/` with `0600` permissions.
## Tools (10)
| Tool | Purpose |
|---|---|
| `list_folders` | Folder tree with counts |
| `create_folder` | Create a mail folder |
| `list_emails` | Page through a folder (subject, sender, preview, unread) |
| `search_emails` | KQL search across all folders (`from:`, `subject:`, free text) |
| `read_email` | Full message body (HTML converted to clean text), recipients, attachments list |
| `move_emails` | Move messages between folders |
| `batch_move_emails` | Bulk triage in one call |
| `create_draft` | New outbound draft |
| `create_reply_draft` | Reply/reply-all draft on an existing thread |
| `summarize_folder` | Folder statistics (volume, unread, top senders) |
## Setup
### 1. Register a (free) app in Entra ID
[Microsoft Entra admin center](https://entra.microsoft.com) → App registrations → New registration:
- **Supported account types**: your tenant only, or multi-tenant + personal accounts (then use `OUTLOOK_MCP_TENANT_ID=common`)
- **Redirect URI**: platform *Mobile and desktop applications* → add `http://localhost:3847/auth/callback`
- **Authentication** → enable *Allow public client flows*
- **API permissions** → Microsoft Graph → *Delegated* → `Mail.ReadWrite`, `MailboxSettings.Read`
No client secret is needed (public client + PKCE).
### 2. Build and authenticate
```bash
npm install
npm run build
export OUTLOOK_MCP_CLIENT_ID=<your app client id>
export OUTLOOK_MCP_TENANT_ID=<your tenant id or "common">
npm run auth # opens browser once; token cache persists at ~/.outlook-mcp/
```
### 3. Connect an agent
**Claude Code** — `.mcp.json` in your project (or `claude mcp add`):
```json
{
"mcpServers": {
"outlook": {
"command": "node",
"args": ["/path/to/outlook-mcp/dist/index.js"],
"env": {
"OUTLOOK_MCP_CLIENT_ID": "<client id>",
"OUTLOOK_MCP_TENANT_ID": "<tenant id>"
}
}
}
}
```
**OpenAI Codex** — `~/.codex/config.toml`:
```toml
[mcp_servers.outlook]
command = "node"
args = ["/path/to/outlook-mcp/dist/index.js"]
env = { OUTLOOK_MCP_CLIENT_ID = "<client id>", OUTLOOK_MCP_TENANT_ID = "<tenant id>" }
```
Same server, same tools, either agent. That portability is the point of MCP.
## Architecture
```
┌─────────────┐ stdio ┌──────────────┐ HTTPS ┌─────────────────┐
│ Claude Code │◄──────────────►│ │◄─────────────►│ Microsoft Graph │
├─────────────┤ JSON-RPC │ outlook-mcp │ REST v1.0 │ /me/messages │
│ OpenAI Codex│◄──────────────►│ (Node/TS) │ │ /me/mailFolders│
└─────────────┘ └──────┬───────┘ └─────────────────┘
│
┌──────┴───────┐
│ MSAL + PKCE │ Mail.ReadWrite only —
│ token cache │ no Mail.Send on the token
└──────────────┘
```
- **Transport**: stdio (JSON-RPC), one process per agent session
- **Auth**: MSAL Auth Code Flow + PKCE, localhost callback, silent refresh from disk cache
- **Bodies**: HTML mail converted to clean plain text before it reaches the model (token efficiency)
- **Validation**: every tool input validated with zod before any Graph call
## Development
```bash
npm run dev # tsc --watch
npm run inspect # MCP Inspector against the built server
```
## License
MIT © Don Fournier
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues