outlook-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| OUTLOOK_MCP_CONFIG | No | Overrides the path of the config file (default ~/.outlook_mcp_config.json) that stores the protected senders keep-list. | |
| OUTLOOK_MCP_CLIENT_ID | Yes | Your Azure app registration's Application (client) ID, used for the OAuth device-code login to Microsoft Graph. |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| list_foldersA | List all mail folders (Inbox, Archive, Deleted Items, custom folders, etc.) with their ids and display names. |
| inbox_summaryA | Quick counts for the inbox: total messages and unread messages. |
| list_messagesA | List messages in a folder, newest first (unless searching). Returns compact rows keyed by a short Args:
folder: folder id or well-known name (inbox, archive, deleteditems, sentitems, drafts, junkemail).
top: max number of messages to return per page (ignored if fetch_all is True).
skip: number of messages to skip, for manual paging (ignored if fetch_all is True).
filter_query: raw OData $filter, e.g. "receivedDateTime le 2026-01-01T00:00:00Z" or "isRead eq false".
search: free-text search (Graph $search); when set, filter_query/order are ignored.
fetch_all: if True, follow pagination automatically and return up to max_results messages
instead of a single page of |
| get_messageA | Get one message by ref (or raw id). Body is returned as plaintext and truncated to max_chars to keep it token-cheap. Args: message_id: a short ref from list_messages (e.g. "m12"), or a raw Graph id. include_body: set False to get metadata only (subject/from/date), no body. max_chars: truncate the plaintext body to this many characters (0 = no limit). |
| archive_messageB | Move a message (by ref or raw id) to the Archive folder. |
| move_messageB | Move a message (by ref or raw id) to a folder (id or well-known name). |
| delete_messageA | Move a message (by ref or raw id) to Deleted Items — recoverable from there until you empty it. Marks it read first (mark_as_read=False to keep it unread) so deleted mail doesn't linger in your unread count. |
| mark_readB | Mark a message (by ref or raw id) as read or unread. |
| create_reply_draftA | Create a draft reply to a message (by ref or raw id). Does NOT send it; a human reviews/sends from Outlook. |
| bulk_mark_readB | Mark many messages (refs or raw ids) read/unread in one call (Graph $batch). |
| bulk_move_messagesB | Move many messages (refs or raw ids) to a folder (id or well-known name) in one call. |
| bulk_archive_messagesB | Move many messages (refs or raw ids) to the Archive folder in one call. |
| bulk_delete_messagesA | Move many messages (refs or raw ids) to Deleted Items in one call, marking them read first (mark_as_read=False to keep them unread). Refuses and deletes nothing if any message is from a protected sender (see protected_senders) — re-call with allow_protected=True to override. Senders are only known for messages this server has already listed or read; refs it has never seen cannot be checked. |
| bulk_by_queryA | Find every message matching a query and apply one action to all of them in a single call — the message ids never enter the conversation, so this is by far the cheapest way to triage in bulk. action: one of archive, delete, mark_read, mark_unread, move
(move requires destination_folder, which may be a folder display name).
folder / filter_query / search: same matching semantics as list_messages
(fetches all matches up to max_results).
received_before / received_after: ISO dates (e.g. "2026-01-01"); applied
client-side AFTER matching, so they compose with |
| create_draftA | Compose a brand-new draft email (NOT a reply). It is saved to Drafts and never sent — you review and send it from Outlook. Args: to: recipient email addresses. subject: subject line. body: message body (plain text by default; set html=True to send HTML). cc: optional cc email addresses. html: treat body as HTML instead of plain text. |
| create_folderB | Create a mail folder. parent_folder (id or display name) nests it; omit for a top-level folder. Useful before moving mail into a new folder. |
| sender_summaryA | Group a whole folder by sending address and report, per sender, how many messages there are and how many were actually opened. This is the tool to reach for before proposing anything for deletion. A sender's read rate is measured evidence of whether the mailbox owner wants their mail; guessing from the brand name is not, and gets it wrong. Returns rows {sender, brand, domain, count, read, unread, read_pct,
newest, oldest, sample_ref}, biggest sender first. Args: folder: folder id or well-known name. min_count: drop senders with fewer than this many messages. top: return at most this many sender rows (after sorting by count). max_results: cap on messages scanned. unread_only: summarise only unread mail. |
| find_newslettersA | Scan a folder for bulk mail (messages carrying a List-Unsubscribe header) and report how each sender can be unsubscribed. Grouped by sender by default, because unsubscribing is per sender while the underlying listing is per message — the ungrouped form makes the model dedupe 200 rows by hand to reach the ~50 decisions that actually exist. Each row carries read/unread counts so a sender can be judged on engagement rather than on what its name suggests. IMPORTANT: the List-Unsubscribe header marks a bulk sender, not junk. Order confirmations, delivery updates, statements and voucher codes all carry it. Treat this as a list of candidates to show the user, never as a list to sweep. Returns {checked, headers_ok, header_fetch_failed, newsletter_messages, senders: [...]}. The header counts are there so a discrepancy between this and bulk_by_query(only_newsletters=True) is visible rather than mysterious: header sub-requests that fail are silently dropped from the scan. Args: folder: folder id or well-known name. max_check: how many recent messages to scan. group_by_sender: set False for the raw per-message rows. |
| unsubscribeA | Unsubscribe from the bulk sender of a message (ref or raw id). Reads the message's List-Unsubscribe headers and:
With execute=False (default) it only reports the available method/target so you can decide. Caveat: unsubscribing confirms your address is live, so do it for legitimate senders — for actual spam, prefer delete/block instead. |
| unsubscribe_manyA | Unsubscribe from many bulk senders in one call. Deduplicates on the unsubscribe target, never on the sender: LinkedIn job alerts all arrive from one address but carry a different savedSearchId each, so skipping by sender leaves most streams subscribed while reporting success. Two messages for the same stream still carry different per-message tokens and so both get posted — a wasted request is cheaper than a missed unsubscribe. Same per-message semantics as unsubscribe(), but the header fetch is batched and the whole sweep is one tool call instead of one per sender. Returns {attempted, unsubscribed, needs_browser, drafted, failed,
results: [...]}. |
| protected_sendersA | Read or edit the keep-list of senders that bulk operations won't touch. Patterns are matched case-insensitively against the sending address: one containing * or ? is a glob over the whole address ("*@edfenergy.com"), one without is a substring test ("dwp"). Use this for the mail that would be expensive to lose — benefits, legal, medical, bills, anything with a case number — so a broad sweep can't reach it. bulk_by_query excludes protected senders by default and says how many it held back; bulk_delete_messages refuses outright. Both can be overridden per call once you've looked. Call with no arguments to just read the list. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 21 tools
The core distinction between single-message and bulk tools is mostly clear, but bulk_by_query overlaps heavily with the dedicated bulk_* tools and with archive_message/move_message/delete_message/mark_read. archive_message also largely duplicates move_message to Archive, and find_newsletters overlaps with bulk_by_query(only_newsletters=True), so an agent has several plausible routes for the same operation.
Most tools use snake_case and are readable, but the naming convention is mixed: many are verb_noun (list_messages, move_message, create_draft), while others are noun-centric or phrase-based (inbox_summary, sender_summary, bulk_by_query, protected_senders). It is not chaotic, but the pattern is not predictable enough for a high score.
At 21 tools, the server is in the heavy borderline range. The domain is complex enough to justify many operations, but there is noticeable duplication between general bulk actions, dedicated bulk tools, and single-message action tools, which inflates the surface.
The tool set covers the main mail triage lifecycle well: folders, listing/reading messages, summaries, drafts and reply drafts, single and bulk move/delete/archive/mark operations, newsletter discovery/unsubscribe, and protected senders. Minor gaps remain, such as sending mail, attachment handling, folder rename/delete, and draft editing, but core workflows are covered.