
A comprehensive personal Outlook integration via Microsoft Graph API with 62 tools spanning mail, calendar, contacts, to-do lists, and drafts. Supports all the operations you'd expect: reading and searching messages, managing Focused Inbox classification, sending and replying to mail, creating calendar events with recurring expansion, CRUD on contacts and tasks, plus batch operations and attachment handling including large file upload sessions. Designed for personal Microsoft accounts only (Outlook.com, Hotmail, Live), not Entra ID work accounts. You bring your own Azure app registration, tokens live in your OS keyring, and there's optional read-only mode plus granular category permissions. Built for agent builders who need typed MCP tool schemas instead of parsing CLI stdout.
Public tool metadata for what this MCP can expose to an agent.
OUTLOOK_ADD_EVENT_ATTACHMENTAdds an attachment to a specific Outlook calendar event. Use when you need to attach a file or nested item to an existing event.6 paramsAdds an attachment to a specific Outlook calendar event. Use when you need to attach a file or nested item to an existing event.
contentBytesstringevent_idstringitemobjectnamestringodata_typestring#microsoft.graph.fileAttachment · #microsoft.graph.itemAttachmentuser_idstringOUTLOOK_ADD_MAIL_ATTACHMENTTool to add an attachment to an email message. Use when you have a message ID and need to attach a small (<3 MB) file or reference.10 paramsTool to add an attachment to an email message. Use when you have a message ID and need to attach a small (<3 MB) file or reference.
contentBytesstringcontentIdstringcontentLocationstringcontentTypestringisInlinebooleanitemobjectmessage_idstringnamestringodata_typestringuser_idstringOUTLOOK_CALENDAR_CREATE_EVENTCreates a new Outlook calendar event, ensuring `start_datetime` is chronologically before `end_datetime`.13 paramsCreates a new Outlook calendar event, ensuring `start_datetime` is chronologically before `end_datetime`.
attendees_infoarraybodystringcategoriesarrayend_datetimestringis_htmlbooleanis_online_meetingbooleanlocationstringonline_meeting_providerstringshow_asstringstart_datetimestringsubjectstringtime_zonestringuser_idstringOUTLOOK_CREATE_ATTACHMENT_UPLOAD_SESSIONTool to create an upload session for large (>3 MB) message attachments. Use when you need to upload attachments in chunks.3 paramsTool to create an upload session for large (>3 MB) message attachments. Use when you need to upload attachments in chunks.
attachmentItemobjectmessage_idstringuser_idstringOUTLOOK_CREATE_CALENDARTool to create a new calendar in the signed-in user's mailbox. Use when organizing events into a separate calendar.4 paramsTool to create a new calendar in the signed-in user's mailbox. Use when organizing events into a separate calendar.
colorstringauto · lightBlue · lightGreen · lightOrange · lightGray · lightYellowhexColorstringnamestringuser_idstringOUTLOOK_CREATE_CONTACTCreates a new contact in a Microsoft Outlook user's contacts folder.15 paramsCreates a new contact in a Microsoft Outlook user's contacts folder.
birthdaystringbusinessPhonesarraycategoriesarraycompanyNamestringdepartmentstringdisplayNamestringemailAddressesarraygivenNamestringhomePhonestringjobTitlestringmobilePhonestringnotesstringofficeLocationstringsurnamestringuser_idstringOUTLOOK_CREATE_CONTACT_FOLDERTool to create a new contact folder in the user's mailbox. Use when needing to organize contacts into custom folders.3 paramsTool to create a new contact folder in the user's mailbox. Use when needing to organize contacts into custom folders.
displayNamestringparentFolderIdstringuser_idstringOUTLOOK_CREATE_DRAFTCreates an Outlook email draft with subject, body, recipients, and an optional attachment. Supports creating drafts as part of existing conversation threads by specifying a conversationId; attachments require a name, mimetype, and content.8 paramsCreates an Outlook email draft with subject, body, recipients, and an optional attachment. Supports creating drafts as part of existing conversation threads by specifying a conversationId; attachments require a name, mimetype, and content.
attachmentstringbcc_recipientsarraybodystringcc_recipientsarrayconversation_idstringis_htmlbooleansubjectstringto_recipientsarrayOUTLOOK_CREATE_DRAFT_REPLYCreates a draft reply in the specified user's Outlook mailbox to an existing message (identified by a valid `message_id`), optionally including a `comment` and CC/BCC recipients.5 paramsCreates a draft reply in the specified user's Outlook mailbox to an existing message (identified by a valid `message_id`), optionally including a `comment` and CC/BCC recipients.
bcc_emailsarraycc_emailsarraycommentstringmessage_idstringuser_idstringOUTLOOK_CREATE_EMAIL_RULECreate email rule filter with conditions and actions5 paramsCreate email rule filter with conditions and actions
actionsobjectconditionsobjectdisplayNamestringisEnabledbooleansequenceintegerOUTLOOK_CREATE_MAIL_FOLDERTool to create a new mail folder. Use when you need to organize email into a new folder.3 paramsTool to create a new mail folder. Use when you need to organize email into a new folder.
displayNamestringisHiddenbooleanuser_idstringOUTLOOK_CREATE_MASTER_CATEGORYTool to create a new category in the user's master category list. Use after selecting a unique display name.2 paramsTool to create a new category in the user's master category list. Use after selecting a unique display name.
colorstringpreset0 · preset1 · preset2 · preset3 · preset4 · preset5displayNamestringOUTLOOK_DELETE_CONTACTPermanently deletes an existing contact, using its `contact_id` (obtainable via 'List User Contacts' or 'Get Contact'), from the Outlook contacts of the user specified by `user_id`.2 paramsPermanently deletes an existing contact, using its `contact_id` (obtainable via 'List User Contacts' or 'Get Contact'), from the Outlook contacts of the user specified by `user_id`.
contact_idstringuser_idstringOUTLOOK_DELETE_EMAIL_RULEDelete an email rule1 paramsDelete an email rule
ruleIdstringOUTLOOK_DELETE_EVENTDeletes an existing calendar event, identified by its unique `event_id`, from a specified user's Microsoft Outlook calendar, with an option to send cancellation notifications to attendees.3 paramsDeletes an existing calendar event, identified by its unique `event_id`, from a specified user's Microsoft Outlook calendar, with an option to send cancellation notifications to attendees.
event_idstringsend_notificationsbooleanuser_idstringOUTLOOK_DELETE_MAIL_FOLDERDelete a mail folder from the user's mailbox. Use when you need to remove an existing mail folder.2 paramsDelete a mail folder from the user's mailbox. Use when you need to remove an existing mail folder.
folder_idstringuser_idstringOUTLOOK_DOWNLOAD_OUTLOOK_ATTACHMENTDownloads a specific file attachment from an email message in a Microsoft Outlook mailbox; the attachment must contain 'contentBytes' (binary data) and not be a link or embedded item.4 paramsDownloads a specific file attachment from an email message in a Microsoft Outlook mailbox; the attachment must contain 'contentBytes' (binary data) and not be a link or embedded item.
attachment_idstringfile_namestringmessage_idstringuser_idstringOUTLOOK_GET_CALENDAR_VIEWGet events ACTIVE during a time window (includes multi-day events). Use for "what's on my calendar today/this week" or availability checks. Returns events overlapping the time range. For keyword search or filters by category, use OUTLOOK_LIST_EVENTS instead.7 paramsGet events ACTIVE during a time window (includes multi-day events). Use for "what's on my calendar today/this week" or availability checks. Returns events overlapping the time range. For keyword search or filters by category, use OUTLOOK_LIST_EVENTS instead.
calendar_idstringend_datetimestringselectarraystart_datetimestringtimezonestringtopintegeruser_idstringOUTLOOK_GET_CONTACTRetrieves a specific Outlook contact by its `contact_id` from the contacts of a specified `user_id` (defaults to 'me' for the authenticated user).2 paramsRetrieves a specific Outlook contact by its `contact_id` from the contacts of a specified `user_id` (defaults to 'me' for the authenticated user).
contact_idstringuser_idstringOUTLOOK_GET_CONTACT_FOLDERSTool to retrieve a list of contact folders in the signed-in user's mailbox. Use after authentication when you need to browse or select among contact folders.7 paramsTool to retrieve a list of contact folders in the signed-in user's mailbox. Use after authentication when you need to browse or select among contact folders.
expandarrayfilterstringorderbyarrayselectarrayskipintegertopintegeruser_idstringOUTLOOK_GET_EVENTRetrieves the full details of a specific calendar event by its ID from a user's Outlook calendar, provided the event exists.2 paramsRetrieves the full details of a specific calendar event by its ID from a user's Outlook calendar, provided the event exists.
event_idstringuser_idstringOUTLOOK_GET_MAILBOX_SETTINGSTool to retrieve mailbox settings. Use when you need to view settings such as automatic replies, time zone, and working hours for the signed-in or specified user.3 paramsTool to retrieve mailbox settings. Use when you need to view settings such as automatic replies, time zone, and working hours for the signed-in or specified user.
expandarrayselectarrayuser_idstringOUTLOOK_GET_MAIL_DELTARetrieve incremental changes (delta) of messages in a mailbox. FIRST RUN: Returns ALL messages in folder (use top=50 to limit). Response has @odata.deltaLink. SUBSEQUENT: Pass stored deltaLink to get only NEW/UPDATED/DELETED messages since last sync. Properties available: id,...7 paramsRetrieve incremental changes (delta) of messages in a mailbox. FIRST RUN: Returns ALL messages in folder (use top=50 to limit). Response has @odata.deltaLink. SUBSEQUENT: Pass stored deltaLink to get only NEW/UPDATED/DELETED messages since last sync. Properties available: id,...
delta_tokenstringexpandarrayfolder_idstringselectarrayskip_tokenstringtopintegeruser_idstringOUTLOOK_GET_MAIL_TIPSTool to retrieve mail tips such as automatic replies and mailbox full status. Use when you need to check recipient status before sending mail.3 paramsTool to retrieve mail tips such as automatic replies and mailbox full status. Use when you need to check recipient status before sending mail.
EmailAddressesarrayMailTipsOptionsarrayuser_idstringOUTLOOK_GET_MASTER_CATEGORIESTool to retrieve the user's master category list. Use when you need to get all categories defined for the user.6 paramsTool to retrieve the user's master category list. Use when you need to get all categories defined for the user.
filterstringorderbyarrayselectarrayskipintegertopintegeruser_idstringOUTLOOK_GET_MESSAGERetrieves a specific email message by its ID from the specified user's Outlook mailbox. Use the 'select' parameter to include specific fields like 'internetMessageHeaders' for filtering automated emails.3 paramsRetrieves a specific email message by its ID from the specified user's Outlook mailbox. Use the 'select' parameter to include specific fields like 'internetMessageHeaders' for filtering automated emails.
message_idstringselectarrayuser_idstringOUTLOOK_GET_PROFILERetrieves the Microsoft Outlook profile for a specified user.1 paramsRetrieves the Microsoft Outlook profile for a specified user.
user_idstringOUTLOOK_GET_SCHEDULERetrieves free/busy schedule information for specified email addresses within a defined time window.4 paramsRetrieves free/busy schedule information for specified email addresses within a defined time window.
EndTimeobjectSchedulesarrayStartTimeobjectavailabilityViewIntervalstringOUTLOOK_GET_SUPPORTED_LANGUAGESTool to retrieve supported languages in the user's mailbox. Use when you need to display or select from available mailbox languages.1 paramsTool to retrieve supported languages in the user's mailbox. Use when you need to display or select from available mailbox languages.
user_idstringOUTLOOK_GET_SUPPORTED_TIME_ZONESTool to retrieve supported time zones in the user's mailbox. Use when you need a list of time zones to display or choose from for event scheduling.1 paramsTool to retrieve supported time zones in the user's mailbox. Use when you need a list of time zones to display or choose from for event scheduling.
timeZoneStandardstringWindows · IanaOUTLOOK_LIST_CALENDARSTool to list calendars in the signed-in user's mailbox. Use when you need to retrieve calendars with optional OData queries.6 paramsTool to list calendars in the signed-in user's mailbox. Use when you need to retrieve calendars with optional OData queries.
filterstringorderbyarrayselectarrayskipintegertopintegeruser_idstringOUTLOOK_LIST_CONTACTSRetrieves a user's Microsoft Outlook contacts, from the default or a specified contact folder.6 paramsRetrieves a user's Microsoft Outlook contacts, from the default or a specified contact folder.
contact_folder_idstringfilterstringorderbyarrayselectarraytopintegeruser_idstringOUTLOOK_LIST_EMAIL_RULESList all email rules from inbox1 paramsList all email rules from inbox
topintegerOUTLOOK_LIST_EVENT_ATTACHMENTSTool to list attachments for a specific Outlook calendar event. Use when you have an event ID and need to view its attachments.7 paramsTool to list attachments for a specific Outlook calendar event. Use when you have an event ID and need to view its attachments.
event_idstringfilterstringorderbyarrayselectarrayskipintegertopintegeruser_idstringOUTLOOK_LIST_EVENTSRetrieves events from a user's Outlook calendar via Microsoft Graph API. Supports primary/secondary/shared calendars, pagination, filtering, property selection, sorting, and timezone specification. Use calendar_id to access non-primary calendars.9 paramsRetrieves events from a user's Outlook calendar via Microsoft Graph API. Supports primary/secondary/shared calendars, pagination, filtering, property selection, sorting, and timezone specification. Use calendar_id to access non-primary calendars.
calendar_idstringexpand_recurring_eventsbooleanfilterstringorderbyarrayselectarrayskipintegertimezonestringtopintegeruser_idstringOUTLOOK_LIST_MAIL_FOLDERSTool to list a user's top-level mail folders. Use when you need folders like Inbox, Drafts, Sent Items; set include_hidden_folders=True to include hidden folders.2 paramsTool to list a user's top-level mail folders. Use when you need folders like Inbox, Drafts, Sent Items; set include_hidden_folders=True to include hidden folders.
include_hidden_foldersbooleanuser_idstringOUTLOOK_LIST_MESSAGESRetrieves a list of email messages from a specified mail folder in an Outlook mailbox, with options for filtering (including by conversationId to get all messages in a thread), pagination, and sorting; ensure 'user_id' and 'folder' are valid, and all date/time strings are in I...22 paramsRetrieves a list of email messages from a specified mail folder in an Outlook mailbox, with options for filtering (including by conversationId to get all messages in a thread), pagination, and sorting; ensure 'user_id' and 'folder' are valid, and all date/time strings are in I...
categoriesarrayconversationIdstringfolderstringfrom_addressstringhas_attachmentsbooleanimportancestringis_readbooleanorderbyarrayreceived_date_time_gestringreceived_date_time_gtstringreceived_date_time_lestringreceived_date_time_ltstringselectarraysent_date_time_gtstringsent_date_time_ltstringskipintegersubjectstringsubject_containsstringsubject_endswithstringsubject_startswithstring+2 more parameters. See the server's own documentation for the full schema.
OUTLOOK_LIST_OUTLOOK_ATTACHMENTSLists metadata (like name, size, and type, but not `contentBytes`) for all attachments of a specified Outlook email message.2 paramsLists metadata (like name, size, and type, but not `contentBytes`) for all attachments of a specified Outlook email message.
message_idstringuser_idstringOUTLOOK_LIST_REMINDERSTool to retrieve reminders for events occurring within a specified time range. Use when you need to see upcoming reminders between two datetimes.3 paramsTool to retrieve reminders for events occurring within a specified time range. Use when you need to see upcoming reminders between two datetimes.
endDateTimestringstartDateTimestringuserIdstringOUTLOOK_LIST_USERSTool to list users in Microsoft Entra ID. Use when you need to retrieve a paginated list of users, optionally filtering or selecting specific properties.4 paramsTool to list users in Microsoft Entra ID. Use when you need to retrieve a paginated list of users, optionally filtering or selecting specific properties.
filterstringselectarrayskipintegertopintegerOUTLOOK_MOVE_MESSAGEMove a message to another folder within the specified user's mailbox. This creates a new copy of the message in the destination folder and removes the original message.3 paramsMove a message to another folder within the specified user's mailbox. This creates a new copy of the message in the destination folder and removes the original message.
destination_idstringmessage_idstringuser_idstringOUTLOOK_QUERY_EMAILSPrimary tool for querying Outlook emails with custom OData filters. Build precise server-side filters for dates, read status, importance, subjects, attachments, and conversations. Best for structured queries on message metadata. Returns up to 100 messages per request with pagi...7 paramsPrimary tool for querying Outlook emails with custom OData filters. Build precise server-side filters for dates, read status, importance, subjects, attachments, and conversations. Best for structured queries on message metadata. Returns up to 100 messages per request with pagi...
filterstringfolderstringorderbystringselectarrayskipintegertopintegeruser_idstringOUTLOOK_REPLY_EMAILSends a plain text reply to an Outlook email message, identified by `message_id`, allowing optional CC and BCC recipients.5 paramsSends a plain text reply to an Outlook email message, identified by `message_id`, allowing optional CC and BCC recipients.
bcc_emailsarraycc_emailsarraycommentstringmessage_idstringuser_idstringOUTLOOK_SEARCH_MESSAGESSearch Outlook messages using powerful KQL syntax. Supports sender (from:), recipient (to:, cc:), subject, date filters (received:, sent:), attachments, and boolean logic. Only works with Microsoft 365/Enterprise accounts (no @hotmail.com/@outlook.com). Examples: 'from:user@ex...7 paramsSearch Outlook messages using powerful KQL syntax. Supports sender (from:), recipient (to:, cc:), subject, date filters (received:, sent:), attachments, and boolean logic. Only works with Microsoft 365/Enterprise accounts (no @hotmail.com/@outlook.com). Examples: 'from:user@ex...
enable_top_resultsbooleanfromEmailstringfrom_indexintegerhasAttachmentsbooleanquerystringsizeintegersubjectstringOUTLOOK_SEND_DRAFTTool to send an existing draft message. Use after creating a draft when you want to deliver it to recipients immediately. Example: Send a draft message with ID 'AAMkAG…'.2 paramsTool to send an existing draft message. Use after creating a draft when you want to deliver it to recipients immediately. Example: Send a draft message with ID 'AAMkAG…'.
message_idstringuser_idstringOUTLOOK_SEND_EMAILSends an email with subject, body, recipients, and an optional attachment via Microsoft Graph API. Supports comma-separated email addresses in the to_email field for multiple recipients. Attachments require a non-empty file with valid name and mimetype.11 paramsSends an email with subject, body, recipients, and an optional attachment via Microsoft Graph API. Supports comma-separated email addresses in the to_email field for multiple recipients. Attachments require a non-empty file with valid name and mimetype.
attachmentstringbcc_emailsarraybodystringcc_emailsarrayfromstringis_htmlbooleansave_to_sent_itemsbooleansubjectstringto_emailstringto_namestringuser_idstringOUTLOOK_UPDATE_CALENDAR_EVENTUpdates specified fields of an existing Outlook calendar event. Implementation note: To avoid unintentionally clearing properties, the action first fetches the existing event, merges only the provided fields, and then PATCHes the merged updates. Unspecified fields remain uncha...11 paramsUpdates specified fields of an existing Outlook calendar event. Implementation note: To avoid unintentionally clearing properties, the action first fetches the existing event, merges only the provided fields, and then PATCHes the merged updates. Unspecified fields remain uncha...
attendeesarraybodyobjectcategoriesarrayend_datetimestringevent_idstringlocationobjectshow_asstringstart_datetimestringsubjectstringtime_zonestringuser_idstringOUTLOOK_UPDATE_CONTACTUpdates an existing Outlook contact, identified by `contact_id` for the specified `user_id`, requiring at least one other field to be modified.16 paramsUpdates an existing Outlook contact, identified by `contact_id` for the specified `user_id`, requiring at least one other field to be modified.
birthdaystringbusinessPhonesarraycategoriesarraycompanyNamestringcontact_idstringdepartmentstringdisplayNamestringemailAddressesarraygivenNamestringhomePhonesarrayjobTitlestringmobilePhonestringnotesstringofficeLocationstringsurnamestringuser_idstringOUTLOOK_UPDATE_EMAILUpdates specified properties of an existing email message; `message_id` must identify a valid message within the specified `user_id`'s mailbox.9 paramsUpdates specified properties of an existing email message; `message_id` must identify a valid message within the specified `user_id`'s mailbox.
bcc_recipientsarraybodyobjectcc_recipientsarrayimportancestringis_readbooleanmessage_idstringsubjectstringto_recipientsarrayuser_idstringOUTLOOK_UPDATE_EMAIL_RULEUpdate an existing email rule6 paramsUpdate an existing email rule
actionsobjectconditionsobjectdisplayNamestringisEnabledbooleanruleIdstringsequenceintegerOUTLOOK_UPDATE_MAILBOX_SETTINGSTool to update mailbox settings for the signed-in user. Use when you need to configure automatic replies, default time zone, language, or working hours. Example: schedule automatic replies for vacation.4 paramsTool to update mailbox settings for the signed-in user. Use when you need to configure automatic replies, default time zone, language, or working hours. Example: schedule automatic replies for vacation.
automaticRepliesSettingobjectlanguageobjecttimeZonestringworkingHoursobjectMCP 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 68 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, multiple mailboxes (one server per account via OUTLOOK_MCP_CONFIG_DIR), 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 68 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.
68 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. Note this limits the tools, not the token -- see What read_only does and does not do.permanent: true.7d is seven days ago, +7d is seven days from now, now is this moment. Units: m, h, d, w.attachments_dir, so a message that asks an agent to mail a file elsewhere on disk cannot be obeyed.delta_token is caller-held state, so it is untrusted input. Every URL that would carry a Graph bearer token is parsed and required to be https on graph.microsoft.com, which is what stops a poisoned cursor from redirecting your mailbox token to someone else. Its path must also be the delta endpoint of the tool it was handed to, so a cursor cannot point a delta tool at some other part of the mailbox.morning_brief, triage_folder and catch_up ship as MCP prompts, so the common sequences do not have to be reconstructed call by call.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, quoted prior-message text in threads, body previews and categories on inbox listings — typical payload reduction ~10×. On outlook_list_events the trade is different and smaller: the attendee list becomes a count, and organizer, response_status, type and show_as come off, so a concise scan cannot tell a recurring occurrence from a one-off. 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.ReadWriteMailboxSettings.ReadUser.Read, offline_accessNo client secret is needed. The device code flow uses public client auth.
Option A — from PyPI (recommended):
uv tool install outlook-graph-mcp
# or: pipx install outlook-graph-mcp
# or: pip install outlook-graph-mcp
Option B — from source:
git clone https://github.com/mpalermiti/outlook-mcp.git
cd outlook-mcp
uv sync
Create ~/.outlook-mcp/config.json:
{
"client_id": "YOUR_APPLICATION_CLIENT_ID",
"tenant_id": "consumers",
"timezone": "America/Los_Angeles",
"read_only": true,
"attachments_dir": "~/.outlook-mcp/attachments"
}
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:
{
"mcpServers": {
"outlook": {
"command": "outlook-mcp"
}
}
}
If installed from source:
{
"mcpServers": {
"outlook": {
"command": "uv",
"args": ["--directory", "/path/to/outlook-mcp", "run", "outlook-mcp"]
}
}
}
For OpenClaw, use the openclaw mcp CLI — it writes to mcp.servers in ~/.openclaw/openclaw.json for you:
# If installed from PyPI:
openclaw mcp set outlook '{"command":"outlook-mcp"}'
# If installed from source:
openclaw mcp set outlook '{"command":"uv","args":["--directory","/path/to/outlook-mcp","run","outlook-mcp"]}'
# Verify:
openclaw mcp list
openclaw mcp show outlook --json
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:
uv run outlook-mcp auth
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.
The consent screen lists the full read-write set (Mail.ReadWrite, Mail.Send, Calendars.ReadWrite, Contacts.ReadWrite, Tasks.ReadWrite, MailboxSettings.Read, User.Read) — even when the config starts with read_only: true, because read_only gates the tools, not the token (see What read_only does and does not do), and the scopes a first consent leaves out can never be granted later without logging in again. (The one exception is read_only_consent: true, which asks for the read permissions only; it exists for a separate read-only app, described under the same heading.) Every request afterwards — silent refresh and each Graph call — uses the .default scope, which on an already-consented account means exactly "the set you granted". That ordering is deliberate: on personal accounts a first consent asking only for .default can land a session with no delegated permissions, which then can't be redeemed (AADSTS70000) without logging in again (#82).
Other CLI commands:
uv run outlook-mcp status # Check auth status
uv run outlook-mcp logout # Clear credentials
uv run outlook-mcp serve # Start MCP server (default, used by OpenClaw/Claude)
me-token-to-replace is invalid on every callYou're on 1.22.0 installed after 2026-09-18. A fresh install of that version resolves microsoft-kiota-* 1.13 or later, which msgraph-core doesn't yet handle, so every /me request reaches Graph as /users/me-token-to-replace. Fixed in 1.22.1, which caps kiota below 1.13 (#80):
uv tool upgrade outlook-graph-mcp
# or: pipx upgrade outlook-graph-mcp
# or: pip install --upgrade outlook-graph-mcp
Your config and sign-in are untouched; no re-auth needed.
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:
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt # httpx + Python ssl
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt # azure-identity auth
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.
Dates. Every datetime parameter (after, before, start, end, due,
deferred_send_datetime) accepts ISO 8601 — 2026-10-22 or 2026-10-22T14:30:00Z — or a
relative offset: 7d is seven days ago, +7d is seven days from now, and now is
this moment. Units are m, h, d, w. Bare means ago, matching the usual CLI
convention, so a due date in the future needs the +. Zone-less input is interpreted in your
configured timezone; responses are always UTC.
| 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 — occurrence or exception for an instance of a series, singleInstance for a one-off — so a listing tells recurring work apart without a second call. A seriesMaster never appears here: calendarView returns expanded instances, so use outlook_get_event to see a master. Each event also carries show_as, the free/busy status Outlook labels "Show as". Configurable via days, after, before. calendar selects which calendar to read: omit (or "primary") for the default, otherwise a display name (case-insensitive; not-found and ambiguous errors name what exists) or an ID from outlook_list_calendars. A cursor continues the listing it came from, so later pages need neither calendar nor a second lookup. concise=True omits type and show_as. |
outlook_get_event | Get full event details: attendees, body, online meeting URL, recurrence, type (singleInstance / seriesMaster / occurrence / exception), show_as. |
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). Each changed event carries every field the default outlook_list_events listing returns, type and show_as included, plus is_deleted (concise=True has its own narrower shape, which the delta tool does not mirror). Unlike the listing, this endpoint does return seriesMaster items, so a caller seeding from outlook_list_events should expect ids here that the seed never held. Deletes come back as {id, is_deleted: True} and nothing else. 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. show_as sets the free/busy status Outlook labels "Show as" — free, tentative, busy, oof (out of office), workingElsewhere, or unknown; omit it and Graph applies its own default of busy. |
outlook_update_event | Update event fields (subject, time, location, body, attendees, all-day, show_as). 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, while show_as patches on its own. Patching a time keeps the zone the event is anchored in; timezone (with start+end) re-anchors it elsewhere, which is how a series created before zones existed gets repaired. A start, end or recurrence patch to a series with edited or deleted occurrences is refused, naming them — Graph would silently discard every one. |
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. Summaries carry categories; outlook_search_contacts omits the key because Graph's $search does not return it. |
outlook_search_contacts | Search contacts by name or email. |
outlook_get_contact | Get full contact details by ID, including home/business/other addresses, categories and personal notes. |
outlook_create_contact | Create a new contact. |
outlook_update_contact | Update contact fields. home_address, business_address and other_address take the shape outlook_get_contact returns — any subset of street, city, state, postal_code, country_or_region — and replace that whole address, so pass back every part you want to keep. Omit one to leave it untouched. |
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_get_task | Get full task details: notes (body), checklist items (ordered unchecked-first), due, recurrence flag. |
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. |
outlook_add_checklist_item | Add a checklist item (sub-step) to a task. |
outlook_update_checklist_item | Update a checklist item — mark done (is_checked) or rename (partial patch). |
outlook_delete_checklist_item | Delete a checklist item from a task. |
outlook_list_task_attachments | List attachments on a To Do task (id, name, size, content_type) with pagination. |
outlook_download_task_attachment | Download a task attachment's content to a local file (confined to attachments_dir). |
outlook_upload_task_attachment | Attach a local file (from attachments_dir) to a task via inline base64 POST (1 byte – 20 MiB). |
outlook_delete_task_attachment | Remove an attachment from a To Do 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. |
Since 1.20.0, these tools only reach
attachments_dir(default~/.outlook-mcp/attachments). A bare filename resolves inside it; a path outside it is refused, including via a symlink. To email a file, move it there first — or widenattachments_dir, understanding that anything reachable from it can be sent. Before 1.20.0 these tools could read any file the server process could read, which meant an email asking an agent to attach one could be obeyed.The same fence covers the To Do attachment tools (
outlook_upload_task_attachment,outlook_download_task_attachment): uploads read fromattachments_dirand downloads write into it, so neither can sweep arbitrary files off disk. (Downloads are nottodo_write-gated — they are reads, likeoutlook_download_attachment; the fence, not the category, is what confines them.)
| Tool | Description |
|---|---|
outlook_list_attachments | List attachments on a message. |
outlook_download_attachment | Download an attachment and save decoded bytes into attachments_dir. |
outlook_send_with_attachments | Send a message with attachments read from attachments_dir (auto upload session for >3MB). |
outlook_attach_to_draft | Add attachments from attachments_dir 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). |
Three workflows ship as MCP prompts, so the common sequences do not have to be reconstructed call by call. Any MCP client that supports prompts will list them; in most clients they appear as slash commands or a prompt picker.
| Prompt | Arguments | What it does |
|---|---|---|
morning_brief | folder (default inbox) | Today's events, unread mail and tasks due, in the cheapest order — one scan each, concise=True, batched reads. |
triage_folder | folder (default inbox), count (default 50) | One cheap scan of a folder, sorted into reply / archive / junk, applied with a single outlook_batch_triage call rather than one call per message. |
catch_up | since (default 24h) | What changed in mail, calendar and contacts, via the delta path — roughly ten times cheaper than re-scanning on a schedule. |
They cost nothing until invoked: prompts/list carries only a name and one line each, and
the body is fetched on use.
Config lives at ~/.outlook-mcp/config.json (created with 0600 permissions on macOS and Linux; see Config permissions below for Windows). It is read as UTF-8 on every platform, and a byte-order mark is accepted. Set the OUTLOOK_MCP_CONFIG_DIR environment variable to move that settings directory (config.json, auth record, and the attachments default move with it) — see Two accounts, two instances below.
| 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"). Interprets zone-less dates, and anchors every event you create — a recurring event is expanded in this zone, so on the default "UTC" a 09:00 weekly meeting shifts an hour when the clocks change. Set it to where you are. |
read_only | bool | false | When true, all write tools (send, reply, move, delete, create, update, RSVP) return an error. Gates the tools, not the Microsoft token -- see below. |
read_only_consent | bool | false | When true, outlook-mcp auth asks Microsoft for the read permissions only, instead of the read-write set. For a second, read-only app registration -- see What read_only does and does not do. Requires read_only: true; the config is refused without it. |
attachments_dir | string | "~/.outlook-mcp/attachments" | The only directory the attachment tools may read from or write to. Every path an agent supplies is resolved and must land inside it — a symlink out or a .. is refused. Widen it only if you understand that anything reachable can be emailed. |
allow_categories | list[string] | [] | Optional. Restrict write tools to specific categories (see below). Empty list = all writes allowed when read_only: false. |
allow_unencrypted_token_cache | bool | false | Permit the OAuth token cache to be written in cleartext when the platform has no encrypted store (Linux without libsecret). Off by default: authentication stops with an explanation rather than silently persisting a reusable Graph token in plaintext. macOS and Windows always encrypt and are unaffected. |
OUTLOOK_MCP_TOOLSETSAll 68 tool schemas load into the client's context every turn (the chars/4 proxy test_tool_surface_budget.py measures with; a different yardstick than the ~8.6k o200k figure in ROADMAP for the 62-tool surface). 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.
# e.g. a recurring mail + calendar agent: ~30 tools instead of 68 (~55% fewer tool tokens/turn)
OUTLOOK_MCP_TOOLSETS="mail,calendar,digest,delta"
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.
OUTLOOK_MCP_CONFIG_DIROne server process serves one mailbox. To work against two accounts, register two client entries and give each its own settings directory with OUTLOOK_MCP_CONFIG_DIR — pair it with OUTLOOK_MCP_TOOLSETS so each instance also only loads the tool groups it needs:
{
"mcpServers": {
"outlook-net": {
"command": "outlook-mcp",
"env": {
"OUTLOOK_MCP_TOOLSETS": "mail,calendar,contacts",
"OUTLOOK_MCP_CONFIG_DIR": "~/.outlook-mcp-net"
}
},
"outlook-neko": {
"command": "outlook-mcp",
"env": {
"OUTLOOK_MCP_TOOLSETS": "todo",
"OUTLOOK_MCP_CONFIG_DIR": "~/.outlook-mcp-neko"
}
}
}
}
Then run outlook-mcp auth once per instance, with the same env set, to write each auth record in its own directory. Each instance reads its own config.json (own client_id, timezone, permissions) from its own directory.
Only move the config directory — never HOME. The token cache is not in it: it stays in the OS keyring, and on macOS every azure-identity cache on the host shares one Keychain item, coordinated through a signal file — on this server, ~/.IdentityService/outlook-mcp.nocae (azure-identity appends .nocae to every non-CAE cache name; a CAE cache would be a different file over the same item). Both processes must keep consulting that same signal file so their cache writes lock and merge into the one shared entry — which is exactly what moving the config directory preserves and redirecting HOME (or the cache location) would break: two signal files that each believe they own the Keychain item overwrite each other's token. OUTLOOK_MCP_CONFIG_DIR deliberately moves only where config.json, the auth record, and attachments live; unset or empty keeps the default ~/.outlook-mcp.
read_only does and does not doread_only: true stops outlook-mcp's write tools from running. Ask it to send mail and it
refuses.
It does not make your Microsoft credential read-only. The first sign-in consents the full read-write set, even with read_only: true in the config — the scopes a first consent leaves out can never be added without logging in again (every refresh afterwards uses .default -- "everything this account has already consented" -- so a session granted only read scopes would fail every write with 403 no matter what the config says). The stored token can send mail whether read_only is on or off, and a session consented before you turned read_only on keeps its write scopes.
Two consequences worth understanding:
read_only is a line in a text file. Anything able to edit ~/.outlook-mcp/config.json
turns it off and has write access immediately -- no re-authentication, no new consent
prompt.So treat read_only as a guardrail against an agent doing something rash, not as a
security boundary. If you want a credential that genuinely cannot write, have Microsoft
enforce it rather than us:
Register a second Azure app and give it the read permissions only: Mail.Read,
Calendars.Read, Contacts.Read, Tasks.Read, MailboxSettings.Read, User.Read. It has
to be an app this account has never granted write access to. Microsoft remembers consent
per app, and a refresh returns everything that app was ever granted.
Point client_id at it and set both keys:
{ "client_id": "<the read-only app>", "read_only": true, "read_only_consent": true }
Run outlook-mcp auth. With read_only_consent the consent screen lists the read
permissions only. Without it, sign-in would ask this app for the read-write set too.
read_only_consent is refused without read_only: true: a sign-in that asked only for read
access cannot write, so a server expecting writes would fail on every one. And a sign-in
belongs to the app it was made with. After client_id changes, the server will not use the
old one — outlook-mcp status and outlook_auth_status say so until you run
outlook-mcp auth again.
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 — your own calendar. The parts that email other people text the agent wrote need mail_send as well: inviting attendees, rewording (subject, body, location) any event that already has them, and adding a message to an RSVP. A bare RSVP, a time change and a cancellation still notify the people involved, but carry nothing the agent wrote |
contacts_write | create/update/delete contact | Moderate |
todo_write | create/update/complete/delete task, checklist items; upload/delete task attachments | Moderate — your own task list, but outlook_upload_task_attachment reads local files from attachments_dir and pushes their bytes to Graph, and task/checklist/attachment deletes are irreversible. Listing and downloading attachments are plain reads, gated like every other read (not at all) and fenced to attachments_dir |
Example policies:
Draft-only assistant (agent can compose drafts, you review and send):
{ "read_only": false, "allow_categories": ["mail_drafts", "mail_triage", "todo_write"] }
(Note that todo_write includes the write-side task-attachment tools — file reads
from attachments_dir, uploads to Graph, and irreversible deletes. Listing and
downloading task attachments are reads and are not write-gated, like the mail
attachment reads — see the table above.)
Calendar-only (agent can manage your schedule, nothing else):
{ "read_only": false, "allow_categories": ["calendar_write"] }
(It cannot invite anyone. An invitation is an email, so attendees need mail_send too — add it
if the agent should set up meetings with other people, not just block out your own time.)
Full write access (agent can do everything):
{ "read_only": false }
Read-only (safest default, no writes):
{ "read_only": true }
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.
outlook_list_tasks.)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 and the config file 0600, and a loose mode on the file is repaired on load. On Windows those POSIX modes cannot be enforced — os.chmod there sets only the read-only attribute — so access is governed by the path's Windows ACL, including whatever it inherits from the directory it was created under, which this server neither applies nor verifies. Symlinked configs are rejected on every platform.# Install dev dependencies
uv sync --extra dev
# Run tests
uv run pytest
# Lint
uv run ruff check src/ tests/ scripts/
# Format (CI fails if `ruff format --check` would change a file)
uv run ruff format src/ tests/ scripts/
# Run server locally (stdio)
uv run outlook-mcp
Requirements: Python 3.10+
MIT. See LICENSE.