The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Outlook MCP listing page.
MCP server for Microsoft Outlook personal accounts via Microsoft Graph API.
Personal Microsoft accounts only —
@outlook.com,@hotmail.com,@live.com. Work/school accounts (Entra ID) are not supported in v1.
Disclaimer: Independent open-source project. Not affiliated with, endorsed by, or supported by Microsoft Corporation. "Outlook" and "Microsoft Graph" are trademarks of Microsoft.
You'll like this if you're:
allow_categories, optional read_only mode, zero telemetryThis isn't for you if you need work/school M365 accounts (use Microsoft's official tooling — Entra ID auth and admin-consent flows are out of scope here), or if a basic mail-only client would suffice (this has 62 tools — way more than you need for "read my inbox").
This is the only first-class MCP server in the personal-Outlook space — most alternatives are bash scripts or skill-shaped CLI wrappers the agent shells out to. That distinction matters: the agent gets typed tool schemas with structured args/returns, not stdout it has to parse. Other things you won't find elsewhere: /$batch-optimized triage (10-20× faster on bulk ops), recursive folder ops with name resolution, granular per-category permissions, multi-account support, and full attachment write paths including >3MB upload sessions for drafts.
Give your AI agent full Outlook access. Example prompts that just work:
The server exposes 62 discrete tools so the agent can compose its own workflow — read, triage, write, schedule, track tasks — without hardcoded macros.
~/.claude/settings.json under mcpServersListed on the official MCP Registry as io.github.mpalermiti/outlook-mcp.
62 tools across 13 categories:
$batch, search (KQL), list folders, delta-sync inbox changes, composed "since last call" digest across mail/events/contactsDesign principles:
azure-identity (macOS Keychain, Windows Credential Store, Linux Secret Service).read_only: true in config to block all write operations.permanent: true.Two pure-code upgrades that make the same 57 tools cheaper and more recoverable for AI agents:
Concise mode — pass concise=True to the five high-volume read tools (outlook_list_inbox, outlook_read_message, outlook_search_mail, outlook_list_events, outlook_list_thread) to drop bulky fields: full message bodies, per-event attendee lists, quoted prior-message text in threads, body previews/categories on inbox listings. Typical payload reduction ~10×. Default concise=False preserves the existing response shape — strict backward compat.
Structured Graph errors — every tool wraps msgraph SDK exceptions into {code, message, action} responses with operator-friendly recovery hints: re-auth on 401, a link to the repo's ROADMAP dead-ends list on 403/ErrorAccessDenied, re-list on 404/ErrorItemNotFound, back-off on 429, retry on 503. OutlookMCPError subclasses and validation errors pass through unchanged.
You need to register a free Azure AD app to get a client ID.
Microsoft has deprecated app registration for personal accounts without an Azure AD tenant. You need to create a free Azure account first:
@outlook.com account. Requires a credit card for identity verification but won't charge you. This creates a proper Azure AD tenant.Go to App Registrations and sign in with your @outlook.com account.
Click "+ New registration" and fill in:
mp-outlook-mcp — names like "Outlook MCP" will be rejected)Click Register. Copy the Application (client) ID from the overview page.
Go to Authentication (Preview) → Settings tab → toggle "Allow public client flows" to Yes → Save.
Go to API permissions → Add a permission → Microsoft Graph → Delegated permissions → add:
Mail.ReadWrite, Mail.SendCalendars.ReadWriteContacts.ReadWrite, Tasks.ReadWriteUser.Read, offline_accessNo client secret is needed. The device code flow uses public client auth.
Option A — from PyPI (recommended):
Option B — from source:
Create ~/.outlook-mcp/config.json:
The only required field is client_id. Everything else has sensible defaults. Start with read_only: true — flip to false when you're comfortable.
If installed from PyPI:
If installed from source:
For OpenClaw, use the openclaw mcp CLI — it writes to mcp.servers in ~/.openclaw/openclaw.json for you:
Restart the OpenClaw gateway after registering. See the OpenClaw MCP docs for SSE/HTTP transport variants.
Run this once on the machine where the MCP server will run:
You'll get a URL and a code. Open the URL in any browser, enter the code, and sign in with your Microsoft account. Tokens are cached in the OS keyring — the MCP server picks them up automatically.
Other CLI commands:
SSL: CERTIFICATE_VERIFY_FAILED on LinuxIf auth fails with [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate, your Python environment can't find the system CA bundle. This is common on minimal/container Linux images and with the isolated venv from uv tool install.
Point Python at your system CA bundle. Set both variables — auth (via azure-identity → requests) reads REQUESTS_CA_BUNDLE, while the delta/$batch paths (via httpx) read SSL_CERT_FILE:
The path varies by distro: Debian/Ubuntu use /etc/ssl/certs/ca-certificates.crt; RHEL/Fedora use /etc/pki/tls/certs/ca-bundle.crt. If the file is missing, install your distro's CA package (ca-certificates). Set these in the same environment your MCP client launches the server from so they apply at runtime, not just to the one-time auth command.
A one-time startup warning about the token cache falling back to plaintext means libsecret/PyGObject isn't importable — see Privacy and Security for the fix.
| Tool | Description |
|---|---|
outlook_auth_status | Check if authenticated and whether read-only mode is active. |
Note: Authentication is handled via the CLI (
outlook-mcp auth), not through MCP tools. See Authenticate above.
| Tool | Description |
|---|---|
outlook_list_inbox | List messages in a folder. folder accepts display names, well-known names, or Graph IDs. Filter by read status, sender, date range, Focused Inbox classification. Pagination via skip. |
outlook_read_message | Get full message by ID. Format: text, html, or full (both). Pass include_deferred_send=True to also surface the draft's scheduled delivery time. |
outlook_read_messages | Bulk read up to 20 messages by ID via Graph $batch in one round-trip. Per-message shape matches outlook_read_message byte-for-byte for the same (format, concise, include_deferred_send). Partial-failure tolerant: 404s on some IDs surface in failures[] without failing the whole call. Use NOT N outlook_read_message calls. |
outlook_search_mail | Search mail using KQL query. Optionally scope to a folder by name or ID. |
outlook_list_folders | List mail folders with counts, parent_id, and child_count. Pass recursive=true to walk the full folder tree (subfolders included). |
outlook_list_inbox_delta | List only inbox changes since the last call. First call returns a full snapshot plus a delta_token; subsequent calls (token passed back) return only added/updated/deleted items. Deletes come back as {id, is_deleted: True}. Cursor is stateless — agent persists and replays. |
outlook_changes_since | One structured "since last call" digest composing mail/events/contacts deltas. Returns counts + urgent_flagged mail + top-5 by_sender + new/cancelled events. Each resource has an independent delta_token; stale-token recovery (HTTP 410) auto-resyncs that resource and surfaces _meta.resync. First-call snapshot is filtered to fallback_window_hours (default 24). Designed for recurring agent loops. |
| Tool | Description |
|---|---|
outlook_send_message | Send email. Supports TO/CC/BCC, HTML body, importance level. |
outlook_reply | Reply or reply-all to a message. |
outlook_forward | Forward a message to one or more recipients with optional comment. |
| Tool | Description |
|---|---|
outlook_move_message | Move a message to a folder by name or ID. |
outlook_delete_message | Delete a message. Soft delete (Deleted Items) by default. permanent: true for hard delete. |
outlook_flag_message | Set follow-up flag: flagged, complete, or notFlagged. |
outlook_categorize_message | Set categories on a message. |
outlook_mark_read | Mark a message as read or unread. |
outlook_reclassify_message | Move a message between Focused Inbox and Other (focused / other). |
outlook_list_inbox_overrides | List Focused Inbox per-sender override rules. |
outlook_set_inbox_override | Upsert a per-sender Focused Inbox override (focused / other). Case-insensitive sender matching; PATCH-if-exists, else POST. |
outlook_delete_inbox_override | Delete a Focused Inbox override by ID. |
| Tool | Description |
|---|---|
outlook_list_events | List events in a date range. Expands recurring events. Each event carries type, so a series master is distinguishable from a one-off. Configurable via days, after, before. |
outlook_get_event | Get full event details: attendees, body, online meeting URL, recurrence, type (singleInstance / seriesMaster / occurrence / exception). |
outlook_list_events_delta | List only event changes inside a window since the last call. start and end (ISO 8601) required on the first call (Graph constraint — no whole-calendar sync). Deletes come back as {id, is_deleted: True}. Cursor is stateless. |
| Tool | Description |
|---|---|
outlook_create_event | Create event with location and attendees. (is_online has no effect on personal accounts — Graph ignores isOnlineMeeting for consumer mailboxes.) Pass recurrence to create a series: a shorthand (daily, weekdays, weekly, monthly, yearly, anchored on start) or a full Graph recurrence object for anything else. range.startDate defaults to the event's start date. |
outlook_update_event | Update event fields (subject, time, location, body, attendees, all-day). Only patches changed fields. Pass recurrence to turn a single event into a series, or remove_recurrence=True to turn a series back into a single event. attendees replaces the whole guest list and emails invitations/cancellations; is_all_day needs start+end in the same call. |
outlook_delete_event | Delete a calendar event. |
outlook_rsvp | RSVP to an event: accept, decline, or tentative. Optionally include a message. |
| Tool | Description |
|---|---|
outlook_list_contacts | List contacts with cursor pagination. |
outlook_search_contacts | Search contacts by name or email. |
outlook_get_contact | Get full contact details by ID. |
outlook_create_contact | Create a new contact. |
outlook_update_contact | Update contact fields. |
outlook_delete_contact | Delete a contact. |
outlook_list_contacts_delta | List only contact changes since the last call. Deletes come back as {id, is_deleted: True}. Cursor is stateless. |
| Tool | Description |
|---|---|
outlook_list_task_lists | List To Do lists. |
outlook_list_tasks | List tasks with status filter and pagination. |
outlook_create_task | Create task with due date, importance, recurrence. |
outlook_update_task | Update task fields. |
outlook_complete_task | Mark task as completed. |
outlook_delete_task | Delete a task. |
| Tool | Description |
|---|---|
outlook_list_drafts | List draft messages with pagination. |
outlook_create_draft | Create a draft. Supports scheduled delivery via deferred_send_datetime (server-side, Outlook-desktop-compatible "Delay Delivery"). |
outlook_update_draft | Update draft fields. Accepts is_html=True for HTML bodies and deferred_send_datetime to set or clear the scheduled delivery time. |
outlook_send_draft | Send an existing draft. |
outlook_delete_draft | Delete a draft. |
| Tool | Description |
|---|---|
outlook_list_attachments | List attachments on a message. |
outlook_download_attachment | Download attachment and save decoded bytes to a file. |
outlook_send_with_attachments | Send message with file attachments (auto upload session for >3MB). |
outlook_attach_to_draft | Add attachments to an existing draft (auto upload session for >3MB). |
outlook_remove_draft_attachment | Remove a single attachment from a draft. |
| Tool | Description |
|---|---|
outlook_create_folder | Create mail folder (top-level or nested). |
outlook_rename_folder | Rename a mail folder. |
outlook_delete_folder | Delete a mail folder (refuses well-known folders). |
| Tool | Description |
|---|---|
outlook_list_thread | Get all messages in a conversation thread. |
outlook_copy_message | Copy a message to another folder. |
outlook_batch_triage | Batch move/flag/categorize/mark_read (max 20 per call). Single Graph /$batch round-trip — 10-20× faster than per-message calls for large triage. |
| Tool | Description |
|---|---|
outlook_whoami | Get current user profile. |
outlook_list_calendars | List available calendars. |
outlook_list_categories | List category definitions with colors. |
outlook_get_mail_tips | Pre-send check (OOF, delivery restrictions). |
outlook_list_accounts | List configured accounts. |
outlook_switch_account | Switch active account. |
Config lives at ~/.outlook-mcp/config.json (created with 0600 permissions).
| Field | Type | Default | Description |
|---|---|---|---|
client_id | string | null | Azure AD application (client) ID. Required for auth. |
tenant_id | string | "consumers" | Azure AD tenant. Use "consumers" for personal Microsoft accounts. |
timezone | string | "UTC" | IANA timezone (e.g. "America/New_York"). Used for relative date computations in calendar tools. |
read_only | bool | false | When true, all write tools (send, reply, move, delete, create, update, RSVP) return an error. |
allow_categories | list[string] | [] | Optional. Restrict write tools to specific categories (see below). Empty list = all writes allowed when read_only: false. |
OUTLOOK_MCP_TOOLSETSAll 62 tool schemas load into the client's context every turn (~8.6k tokens). A client that only needs part of the surface can set the OUTLOOK_MCP_TOOLSETS environment variable to a comma-separated list of tool groups, and only those load. The account group (auth / identity) is always available.
Groups: mail, drafts, attachments, calendar, contacts, todo, folders, digest, delta, admin. Unset (the default) loads everything — fully backward compatible. This only affects which tools are advertised; enabled tools behave identically.
By default, read_only: false unlocks all write tools. For finer control, set allow_categories to restrict write access to specific categories. Read tools (list, search, get) are always allowed — allow_categories only narrows the write surface.
Available categories:
| Category | Tools | Risk |
|---|---|---|
mail_drafts | create/update/delete draft | Safe — drafts only, no send |
mail_triage | move, delete (soft), flag, categorize, mark read, copy, batch | Moderate — reversible except hard delete |
mail_folders | create/rename/delete folder | Moderate |
mail_send | send, reply, forward, send_draft, send_with_attachments | Dangerous — sends email on your behalf |
calendar_write | create/update/delete event, RSVP | Moderate — creates calendar entries |
contacts_write | create/update/delete contact | Moderate |
todo_write | create/update/complete/delete task | Safe — your own task list |
Example policies:
Draft-only assistant (agent can compose drafts, you review and send):
Calendar-only (agent can manage your schedule, nothing else):
Full write access (agent can do everything):
Read-only (safest default, no writes):
When allow_categories is set, any tool in a non-allowed category returns a permission-denied error (PermissionDeniedError) naming the blocked category. When allow_categories is empty (or unset) and read_only is false, all write tools are permitted. read_only: true always takes precedence — if set, all writes are blocked regardless of allow_categories. Unknown category names are rejected at config load time with a validation error; only the seven names above are accepted.
graph.microsoft.com and login.microsoftonline.com.azure-identity's TokenCachePersistenceOptions. On macOS the OS Keychain is used; on Windows, DPAPI; on Linux with PyGObject/libsecret available, gnome-keyring. On Linux without libsecret (e.g. the isolated venv created by uv tool install), tokens fall back to a 0600 plaintext file at ~/.IdentityService/ and the MCP logs a one-time warning at startup. For encrypted storage on Linux, install python3-gi gnome-keyring libsecret-1-0 and re-create the venv with --system-site-packages.0700, config file is 0600. Symlinked configs are rejected.Requirements: Python 3.10+
MIT. See LICENSE.