outlook-mcp-server
# outlook-mcp-server
A local [MCP](https://modelcontextprotocol.io) server that gives Claude
(Desktop or Code) read/write access to a **personal** Hotmail / Outlook.com
mailbox via the Microsoft Graph API, using the OAuth 2.0 authorization code
flow (with PKCE) against the Microsoft identity platform.
It exposes six tools: `list_messages`, `get_message`, `search_messages`,
`send_message`, `create_draft`, and `list_folders`.
Everything runs locally over stdio — there is no hosted service, and your
mail never passes through anything but your machine and Microsoft's own
Graph API.
## How it works
- **Auth**: [MSAL Node](https://www.npmjs.com/package/@azure/msal-node)
runs an authorization-code + PKCE flow against
`https://login.microsoftonline.com/consumers` (personal accounts only —
see [Tenant choice](#tenant-choice)), using a short-lived local HTTP
server as the redirect target. Tokens (including the `offline_access`
refresh token) are cached and silently refreshed on future runs.
- **Storage**: the token cache is serialized by MSAL, encrypted with
AES-256-GCM using a locally-generated key, and written to
`~/.outlook-mcp-server/token-cache.enc` (mode `0600`). The key itself
lives in `~/.outlook-mcp-server/cache.key` (also `0600`). See
[Security notes](#security-notes) for the threat model this does (and
doesn't) cover.
- **Graph calls**: a thin `fetch`-based client calls
`https://graph.microsoft.com/v1.0/...` with the current access token.
- **MCP server**: built on `@modelcontextprotocol/sdk`, speaking stdio, so
it can be launched directly by Claude Desktop / Claude Code as a child
process.
## Prerequisites
- Node.js 18+
- A Microsoft account (Hotmail, Outlook.com, or Live) — the mailbox you
want Claude to access.
- A free Azure account to register the app (any Microsoft account can do
this — it does not need to be a paid Azure subscription).
## 1. Install
```bash
git clone <this repo>
cd outlook-mcp-server
npm install
```
## 2. Register an app in the Azure Portal
This registration is what issues the client ID this server uses to talk to
Microsoft Graph on your behalf. `npm run setup` (below) walks you through
this interactively, but the steps are:
1. Go to [portal.azure.com](https://portal.azure.com) and sign in with any
Microsoft account.
2. Search for **App registrations** → **+ New registration**.
3. Fill in the form:
- **Name**: anything, e.g. `outlook-mcp-server`.
- **Supported account types**: **"Personal Microsoft accounts only"**.
This is what restricts the app to Hotmail/Outlook.com/Live accounts
rather than a work/school (Azure AD) tenant.
- **Redirect URI**: platform **"Public client/native (mobile & desktop)"**,
value `http://localhost:8765/callback` (or another port — just be
consistent when the setup script asks).
4. Click **Register**, then copy the **Application (client) ID** from the
Overview page.
5. Go to **API permissions** → **+ Add a permission** → **Microsoft Graph**
→ **Delegated permissions**, and add:
- `Mail.Read`
- `Mail.ReadWrite`
- `Mail.Send`
- `offline_access` (often present by default)
Personal Microsoft account delegated permissions like these don't need
admin consent — you consent yourself during sign-in in step 3 below.
6. *(Optional, advanced)* If you'd rather use a confidential client with a
client secret instead of the public-client PKCE flow, add a **Web**
platform redirect URI and create a secret under **Certificates &
secrets**. Most people should skip this.
## 3. Run setup (auth + config)
```bash
npm run setup
```
This will:
1. Print the walkthrough above.
2. Prompt for the client ID (and optional secret / tenant / redirect URI),
and save it to `~/.outlook-mcp-server/config.json`.
3. Open your browser to sign in and consent.
4. Verify the token works by calling `GET /me`, printing your name/email.
5. Print the JSON snippet to add to your Claude config (see below).
To re-authenticate later (revoked token, switching accounts, etc.) without
re-entering the app registration details:
```bash
npm run login
```
## 4. Build and register with Claude
```bash
npm run build
```
**Claude Desktop** — add to `claude_desktop_config.json`
(`~/Library/Application Support/Claude/claude_desktop_config.json` on
macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows):
```json
{
"mcpServers": {
"outlook": {
"command": "node",
"args": ["/absolute/path/to/outlook-mcp-server/dist/src/index.js"]
}
}
}
```
**Claude Code**:
```bash
claude mcp add outlook -- node /absolute/path/to/outlook-mcp-server/dist/src/index.js
```
Restart Claude Desktop / Claude Code. The tools below should now be
available.
## Tools
| Tool | Description |
| --- | --- |
| `list_messages` | List messages from a folder (default `inbox`), with `since`/`until` date filters, `unreadOnly`, sorting, and pagination. |
| `get_message` | Fetch the full content (body, all recipients) of one message by ID. |
| `search_messages` | Free-text search (`$search`) across mail, optionally scoped to a folder. |
| `send_message` | Send an email immediately (`to`/`cc`/`bcc`, subject, text or HTML body). |
| `create_draft` | Create a draft in the Drafts folder without sending. |
| `list_folders` | List mail folders and their IDs, for use with the `folder` parameter above. |
All tools return JSON (as MCP text content) and surface Graph API errors as
tool errors rather than crashing the server.
## Tenant choice
By default this uses the `consumers` tenant
(`https://login.microsoftonline.com/consumers`), which **only** accepts
personal Microsoft accounts (Hotmail/Outlook.com/Live) — a work/school
account will be rejected at sign-in. If you need to support both personal
and Azure AD accounts, set the tenant to `common` during `npm run setup`
(or via `OUTLOOK_MCP_TENANT=common`). This project is designed and tested
for the personal-account (`consumers`) case.
## Configuration reference
Everything can be set via `npm run setup` (written to
`~/.outlook-mcp-server/config.json`) or via environment variables, which
take precedence — see `.env.example`:
| Variable | Purpose |
| --- | --- |
| `OUTLOOK_MCP_CLIENT_ID` | Azure app registration's client ID. |
| `OUTLOOK_MCP_CLIENT_SECRET` | Only if using a confidential client (Web platform). |
| `OUTLOOK_MCP_TENANT` | `consumers` (default) or `common`. |
| `OUTLOOK_MCP_REDIRECT_URI` | Must match the Azure app registration. |
| `OUTLOOK_MCP_CONFIG_DIR` | Where config/token cache are stored. Defaults to `~/.outlook-mcp-server`. |
## Security notes
- The token cache is encrypted at rest with a locally-generated AES-256-GCM
key (`~/.outlook-mcp-server/cache.key`, mode `0600`). This protects
against casual disclosure — accidental commits, backups, other
unprivileged users on a shared machine — but **not** against an attacker
who already has read access to your user account's files, since the key
sits next to the encrypted cache. For stronger protection, swap the
`ICachePlugin` in `src/auth/tokenCache.ts` for one backed by your OS
keychain (e.g. via `keytar`) — the plugin interface is intentionally
isolated to that one file.
- Never commit `~/.outlook-mcp-server/` (it's outside the repo by default)
or a `.env` file containing `OUTLOOK_MCP_CLIENT_SECRET`.
- `send_message` sends immediately with no confirmation step inside this
server — Claude is expected to confirm intent with you before calling it
for anything sensitive. Prefer `create_draft` when you want a review step.
- Requested scopes are limited to `Mail.Read`, `Mail.ReadWrite`,
`Mail.Send`, and `offline_access` — no calendar, contacts, or broader
`Mail.*` application-level access.
## Troubleshooting
- **`AADSTS50020` / "user account ... does not exist in tenant"** — you're
hitting a tenant that doesn't accept personal accounts, or you're signing
in with a work/school account against `consumers`. Confirm the app
registration's "Supported account types" is "Personal Microsoft accounts
only" and that `OUTLOOK_MCP_TENANT` is `consumers` (or `common` if you
intentionally want both).
- **`AADSTS50011` / redirect URI mismatch** — the `redirectUri` in
`~/.outlook-mcp-server/config.json` must exactly match a redirect URI
configured on the Azure app registration, including the port.
- **"Not signed in"** tool errors — run `npm run login`.
- **Port already in use during setup/login** — another process is using
the redirect URI's port; stop it, or reconfigure the app registration and
`npm run setup` with a different port.
## Development
```bash
npm run dev # run the MCP server directly from TypeScript (stdio)
npm run build # compile to dist/
npm run clean # remove dist/
```
## Project structure
```
src/
index.ts MCP server entrypoint (stdio transport)
config.ts Config loading (env + config file)
auth/
crypto.ts AES-256-GCM file encryption helpers
tokenCache.ts MSAL ICachePlugin backed by crypto.ts
msalClient.ts MSAL app factory + silent token acquisition
loginFlow.ts Interactive loopback OAuth flow
graph/
client.ts Generic Microsoft Graph fetch wrapper
mail.ts Mail-specific Graph calls
types.ts Graph response types
tools/ One file per MCP tool, registered in index.ts
scripts/
setup.ts Interactive one-time (and re-runnable) setup
```
TDQS
Scored across 6 tools
Most tools have clearly distinct purposes: listing, fetching, searching, folder enumeration, sending, and drafting. The only possible confusion is between list_messages and search_messages, but their descriptions clearly differentiate structured filtering from full-text relevance search.
All tool names follow a consistent verb_noun snake_case pattern: list_messages, get_message, search_messages, list_folders, send_message, create_draft. There are no style mixups or vague verbs.
Six tools is a well-scoped set for an Outlook mail server. Each tool covers a distinct core email operation without unnecessary bloat or missing essentials.
The tool surface covers the primary mail workflows: listing, searching, reading, sending, drafting, and folder navigation. Some common operations like reply, forward, delete, move, or updating drafts are absent, but the core read/send workflow is solid.