MCP Server for Multiple Outlook Accounts
by uaixo
README.md
# MCP Server for Multiple Outlook Accounts
A **local, single-connector** [Model Context Protocol](https://modelcontextprotocol.io) server
that lets an AI assistant operate **several Outlook / Microsoft 365 mailboxes at once** โ
searching, reading, drafting, sending, and organising mail โ through one safety-annotated tool
surface, with OAuth tokens that **never leave your machine**.
This is the **Outlook / Microsoft Graph** provider variant of a provider-neutral specification.
- ๐ [`doc/business-specification.md`](./doc/business-specification.md) โ provider-neutral business + functional spec (the contract).
- ๐ [`doc/provider-mapping.md`](./doc/provider-mapping.md) โ how neutral requirements bind to Microsoft Graph.
- ๐๏ธ [`doc/architecture.md`](./doc/architecture.md) โ how this variant is designed and built.
- โ
[`doc/traceability-matrix.md`](./doc/traceability-matrix.md) โ every requirement โ module โ test.
- ๐ [`doc/ONBOARDING.md`](./doc/ONBOARDING.md) โ operator guide: register an Entra app, handle consent, connect a mailbox.
- โ
[`doc/LIVE-ACCEPTANCE.md`](./doc/LIVE-ACCEPTANCE.md) โ live acceptance runbook + `npm run live-smoke` (the remaining work).
- ๐ค [`doc/CONTRIBUTING.md`](./doc/CONTRIBUTING.md) โ handoff guide: how we work, workflow, and the per-phase record.
- ๐ [`doc/HANDOVER.md`](./doc/HANDOVER.md) โ handover snapshot: what's done and the outstanding-work checklist.
---
## Project status: feature-complete (offline build)
Built with **TypeScript 6.0.3**; build, typecheck, tests (179), and format all green. All eight
capabilities are implemented and the offline build is feature-complete. What exists today:
- โ
Architecture design + requirements traceability matrix (`doc/`).
- โ
**Auth core:** Entra credential-source discovery, MSAL public-client (consent + silent refresh),
secure token store (`0600`/`0700`, atomic + cross-process-locked), account-selection registry.
- โ
**Account-management CLI:** `outlook-mcp-auth connect | list | remove`.
- โ
**Microsoft Graph client:** thin `fetch` wrapper with a per-request timeout, bounded jittered
retry (with the no-duplicate-send policy), and actionable error mapping.
- โ
**Read tools:** `list_accounts` (C1), `search_conversations` (C2), `read_conversation` (C3) โ with
Gmail-style search-operator translation and bounded, truncating output.
- โ
**Write tools:** `create_draft` (C4) and `send_message` (C5) โ recipient parsing
(`Display Name <addr>`), header-injection stripping, allow-listed/TOCTOU-safe attachments,
local outgoing-size validation, and reply threading. Small attachments ride inline; large ones
(> ~3 MB) upload to the draft via a Graph **upload session**, so send becomes
create-draft โ upload โ send with the final `/send` under the `nonDuplicable` retry policy โ
a retry can never double-deliver.
- โ
**Organise tools:** `list_labels` (C6), `create_label` (C7), and `organize_mail` (C8) โ the
label-decomposition fan-out that maps one neutral organise request to the right mix of Graph
category-PATCH / read-state / `move` calls (archive, **trash**, **junk** โ mutually exclusive),
applied per message across a conversation under a bounded concurrency limit, reporting the union
of resulting labels. `list_labels` enumerates folders recursively (full paths).
- โ
**Hardening + onboarding:** a secret-redaction boundary so tokens/credentials can never reach
the logs (NFR-SEC-6), and the operator [onboarding guide](./doc/ONBOARDING.md) (Entra app
registration, unverified-app consent, the connect CLI).
**Remaining:** the **live** acceptance runs (real browser consent + real Graph calls) that the
operator performs against an Entra app registration + Outlook mailbox. See `doc/architecture.md` ยง13.
> Tests mock Microsoft Graph and MSAL. Live `ยง13` acceptance โ real browser consent and Graph calls โ
> requires an Entra app registration + Outlook mailboxes and is run locally by the operator.
---
## Capabilities (spec ยง5)
| Tool | Purpose | Destructive? | Status |
| ---------------------- | -------------------------- | ------------ | ------- |
| `list_accounts` | List connected mailboxes | No | โ
live |
| `search_conversations` | Search a mailbox (paged) | No | โ
live |
| `read_conversation` | Read a full conversation | No | โ
live |
| `create_draft` | Compose a draft (not sent) | No | โ
live |
| `send_message` | Send immediately | **Yes** | โ
live |
| `list_labels` | List categories + folders | No | โ
live |
| `create_label` | Create a category/folder | No | โ
live |
| `organize_mail` | Tag / move / read-state | **Yes** | โ
live |
Plus an out-of-band account-management CLI (`outlook-mcp-auth connect | list | remove`).
---
## Development
Requires **Node.js โฅ 18** (developed on Node 22).
```bash
npm install
npm run typecheck # tsc --noEmit
npm run build # compile to dist/
npm test # vitest (Graph/MSAL mocked)
npm run format:check # prettier
```
### Configuration
All operational knobs are environment variables (see [`.env.example`](./.env.example)):
| Env var | Meaning | Default |
| -------------------------------- | --------------------------------------------- | ---------------- |
| `OUTLOOK_MCP_DATA_DIR` | tokens + app-registration configs | `~/.outlook-mcp` |
| `OUTLOOK_OAUTH_CREDENTIALS` | pin one app registration (disables discovery) | unset |
| `OUTLOOK_MCP_ATTACHMENTS_DIR` | allow-list for `path` attachments | unset (disabled) |
| `OUTLOOK_MCP_LOCK_TIMEOUT_MS` | token-store lock wait | `12000` |
| `OUTLOOK_MCP_REQUEST_TIMEOUT_MS` | per-Graph-call timeout | `30000` |
### Connecting a mailbox
> For the full walkthrough โ Entra app registration, the unverified-app consent policy, and
> troubleshooting โ see **[`doc/ONBOARDING.md`](./doc/ONBOARDING.md)**. The short version:
1. Register a **public-client** app in Entra ID with the redirect URI `http://localhost` and the
delegated scopes `Mail.ReadWrite`, `Mail.Send`, `User.Read`, `offline_access`.
2. Drop a `credentials*.json` into the data dir (or point `OUTLOOK_OAUTH_CREDENTIALS` at it):
```json
{ "clientId": "<application-client-id>", "tenant": "common" }
```
Multiple `credentials*.json` files are auto-discovered, so accounts under different app
registrations each refresh with the client that authorised them.
3. Connect, list, and remove mailboxes:
```bash
outlook-mcp-auth connect # opens the browser for consent
outlook-mcp-auth connect --source acme # pick a specific app registration
outlook-mcp-auth list
outlook-mcp-auth remove user@example.com
```
### Security posture
OAuth tokens are stored only on the local machine (file mode `600` in a `700` data dir). Reading
local files by path for attachments is **disabled by default** and only allowed from an explicit
allow-list. The server never logs tokens, credentials, or message content. See
`doc/architecture.md` ยง9.
## License
MIT โ see [LICENSE](./LICENSE).
TDQS
A4.2/5.0
Scored across 1 tool
Disambiguation5/5
With only one tool, there is no possibility of ambiguity. Agents will always select list_accounts when any tool is needed.
Naming Consistency5/5
The single tool follows a clear verb_noun pattern (list_accounts), making its purpose immediately understandable.
Tool Count1/5
A single listing tool is far too few for a server purporting to handle multiple Outlook accounts. The user would expect tools for email operations, calendar management, or at least sending/reading messages.
Completeness1/5
The server only lists accounts and provides no way to actually interact with them. There are obvious missing operations like read_email, send_email, search_mailbox, or manage folders, leaving the surface severely incomplete.
Maintenance
ActivityInactive
ResponsivenessNo issues