MCP Server for Multiple Outlook Accounts
MCP Server for Multiple Outlook Accounts
A local, single-connector Model Context Protocol 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โ provider-neutral business + functional spec (the contract).๐
doc/provider-mapping.mdโ how neutral requirements bind to Microsoft Graph.๐๏ธ
doc/architecture.mdโ how this variant is designed and built.โ
doc/traceability-matrix.mdโ every requirement โ module โ test.๐
doc/ONBOARDING.mdโ operator guide: register an Entra app, handle consent, connect a mailbox.โ
doc/LIVE-ACCEPTANCE.mdโ live acceptance runbook +npm run live-smoke(the remaining work).๐ค
doc/CONTRIBUTING.mdโ handoff guide: how we work, workflow, and the per-phase record.๐
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
fetchwrapper 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) andsend_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/sendunder thenonDuplicableretry policy โ a retry can never double-deliver.โ Organise tools:
list_labels(C6),create_label(C7), andorganize_mail(C8) โ the label-decomposition fan-out that maps one neutral organise request to the right mix of Graph category-PATCH / read-state /movecalls (archive, trash, junk โ mutually exclusive), applied per message across a conversation under a bounded concurrency limit, reporting the union of resulting labels.list_labelsenumerates 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 (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
ยง13acceptance โ 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 connected mailboxes | No | โ live |
| Search a mailbox (paged) | No | โ live |
| Read a full conversation | No | โ live |
| Compose a draft (not sent) | No | โ live |
| Send immediately | Yes | โ live |
| List categories + folders | No | โ live |
| Create a category/folder | No | โ live |
| 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).
npm install
npm run typecheck # tsc --noEmit
npm run build # compile to dist/
npm test # vitest (Graph/MSAL mocked)
npm run format:check # prettierConfiguration
All operational knobs are environment variables (see .env.example):
Env var | Meaning | Default |
| tokens + app-registration configs |
|
| pin one app registration (disables discovery) | unset |
| allow-list for | unset (disabled) |
| token-store lock wait |
|
| per-Graph-call timeout |
|
Connecting a mailbox
For the full walkthrough โ Entra app registration, the unverified-app consent policy, and troubleshooting โ see
doc/ONBOARDING.md. The short version:
Register a public-client app in Entra ID with the redirect URI
http://localhostand the delegated scopesMail.ReadWrite,Mail.Send,User.Read,offline_access.Drop a
credentials*.jsoninto the data dir (or pointOUTLOOK_OAUTH_CREDENTIALSat it):{ "clientId": "<application-client-id>", "tenant": "common" }Multiple
credentials*.jsonfiles are auto-discovered, so accounts under different app registrations each refresh with the client that authorised them.Connect, list, and remove mailboxes:
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.