Skip to main content
Glama
dbfournier33

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