Outlook MCP
by IamMattL
README.md
# Outlook MCP (personal account) on Cloudflare Workers
A remote MCP server that gives an MCP client (Claude, etc.) access to a personal
Outlook.com mailbox and calendar through Microsoft Graph.
```
MCP client ──OAuth 2.1 + DCR──▶ this Worker ──OAuth (PKCE, confidential)──▶ Microsoft (consumers)
◀──Bearer token──── /mcp ──Graph API──▶ graph.microsoft.com
```
- The Worker is its own OAuth authorization server (`@cloudflare/workers-oauth-provider`),
so MCP clients register dynamically and get tokens issued by you, not Microsoft.
- Microsoft tokens live only inside the grant's `props`, which the provider encrypts in KV.
- Our access-token TTL tracks Microsoft's; when the client refreshes, `tokenExchangeCallback`
refreshes the upstream Microsoft token too (Microsoft rotates refresh tokens, so the new one is kept).
- An approval page is shown on every authorization (defends against a rogue registered client
piggy-backing on your Microsoft session), and only accounts in `ALLOWED_EMAILS` can complete sign-in.
- Clients may only redirect to an exact URI in `ALLOWED_REDIRECT_URIS` (Claude's callbacks by
default). Registration is open to anyone, so without this a phishing link could get your approval
sent to an attacker's server. Add `http://localhost:6274/oauth/callback` while using the Inspector,
and take it out again afterwards.
- MCP transport: stateless Streamable HTTP via `createMcpHandler` from `agents/mcp/server`
(MCP SDK v2). No Durable Objects needed.
## Tools
| Tool | What it does |
|---|---|
| `whoami` | Signed-in account |
| `list_folders` | Mail folders with counts |
| `list_messages` | Recent messages in a folder (optionally unread only) |
| `search_mail` | KQL full-text search across mail |
| `get_message` | Full message as plain text |
| `update_message` | Mark read/unread, flag/unflag |
| `move_message` | Move to a folder (archive, deleteditems, …) |
| `create_draft` | Save a new draft |
| `send_mail` | Send a new email |
| `reply_to_message` | Reply / reply-all, as draft or sent |
| `list_events` | Calendar view between two times (recurrences expanded) |
| `create_event` | Create an event (invites attendees) |
| `delete_event` | Delete an event |
## Setup
### 1. Register the app in Entra ID
Azure portal → **Microsoft Entra ID → App registrations → New registration**
- **Supported account types:** *Personal Microsoft accounts only*
(or "Any Entra ID tenant + personal accounts" — both work with the `/consumers` authority used here).
- **Redirect URI:** platform **Web**, `https://outlook-mcp.<your-subdomain>.workers.dev/callback`
(add `http://localhost:8787/callback` too for local dev).
- **Certificates & secrets → New client secret.** Copy the *value*.
- **API permissions → Microsoft Graph → Delegated:** `User.Read`, `Mail.ReadWrite`, `Mail.Send`,
`Calendars.ReadWrite`, `offline_access`, `openid`, `profile`, `email`.
(No admin consent needed — you consent on first sign-in.)
Copy the **Application (client) ID** from the Overview page.
### 2. Configure the Worker
```bash
npm install
npx wrangler kv namespace create OAUTH_KV # paste the id into wrangler.jsonc
```
Edit `wrangler.jsonc` vars: `PUBLIC_URL`, `MS_CLIENT_ID`, `ALLOWED_EMAILS`, `TIMEZONE`. Then:
```bash
npx wrangler secret put MS_CLIENT_SECRET
npx wrangler types --strict-vars=false # regenerate Env types after config changes
npm run deploy
```
### 3. Connect
- **Claude app / claude.ai:** Settings → Connectors → *Add custom connector* →
`https://outlook-mcp.<your-subdomain>.workers.dev/mcp`
- **Inspector:** `npx @modelcontextprotocol/inspector` → Streamable HTTP → same URL.
You'll see the approval page, then Microsoft sign-in/consent, then you're connected.
## Local dev
```bash
cp .dev.vars.example .dev.vars # put the client secret in it
npm run dev -- --var PUBLIC_URL:http://localhost:8787
```
## Notes / limits
- `ALLOWED_EMAILS` is compared with Graph's `mail` (falling back to `userPrincipalName`) for the
signed-in user. Use the address shown by `GET /me` if your account has aliases.
- Microsoft consumer refresh tokens expire after ~90 days of inactivity; our grant TTL matches
(`refreshTokenTTL`). After that, just reconnect.
- To revoke everything: delete the grant in KV (or rotate the client secret / remove app consent at
https://account.live.com/consent/Manage).
- Attachments aren't exposed yet — `GET /me/messages/{id}/attachments` is the natural next tool.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues