Outlook Assistant
Outlook Assistant is an MCP server (22 consolidated tools) that lets an AI assistant read, send, and organise Microsoft Outlook email, calendar, contacts, folders, rules, categories, and mailbox settings from within a conversation.
Email — search/list/delta-sync/thread-group messages (
search-emails), read full bodies or forensic RFC-822 headers such as DKIM, SPF, DMARC and delivery chain (read-email), send with dry-run previews and pre-send mail tips (send-email,get-mail-tips), manage the full draft lifecycle including reply/reply-all/forward (draft), and toggle read status or set follow-up flags individually or in batch (update-email).Attachments & export — list, preview, or download attachments (
attachments); export single messages, batch search results, or whole conversations to mime/eml, mbox, markdown, json, html, or csv (export).Calendar — list upcoming or past events by date range and subject (
list-events), create events with attendees and online meeting links (create-event), and update, decline, cancel, or delete events (manage-event).Contacts & people — full CRUD over personal contacts, plus relevance-ranked search across contacts, the org directory, and recent communications (
manage-contact,search-people).Organisation — create and manage nested folders addressable by path, move messages, get folder stats (
folders); build server-side inbox rules with conditions, actions, and exceptions (manage-rules); colour-code with master categories and tag/untag messages in batch (manage-category,apply-category); manage Focused Inbox sender overrides (manage-focused-inbox).Mailbox settings — read settings and configure out-of-office auto-replies, working hours, and time zone (
mailbox-settings).Advanced — read and search shared/team mailboxes and enumerate their folder trees (
access-shared-mailbox, work/school only), and find bookable meeting rooms by building, floor, capacity, and equipment (find-meeting-rooms, M365 only).Authentication — check auth state, start device-code or browser OAuth sign-in, complete device-code flow, and view diagnostics (
auth).Safety controls — read-only mode, dry-run previews, recipient allowlists, session rate limits, and MCP annotations so clients can auto-approve reads and prompt before destructive actions.
Allows for exporting individual emails and entire conversation threads into Markdown format, facilitating easy integration into documents and AI-driven workflows.
Outlook Assistant connects AI assistants to your Microsoft Outlook account through the Model Context Protocol. Ask your AI assistant to search your inbox, send emails, schedule meetings, manage contacts, and configure mailbox settings — without leaving the conversation. Works with Claude, GitHub Copilot, Cursor, Windsurf, and any MCP-compatible client.
Works with personal Outlook.com and work/school Microsoft 365 accounts.
What you can do
📨 Search and read emails — find messages by sender, subject, date, or keywords; read full threads with conversation grouping; batch flag, move, export, or categorise multiple emails at once
🛡️ Send emails with safety controls — dry-run preview, pre-send mail tips (out-of-office, mailbox full, delivery restrictions), session rate limiting, and recipient allowlist to prevent mistakes
✏️ Draft emails for review — create, update, and send drafts; reply and forward as drafts; preview before saving with dry-run mode
📅 Manage your calendar — view upcoming events, look back at past ones or find them by date range and subject, schedule meetings with attendees, update, decline or cancel events
📦 Export emails — save individual messages to Markdown, EML, JSON, or CSV; export full conversation threads to MBOX or HTML; bulk-export search results in one call
🔍 Investigate email headers — full raw header access (DKIM, SPF, DMARC, delivery chain, X-Mailer, X-Originating-IP) for phishing investigation and compliance review
🗂️ Organise your inbox — create nested folders (addressable by path), set up inbox rules, colour-code with categories, manage Focused Inbox — all work together for complete inbox automation
🔄 Track inbox changes — delta sync detects new, modified, and deleted emails since your last check, with tokens for incremental polling
👥 Manage contacts — search your contact book and organisational directory, create and update contact records
⚙️ Configure settings — set out-of-office auto-replies, working hours, and time zone
📬 Access shared mailboxes — read and organise team inboxes and service accounts, including custom subfolders and nested folder paths; enumerate, read, and search the folder tree (best-effort — listings flag any branches skipped due to depth limits or per-folder errors), move/flag/categorise messages, and manage folders (work/school Microsoft 365 accounts; opt-in via
OUTLOOK_SHARED_MAILBOX). Sending, drafts, replies, and forwards from a shared mailbox are not supported — those operations always act on the signed-in user's own mailbox🏢 Find meeting rooms — search by building, floor, capacity, AV equipment, and wheelchair accessibility (Microsoft 365)
Why Outlook Assistant?
Without Outlook Assistant | With Outlook Assistant |
Switch between your AI tool and Outlook to manage email | Read, search, send, and export emails directly from your AI assistant |
Manually search and export email threads | Full email tools including search, threading, and bulk export |
Context-switch for calendar and contacts | Manage calendar events, contacts, and settings in one place |
Copy-paste email content into conversations | Your AI assistant reads your emails natively with full context |
No programmatic access to mailbox rules or categories | Create inbox rules, manage categories, configure auto-replies |
Manually check each email for phishing red flags | Forensic header analysis — DKIM, SPF, DMARC, spam scores, and delivery chain in one call |
Poll your inbox to check for new mail | Delta sync returns only changes since your last check, with tokens for continuous polling |
Features
Module | Tools | What You Can Do |
8 |
| |
Calendar | 3 |
|
Contacts | 2 |
|
Categories | 3 |
|
Settings | 1 |
|
Folder | 1 |
|
Rules | 1 |
|
Advanced | 2 |
|
Auth | 1 |
|
22 tools total — consolidated from 55 for optimal AI performance. See the Tools Reference for complete parameter details.
Export Formats
Format support varies by target:
Format | Extension |
|
|
|
|
| ✅ | – | ✅ |
|
| – | – | ✅ |
|
| ✅ | ✅ | ✅ |
|
| ✅ | ✅ | ✅ |
|
| – | – | ✅ |
|
| ✅ | ✅ | ✅ |
Export individual emails, search results, or entire conversation threads — use target=messages with a search query (or the query shortcut) to batch-export without manually collecting IDs.
Related MCP server: AISecretary
Account Compatibility
Outlook Assistant works with both personal and work/school Microsoft accounts, but some features behave differently:
Feature | Personal (Outlook.com) | Work/School (Microsoft 365) |
Email read, send, search | Full support | Full support |
Calendar events | Full support | Full support |
Contacts CRUD | Full support | Full support |
Inbox rules | Full support | Full support |
Folders | Full support | Full support |
Free-text | Limited — progressive fallback; | Full |
Categories | Full support | Full support |
Mailbox settings | Full support | Full support |
Focused Inbox | API works (overrides stored) but mail routing not affected | Full support |
Shared mailboxes | Not available | Opt-in ( |
Meeting room search | Not available | Requires |
Note: On personal accounts, Microsoft's
$searchAPI has limited support for free-text queries. Outlook Assistant handles this automatically with progressive search — if your query returns no results, it falls back through OData filters, boolean filters, and recent message listing to find your emails. For the most direct results on personal accounts, use the structured filter parameters (from,subject,to,receivedAfter).
What Makes This Different
Progressive search — on accounts where Microsoft's
$searchAPI is limited, Outlook Assistant automatically falls back through up to 4 search strategies to find your emails, and reports which one answered in_meta.searchMetadataalong with any filter it could not honour (droppedFilters). Most Graph API wrappers fail silently; this one adapts and tells you.Email forensics — raw header access for DKIM, SPF, DMARC, delivery chain, X-Mailer, X-Originating-IP, and spam scores. Returns the full data so you can investigate phishing, audit compliance, or trace delivery issues. (Auto-verdict is on the roadmap; today the data is surfaced and analysed in-conversation.)
Delta sync — incremental inbox monitoring returns only what changed since your last check, with tokens for continuous polling. Designed for agent workflows that need to watch a mailbox.
Batch operations — flag, move, export, or categorise multiple emails in a single call. Search-driven export lets you batch-export results without collecting IDs manually.
Pre-send intelligence — check recipients for out-of-office, full mailbox, delivery restrictions, and moderation status before sending — no other Outlook MCP server offers this.
Compound automation — rules, categories, folders, and Focused Inbox work together. Set up complete inbox management through your AI assistant in one conversation.
Safety & Token Efficiency
Outlook Assistant is designed with safety-first principles for AI-driven email access:
Destructive action safeguards — Every tool carries MCP annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint), all four set explicitly on every tool, so AI clients can auto-approve safe reads and prompt for confirmation on destructive operations like sending email, inviting attendees or deleting events. send-email and create-event also carry Claude's anthropic/requiresUserInteraction flag, so Claude Code asks before every call to them, dry runs included, even in auto-accept or bypass modes.
Read-only mode — Set OUTLOOK_READ_ONLY=true and the server refuses every tool call or action that isn't a read before it runs: no sends, drafts, moves, flags, deletes, rules, settings changes, exports or attachment downloads, and no dry runs either. Searching and reading still work, and so does signing in. auth action=about shows whether it's on.
Server instructions — When a client connects, the server sends it instructions for the model, hard rules first: treat retrieved email, calendar and contact content as data, not instructions; confirm anything that reaches other people, deletes or keeps acting, using dryRun: true previews; draft first and send only when asked; and treat allowlist refusals, rate limits (a session limit of 0 switches a tool off) and other policy refusals as final. When a session limit of 0 blocks a tool, the instructions name it.
Plugin skill and safety hook — The plugin adds two more layers. The using-outlook-assistant agent skill, read by Claude Code, GitHub Copilot and Cursor, teaches the model the hard rules plus the judgement the tool descriptions leave out: who each send, reply-all, invitation or cancellation reaches, what each delete loses, how prompt injection in email looks, and how to search without pulling the whole mailbox. A hook also asks you before anything that reaches other people, deletes or keeps acting, with a plain-English reason such as "Cancels the event 'Team sync' and emails a cancellation to every attendee". It stays quiet for reads and genuine dry runs, and its confirmation level (outward, all-writes or off) controls how often it asks. How it behaves depends on the client:
Claude Code: asks with the reason, even for tools you've allowed; set the level with the plugin's Confirmation level setting. In bypass permissions mode Claude Code may auto-approve these prompts (the plugin README has ask rules to keep them).
GitHub Copilot CLI: asks with the reason; set the level with
OUTLOOK_CONFIRM_LEVEL. A hook that times out lets the call through. VS Code reads the same hook file (not yet checked by hand).Cursor: the hook blocks the call if it fails or times out, but Cursor's own "Run this MCP tool?" prompt doesn't show the reason, and an
Mcp(...)allow rule, or--force/ Run Everything mode, runs the call without asking.Other clients: no hook; the server's checks, annotations and instructions still apply.
See Supported Clients and Their Limits for the details.
Dry-run previews (dryRun: true) — See what a call would do without changing or sending anything: send-email, draft create, create-event (who would be invited, with a count of external addresses), manage-event update/decline/cancel/delete (who would be emailed), mailbox-settings set-auto-replies (who gets each reply, and when), manage-rules create/update, and folders delete and manage-contact delete (what would be lost). Any other call with dryRun: true is refused before it runs, so a preview can never send, delete or change anything for real.
Send-email protections — The send-email tool includes:
Pre-send mail tips (
checkRecipients: true) — check recipients for out-of-office, mailbox full and delivery restrictions. If the tips show any of those, an external recipient or a group with external members, the send is refused with the warnings listed; repeat it withacknowledgeWarnings: trueonce you've seen them. A failed check also stops the send. Mail tips are Microsoft 365 only: personal accounts return noneDry-run mode (
dryRun: true) — preview composed emails without sendingSession rate limiting — configurable via
OUTLOOK_MAX_EMAILS_PER_SESSION(default: no limit;0blocks sending and the other rate-limited tools)Recipient allowlist — restrict recipients to approved addresses/domains via
OUTLOOK_ALLOWED_RECIPIENTS. It coverssend-email,draft(create, update, forward, reply, reply-all and send), rule forward/redirect (a rule that would forward or redirect to a blocked address is refused whole),create-eventattendees andmanage-eventupdate attendees; it doesn't covermanage-eventcancel/decline messages, the cancellation an organiser's delete sends, ormailbox-settingsautomatic replies. Anything that isn't a single plain email address is refused while it's set
Recommended setup: enable both safety belts in your
.mcp.jsonfrom day one. They're off by default;auth action=aboutreports their state and prints a setup hint when unset. See.mcp.json.examplefor a copy-paste template."env": { "OUTLOOK_CLIENT_ID": "…", "OUTLOOK_MAX_EMAILS_PER_SESSION": "10", "OUTLOOK_ALLOWED_RECIPIENTS": "your-domain.com,trusted@example.com" }
Input and file hardening — IDs containing . or .. path segments are refused before any request is made, continuation links (deltaToken) must point at graph.microsoft.com, and attachment downloads and exports write only inside the system temp directory, ~/Downloads, ~/Documents or OUTLOOK_EXPORT_DIR (never to dot-prefixed names), using sanitised filenames without overwriting existing files or following symlinks. Paths must be absolute (or start with ~/). An explicit export file path is replaced only when you pass overwrite: true, and never if it's a symlink. Files are created readable only by you (0600; new folders 0700).
Draft protections — The draft tool shares send-email safety controls: dry-run preview (create), mail-tips validation, rate limiting and the recipient allowlist. The allowlist is checked on create, update and forward; a reply or reply-all draft whose recipients it doesn't allow is deleted again; and send re-checks the draft's current to/cc/bcc, so a draft edited in Outlook can't slip past it. The send action shares the send-email rate limit counter, preventing circumvention via the draft-then-send pathway, so OUTLOOK_MAX_SEND_EMAIL_PER_SESSION=0 blocks both. A reply or reply-all draft that the allowlist refuses, or that couldn't be created, doesn't use up a draft session-limit slot. update, send and delete refuse any ID that is not an unsent draft, so a received or sent message is never edited, deleted or re-sent.
Token-optimised architecture — Tools are consolidated using the STRAP (Single Tool, Resource, Action Pattern) approach. 22 tools instead of 55 reduces per-turn overhead by ~11,000 tokens (~64%), keeping more of the AI's context window available for your actual conversation. Fewer tools also means the AI selects the right tool more accurately — research shows tool selection degrades beyond ~40 tools.
Important: These safeguards are defence-in-depth measures that reduce risk, but they are not a guarantee against unintended actions. AI-driven access to your email is inherently sensitive — always review tool calls before approving, particularly for sends and deletes. No automated guardrail is foolproof, and you remain responsible for actions taken through your mailbox.
Quick Start
1. Install
npm install -g @littlebearapps/outlook-assistantOr run directly without installing:
npx @littlebearapps/outlook-assistantTo check which version you have, or to see the available options:
outlook-assistant --version # prints e.g. 3.14.1
outlook-assistant --help # usage, options and key environment variablesWith no arguments the server speaks the Model Context Protocol over stdio. It's normally launched by your MCP client rather than run by hand — started from a terminal it will simply wait on stdin.
2. Register an Azure App
You need a Microsoft Azure app registration to authenticate. See the Azure Setup Guide for a detailed walkthrough (including first-time Azure account creation), or if you've done this before:
Create a new app registration at portal.azure.com
Add Microsoft Graph delegated permissions (Mail, Calendar, Contacts)
(Browser flow only) Create a client secret and copy the Value (not the Secret ID). The default device-code sign-in doesn't need one
Under Authentication > Add a platform > Mobile and desktop applications — check
nativeclientURIEnable "Allow public client flows" in Authentication > Advanced settings
(Optional) Set redirect URI to
http://localhost:3333/auth/callback— only needed for browser auth flow
3. Configure Your MCP Client
Client support. Every MCP client gets the server's own checks. The plugin adds the using-outlook-assistant skill and a safety hook in Claude Code, GitHub Copilot and Cursor, with different limits in each. See Supported Clients and Their Limits.
Plugin install. The plugin (plugins/outlook-assistant) bundles the server pinned to an exact version, the skill and the safety hook. It follows both the Claude Code plugin format and the Agent Plugins format used by GitHub Copilot, plus a Cursor manifest (.cursor-plugin/).
Claude Code. The plugin asks for your settings when you enable it (client ID, sign-in audience, send limit per session, allowed recipients, read-only mode and confirmation level):
claude plugin marketplace add littlebearapps/outlook-assistant claude plugin install outlook-assistant@littlebearappsGitHub Copilot CLI. Copilot has no plugin settings, so give your client ID when you first sign in, and set the hook's confirmation level with the
OUTLOOK_CONFIRM_LEVELenvironment variable:copilot plugin marketplace add littlebearapps/outlook-assistant copilot plugin install outlook-assistant@littlebearappsVS Code's Copilot agent reads the same plugin and hook file; that hasn't been checked by hand yet.
Cursor (v3.14.0 or later). Cursor loads the folder as a Cursor plugin (
.cursor-plugin/plugin.json). In Cursor CLI, load it from a clone of this repository withcursor-agent --plugin-dir outlook-assistant/plugins/outlook-assistant. Give your client ID when you first sign in. The v3.13.0 plugin can't sign in from Cursor (AADSTS900023); use the manual config below instead.
Manual config. Use this for Claude Desktop, Codex CLI, Gemini CLI, Windsurf and other MCP clients, or in place of a plugin (you then get no hook). Add to your MCP client config. Only OUTLOOK_CLIENT_ID is needed for the default device-code sign-in; add OUTLOOK_CLIENT_SECRET only if you use the browser flow. You can also leave the client ID out and give it to your assistant when you first connect (auth action=authenticate clientId=…), which saves it to ~/.outlook-assistant-config.json. An OUTLOOK_CLIENT_ID in the environment always takes precedence.
{
"mcpServers": {
"outlook": {
"command": "npx",
"args": ["@littlebearapps/outlook-assistant"],
"env": {
"OUTLOOK_CLIENT_ID": "your-application-client-id"
}
}
}
}claude mcp add outlook \
-e OUTLOOK_CLIENT_ID=your-application-client-id \
-- npx -y @littlebearapps/outlook-assistantThe MCP server reads its settings from the environment your client passes it; it doesn't load a .env file.
VS Code prompts for the client ID the first time the server starts and stores it securely:
{
"inputs": [
{
"type": "promptString",
"id": "outlook-client-id",
"description": "Azure application (client) ID"
}
],
"servers": {
"outlook": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@littlebearapps/outlook-assistant"],
"env": {
"OUTLOOK_CLIENT_ID": "${input:outlook-client-id}"
}
}
}
}Use it from Copilot Chat in Agent mode. To use it in every workspace, add the same entry to your user mcp.json (Command Palette → MCP: Open User Configuration).
Or add manually to .cursor/mcp.json:
{
"mcpServers": {
"outlook": {
"command": "npx",
"args": ["@littlebearapps/outlook-assistant"],
"env": {
"OUTLOOK_CLIENT_ID": "your-application-client-id"
}
}
}
}{
"mcpServers": {
"outlook": {
"command": "npx",
"args": ["@littlebearapps/outlook-assistant"],
"env": {
"OUTLOOK_CLIENT_ID": "your-application-client-id"
}
}
}
}4. Authenticate
Ask your AI assistant to connect to Outlook — it calls the
authtool withaction=authenticateand returns a short code and the URLmicrosoft.com/deviceloginOpen the URL on any device (a private/incognito window avoids cached sessions), enter the code, sign in and grant permissions
Tell your assistant you're done — it calls
authwithaction=device-code-completeTokens are saved locally and refresh automatically
No auth server is needed for this default device-code flow. If you'd rather use the browser redirect flow, see Authentication Flow below.
Installation
Prerequisites
Node.js 18.18.0 or higher (contributors: the dev tooling needs 22.22.1 or higher)
npm (included with Node.js)
Azure account for app registration (free tier works)
From npm (recommended)
npm install -g @littlebearapps/outlook-assistantFrom source
git clone https://github.com/littlebearapps/outlook-assistant.git
cd outlook-assistant
npm installCLI options
Option | What it does |
| Print the version to stdout and exit 0 |
| Print usage, options and key environment variables, and exit 0 |
(none) | Start the MCP server on stdio — the normal mode, invoked by your MCP client |
An unrecognised argument is reported on stderr and exits 1, rather than starting a server that would ignore it.
Azure App Registration
First time with Azure? The Azure Setup Guide covers everything from creating an account to your first authentication, including billing setup and common pitfalls.
Create the App
Open Azure Portal
Sign in with a Microsoft Work or Personal account
Search for App registrations and click New registration
Enter a name (e.g. "Outlook Assistant Server")
Select Accounts in any organizational directory and personal Microsoft accounts
Set redirect URI: platform Web, URI
http://localhost:3333/auth/callbackClick Register
Copy the Application (client) ID
Add Permissions
Go to API permissions > Add a permission > Microsoft Graph > Delegated permissions
Add these required permissions:
offline_access— refresh tokens between sessionsUser.Read— basic profileMail.Read,Mail.ReadWrite,Mail.Send— email operationsCalendars.Read,Calendars.ReadWrite— calendar operationsContacts.Read,Contacts.ReadWrite— contact managementMailboxSettings.ReadWrite— settings, auto-replies, categoriesPeople.Read— people search
Optionally add org-only permissions (work/school accounts only):
Mail.Read.Shared— shared mailbox read access (requested only whenOUTLOOK_SHARED_MAILBOX=reador=true)Mail.ReadWrite.Shared— shared mailbox writes (move/categorise/flag/mark-read; requested only whenOUTLOOK_SHARED_MAILBOX=true)Place.Read.All— meeting room search (requires admin consent)
Click Add permissions
Create a Client Secret
Only needed for the browser redirect flow. Skip this if you sign in with the default device code.
Go to Certificates & secrets > New client secret
Enter a description and select expiration
Click Add
Copy the secret Value immediately — you won't be able to see it again. Use the Value, not the Secret ID.
Configuration
Environment Variables
Set these in your MCP client's "env" block (see Quick Start). The MCP server doesn't load .env files; the browser-flow auth server (npm run auth-server) does, so when running from source you can also keep a .env for it:
cp .env.example .envEdit with your Azure credentials:
OUTLOOK_CLIENT_ID=your-application-client-id
OUTLOOK_CLIENT_SECRET=your-client-secret-VALUE
USE_TEST_MODE=falseNote: The server also accepts
MS_CLIENT_IDandMS_CLIENT_SECRETfor backwards compatibility.
Optional overrides (v3.8.0+) — see .env.example for the full list with commented worked examples:
Variable | Purpose | Default |
| OAuth audience: |
|
| IANA timezone applied to calendar events when callers don't pass one (e.g. |
|
| Default per-session cap for each rate-limited tool, counted separately until the server restarts: | no limit |
| Comma-separated allowlist of domains/addresses for sends, drafts, rule forwards and calendar invitations ( | unrestricted |
| Opt-in shared-mailbox support (work/school only). | unset (off) |
| How many recent messages the client-side search fallback scans. Personal accounts match |
|
| Inactivity timeout for each Graph request attempt, in milliseconds: an attempt that receives no data for this long is abandoned with a timeout error. It isn't an overall deadline, so a slow response that keeps arriving isn't cut off. Throttled ( |
|
| Read-only mode: | off |
| Detailed stderr logs: | off |
| Extra folder that | unset |
OUTLOOK_CONFIRM_LEVEL (outward, all-writes or off; default outward) isn't a server setting: the plugin's safety hook reads it, in clients with no plugin settings (GitHub Copilot, VS Code, Cursor). Set it in the environment the client starts from, not in the server's env block. In Claude Code, use the plugin's Confirmation level setting instead. See Supported Clients and Their Limits.
MCP Client Configuration
See Quick Start — Configure Your MCP Client above for the plugin installs and the Claude Desktop, Claude Code, VS Code / GitHub Copilot, Cursor, and Windsurf configs.
If installed from source, use node instead of npx:
{
"mcpServers": {
"outlook": {
"command": "node",
"args": ["/path/to/outlook-assistant/index.js"],
"env": {
"OUTLOOK_CLIENT_ID": "your-application-client-id"
}
}
}
}Authentication Flow
Device Code Flow (Default — Recommended)
No auth server needed. Works everywhere, including remote/headless environments.
Ask your AI assistant to authenticate (calls
authtool withaction=authenticate)Visit the URL shown (
microsoft.com/devicelogin) on any browser, any deviceEnter the code, sign in with your Microsoft account, and grant permissions
Tell your AI assistant to complete authentication (calls
authwithaction=device-code-complete)Tokens are saved to
~/.outlook-assistant-tokens.jsonand refresh automatically
Prerequisite: Enable "Allow public client flows" in Azure Portal > your app > Authentication > Advanced settings.
Server restarts (v3.7.2+): Device code state is persisted to
~/.outlook-assistant-pending-auth.json, sodevice-code-completeworks even if the MCP server restarts between steps 1 and 4 (e.g., Untether/Telegram bridge, Claude Desktop session changes).
Browser Redirect Flow (Alternative)
For localhost development or if you prefer the traditional OAuth flow, start the auth server. From a source checkout:
npm run auth-serverFrom a global npm install:
node "$(npm root -g)/@littlebearapps/outlook-assistant/outlook-auth-server.js"This starts a local server on port 3333 to handle the OAuth callback. (The outlook-assistant command itself only accepts --version and --help; any other argument exits with an error.)
In your AI assistant, use the
authtool withaction=authenticate, method=browserOpen the provided URL in your browser
Sign in and grant permissions — tokens are saved automatically
Note: The auth server reads
OUTLOOK_CLIENT_IDandOUTLOOK_CLIENT_SECRETfrom environment variables or a.envfile in the directory you start it from. Your MCP client's"env"config only applies to the MCP server process, not a separately-started auth server.Shared mailboxes: the browser flow requests the configured scopes with no fallback. If you enable
OUTLOOK_SHARED_MAILBOX, sign in with the device-code flow.
Directory Structure
outlook-assistant/
├── index.js # Entry point: CLI flags, stdio transport
├── server.js # MCP server factory (capabilities, request handler)
├── tools.js # Tool registry (22 tools)
├── request-handler.js # Routes MCP requests; JSON-RPC errors for unknown methods/tools
├── config.js # Configuration settings
├── outlook-auth-server.js # OAuth server (port 3333)
├── auth/ # Authentication module (1 tool)
├── email/ # Email module (8 tools)
│ ├── mail-tips.js # Pre-send recipient validation
│ ├── headers.js # Email header retrieval
│ ├── mime.js # Raw MIME/EML content
│ ├── conversations.js # Thread listing/export
│ ├── attachments.js # Attachment operations
│ └── ...
├── calendar/ # Calendar module (3 tools)
│ ├── attendees.js # Attendee builder (email or {email, type})
│ └── list.js # list-events filters
├── contacts/ # Contacts module (2 tools)
├── categories/ # Categories module (3 tools)
├── settings/ # Settings module (1 tool)
├── folder/ # Folder module (1 tool; resolve.js resolves paths/IDs)
├── rules/ # Rules module (1 tool)
├── advanced/ # Advanced module (2 tools)
└── utils/
├── graph-api.js # Microsoft Graph API client (includes $batch, path guards)
├── mailbox.js # me vs users/{sharedMailbox} prefix, shared-mailbox opt-in
├── risk-classes.js # Risk class per tool/action; derives annotations and titles
├── tool-error.js # isError tool results with a next step
├── safety.js # Rate limiting, recipient allowlist, dry-run
├── safe-write.js # Exclusive, folder-confined file writes
├── datetime.js # ISO 8601 parsing and timezone conversion
├── odata-helpers.js # OData query building
├── field-presets.js # Token-efficient field selections
├── response-formatter.js # Verbosity levels
└── mock-data.js # Test mode dataTroubleshooting
"Cannot find module '@modelcontextprotocol/sdk/server/index.js'"
npm install"EADDRINUSE: address already in use :::3333"
npx kill-port 3333
npm run auth-server"Invalid client secret" (AADSTS7000215)
You're using the Secret ID instead of the Secret Value. Go to Azure Portal > Certificates & secrets and copy the Value column into OUTLOOK_CLIENT_SECRET.
The Value is shown only once, when the secret is created — if you've navigated away it can't be read again, so create a new secret. An expired secret produces this same error, so check the Expires column too.
Since v3.11.0 the server detects this error and appends the explanation to Microsoft's original message, so you see both the raw error code and what to do about it.
Authentication URL doesn't work
If using browser flow: start the auth server first with npm run auth-server. If using device code flow: visit microsoft.com/devicelogin instead.
Device code "invalid_client"
Enable "Allow public client flows" in Azure Portal > App registrations > Authentication > Advanced settings.
Token refresh fails after ~60 minutes (device code auth)
Fixed in v3.7.2. Earlier versions sent client_secret in token refresh requests for device-code auth, which Microsoft rejects for public client flows. Update to v3.7.2+ or re-authenticate.
"Authentication required."
You're signed out, or the saved token expired and couldn't be refreshed. The error says what to do next: sign in with the auth tool with action=authenticate (add force=true to replace an existing session), then retry the call. auth action=status shows the current state.
Development
Running Tests
npm test # Jest unit tests
npm run inspect # MCP Inspector (interactive)Test Mode
Run with mock data (no real API calls):
USE_TEST_MODE=true npm startExtending the Server
Create a new module directory (e.g.
tasks/)Implement tool handlers in separate files
Export tool definitions from the module's
index.jsAdd the module's tools to the
TOOLSarray intools.jsClassify every tool and action in
utils/risk-classes.js(a test fails on anything unclassified); the annotations and title come from thereAdd tests in
test/Update
docs/quickrefs/tools-reference.md
Documentation
Guide | Description |
Install, configure, and authenticate — start here | |
Install per client, what the skill and safety hook do in each, and known limits | |
Azure account creation, app registration, permissions, and secrets | |
30 practical guides for email, calendar, contacts, and settings | |
Active milestones (v3.14.1, v3.15.0, v4.0.0, v3.8.x, v3.16.0+) and recent releases | |
Known errors and fixes, including auth, search, export and shared mailboxes | |
Install, accounts, permissions, tokens, updates, uninstall | |
All 22 tools with parameters | |
Tool selection and workflow patterns for AI agents |
Full documentation: docs/
Known Limitations
Personal account search: Free-text
queryand the rawsearchExpression(formerlykqlQuery) rely on Microsoft's$searchAPI, which has limited support on personal Outlook.com accounts.querymitigates this with progressive fallback (OData filters, boolean filters, then a client-side scan). Field-scoped$search(e.g.subject:"…") is rejected outright there; since v3.10.0from:/to:/subject:expressions are translated into the closest equivalent OData filters and retried, but boolean operators, grouping, wildcards and other field prefixes are not — those still terminate with an explicit no-results rather than a silent broader search. Structured filters (from,subject,to,receivedAfter) remain the most direct route. Cross-folder search (searchAllFolders: true) returns a superset of inbox-only results. Note thatqueryandsearchExpressionare not interchangeable there:searchExpressiongoes to$search, which matches the whole message including the body and ranks by relevance rather than date, whilequeryfalls back to a subject substring match that never reads bodies.tosearch depth on personal accounts: the server-side recipient filter is rejected, sotois matched locally over the 500 most recent messages (OUTLOOK_SEARCH_SCAN_LIMIT, max 5000). On a large archive that excludes older mail — pairtowithreceivedAfter/receivedBefore. Since v3.11.1 the response says so whenever the scan was truncated, whether or not it matched.Focused Inbox: Only available on work/school Microsoft 365 accounts.
Shared mailboxes: Require a work/school account and are opt-in: set
OUTLOOK_SHARED_MAILBOX=read(read) or=true(read and organise), restart the server, then re-authenticate withauth action=authenticate force=true. Until then,sharedMailboxcalls are refused with setup guidance (access-shared-mailboxkeeps its previous well-known-folder behaviour).auth action=aboutshows whether the shared scopes were actually granted. Support covers reading and organising only. Reading needsMail.Read.Shared; organising (move/categorise/flag/mark-read/create folders viasharedMailbox) needsMail.ReadWrite.Shared— add it in Azure and re-authenticate (until then, shared-scoped writes fail with 403; they never fall back to your own mailbox). Custom subfolders are supported — passfolderas a display name or nested path (e.g.Inbox/Vendors/Acme), a rawfolderId, or uselistFolders: true(orfolders action=list, sharedMailbox: …) to discover them. Sending, drafts, replies, and forwards from a shared mailbox are not supported —send-emailanddraft(including reply/reply-all/forward) always act on the signed-in user's own mailbox, andMail.Send.Sharedis not requested.Meeting room search: Requires
Place.Read.Allpermission with admin consent (work/school accounts only).Export default path: Exports and attachment downloads save to the system temp directory by default (a batch
exportwithtarget=messagesneeds anoutputDir). UseoutputDir(orsavePath) with an absolute path (or one starting with~/) inside the system temp directory,~/Downloads,~/DocumentsorOUTLOOK_EXPORT_DIR; relative paths and other folders are refused. An existingsavePathfile is replaced only withoverwrite: true.list-eventsdate filters:startAfter/startBeforemust includeZor a ±hh:mm offset; zone-less and date-only values are rejected rather than guessed.
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Security
For security concerns, please see our Security Policy. Do not open public issues for vulnerabilities.
Changelog
See CHANGELOG.md for version history.
About
Built and maintained by Little Bear Apps. Outlook Assistant is open source under the MIT License.
Available Tools
22 toolsapply-categoryApply CategoriesAIdempotent
Tag or untag email messages with master categories (those created via manage-category). action=set (default) replaces the message's category set with the supplied categories array. action=add appends categories to whatever's already on the message. action=remove removes only the named categories, leaving the rest. Accepts either messageId (single) or messageIds (batch: one request per message). categories are matched by display name — names must already exist in the target mailbox's master list. For your own mailbox, create them via manage-category first; for a shared mailbox, the names must already exist there (manage-category only manages the signed-in account's master list). Pass sharedMailbox (or alias email) to categorise messages in a shared/delegated mailbox (default: the signed-in account; requires Mail.ReadWrite.Shared + delegate access). Returns per-message confirmation.
| Name | Required | Description | Default |
|---|---|---|---|
| No | Alias for `sharedMailbox`. | ||
| action | No | set (replace all), add (append), remove (remove specific). Default: set | |
| messageId | No | Single message ID to categorise | |
| categories | Yes | Category display names to apply/remove (required) | |
| messageIds | No | Array of message IDs to categorise (batch operation) | |
| sharedMailbox | No | Email address of the shared/delegated mailbox whose messages to categorise (default: the signed-in account). Requires delegate access + Mail.ReadWrite.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare the safety profile (not read-only, idempotent, non-destructive), so the bar is lower. The description adds real context beyond them: categories must pre-exist in the mailbox's master list, shared mailbox use requires Mail.ReadWrite.Shared + delegate access and the OUTLOOK_SHARED_MAILBOX opt-in, and it confirms per-message return output. Loses a point for not clarifying error/partial-failure behavior on batch operations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but well-organized; the core purpose leads, followed by action semantics, then prerequisites. Each sentence carries information, though the category-creation prerequisites (manage-category before shared mailbox caveats) are slightly repetitive and could be tightened.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 6-param mutation tool with no output schema, the description covers action semantics, batch vs single, prerequisites, permissions, and return format. It is nearly complete, with only edge-case behavior (e.g., what happens on invalid category names) left unspecified.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description goes beyond the schema by explaining that `categories` are matched by display name and must already exist in the target mailbox, that `sharedMailbox`/`email` are aliases, and that batch yields one request per message — meaningful semantics not captured in the enum/field descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb and resource ('Tag or untag email messages with master categories') and immediately scopes it to the master list created via `manage-category`, cleanly separating it from that sibling. An agent can tell this applies categories to messages rather than managing the category list itself.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly distinguishes the three `action` modes (set replaces, add appends, remove removes only named), explains single vs batch via `messageId`/`messageIds`, and states when to use `sharedMailbox`/`email`. It also routes the agent to `manage-category` for creating categories and warns that it won't work for shared mailboxes.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
attachmentsAttachmentsA
Inspect or retrieve email attachments. action=list (default) returns metadata for all attachments on messageId (id, name, contentType, size, isInline) — read-only. action=view returns inline content for text/JSON/XML attachments via attachmentId; binary types require download. action=download saves the attachment under a new, unique name in outputDir (default system temp directory, auto-created; must be inside the temp directory, ~/Downloads, ~/Documents or OUTLOOK_EXPORT_DIR) and returns the saved file path. messageId is required for all actions; attachmentId is required for view/download. If messageId came from a shared/delegated mailbox, pass the same sharedMailbox (or alias email) — attachment IDs are scoped to the message and fail under /me otherwise. Use outputVerbosity to control list field count.
| Name | Required | Description | Default |
|---|---|---|---|
| No | Alias for `sharedMailbox`. | ||
| action | No | Action to perform (default: list) | |
| savePath | No | DEPRECATED alias for `outputDir`. Will be removed in a future release. | |
| messageId | Yes | Email message ID (required) | |
| outputDir | No | Absolute directory (or ~/…) to save the file in (action=download, default: system temp directory). Auto-created if missing. Must be inside the system temp directory, ~/Downloads, ~/Documents or OUTLOOK_EXPORT_DIR, with no dot-prefixed folder names. | |
| attachmentId | No | Attachment ID (action=view/download, required) | |
| sharedMailbox | No | Email address of the shared/delegated mailbox the messageId belongs to. Required when the message came from a shared mailbox. Requires delegate access + Mail.Read.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With annotations already declaring openWorldHint and destructiveHint=false, the description still adds real behavioral context: list is read-only, download writes a file under a new unique name with path restrictions and auto-creation, and attachment IDs are message-scoped and fail under /me without sharedMailbox. It does not, however, disclose rate limits or failure behavior for download when a name collides beyond the 'unique name' note.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but well organized, front-loading the action semantics and routing constraints before parenthetical defaults. It is longer than average because it covers three actions, and the 'Use outputVerbosity' sentence references a parameter absent from the schema, which slightly muddies an otherwise tight structure.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description compensates by naming the list fields returned (id, name, contentType, size, isInline) and the download return value (saved file path), plus the inline-content behavior for view. Everything needed to invoke each action correctly is present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds cross-parameter dependency semantics the schema does not: messageId required for all actions, attachmentId required only for view/download, and sharedMailbox/email needing to mirror the mailbox the messageId came from. It also names outputVerbosity, which is not present in the input schema at all.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb+resource ('Inspect or retrieve email attachments') and then enumerates the three distinct operations the single tool supports, each with its own return shape. An agent can distinguish list vs view vs download without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states the default (action=`list`), explains that binary types cannot be viewed and must be downloaded, and gives the exact condition to use the sharedMailbox parameter ('if messageId came from a shared/delegated mailbox'). Alternatives and when-not conditions are explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
authAuthenticationA
Manage authentication with the Microsoft Graph API. action=status (default) returns the current auth state, refreshing an expired access token while the refresh token is still valid (~90 days); call it first to check before other tools. action=authenticate starts sign-in: method: "device-code" (default, works headlessly) returns a code + URL for the user to visit; method: "browser" uses the local auth server on :3333 (run npm run auth-server first). force: true re-authenticates over an existing valid session. If sign-in reports that OUTLOOK_CLIENT_ID is not configured, ask the user for their Azure Application (client) ID and pass it as clientId. action=device-code-complete finishes device-code sign-in once the browser shows it succeeded. action=about returns server version, configured audience, scope list and other diagnostics. Tokens persist to ~/.outlook-assistant-tokens.json and survive server restarts.
| Name | Required | Description | Default |
|---|---|---|---|
| force | No | Force re-authentication even if already authenticated (action=authenticate only) | |
| action | No | Action to perform (default: status) | |
| method | No | Auth method for action=authenticate. device-code (default): no auth server needed, works remotely. browser: traditional OAuth redirect via port 3333. | |
| clientId | No | Optional, action=authenticate only. The user's Azure Application (client) ID (a GUID from the app registration's Overview page). Saved to `~/.outlook-assistant-config.json` and used from then on; it is not a secret. The OUTLOOK_CLIENT_ID environment variable takes precedence when set. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only say readOnlyHint=false, idempotentHint=false, destructiveHint=false; the description goes well beyond by disclosing token persistence to ~/.outlook-assistant-tokens.json, survival across restarts, the ~90-day refresh-token window, the local auth server port, and the interactive code+URL handoff. It also clarifies the sequence needed to finish device-code sign-in. (Note: the openWorldHint=false annotation sits awkwardly with the described Microsoft Graph sign-in flow, but the description itself is consistent with the mutation/idempotency hints.)
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
It is long but organized action-by-action and front-loaded with the purpose plus the most important routing instruction ('call it first'). Most sentences carry unique operational detail, though the density is high enough that an agent must parse a multi-clause paragraph rather than scan.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description correctly carries the return-value burden: status returns auth state, about returns version/audience/scopes/diagnostics, authenticate returns a code plus URL. Failure handling (client ID not configured) and setup prerequisites (npm run auth-server) are also covered, leaving no obvious gap for a four-action auth tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real meaning the schema does not: the default action is status, device-code is the default method, force re-authenticates 'over an existing valid session', and clientId is persisted to ~/.outlook-assistant-config.json with OUTLOOK_CLIENT_ID taking precedence. It also explains the device-code-complete sequencing, which the schema leaves as a bare enum value.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource ('Manage authentication with the Microsoft Graph API') and enumerates every action (status, authenticate, device-code-complete, about) with what each returns or initiates. An agent can distinguish this from all 21 email/calendar/contact siblings at a glance, since none of them handle credentials or sessions.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit routing: 'call it first to check before other tools' for action=status, device-code vs browser selection ('works headlessly' vs 'run npm run auth-server first'), when to use force:true, and when to ask the user for a client ID. It also names the fallback condition (OUTLOOK_CLIENT_ID not configured) that triggers the clientId parameter.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create-eventCreate Calendar EventADestructive
Create a new calendar event on the signed-in user's default calendar. Returns the created event with its id, webLink, and (if attendees are present) an auto-generated online-meeting URL — attendees receive invitations on save, so pass dryRun: true first to preview who would be invited (and how many are external) without creating anything. When OUTLOOK_ALLOWED_RECIPIENTS is set, every attendee must be allowed or nothing is created. Times use the configured timezone (default Australia/Melbourne; override with OUTLOOK_DEFAULT_TIMEZONE); omit the Z suffix to send local time. Use manage-event action=update to modify an event after creation, or manage-event action=cancel/delete to remove it. Real creates count against the session limit (OUTLOOK_MAX_CREATE_EVENT_PER_SESSION, else OUTLOOK_MAX_EMAILS_PER_SESSION); 0 refuses every create.
| Name | Required | Description | Default |
|---|---|---|---|
| end | Yes | The end time of the event in ISO 8601 format | |
| body | No | Optional body content for the event | |
| start | Yes | The start time of the event in ISO 8601 format | |
| dryRun | No | Preview only: nothing is created and no invitations are sent. Shows who would be invited and how many are external (default false). | |
| subject | Yes | The subject of the event | |
| attendees | No | Attendees: email address strings (required attendees) or {email, type} objects, where type is 'required', 'optional' or 'resource' (a room or equipment) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the annotations (readOnly=false, destructive=true): discloses that attendees receive invitations on save, the OUTLOOK_ALLOWED_RECIPIENTS gating, timezone resolution rules, session-limit enforcement, and that 0 refuses every create. This is exactly the destructive/side-effect context an agent needs.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and return value in the first sentence, then progressively adds side effects, alternatives, and limits. It is long and somewhat parenthetical, but nearly every clause carries decision-relevant information, so little is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, yet the description names the returned fields (id, webLink, conditional online-meeting URL), covers the destructive side effects, the safe dryRun path, and the limit/timezone configuration. Completeness is high for a 6-parameter mutating tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, but the description adds real value the schema lacks: how timezone interacts with the start/end ISO strings ('omit the Z suffix to send local time') and the semantics of dryRun's preview output. It does not restate subject/body beyond what the schema already documents.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Create a new calendar event on the signed-in user's default calendar'), names the return payload, and implicitly contrasts with siblings like manage-event and list-events. An agent can distinguish this from every sibling without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes to alternatives: use manage-event action=update to modify, action=cancel/delete to remove. It also prescribes the dryRun:true preview workflow before committing, and warns that dryRun avoids wasting the session create limit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
draftDraft OperationsADestructive
Draft lifecycle for review-before-send workflows (destructive: covers send and delete). action=create saves a new draft and returns its id (dryRun: true previews without saving; checkRecipients: true runs mail-tips first). action=update patches a draft by id (only fields passed change). action=send sends a draft and counts against the send-email session limit (0 refuses every send). action=delete moves a draft to Recoverable Items (restorable for a limited time). update/send/delete refuse any id that is not an unsent draft. action=reply/reply-all creates a reply draft from a message id (comment prepends text; not with body). action=forward creates a forward draft (needs id and to). The recipient allowlist (OUTLOOK_ALLOWED_RECIPIENTS) applies to create/update/forward, to the draft's current to/cc/bcc on send, and to reply/reply-all, whose draft is deleted if a recipient is not allowed. Returns the draft on create/update/reply/forward; a status on send/delete.
| Name | Required | Description | Default |
|---|---|---|---|
| cc | No | Comma-separated CC email addresses | |
| id | No | Draft or message ID. Required for update/send/delete/reply/reply-all/forward. update/send/delete need a draft ID; reply/reply-all/forward take any message ID. | |
| to | No | Comma-separated recipient email addresses (optional for create/update, required for forward) | |
| bcc | No | Comma-separated BCC email addresses | |
| body | No | Email body (plain text or HTML) | |
| action | Yes | Action to perform (required) | |
| dryRun | No | Preview only (action=create): shows the draft without saving it. Other actions refuse dryRun and change nothing. Default false. | |
| comment | No | Comment text for reply/forward (prepended to original message). Cannot combine with body. | |
| subject | No | Email subject | |
| importance | No | Email importance (default: normal) | |
| checkRecipients | No | Check recipients for out-of-office, delivery restrictions before saving (action=create, default: false) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the annotations: delete moves to Recoverable Items (restorable for a limited time), send counts against the send-email session limit with 0 refusing all sends, update/send/delete refuse non-draft ids, and reply/forward drafts are deleted if a recipient is outside the allowlist. This is exactly the kind of consequence detail annotations cannot carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the lifecycle and destructive scope in the first clause, then walks actions compactly. Dense and information-rich with little waste, though the long allowlist sentence is somewhat sprawling and could be tightened.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an 11-parameter, 7-action tool with no output schema, the description covers action semantics, id requirements, dry-run/mail-tips flags, allowlist enforcement, and even return shape (draft vs status). Nothing needed to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, but the description adds real meaning: dryRun previews only on create and is refused elsewhere, checkRecipients runs mail-tips first, comment cannot combine with body, and the recipient allowlist scope across actions. Minor overlap with schema text but net additive.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Names a specific resource (draft lifecycle) and enumerates all seven actions with their individual effects, so the agent knows exactly what the tool does. It clearly separates itself from siblings like send-email and update-email by framing the tool as draft-centric with send/delete as lifecycle endpoints.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives per-action conditions: create for new drafts, update for patching by id, send/delete require an unsent draft id, reply/reply-all/forward build from a message id. It does not explicitly route the agent to sibling send-email/update-email when no draft exists, so the boundary with those tools is implied rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
exportExport EmailsADestructive
Export emails to files. target=message (default) exports one email by id (mime/eml/markdown/json/csv) to savePath: a directory (or a path ending in /, created if missing) gets a new, unique file name; a file path is created new and an existing file is replaced only with overwrite: true. target=messages batch-exports emailIds, or matches for searchQuery/query, into outputDir, at most 100 messages per call. target=conversation exports a thread (up to 1000 messages) by conversationId into outputDir (eml/mbox/markdown/json/html/csv; order: "reverse" for newest first). target=mime returns raw RFC-822 MIME for id (headersOnly, base64, maxSize, default 1MB). Files are written only inside the system temp directory (the default), ~/Downloads, ~/Documents or OUTLOOK_EXPORT_DIR, never to dot-prefixed names. Pass sharedMailbox (alias email) when the ids come from a shared mailbox. includeAttachments defaults to true for one message, false for batch.
| Name | Required | Description | Default |
|---|---|---|---|
| id | No | Email ID (target=message/mime, required) | |
| No | Alias for `sharedMailbox`. | ||
| order | No | Message order (target=conversation, default: chronological) | |
| query | No | Free-text search shortcut (target=messages). Equivalent to passing searchQuery: { subject: <query> }. Convenience alias for callers used to search-emails. | |
| base64 | No | Return base64 encoded (target=mime) | |
| format | No | Export format. Valid values vary by target: target=message accepts mime/eml/markdown/json/csv (mbox and html are conversation-only). target=conversation accepts eml/mbox/markdown/json/html/csv. target=messages (batch) accepts mime/eml/markdown/json (one file per message) or csv (one file). mime is an alias for eml (same RFC822 bytes, .eml extension on disk). | |
| target | No | Export target (default: message) | |
| maxSize | No | Max content size in bytes (target=mime, default: 1MB) | |
| emailIds | No | Email IDs to export (target=messages). At most 100 per call: any beyond the first 100 are left out, and the result says how many. | |
| savePath | No | Absolute file path or directory, or one starting with ~/ (target=message). Relative paths are refused. Must be inside the system temp directory, ~/Downloads, ~/Documents or OUTLOOK_EXPORT_DIR. An existing file is not replaced unless overwrite is true. | |
| outputDir | No | Absolute output directory, or one starting with ~/ (target=messages, required; target=message/conversation, default: system temp directory). Must be inside the system temp directory, ~/Downloads, ~/Documents or OUTLOOK_EXPORT_DIR. | |
| overwrite | No | Replace an existing file at savePath (target=message, default: false). Never replaces a symlink, a hard-linked file, a dotfile, or a file in a dot-directory below the allowed folder. | |
| headersOnly | No | MIME headers only, no body (target=mime) | |
| searchQuery | No | Search to find emails (target=messages, alternative to emailIds) | |
| sharedMailbox | No | Email address of a shared/delegated mailbox to export from (default: the signed-in account). Applies to all targets (message/messages/conversation/mime) — pass it whenever the id(s)/conversationId/searchQuery belong to a shared mailbox. Requires delegate access + Mail.Read.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance). | |
| conversationId | No | Conversation ID (target=conversation, required) | |
| includeAttachments | No | Include attachments (default: true for single, false for batch) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare destructiveHint=true, readOnlyHint=false and idempotentHint=false, and the description adds substantial context beyond them: files are only written inside temp/~/Downloads/~/Documents/OUTLOOK_EXPORT_DIR, dot-prefixed names and symlinks/hardlinks are never replaced, directories are created if missing, batch caps at 100, and sharedMailbox requires delegate access plus OUTLOOK_SHARED_MAILBOX opt-in or the call is refused.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single dense paragraph, but it is front-loaded with the default target and each clause is scoped by `target=` markers, so it scans predictably. No filler sentences, though the length is near the upper bound for readability.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 17-parameter, zero-required tool with no output schema, the description covers every target's required inputs, defaults, limits, path safety rules, and the shared-mailbox prerequisite. Nothing needed to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, but the description adds cross-parameter semantics the schema states only piecemeal: format validity varies by target, includeAttachments defaults differ between single and batch, and savePath directory-vs-file resolution rules (path ending in `/` is created and given a unique name).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (export) and resource (emails to files) and enumerates the four distinct target modes with what each produces. An agent can tell immediately that this writes email content to disk, distinct from read-email or search-emails.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear per-target usage: which parameters apply to message vs messages vs conversation vs mime, and that emailIds and searchQuery/query are alternatives for batch. It even notes `query` is a convenience alias for callers used to search-emails. It never explicitly says when to prefer this over read-email for simple viewing, so it stops short of full sibling routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
find-meeting-roomsMeeting RoomsARead-onlyIdempotent
Discover bookable meeting rooms in the user's organisation via the Graph rooms endpoint (read-only). Returns room resources with displayName, emailAddress, building, floor, capacity, and bookingType — suitable for piping into create-event as attendees. Filter by query (matches name/email), building, floor, or minimum capacity. Returns empty list on personal accounts (the rooms endpoint is M365-only). Use outputVerbosity to control field count.
| Name | Required | Description | Default |
|---|---|---|---|
| floor | No | Filter by floor number | |
| query | No | Search query (room name, email) | |
| building | No | Filter by building name | |
| capacity | No | Minimum capacity required | |
| outputVerbosity | No | Output detail level (default: standard) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint/idempotentHint/non-destructive, so safety is covered. The description adds genuinely new behavioral context beyond the annotations: the M365-only limitation and empty-list return on personal accounts, plus the enumerated return fields and verbosity control.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three dense sentences, front-loaded with what the tool does and what it returns before the filter details. Slightly packed with backticked tokens, but each clause carries information and nothing is redundant padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description compensates by enumerating the returned fields (displayName, emailAddress, building, floor, capacity, bookingType) and the edge case of personal accounts. Behavior and return shape are adequately covered for a zero-required-param read tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema descriptions already state the same semantics ('Search query (room name, email)', 'Minimum capacity required'). The description restates query-matches-name/email and capacity-as-minimum, adding little beyond what the schema carries, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Discover bookable meeting rooms'), names the underlying Graph rooms endpoint, and distinguishes itself from siblings by naming `create-event` as the downstream consumer. An agent can tell exactly what this returns and how it differs from people/event search tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear filter semantics and an explicit when-not condition: 'Returns empty list on personal accounts (the rooms endpoint is M365-only).' It doesn't name a fallback tool for non-M365 users, but the usage context is otherwise unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
foldersMail FoldersADestructive
Manage mail folders. Address a folder by name, by slash-separated path for nested folders (e.g. Inbox/Clients/Acme, case-insensitive) or by ID; a bare name matches a unique top-level folder first, then nested ones (an ambiguous name returns the candidates). action=list (default) returns the tree with each folder's path and id (includeItemCounts, includeChildren). action=create makes name under the root or parentFolder/parentFolderId. action=move moves emailIds into targetFolder/targetFolderId. action=stats returns total/unread counts for folder or folderId. action=delete removes a folder (folderName/path or folderId) with everything in it, subfolders included. It skips Deleted Items and Graph documents no restore path, so pass dryRun: true first to see what would be lost. Protected folders (Inbox, Sent Items, etc.) can't be deleted. Every action accepts sharedMailbox (alias email) to work in a shared or delegated mailbox (default: your own).
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | Name of the folder to create (action=create, required) | |
| No | Alias for `sharedMailbox`. | ||
| action | No | Action to perform (default: list) | |
| dryRun | No | Preview only (action=delete): nothing is deleted. Shows the folder and how many items and subfolders would be lost. Other actions refuse dryRun and change nothing. Default false. | |
| folder | No | Folder name or path (inbox, sent, "Triage/Delete", etc.). Default: inbox (action=stats) | |
| emailIds | No | Comma-separated list of email IDs to move (action=move, required) | |
| folderId | No | Folder ID (action=stats/delete) | |
| folderName | No | Folder name or path to delete — resolved to ID (action=delete). Cannot delete protected folders (Inbox, Drafts, Sent, etc.) | |
| parentFolder | No | Parent folder name or path (e.g. "Clients/Acme"); default is root (action=create) | |
| sourceFolder | No | Ignored: action=move moves each email by ID from wherever it is. Accepted for older callers. | |
| targetFolder | No | Destination folder name or path, e.g. "Triage/Delete" (action=move; or use targetFolderId) | |
| sharedMailbox | No | Email address of a shared/delegated mailbox to target (all actions; default: the signed-in account). Requires delegate access + Mail.Read.Shared (list/stats) or Mail.ReadWrite.Shared (create/move/delete). Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance). | |
| parentFolderId | No | Parent folder ID — alternative to parentFolder for unambiguous targeting (action=create) | |
| targetFolderId | No | Destination folder ID — alternative to targetFolder for unambiguous/nested targeting (action=move) | |
| includeChildren | No | Include child folders in hierarchy (action=list) | |
| outputVerbosity | No | Output detail level (action=stats, default: standard) | |
| includeItemCounts | No | Include counts of total and unread items (action=list) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveHint=true, but the description adds substantial context the annotations cannot convey: delete wipes subfolders too, skips Deleted Items, and has no restore path; dryRun previews losses; protected folders are undeletable; sharedMailbox requires delegate access plus Mail.Read.Shared/Mail.ReadWrite.Shared and the OUTLOOK_SHARED_MAILBOX server opt-in, otherwise the call is refused.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the tool's core behavior, then a compact action-by-action walkthrough; every sentence carries operational information. It is a dense single paragraph rather than a scannable structure, which slightly hurts readability for a 17-parameter multi-action tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex five-action, 17-parameter tool with no output schema, the description covers addressing semantics, per-action defaults, the dryRun safety workflow, protected-folder constraints, and shared-mailbox prerequisites. Nothing an agent needs to call any action correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description goes further by documenting folder addressing rules the schema does not: name vs slash-separated path (case-insensitive) vs ID, bare-name resolution order (unique top-level first, then nested), and ambiguity returning candidates. Defaults for list/stats and sharedMailbox aliasing are also clarified.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource ('Manage mail folders') and then enumerates each of the five actions with its concrete effect (list returns the tree with paths/ids, create makes a name under a parent, move relocates emailIds, stats returns counts, delete removes a folder and its contents). An agent can tell this apart from siblings like manage-rules or access-shared-mailbox without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use detail per action, states the default (action=list), and includes when-not guidance (protected folders can't be deleted, pass dryRun first before destructive deletes). It does not, however, route the agent away from sibling tools that also touch mail organization (manage-rules, manage-category), so the alternative-selection layer is only intra-tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get-mail-tipsMail TipsARead-onlyIdempotent
Pre-send recipient validation via Graph POST /me/getMailTips (read-only; uses the existing Mail.Read scope — no extra permissions). Returns per-recipient tips covering automatic replies (out-of-office), mailbox full status, custom admin mail tips, delivery restrictions, moderation requirements, external-vs-internal scope, max message size, and group member counts (total + external). Use ahead of send-email or draft action=create to catch issues like OOO replies or external-recipient warnings before the message goes out; send-email/draft accept checkRecipients: true to invoke this automatically. Accepts either a comma-separated string or an array of addresses; tipTypes filters which tips are requested (defaults to all).
| Name | Required | Description | Default |
|---|---|---|---|
| tipTypes | No | Comma-separated tip types to request (default: all). Options: automaticReplies, mailboxFullStatus, customMailTip, externalMemberCount, totalMemberCount, maxMessageSize, deliveryRestriction, moderationStatus, recipientScope, recipientSuggestions | |
| recipients | Yes | Email addresses to check for mail tips |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so the safety profile is covered. The description adds genuinely new context: it uses the existing Mail.Read scope with no extra permissions, and it enumerates the per-recipient tip categories returned. It doesn't mention rate limits or latency, which keeps it from a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is information-dense but front-loads the core purpose and endpoint, then usage, then parameters. It is longer than typical but nearly every clause earns its place; the enumerated tip types are slightly list-heavy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the burden of explaining what comes back, and it does so thoroughly by enumerating the tip categories. Combined with permission scope, usage guidance, and parameter behavior, an agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters (recipients, tipTypes) are already documented in the schema. The description restates that recipients accepts a comma-separated string or array and that tipTypes defaults to all, but adds no syntax or behavioral detail beyond the schema. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific action (pre-send recipient validation), the resource (mail tips), and the underlying Graph endpoint POST /me/getMailTips. It distinguishes itself from send-email and draft by positioning itself as a pre-flight check, so an agent can tell it apart from siblings without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says to use it ahead of send-email or draft action=create to catch OOO replies or external-recipient warnings, and notes that those tools can invoke it automatically via checkRecipients: true. This is a clear when-to-use with named alternatives and the condition that selects them.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list-eventsList Calendar EventsARead-onlyIdempotent
List calendar events for the signed-in user (read-only). By default returns upcoming events (start ≥ now). Optional startAfter, startBefore and subject filters find past, current or specifically-named events; supplying any of them replaces the default "now" lower bound and the filters are AND-ed together. Results are oldest first, except when the search only looks backwards (startBefore without startAfter, or subject alone), where they are newest first. Each event shows its subject, location, start/end, a body preview and its id. Use count (default 10, max 100) to control page size. Each start/end is returned as a canonical UTC ISO-8601 instant (e.g. 2026-04-02T22:00:00.000Z) followed by a labelled local rendering in the configured display timezone (default Australia/Melbourne; override with OUTLOOK_DEFAULT_TIMEZONE) — the UTC value is authoritative, so consumers never have to guess the zone.
| Name | Required | Description | Default |
|---|---|---|---|
| count | No | Number of events to retrieve (default: 10, max: 100) | |
| subject | No | Optional substring (max 255 characters) to match against the event subject, case-insensitive (Graph `contains()`). Useful for finding past or current events by name; on its own, results are newest first. | |
| startAfter | No | Optional ISO 8601 datetime with `Z` or a ±hh:mm offset (required). Only return events whose start is on or after this time. Replaces the default "now" lower bound when supplied. Example: "2026-01-01T00:00:00Z" or "2026-01-01T09:00:00+10:00". | |
| startBefore | No | Optional ISO 8601 datetime with `Z` or a ±hh:mm offset (required). Only return events whose start is strictly before this time. Combine with `startAfter` to bound a window; on its own, results are newest first. Example: "2026-02-01T00:00:00Z". |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover safety (readOnly, idempotent, non-destructive), and the description adds substantial extra behavior: the default 'now' lower bound, the sort-order inversion when searching backwards, page-size limits, and the authoritative-UTC-plus-local-rendering time format.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads purpose, scope, and default behavior, then layers filter and timezone details. Dense and mostly waste-free, though the timezone rendering sentence is longer than strictly necessary.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description fully covers the returned fields (subject, location, start/end, body preview, id) and their format, so an agent knows what to expect without one.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so each parameter is already documented. The description nonetheless adds interaction semantics the schema lacks: that supplying any filter replaces the default lower bound, that filters are AND-ed, and how each affects ordering.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource (list calendar events) scoped to the signed-in user and explicitly read-only, which cleanly separates it from write-oriented siblings like create-event and manage-event.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Clearly explains the default behavior (upcoming events from now) and when the optional filters change that default, including the AND-ed combination and sort-order effects. It does not name or contrast against sibling tools, so it stops short of full when/when-not/alternatives guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
mailbox-settingsMailbox SettingsADestructiveIdempotent
Read or update mailbox-level settings (idempotent — safe to retry; sets are PATCH-style and merge with existing state). action=get (default) returns settings — use section to filter (language, timeZone, workingHours, automaticRepliesSetting, or all). action=set-auto-replies configures out-of-office: enabled true/false, optional startDateTime/endDateTime (ISO 8601) for scheduled mode, internalReplyMessage and (optionally) externalReplyMessage and externalAudience (none/contactsOnly/all); pass dryRun: true to preview who would get replies without changing anything. action=set-working-hours updates the schedule: startTime/endTime (HH:MM) and daysOfWeek (array of monday..sunday). Returns the updated settings object on set actions.
| Name | Required | Description | Default |
|---|---|---|---|
| action | No | Action to perform (default: get) | |
| dryRun | No | Preview only (action=set-auto-replies): nothing is changed. Shows who would get automatic replies, the schedule and each message length. Other actions refuse dryRun and change nothing. Default false. | |
| enabled | No | Enable (true) or disable (false) automatic replies (action=set-auto-replies) | |
| endTime | No | Work end time in HH:MM format, e.g. '17:00' (action=set-working-hours) | |
| section | No | Specific section to retrieve (action=get, default: all) | |
| timeZone | No | Time zone name, e.g. 'Australia/Melbourne' (action=set-working-hours) | |
| startTime | No | Work start time in HH:MM format, e.g. '09:00' (action=set-working-hours) | |
| daysOfWeek | No | Work days, e.g. ['monday','tuesday','wednesday','thursday','friday'] (action=set-working-hours) | |
| endDateTime | No | End date/time for scheduled mode, ISO 8601 format (action=set-auto-replies) | |
| startDateTime | No | Start date/time for scheduled mode, ISO 8601 format (action=set-auto-replies) | |
| externalAudience | No | Who receives external reply (action=set-auto-replies) | |
| externalReplyMessage | No | Reply message for external senders (action=set-auto-replies) | |
| internalReplyMessage | No | Reply message for internal senders (action=set-auto-replies) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description corroborates the idempotentHint annotation ('idempotent — safe to retry') and adds real value beyond structured fields by disclosing PATCH-style merge semantics and the dryRun preview behavior ('nothing is changed'). It also states the return value ('Returns the updated settings object on set actions'). It does not address the destructiveHint annotation directly (i.e., that set actions overwrite existing working hours/auto-reply configuration), so not a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core read/update framing, then grouped by action, so every sentence maps to a decision the agent must make. It is somewhat dense and re-enumerates enum values already present in the schema, which is redundant length rather than dead weight.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 13-parameter, zero-required, multi-action tool with no output schema, the description supplies the missing glue: action semantics, parameter-to-action mapping, formats (ISO 8601, HH:MM), and the return value. Complete enough to invoke correctly; only permission/scoping caveats are absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents every parameter, making 3 the baseline. The description adds cross-parameter meaning the schema cannot express: which parameters belong to which action, that startDateTime/endDateTime select scheduled mode, that externalReplyMessage is optional, and that dryRun is rejected for non-set-auto-replies actions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening clause states a specific verb pair and resource ('Read or update mailbox-level settings') and then enumerates the three actions (get, set-auto-replies, set-working-hours), which no sibling tool covers. An agent can distinguish it from manage-rules, manage-focused-inbox, and access-shared-mailbox without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear per-action context: action defaults to get, section filters the read, set-auto-replies configures out-of-office, set-working-hours updates the schedule. It also explains the dryRun preview path for set-auto-replies. It does not state when NOT to use this tool or name a preferred alternative for overlapping tasks, so it stops short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
manage-categoryMaster CategoriesADestructive
Manage the user's master category list (the colour-coded labels available across mail/calendar/contacts). action=list (default) returns categories with id/displayName/color. action=create adds a new category — displayName required, color optional (preset0-preset24, e.g. preset0=Red, preset7=Blue). action=update (alias set — deprecated) changes name/colour by id. action=delete removes a category — this does NOT untag messages already labelled with it; existing messages retain the orphaned label until manually cleaned. Use apply-category to tag/untag specific messages.
| Name | Required | Description | Default |
|---|---|---|---|
| id | No | Category ID (action=update/delete, required) | |
| color | No | Color preset, e.g. preset0=Red, preset7=Blue (action=create/update) | |
| action | No | Action to perform (default: list). 'set' is a deprecated alias for 'update'. | |
| categoryId | No | DEPRECATED: alias for `id`. Will be removed in a future release. | |
| displayName | No | Category name (action=create required, action=update optional) | |
| outputVerbosity | No | Output detail level (action=list, default: standard) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveHint=true, but the description adds genuinely non-obvious behavior: delete does NOT untag messages, leaving orphaned labels until manual cleanup. It also flags the deprecated 'set' alias, which the annotation set cannot convey. It doesn't mention idempotency or permission requirements.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single dense paragraph that front-loads the resource, then walks actions in a predictable order, closing with the destructive caveat and the sibling redirect. No sentence is redundant.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the return-shape burden and does so for list (id/displayName/color), but is silent on what create/update/delete return. It compensates well on the destructive-delete semantics, so the gap is minor.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% so the baseline is 3, but the description maps parameters to actions ('displayName required' for create, 'color optional', 'id' for update/delete) and repeats the preset0=Red/preset7=Blue decoding, adding conditional-requirement context the flat schema does not surface.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific resource (the user's master category list) and enumerates all four operations with their exact effects. The parenthetical 'the colour-coded labels available across mail/calendar/contacts' disambiguates the resource, and the closing pointer to apply-category separates this tool from the tagging sibling.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Each action is paired with its trigger condition ('action=list (default)', 'action=create adds', 'action=delete removes'), and it explicitly routes tagging/untagging to apply-category. It lacks a stated 'when not to use' or prerequisite/permission guidance, but the action-level routing is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
manage-contactContactsADestructive
Full CRUD over the signed-in user's personal Outlook contacts (destructive: covers delete action). action=list (default) returns contacts with pagination via skip/count (default 50). action=search returns contacts matching query against name/email (default 25). action=get returns full contact detail by id. action=create adds a new contact and returns its id. action=update patches the given fields by id (only fields passed are changed). action=delete removes the contact by id; it skips Deleted Items, so treat it as permanent and pass dryRun: true first to confirm which contact it is. Use outputVerbosity (minimal/standard/full) on list/search to control field count. Searches only your personal contact store; for relevance-ranked search across contacts, the directory and recent communications, use search-people.
| Name | Required | Description | Default |
|---|---|---|---|
| id | No | Contact ID (action=get/update/delete, required) | |
| skip | No | Pagination offset for action=list (default: 0). Use the value suggested by the previous page response. | |
| count | No | Number of results (action=list default: 50, action=search default: 25) | |
| No | Primary email address (action=create/update) | ||
| notes | No | Personal notes (action=create/update) | |
| query | No | Search query for name or email (action=search, required) | |
| action | No | Action to perform (default: list) | |
| dryRun | No | Preview only (action=delete): nothing is deleted. Shows which contact would be removed. Other actions refuse dryRun and change nothing. Default false. | |
| emails | No | Multiple email addresses (action=create/update). First entry is primary. | |
| folder | No | Contact folder ID (action=list) | |
| jobTitle | No | Job title (action=create/update) | |
| lastName | No | Surname (action=create/update). Maps to Graph `surname`. | |
| firstName | No | Given name (action=create/update). Maps to Graph `givenName`. If displayName not provided, will be combined with lastName. | |
| companyName | No | Company name (action=create/update) | |
| displayName | No | Full name (action=create/update) | |
| mobilePhone | No | Mobile phone number (action=create/update) | |
| outputVerbosity | No | Output detail level (action=list/search, default: standard) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the annotations: delete skips Deleted Items so it is irreversible, dryRun is only honored on delete (other actions refuse it and change nothing), update patches only the fields passed, and pagination defaults differ per action (50 list / 25 search). The annotations only carry destructiveHint=true and readOnlyHint=false; the description supplies the operational consequences.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads scope and the destructive warning, then walks actions in a consistent pattern with no filler sentences. It is dense but slightly long, with pagination and verbosity details that are partly duplicated by the schema descriptions.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 17-parameter, action-dispatch CRUD tool with no output schema, the description covers every action, defaults, side effects, safety workflow, and sibling routing. Nothing an agent needs to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real meaning: `outputVerbosity` values control returned field count, `skip` should echo the previous page's suggested value, `id` scoping across get/update/delete, and update's patch-only semantics. It largely restates defaults already in the schema, which caps it below 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (full CRUD) plus the exact resource (signed-in user's personal Outlook contacts) and enumerates every action with its effect. It also distinguishes itself from the sibling `search-people` by scope (personal store only), so an agent can pick between them without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit per-action routing (list/search/get/create/update/delete), names the required inputs for each, and states the alternative for ranked cross-source search (`search-people`). It also prescribes a workflow: pass `dryRun: true` before a permanent delete to confirm the target contact.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
manage-eventManage Calendar EventADestructive
Manage an existing calendar event. dryRun: true previews any action without changing or sending anything: who would be emailed, with an external count (update also: who is added or removed, and the PATCH body). action=update edits fields via PATCH (subject, start, end, attendees, body, location, isOnlineMeeting, sensitivity, showAs, importance, categories, reminderMinutesBeforeStart); only fields you pass change. action=decline declines an invitation (optional comment; sendResponse: false declines without notifying the organiser). action=cancel cancels an event you organised and emails attendees. action=delete removes the event from your calendar (Graph documents no guaranteed recovery); deleting a meeting you organised that has attendees still emails them a cancellation, so use cancel with a comment to control that message. Returns the updated event on update; a confirmation otherwise. There is no accept action: accept invitations in the Outlook UI, as Graph's accept verb is unreliable.
| Name | Required | Description | Default |
|---|---|---|---|
| id | No | Alias for `eventId` (canonical per the v3.7.3 alias pass). | |
| end | No | New end time as ISO 8601 string or {dateTime, timeZone} object (action=update only) | |
| body | No | New body content (action=update only) | |
| start | No | New start time as ISO 8601 string or {dateTime, timeZone} object (action=update only) | |
| action | Yes | Action to perform (required) | |
| dryRun | No | Preview only: nothing is changed or sent. Shows who would be emailed (decline/cancel/delete) or the PATCH body (update). Default false. | |
| showAs | No | Free/busy status shown to others (action=update only) | |
| comment | No | Message sent with a decline or cancel (optional; omitted if not given) | |
| eventId | No | The ID of the event | |
| subject | No | New subject (action=update only) | |
| location | No | New location display name (action=update only) | |
| attendees | No | Full replacement attendee list — pass the complete desired list, or [] to clear (action=update only). Each entry is an email address string or an {email, type} object (type 'required', 'optional' or 'resource'). A string, or an object without a type, keeps the type that address already has on the event (new addresses are required); an explicit type always wins. When OUTLOOK_ALLOWED_RECIPIENTS is set, every address on the list must be allowed or the update is refused. | |
| categories | No | Full replacement category list — pass [] to clear (action=update only) | |
| importance | No | Event importance flag (action=update only) | |
| sensitivity | No | Event sensitivity classification (action=update only) | |
| sendResponse | No | Send the decline to the organiser (action=decline only; default true). Pass false to decline without notifying the organiser. | |
| isOnlineMeeting | No | Toggle online meeting flag (action=update only) | |
| reminderMinutesBeforeStart | No | Minutes before start to fire the reminder (action=update only) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true and non-idempotent, but the description adds substantially: dryRun previews emails and PATCH body, delete has no guaranteed recovery, deleting an organised meeting still emails attendees a cancellation, and sendResponse: false declines silently. This goes well beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well front-loaded with the dryRun behaviour first, then actions in order, ending with the no-accept note. Some density from the long parenthetical field lists, but every sentence adds actionable information and nothing is redundant.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive, non-idempotent mutation tool with no output schema, the description covers the behavioural risks (irreversibility, unintended emails), the preview mechanism, action semantics, and the accept workaround. An agent has enough to invoke correctly and avoid surprises.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema documents each parameter including the complex attendees replacement semantics. The description adds meaning beyond the schema by grouping parameters by action, noting 'only fields you pass change', and clarifying cancel/delete email behaviour tied to comment. Small gap: reminderMinutesBeforeStart, isOnlineMeeting, and the full field list aren't individually explained in prose, but the action-fields mapping is valuable.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description specifies a clear verb and resource and distinguishes the four actions: update (edits fields via PATCH), decline (declines invitation), cancel (cancels an event you organised and emails attendees), and delete (removes the event). It also names sibling create-event by positioning itself as operating on an existing event.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit guidance on when to use cancel versus delete ('use cancel with a comment to control that message'), that accept is unavailable and should be done in the Outlook UI, and the role of dryRun as a preview. This is the strongest kind of when-to-use/when-not-to guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
manage-focused-inboxFocused InboxADestructive
Manage Focused Inbox sender overrides — explicit rules that force messages from a given sender into Focused or Other regardless of the ML classifier. action=list (default) returns existing overrides with id/sender/classifyAs. action=set creates or updates an override for emailAddress (optional name), routing future mail to focused (default) or other. action=delete removes the override for emailAddress. Note: this only works on accounts that have Focused Inbox enabled — personal Outlook.com accounts without it return an empty list.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | Sender display name (action=set) | |
| action | No | Action to perform (default: list) | |
| classifyAs | No | Where to put emails from this sender (action=set, default: focused) | |
| emailAddress | No | Sender email address (action=set/delete, required) | |
| outputVerbosity | No | Output detail level (action=list, default: standard) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveHint=true, readOnlyHint=false and idempotentHint=false, and the description adds substance on top: delete removes the override, set creates or updates, and accounts lacking Focused Inbox silently return an empty list rather than erroring. That last caveat is genuine context an annotation cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the resource definition, then three tight action clauses, then the account caveat. No sentence is redundant and the ordering matches how an agent would reason about the call.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description usefully names the returned fields for list (id/sender/classifyAs), covers defaults for every optional parameter, and discloses the empty-list edge case. An agent has everything needed to pick an action and call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the schema already documents defaults, enums and which action each parameter belongs to, so the baseline is 3. The description restates those mappings and clarifies that emailAddress is the keying field, but adds no syntax or format detail beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Manage Focused Inbox sender overrides') and immediately defines the resource as explicit per-sender rules that override the ML classifier, which cleanly separates it from generic siblings like manage-rules. The three action values are enumerated with their effects.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear per-action conditions (list=default, set=create/update for emailAddress, delete=remove) plus a real prerequisite: the account must have Focused Inbox enabled. It stops short of explicitly routing the agent away from manage-rules or mailbox-settings when a user actually wants general rule configuration.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
manage-rulesInbox RulesADestructive
Server-side inbox rules (destructive: covers delete; dryRun previews create/update). Rules run on the Exchange server whichever client is open. action=list (default) returns rules with id/name/sequence; includeDetails: true adds conditions/actions/exceptions. action=create builds a rule from condition params (12, e.g. fromAddresses, containsSubject, bodyContains, hasAttachments, importance, sensitivity), action params (9, e.g. moveToFolder/copyToFolder — folder name, nested path like Triage/Delete, or ID — forwardTo, redirectTo, assignCategories, markAsRead, delete) and optional except* exceptions. action=update patches fields by ruleId or ruleName. action=reorder changes priority via sequence (lower runs first). action=delete removes a rule. The recipient allowlist applies to forwardTo/redirectTo. There is no permanentDelete action. Changes count against the session limit (OUTLOOK_MAX_MANAGE_RULES_PER_SESSION, else OUTLOOK_MAX_EMAILS_PER_SESSION); 0 refuses them.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | Rule name (action=create required, action=update to rename) | |
| action | No | Action to perform (default: list) | |
| dryRun | No | Preview only (action=create or update): shows the rule without creating or changing it. Other actions refuse dryRun and change nothing. Default false. | |
| ruleId | No | ID of existing rule (action=update/delete) | |
| ruleName | No | Name of existing rule (action=update/reorder/delete) | |
| sentCcMe | No | Match emails where I am in CC (action=create/update) | |
| sentToMe | No | Match emails sent to me (action=create/update) | |
| sequence | No | Execution order, lower = higher priority (action=create default: auto, action=reorder required) | |
| forwardTo | No | Comma-separated emails to forward matching messages to (action=create/update) | |
| isEnabled | No | Enable/disable rule (action=create default: true, action=update) | |
| importance | No | Match emails with this importance (action=create/update) | |
| markAsRead | No | Mark matching emails as read (action=create/update) | |
| redirectTo | No | Comma-separated emails to redirect matching messages to (action=create/update) | |
| displayName | No | Alias for `name` (matches Graph's own `displayName` field). | |
| sensitivity | No | Match emails with this sensitivity (action=create/update) | |
| bodyContains | No | Comma-separated body text keywords (OR logic) (action=create/update) | |
| copyToFolder | No | Folder to copy matching emails to: a name, a nested path like `Projects/Backup`, a well-known name, or a folder ID (action=create/update) | |
| moveToFolder | No | Folder to move matching emails to: a name, a nested path like `Triage/Delete`, a well-known name (e.g. `archive`), or a folder ID (action=create/update) | |
| sentOnlyToMe | No | Match emails where I am the only recipient (action=create/update) | |
| deleteMessage | No | Move matching emails to Deleted Items (action=create/update) | |
| fromAddresses | No | Comma-separated sender emails to match (action=create/update) | |
| hasAttachments | No | Match emails with attachments (action=create/update) | |
| includeDetails | No | Include detailed conditions, actions, and exceptions (action=list) | |
| markImportance | No | Set importance on matching emails (action=create/update) | |
| senderContains | No | Comma-separated partial sender matches (action=create/update) | |
| containsSubject | No | Comma-separated subject keywords (OR logic). e.g. "invoice, receipt, payment" (action=create/update) | |
| sentToAddresses | No | Comma-separated recipient emails to match (action=create/update) | |
| assignCategories | No | Comma-separated Outlook categories to assign (action=create/update) | |
| isAutomaticReply | No | Match automatic reply emails (action=create/update) | |
| recipientContains | No | Comma-separated partial recipient matches (action=create/update) | |
| exceptBodyContains | No | Comma-separated body keywords to exclude (action=create/update) | |
| exceptFromAddresses | No | Comma-separated sender emails to exclude (action=create/update) | |
| stopProcessingRules | No | Stop evaluating subsequent rules (action=create/update) | |
| exceptHasAttachments | No | Exclude emails with attachments (action=create/update) | |
| exceptSenderContains | No | Comma-separated partial sender matches to exclude (action=create/update) | |
| bodyOrSubjectContains | No | Comma-separated keywords matching body OR subject (OR logic) (action=create/update) | |
| exceptSubjectContains | No | Comma-separated subject keywords to exclude (action=create/update) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial context beyond the annotations: delete is the destructive path, dryRun previews create/update, rules execute server-side regardless of client, the recipient allowlist applies to forwardTo/redirectTo, changes count against a session limit governed by OUTLOOK_MAX_MANAGE_RULES_PER_SESSION (falling back to OUTLOOK_MAX_EMAILS_PER_SESSION) with 0 refusing changes, and there is no permanentDelete action.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single dense paragraph that front-loads the destructive warning and dryRun semantics before the action breakdown. Nearly every clause carries information, though the action list and parameter-group enumeration could be tightened.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 37-parameter, multi-action tool with no output schema, the description covers action semantics, defaults, side effects, allowlist constraints, and session limits, and briefly indicates the list return shape (id/name/sequence). An agent has what it needs to choose an action and call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3, but the description adds value by grouping parameters (12 condition params, 9 action params, except* exceptions) and noting folder syntax (name, nested path like Triage/Delete, well-known name, or ID). Most field-level detail, however, is already in the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific resource (server-side inbox rules) and enumerates the exact action set: list, create, update, reorder, delete. An agent can distinguish this from siblings like manage-category or manage-focused-inbox without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives per-action conditions and defaults (action=list default, dryRun only valid for create/update and refused elsewhere, ruleId vs ruleName for update/delete/reorder). It does not explicitly route to sibling tools, but the action-level guidance is concrete and actionable.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read-emailRead EmailARead-onlyIdempotent
Read a single email by id (read-only). Returns subject, from/to/cc, date and the body as Markdown (HTML stripped to text): up to 2,000 characters by default, up to 40,000 with outputVerbosity: full; a cut body ends with a note on how to get the rest. With headersMode: true: returns RFC-822 forensic headers in place of the body (DKIM, SPF, DMARC, Received chain, Message-ID, Authentication-Results) — importantOnly: true for the security-relevant subset, groupByType: true for a category-bucketed view, raw: true for JSON. With includeHeaders: true (non-headers-mode): adds basic headers alongside the body. If the id came from a shared/delegated mailbox (e.g. via search-emails or access-shared-mailbox with sharedMailbox set), you MUST pass the same sharedMailbox (or alias email) here — message IDs are mailbox-scoped, and reading a shared-mailbox id without it fails with 404 ErrorInvalidMailboxItemId.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | ID of the email to read | |
| raw | No | Return the headers as raw JSON, not Markdown (headersMode only, default: false) | |
| No | Alias for `sharedMailbox`. | ||
| groupByType | No | Group headers by category (headersMode only, default: false) | |
| headersMode | No | Return forensic headers in place of the email content (default: false) | |
| importantOnly | No | Show only important headers (headersMode only, default: false) | |
| sharedMailbox | No | Email address of the shared/delegated mailbox the id belongs to. Required when the id was obtained from a shared mailbox — message IDs are mailbox-scoped and reading without it returns 404 ErrorInvalidMailboxItemId. Requires delegate access + Mail.Read.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance). | |
| includeHeaders | No | Include basic headers alongside email content (default: false) | |
| outputVerbosity | No | Output detail level (default: standard). minimal: body preview only; standard: body up to 2,000 characters; full: adds IDs, body up to 40,000 characters. For a longer body, export it with `export` target=message. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Well beyond the annotations: it discloses default truncation at 2,000 characters vs 40,000 with outputVerbosity: full, that truncated bodies end with a remediation note, the exact failure mode (404 ErrorInvalidMailboxItemId) for mailbox-scoped ids, and that shared-mailbox access requires delegate access, Mail.Read.Shared, and the OUTLOOK_SHARED_MAILBOX opt-in.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single dense paragraph, but it is front-loaded with the core behavior (read by id, what is returned, truncation limits) before moving to the mode flags and the critical sharedMailbox warning. Some phrasing duplicates the schema's sharedMailbox text, which costs a little efficiency.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 9-parameter, mode-heavy tool with no output schema, the description covers what comes back, how much, how it is truncated, how the header modes alter the payload, and the single highest-risk failure mode. An agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, but the description adds cross-parameter semantics the schema does not: raw/importantOnly/groupByType are explicitly scoped to headersMode, and includeHeaders is described as the non-headers-mode variant. The outputVerbosity character budgets and the sharedMailbox 404 consequence go beyond the field-level descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Read a single email by id') and immediately scopes it as read-only, which distinguishes it from siblings like search-emails, send-email, and update-email. The return payload is enumerated concretely (subject, from/to/cc, date, body as Markdown).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear mode-selection guidance (headersMode, includeHeaders, outputVerbosity) and a hard routing rule: if the id came from search-emails or access-shared-mailbox with sharedMailbox set, the same sharedMailbox MUST be passed here. It does not explicitly state when NOT to use this tool (e.g. for multiple messages), so it stops short of full when/when-not/alternatives coverage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search-emailsSearch EmailsARead-onlyIdempotent
Search, list, delta-sync or thread-group emails (read-only); parameters set the mode. No params: recent emails in folder (default inbox). query/from/to/subject/date filters: search, combined as an OData filter. searchExpression (deprecated alias kqlQuery): a raw Graph $search expression. deltaMode: true: current state plus a deltaToken to pass back next time for changes only. groupByConversation: true: conversation threads. conversationId: every message in one thread. internetMessageId: the message with that RFC Message-ID. sharedMailbox (alias email) searches a shared/delegated mailbox, custom folders and nested paths included. Personal Outlook.com accounts have limited $search, so the tool falls back to OData filters and a recent listing automatically; structured filters (from/subject/receivedAfter/hasAttachments/unreadOnly) give cleaner results there. Returns up to count messages (id/subject/from/receivedDateTime/preview); outputVerbosity expands them.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | Filter by recipient email/name. Personal Outlook.com accounts reject the server-side recipient filter, in which case this is matched locally over the 500 most recent messages only (raise with `OUTLOOK_SEARCH_SCAN_LIMIT`). On a large archive, pair `to` with `receivedAfter`/`receivedBefore` to reach older mail; the response says so when the scan was truncated. | |
| from | No | Filter by sender email/name | |
| count | No | Number of results (list default: 25, search default: 10, max: 50). There is no page cursor: when the result says more emails are available, raise `count` or narrow `receivedAfter`/`receivedBefore`. | |
| No | Alias for `sharedMailbox`. | ||
| query | No | Search query text. Omit for list mode. On personal Outlook.com accounts Graph `$search` is unavailable, so this falls back to a subject substring match (all words must appear in the subject) — precise, but it does NOT search message bodies. Use `searchExpression` when you need body content. | |
| folder | No | Email folder (default: 'inbox'). Accepts a well-known name, a custom/localized display name, or a nested path like `Inbox/Subfolder`. | |
| subject | No | Filter by subject | |
| kqlQuery | No | DEPRECATED alias for `searchExpression` (this was never full KQL — it is a Graph `$search` expression). | |
| deltaMode | No | Enable delta sync mode. Returns only changes since last sync. Use deltaToken for subsequent calls; an initial sync larger than `maxResults` arrives over several pages, each returning a continuation token to pass back until a delta token is returned. Honors `sharedMailbox`/`email` (and custom `folder` paths) to sync within a shared/delegated mailbox. | |
| deltaToken | No | Token from previous delta call for incremental sync (deltaMode only). The token is authoritative — it encodes its own mailbox and folder, so `folder`/`sharedMailbox` are ignored and a token from a different mailbox is rejected. | |
| maxResults | No | Delta sync page size (deltaMode only): 1-200, default 100, sent to Graph as the `Prefer: odata.maxpagesize` header. It sizes each page, not the whole sync: while a page returns a continuation token, keep calling with that token until a delta token is returned, and pass the same `maxResults` on every page (an omitted value means 100). | |
| unreadOnly | No | Filter to unread emails only | |
| receivedAfter | No | Filter emails received after date (ISO 8601) | |
| sharedMailbox | No | Email address of a shared/delegated mailbox to search (default: the signed-in account). Combine with `folder` (incl. custom subfolders/paths) or `searchAllFolders`. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance). | |
| conversationId | No | Get the messages in a conversation thread by conversationId, oldest first (up to 100; a longer thread is marked truncated — use `export target=conversation` for up to 1000). Honors `sharedMailbox`/`email` to thread within a shared/delegated mailbox. | |
| hasAttachments | No | Filter to emails with attachments | |
| includeHeaders | No | Include email headers for each message (conversationId only) | |
| receivedBefore | No | Filter emails received before date (ISO 8601) | |
| outputVerbosity | No | Output detail level (default: standard) | |
| searchAllFolders | No | Search across all mail folders | |
| searchExpression | No | Raw Microsoft Graph `$search` expression for advanced server-side search, e.g. `subject:"invoice"`, `from:github.com`, or `foo OR bar`. Quote your own phrases, escaping any `"` or `\` inside them with a backslash; a single bare token is auto-quoted and escaped for you. Pair with `searchAllFolders: true` for cross-folder search. Bypasses other search params. NOTE: personal Outlook.com accounts reject field-scoped `$search` outright; since v3.10.0 recognised `from:`/`to:`/`subject:` expressions are translated into the closest equivalent OData filters and retried automatically (a `subject:` term becomes a substring match, so it is close but not identical) (reported as strategy `raw-kql-translated`). Expressions that cannot be translated exactly — free text, `AND`/`OR`, unknown prefixes — are not retried, so use `query` for those there. RELEVANCE, NOT RECENCY: an untranslated expression is answered by Graph `$search` over the whole message including the body, ranked by relevance and not sorted by date, so top hits can look unrelated to a caller expecting a subject match. `query` is the more predictable choice for a term you expect in a subject line; `searchExpression` is the one that reaches body text. | |
| internetMessageId | No | Look up email by Message-ID header (e.g. <abc123@example.com>). For threading/deduplication. Honors `sharedMailbox`/`email` to look up within a shared/delegated mailbox. | |
| groupByConversation | No | List conversations (threads) grouped by conversationId, not individual emails. Honors `sharedMailbox`/`email` (and custom `folder` paths) to group within a shared/delegated mailbox. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only, idempotent, non-destructive, openWorld, so the bar is lower, yet the description still adds real behavior: automatic OData fallback on personal accounts, `deltaToken` being authoritative/rejected cross-mailbox, shared-mailbox opt-in refusal, and scan truncation. It stops short of describing pagination cursor absence and rate limits in the description itself, though those appear in the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The mode summary is front-loaded and each clause is terse shorthand rather than prose, but the single dense paragraph repeats substantial ground already covered by the 23 schema descriptions, making it heavier than strictly necessary.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 23-parameter multi-mode tool with no output schema, the description covers mode selection, per-mode caveats, fallback behavior, error/opt-in conditions, and the return shape (id/subject/from/receivedDateTime/preview plus outputVerbosity). Nothing essential to correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 100% schema coverage the baseline is 3, and the schema already documents each parameter thoroughly; the description adds connective meaning by framing parameters as mode selectors and flagging the `kqlQuery` deprecation and the relevance-not-recency caveat for `searchExpression`. That is genuine added value beyond the schema fields.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb set and resource: 'Search, list, delta-sync or thread-group emails (read-only); parameters set the mode.' An agent immediately understands this is a multi-mode read tool and that the parameters select the mode, which cleanly separates it from read-email and folders.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit mode-selection map (no params = recent listing; filters = OData search; searchExpression = raw $search; deltaMode = sync; groupByConversation = threads) and names alternatives where it matters, e.g. preferring structured filters or `query` over `searchExpression` on personal Outlook.com accounts.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search-peoplePeople SearchARead-onlyIdempotent
Relevance-ranked search across personal contacts, organisation directory, and recent communications via the Microsoft Graph People API (read-only). Returns people objects with displayName, emailAddresses, companyName, jobTitle, and relevance metadata, for "who is X?" or "who do I email about Y?" lookups. For entries from your personal contact store only, use manage-contact action=search.
| Name | Required | Description | Default |
|---|---|---|---|
| count | No | Maximum results to return (default: 25, max: 50) | |
| query | Yes | Search query (name, email, company) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare the safety profile (read-only, idempotent, open-world, non-destructive), so the bar is lower, yet the description still adds real value: relevance-ranked ordering, the three data sources searched, and the five fields returned. That return-shape disclosure matters especially since there is no output schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences with zero filler, ordered purpose → returns → alternative routing, so the most decision-relevant information leads. Each sentence carries a distinct load.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter, flat, read-only search with complete schema coverage and full annotations, the description supplies the only missing pieces: the result shape (compensating for the absent output schema) and the sibling boundary. Nothing an agent needs to call it correctly is absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so both `query` and `count` (with its default/max bounds) are already fully documented in the schema. The description adds only the framing that `query` covers name, email, or company, which duplicates rather than extends the schema's own wording. Baseline 3 is correct.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('relevance-ranked search') plus the exact resource scope (personal contacts, organisation directory, recent communications) and the backing API. The named return fields let an agent confirm this is a people-lookup tool distinct from search-emails or list-events without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete triggering intents ('who is X?', 'who do I email about Y?') and explicitly routes the personal-contact-store-only case to a named sibling with its exact action argument. Nothing about when-to-use is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
send-emailSend EmailADestructive
Compose and send an email immediately (destructive: sends external comms). Returns a confirmation. Safety controls: dryRun: true returns the composed message for review without sending; checkRecipients: true runs get-mail-tips first and returns its warnings. If the tips show an out-of-office reply, a full mailbox, a delivery restriction or external recipients, the send is refused until repeated with acknowledgeWarnings: true. Personal Outlook.com accounts return no tips, and no warnings is not proof of delivery. Subject to the session limit (OUTLOOK_MAX_SEND_EMAIL_PER_SESSION, else OUTLOOK_MAX_EMAILS_PER_SESSION; 0 refuses every send) and recipient allowlist (OUTLOOK_ALLOWED_RECIPIENTS) when configured; both refuse before any Graph request. For a review-before-send workflow, use draft (action=create → update → send); a draft can be checked in Outlook before it goes. Comma-separated recipient strings or arrays both accepted.
| Name | Required | Description | Default |
|---|---|---|---|
| cc | No | Comma-separated CC email addresses | |
| to | Yes | Comma-separated recipient email addresses | |
| bcc | No | Comma-separated BCC email addresses | |
| body | Yes | Email body (plain text or HTML) | |
| dryRun | No | Preview email without sending (default: false). Returns composed email for review. | |
| subject | Yes | Email subject | |
| importance | No | Email importance (default: normal) | |
| checkRecipients | No | Check recipients with mail tips before sending (default: false). Out-of-office, mailbox full, delivery restrictions or external recipients refuse the send unless acknowledgeWarnings=true. Combine with dryRun=true for pre-send review. | |
| saveToSentItems | No | Save to sent items (default: true) | |
| acknowledgeWarnings | No | Send even though checkRecipients flagged an out-of-office reply, a full mailbox, a delivery restriction or external recipients (default: false). Without it those warnings refuse the send. Pass only after the user has seen the warnings. No effect without checkRecipients. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true and openWorldHint=true, but the description adds substantial behavioral context: the session-limit env vars, recipient allowlist enforcement, the refuse-before-Graph-request behavior, the acknowledgeWarnings gate, and the caveat that Outlook.com accounts return no tips and no warnings is not proof of delivery.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense and front-loaded, with the destructive warning and primary purpose stated first, then safety controls, then the draft alternative. It is on the longer side with several env-var names, but nearly every sentence carries actionable information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, but the description states it returns a confirmation. Combined with the safety-control, refusal, and limit semantics, an agent has everything needed to invoke this 10-parameter mutation tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds cross-parameter logic the schema lacks: the interaction between checkRecipients and acknowledgeWarnings, that acknowledgeWarnings has no effect without checkRecipients, and that comma-separated strings or arrays are both accepted.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('compose and send an email immediately') plus the return behavior ('Returns a confirmation') and explicitly flags the destructive nature. It clearly differentiates from the sibling 'draft' tool by naming it as the alternative for review-before-send.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly names alternatives and the conditions that select them: dryRun for preview, checkRecipients for mail-tip gating, and 'For a review-before-send workflow, use draft'. It also states when-not (warnings refuse the send until acknowledgeWarnings) and the allowlist/session-limit refusal conditions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
update-emailUpdate EmailAIdempotent
Update message state without modifying content (idempotent — safe to retry). action=mark-read/mark-unread sets isRead on a single message by id. action=flag sets a follow-up flag with optional dueDateTime/startDateTime: ISO 8601 with a time, kept as that exact instant when it has Z or a ±hh:mm offset and read in OUTLOOK_DEFAULT_TIMEZONE when it has none; date-only or unparseable values are refused before any change. With only dueDateTime, the start is 09:00 on the due date, or the due time if earlier. action=unflag clears the flag; action=complete marks it done. Flag/unflag/complete take id (single) or ids (batch, updated one at a time: one PATCH each, not Graph $batch). sharedMailbox (alias email) updates messages in a shared/delegated mailbox (default: the signed-in account; needs Mail.ReadWrite.Shared and delegate access). Returns a status per message.
| Name | Required | Description | Default |
|---|---|---|---|
| id | No | Single message ID (required for mark-read/mark-unread; flag actions take `id` or `ids`) | |
| ids | No | Array of message IDs for batch flag/unflag/complete operations | |
| No | Alias for `sharedMailbox`. | ||
| action | Yes | Action to perform (required) | |
| dueDateTime | No | Due date/time for follow-up (action=flag). ISO 8601 with a time: "2026-03-01T09:00:00Z" or "2026-03-01T09:00:00+10:00" is that exact instant; "2026-03-01T09:00:00" (no zone) is read in the default timezone (OUTLOOK_DEFAULT_TIMEZONE). | |
| sharedMailbox | No | Email address of the shared/delegated mailbox whose message(s) to update (default: the signed-in account). Requires delegate access + Mail.ReadWrite.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance). | |
| startDateTime | No | Start date/time for follow-up (action=flag), same format as dueDateTime. Defaults to 09:00 on the due date in the default timezone (capped at the due time) when only dueDateTime is given. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the annotations: it explains why idempotency holds (safe to retry), that batch updates issue one PATCH per message rather than a Graph $batch, that unparseable or date-only times are refused before any change occurs, and it spells out auth/delegate prerequisites and the server opt-in setting. This is exactly the behavioral context annotations alone cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core guarantee ('update state without modifying content') and every subsequent sentence carries distinct, load-bearing detail. It is dense but somewhat long; a few clauses could be tightened, but nothing is filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 7-parameter mutation tool with no output schema, it covers all the decision-relevant surface: per-action requirements, batch semantics, timezone edge cases, permission/opt-in prerequisites, and even the return shape ('a status per message'). An agent can invoke this correctly without further inference.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema coverage is 100%, the description adds semantics the schema does not: the timezone resolution rule for zoned vs unzoned ISO strings, the 09:00 default start (capped at the due time) when only `dueDateTime` is given, the `email` alias for `sharedMailbox`, and the single-vs-batch behavior of `id`/`ids`. These are genuine additions, not restatements.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening clause names the exact operation and its boundary: 'Update message state without modifying content', which cleanly separates it from siblings like read-email, send-email, and draft. It then enumerates the concrete state transitions (read/unread, flag/unflag/complete), so an agent knows precisely what this tool touches.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It maps each action to its purpose and states conditions: mark-read/mark-unread need a single `id`, flag/unflag/complete accept `id` or `ids`, and `sharedMailbox` is only for delegated mailboxes needing Mail.ReadWrite.Shared plus the OUTLOOK_SHARED_MAILBOX opt-in. It does not explicitly route the agent to a sibling (e.g. read-email for content), so usage context is clear but not fully comparative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
17 tool updates
v3.14.1- Changed
access-shared-mailbox3 fields changed- changed
Input schema / properties / folder / descriptionPrevious value: -"Folder to read from (default: inbox)"New value: +"Folder to read from (default: inbox). Accepts a well-known name, a custom/localized display name, or a nested path like `Inbox/Subfolder`." - added
Input schema / properties / folderIdAdded value: +{ + "description": "Exact Graph folder ID to read from (e.g. from `listFolders`). Skips name resolution; takes precedence over `folder`.", + "type": "string" +} - added
Input schema / properties / listFoldersAdded value: +{ + "description": "Enumerate the shared mailbox's full folder tree (names, paths, IDs, item counts) in place of reading messages.", + "type": "boolean" +}
- Changed
apply-category2 fields changed- added
Input schema / properties / emailAdded value: +{ + "description": "Alias for `sharedMailbox`.", + "type": "string" +} - added
Input schema / properties / sharedMailboxAdded value: +{ + "description": "Email address of the shared/delegated mailbox whose messages to categorise (default: the signed-in account). Requires delegate access + Mail.ReadWrite.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance).", + "type": "string" +}
- Changed
attachments3 fields changed- added
Input schema / properties / emailAdded value: +{ + "description": "Alias for `sharedMailbox`.", + "type": "string" +} - changed
Input schema / properties / outputDir / descriptionPrevious value: -"Directory to save file (action=download, default: system tmpdir). Auto-created if missing."New value: +"Absolute directory (or ~/…) to save the file in (action=download, default: system temp directory). Auto-created if missing. Must be inside the system temp directory, ~/Downloads, ~/Documents or OUTLOOK_EXPORT_DIR, with no dot-prefixed folder names." - added
Input schema / properties / sharedMailboxAdded value: +{ + "description": "Email address of the shared/delegated mailbox the messageId belongs to. Required when the message came from a shared mailbox. Requires delegate access + Mail.Read.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance).", + "type": "string" +}
- Changed
auth1 field changed- added
Input schema / properties / clientIdAdded value: +{ + "description": "Optional, action=authenticate only. The user's Azure Application (client) ID (a GUID from the app registration's Overview page). Saved to `~/.outlook-assistant-config.json` and used from then on; it is not a secret. The OUTLOOK_CLIENT_ID environment variable takes precedence when set.", + "type": "string" +}
- Changed
create-event4 fields changed- changed
Input schema / properties / attendees / descriptionPrevious value: -"List of attendee email addresses"New value: +"Attendees: email address strings (required attendees) or {email, type} objects, where type is 'required', 'optional' or 'resource' (a room or equipment)" - added
Input schema / properties / attendees / items / oneOfAdded value: +[ + { + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "email": { + "type": "string" + }, + "type": { + "enum": [ + "required", + "optional", + "resource" + ], + "type": "string" + } + }, + "required": [ + "email" + ], + "type": "object" + } +] - removed
Input schema / properties / attendees / items / typeRemoved value: -"string" - added
Input schema / properties / dryRunAdded value: +{ + "description": "Preview only: nothing is created and no invitations are sent. Shows who would be invited and how many are external (default false).", + "type": "boolean" +}
- Changed
draft2 fields changed- changed
Input schema / properties / dryRun / descriptionPrevious value: -"Preview draft without saving (action=create only, default: false)"New value: +"Preview only (action=create): shows the draft without saving it. Other actions refuse dryRun and change nothing. Default false." - changed
Input schema / properties / id / descriptionPrevious value: -"Draft or message ID. Required for update/send/delete/reply/reply-all/forward."New value: +"Draft or message ID. Required for update/send/delete/reply/reply-all/forward. update/send/delete need a draft ID; reply/reply-all/forward take any message ID."
- Changed
export14 fields changed- added
Input schema / properties / emailAdded value: +{ + "description": "Alias for `sharedMailbox`.", + "type": "string" +} - changed
Input schema / properties / emailIds / descriptionPrevious value: -"Email IDs to export (target=messages)"New value: +"Email IDs to export (target=messages). At most 100 per call: any beyond the first 100 are left out, and the result says how many." - changed
Input schema / properties / format / descriptionPrevious value: -"Export format. Valid values vary by target: target=message accepts mime/eml/markdown/json/csv (mbox and html are conversation-only). target=conversation accepts eml/mbox/markdown/json/html/csv. target=messages (batch) accepts markdown/json/csv. mime is an alias for eml (same RFC822 bytes, .eml extension on disk)."New value: +"Export format. Valid values vary by target: target=message accepts mime/eml/markdown/json/csv (mbox and html are conversation-only). target=conversation accepts eml/mbox/markdown/json/html/csv. target=messages (batch) accepts mime/eml/markdown/json (one file per message) or csv (one file). mime is an alias for eml (same RFC822 bytes, .eml extension on disk)." - changed
Input schema / properties / outputDir / descriptionPrevious value: -"Output directory (target=messages/conversation, required)"New value: +"Absolute output directory, or one starting with ~/ (target=messages, required; target=message/conversation, default: system temp directory). Must be inside the system temp directory, ~/Downloads, ~/Documents or OUTLOOK_EXPORT_DIR." - added
Input schema / properties / overwriteAdded value: +{ + "description": "Replace an existing file at savePath (target=message, default: false). Never replaces a symlink, a hard-linked file, a dotfile, or a file in a dot-directory below the allowed folder.", + "type": "boolean" +} - changed
Input schema / properties / savePath / descriptionPrevious value: -"File path or directory (target=message)"New value: +"Absolute file path or directory, or one starting with ~/ (target=message). Relative paths are refused. Must be inside the system temp directory, ~/Downloads, ~/Documents or OUTLOOK_EXPORT_DIR. An existing file is not replaced unless overwrite is true." - changed
Input schema / properties / searchQuery / descriptionPrevious value: -"Search query to find emails (target=messages, alternative to emailIds)"New value: +"Search to find emails (target=messages, alternative to emailIds)" - added
Input schema / properties / searchQuery / properties / folder / descriptionAdded value: +"Folder to search (default: inbox): a well-known name, display name, `Parent/Child` path or folder ID" - added
Input schema / properties / searchQuery / properties / from / descriptionAdded value: +"Sender address or name to match (Graph `$search`)" - added
Input schema / properties / searchQuery / properties / maxResults / descriptionAdded value: +"Most messages to export (default: 25, max: 100 per call). Newest first, or by relevance when `from`/`subject` is set." - added
Input schema / properties / searchQuery / properties / receivedAfter / descriptionAdded value: +"Only messages received at or after this date/time (ISO 8601)" - added
Input schema / properties / searchQuery / properties / receivedBefore / descriptionAdded value: +"Only messages received at or before this date/time (ISO 8601)" - added
Input schema / properties / searchQuery / properties / subject / descriptionAdded value: +"Subject text to match (Graph `$search`)" - added
Input schema / properties / sharedMailboxAdded value: +{ + "description": "Email address of a shared/delegated mailbox to export from (default: the signed-in account). Applies to all targets (message/messages/conversation/mime) — pass it whenever the id(s)/conversationId/searchQuery belong to a shared mailbox. Requires delegate access + Mail.Read.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance).", + "type": "string" +}
- Changed
folders4 fields changed- added
Input schema / properties / dryRunAdded value: +{ + "description": "Preview only (action=delete): nothing is deleted. Shows the folder and how many items and subfolders would be lost. Other actions refuse dryRun and change nothing. Default false.", + "type": "boolean" +} - added
Input schema / properties / emailAdded value: +{ + "description": "Alias for `sharedMailbox`.", + "type": "string" +} - added
Input schema / properties / sharedMailboxAdded value: +{ + "description": "Email address of a shared/delegated mailbox to target (all actions; default: the signed-in account). Requires delegate access + Mail.Read.Shared (list/stats) or Mail.ReadWrite.Shared (create/move/delete). Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance).", + "type": "string" +} - changed
Input schema / properties / sourceFolder / descriptionPrevious value: -"Source folder name, default is inbox (action=move)"New value: +"Ignored: action=move moves each email by ID from wherever it is. Accepted for older callers."
- Changed
list-events4 fields changed- changed
Input schema / properties / count / descriptionPrevious value: -"Number of events to retrieve (default: 10, max: 50)"New value: +"Number of events to retrieve (default: 10, max: 100)" - added
Input schema / properties / startAfterAdded value: +{ + "description": "Optional ISO 8601 datetime with `Z` or a ±hh:mm offset (required). Only return events whose start is on or after this time. Replaces the default \"now\" lower bound when supplied. Example: \"2026-01-01T00:00:00Z\" or \"2026-01-01T09:00:00+10:00\".", + "format": "date-time", + "type": "string" +} - added
Input schema / properties / startBeforeAdded value: +{ + "description": "Optional ISO 8601 datetime with `Z` or a ±hh:mm offset (required). Only return events whose start is strictly before this time. Combine with `startAfter` to bound a window; on its own, results are newest first. Example: \"2026-02-01T00:00:00Z\".", + "format": "date-time", + "type": "string" +} - added
Input schema / properties / subjectAdded value: +{ + "description": "Optional substring (max 255 characters) to match against the event subject, case-insensitive (Graph `contains()`). Useful for finding past or current events by name; on its own, results are newest first.", + "maxLength": 255, + "type": "string" +}
- Changed
mailbox-settings1 field changed- added
Input schema / properties / dryRunAdded value: +{ + "description": "Preview only (action=set-auto-replies): nothing is changed. Shows who would get automatic replies, the schedule and each message length. Other actions refuse dryRun and change nothing. Default false.", + "type": "boolean" +}
- Changed
manage-contact1 field changed- added
Input schema / properties / dryRunAdded value: +{ + "description": "Preview only (action=delete): nothing is deleted. Shows which contact would be removed. Other actions refuse dryRun and change nothing. Default false.", + "type": "boolean" +}
- Changed
manage-event6 fields changed- changed
Input schema / properties / attendees / descriptionPrevious value: -"Full replacement attendee list — pass complete desired list, or [] to clear (action=update only)"New value: +"Full replacement attendee list — pass the complete desired list, or [] to clear (action=update only). Each entry is an email address string or an {email, type} object (type 'required', 'optional' or 'resource'). A string, or an object without a type, keeps the type that address already has on the event (new addresses are required); an explicit type always wins. When OUTLOOK_ALLOWED_RECIPIENTS is set, every address on the list must be allowed or the update is refused." - added
Input schema / properties / attendees / items / oneOfAdded value: +[ + { + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "email": { + "type": "string" + }, + "type": { + "enum": [ + "required", + "optional", + "resource" + ], + "type": "string" + } + }, + "required": [ + "email" + ], + "type": "object" + } +] - removed
Input schema / properties / attendees / items / typeRemoved value: -"string" - changed
Input schema / properties / comment / descriptionPrevious value: -"Optional comment for declining or cancelling the event"New value: +"Message sent with a decline or cancel (optional; omitted if not given)" - changed
Input schema / properties / dryRun / descriptionPrevious value: -"Preview the PATCH without applying it (action=update only). Returns the body that would be sent to Graph."New value: +"Preview only: nothing is changed or sent. Shows who would be emailed (decline/cancel/delete) or the PATCH body (update). Default false." - added
Input schema / properties / sendResponseAdded value: +{ + "description": "Send the decline to the organiser (action=decline only; default true). Pass false to decline without notifying the organiser.", + "type": "boolean" +}
- Changed
manage-rules3 fields changed- changed
Input schema / properties / copyToFolder / descriptionPrevious value: -"Folder name to copy matching emails to (action=create/update)"New value: +"Folder to copy matching emails to: a name, a nested path like `Projects/Backup`, a well-known name, or a folder ID (action=create/update)" - changed
Input schema / properties / dryRun / descriptionPrevious value: -"Preview rule without creating/updating (action=create, action=update)"New value: +"Preview only (action=create or update): shows the rule without creating or changing it. Other actions refuse dryRun and change nothing. Default false." - changed
Input schema / properties / moveToFolder / descriptionPrevious value: -"Folder name to move matching emails to (action=create/update)"New value: +"Folder to move matching emails to: a name, a nested path like `Triage/Delete`, a well-known name (e.g. `archive`), or a folder ID (action=create/update)"
- Changed
read-email5 fields changed- added
Input schema / properties / emailAdded value: +{ + "description": "Alias for `sharedMailbox`.", + "type": "string" +} - changed
Input schema / properties / headersMode / descriptionPrevious value: -"Return forensic headers instead of email content (default: false)"New value: +"Return forensic headers in place of the email content (default: false)" - changed
Input schema / properties / outputVerbosity / descriptionPrevious value: -"Output detail level (default: standard)"New value: +"Output detail level (default: standard). minimal: body preview only; standard: body up to 2,000 characters; full: adds IDs, body up to 40,000 characters. For a longer body, export it with `export` target=message." - changed
Input schema / properties / raw / descriptionPrevious value: -"Return raw JSON instead of Markdown (headersMode only, default: false)"New value: +"Return the headers as raw JSON, not Markdown (headersMode only, default: false)" - added
Input schema / properties / sharedMailboxAdded value: +{ + "description": "Email address of the shared/delegated mailbox the id belongs to. Required when the id was obtained from a shared mailbox — message IDs are mailbox-scoped and reading without it returns 404 ErrorInvalidMailboxItemId. Requires delegate access + Mail.Read.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance).", + "type": "string" +}
- Changed
search-emails12 fields changed- changed
Input schema / properties / conversationId / descriptionPrevious value: -"Get all messages in a conversation thread by conversationId."New value: +"Get the messages in a conversation thread by conversationId, oldest first (up to 100; a longer thread is marked truncated — use `export target=conversation` for up to 1000). Honors `sharedMailbox`/`email` to thread within a shared/delegated mailbox." - changed
Input schema / properties / count / descriptionPrevious value: -"Number of results (list default: 25, search default: 10, max: 50)"New value: +"Number of results (list default: 25, search default: 10, max: 50). There is no page cursor: when the result says more emails are available, raise `count` or narrow `receivedAfter`/`receivedBefore`." - changed
Input schema / properties / deltaMode / descriptionPrevious value: -"Enable delta sync mode. Returns only changes since last sync. Use deltaToken for subsequent calls."New value: +"Enable delta sync mode. Returns only changes since last sync. Use deltaToken for subsequent calls; an initial sync larger than `maxResults` arrives over several pages, each returning a continuation token to pass back until a delta token is returned. Honors `sharedMailbox`/`email` (and custom `folder` paths) to sync within a shared/delegated mailbox." - changed
Input schema / properties / deltaToken / descriptionPrevious value: -"Token from previous delta call for incremental sync (deltaMode only)"New value: +"Token from previous delta call for incremental sync (deltaMode only). The token is authoritative — it encodes its own mailbox and folder, so `folder`/`sharedMailbox` are ignored and a token from a different mailbox is rejected." - added
Input schema / properties / emailAdded value: +{ + "description": "Alias for `sharedMailbox`.", + "type": "string" +} - changed
Input schema / properties / folder / descriptionPrevious value: -"Email folder (default: 'inbox')"New value: +"Email folder (default: 'inbox'). Accepts a well-known name, a custom/localized display name, or a nested path like `Inbox/Subfolder`." - changed
Input schema / properties / groupByConversation / descriptionPrevious value: -"List conversations (threads) grouped by conversationId instead of individual emails."New value: +"List conversations (threads) grouped by conversationId, not individual emails. Honors `sharedMailbox`/`email` (and custom `folder` paths) to group within a shared/delegated mailbox." - changed
Input schema / properties / internetMessageId / descriptionPrevious value: -"Look up email by Message-ID header (e.g. <abc123@example.com>). For threading/deduplication."New value: +"Look up email by Message-ID header (e.g. <abc123@example.com>). For threading/deduplication. Honors `sharedMailbox`/`email` to look up within a shared/delegated mailbox." - changed
Input schema / properties / kqlQuery / descriptionPrevious value: -"DEPRECATED alias for `searchExpression` (this was never full KQL — it is a Graph `$search` expression). Prefer `searchExpression`."New value: +"DEPRECATED alias for `searchExpression` (this was never full KQL — it is a Graph `$search` expression)." - changed
Input schema / properties / maxResults / descriptionPrevious value: -"Max results per page for delta sync (default: 100, max: 200)"New value: +"Delta sync page size (deltaMode only): 1-200, default 100, sent to Graph as the `Prefer: odata.maxpagesize` header. It sizes each page, not the whole sync: while a page returns a continuation token, keep calling with that token until a delta token is returned, and pass the same `maxResults` on every page (an omitted value means 100)." - changed
Input schema / properties / searchExpression / descriptionPrevious value: -"Raw Microsoft Graph `$search` expression for advanced server-side search, e.g. `subject:\"invoice\"`, `from:github.com`, or `foo OR bar`. Quote your own phrases; a single bare token is auto-quoted. Pair with `searchAllFolders: true` for cross-folder search. Bypasses other search params. NOTE: personal Outlook.com accounts reject field-scoped `$search` outright; since v3.10.0 recognised `from:`/`to:`/`subject:` expressions are translated into the closest equivalent OData filters and retried automatically (a `subject:` term becomes a substring match, so it is close but not identical) (reported as strategy `raw-kql-translated`). Expressions that cannot be translated exactly — free text, `AND`/`OR`, unknown prefixes — are not retried, so use `query` for those there. RELEVANCE, NOT RECENCY: an untranslated expression is answered by Graph `$search` over the whole message including the body, ranked by relevance and not sorted by date, so top hits can look unrelated to a caller expecting a subject match. `query` is the more predictable choice for a term you expect in a subject line; `searchExpression` is the one that reaches body text."New value: +"Raw Microsoft Graph `$search` expression for advanced server-side search, e.g. `subject:\"invoice\"`, `from:github.com`, or `foo OR bar`. Quote your own phrases, escaping any `\"` or `\\` inside them with a backslash; a single bare token is auto-quoted and escaped for you. Pair with `searchAllFolders: true` for cross-folder search. Bypasses other search params. NOTE: personal Outlook.com accounts reject field-scoped `$search` outright; since v3.10.0 recognised `from:`/`to:`/`subject:` expressions are translated into the closest equivalent OData filters and retried automatically (a `subject:` term becomes a substring match, so it is close but not identical) (reported as strategy `raw-kql-translated`). Expressions that cannot be translated exactly — free text, `AND`/`OR`, unknown prefixes — are not retried, so use `query` for those there. RELEVANCE, NOT RECENCY: an untranslated expression is answered by Graph `$search` over the whole message including the body, ranked by relevance and not sorted by date, so top hits can look unrelated to a caller expecting a subject match. `query` is the more predictable choice for a term you expect in a subject line; `searchExpression` is the one that reaches body text." - added
Input schema / properties / sharedMailboxAdded value: +{ + "description": "Email address of a shared/delegated mailbox to search (default: the signed-in account). Combine with `folder` (incl. custom subfolders/paths) or `searchAllFolders`. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance).", + "type": "string" +}
- Changed
send-email2 fields changed- added
Input schema / properties / acknowledgeWarningsAdded value: +{ + "default": false, + "description": "Send even though checkRecipients flagged an out-of-office reply, a full mailbox, a delivery restriction or external recipients (default: false). Without it those warnings refuse the send. Pass only after the user has seen the warnings. No effect without checkRecipients.", + "type": "boolean" +} - changed
Input schema / properties / checkRecipients / descriptionPrevious value: -"Check recipients for out-of-office, mailbox full, delivery restrictions before sending (default: false). Combine with dryRun=true for pre-send review."New value: +"Check recipients with mail tips before sending (default: false). Out-of-office, mailbox full, delivery restrictions or external recipients refuse the send unless acknowledgeWarnings=true. Combine with dryRun=true for pre-send review."
- Changed
update-email5 fields changed- changed
Input schema / properties / dueDateTime / descriptionPrevious value: -"Due date/time for follow-up, ISO 8601 (action=flag)"New value: +"Due date/time for follow-up (action=flag). ISO 8601 with a time: \"2026-03-01T09:00:00Z\" or \"2026-03-01T09:00:00+10:00\" is that exact instant; \"2026-03-01T09:00:00\" (no zone) is read in the default timezone (OUTLOOK_DEFAULT_TIMEZONE)." - added
Input schema / properties / emailAdded value: +{ + "description": "Alias for `sharedMailbox`.", + "type": "string" +} - changed
Input schema / properties / id / descriptionPrevious value: -"Single message ID (required for mark-read/mark-unread, or use instead of ids for flag actions)"New value: +"Single message ID (required for mark-read/mark-unread; flag actions take `id` or `ids`)" - added
Input schema / properties / sharedMailboxAdded value: +{ + "description": "Email address of the shared/delegated mailbox whose message(s) to update (default: the signed-in account). Requires delegate access + Mail.ReadWrite.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance).", + "type": "string" +} - changed
Input schema / properties / startDateTime / descriptionPrevious value: -"Start date/time for follow-up, ISO 8601 (action=flag)"New value: +"Start date/time for follow-up (action=flag), same format as dueDateTime. Defaults to 09:00 on the due date in the default timezone (capped at the due time) when only dueDateTime is given."
1 tool update
v3.11.1- Changed
search-emails3 fields changed- changed
Input schema / properties / query / descriptionPrevious value: -"Search query text. Omit for list mode."New value: +"Search query text. Omit for list mode. On personal Outlook.com accounts Graph `$search` is unavailable, so this falls back to a subject substring match (all words must appear in the subject) — precise, but it does NOT search message bodies. Use `searchExpression` when you need body content." - changed
Input schema / properties / searchExpression / descriptionPrevious value: -"Raw Microsoft Graph `$search` expression for advanced server-side search, e.g. `subject:\"invoice\"`, `from:github.com`, or `foo OR bar`. Quote your own phrases; a single bare token is auto-quoted. Pair with `searchAllFolders: true` for cross-folder search. Bypasses other search params. NOTE: on personal Outlook.com accounts field-scoped `$search` is best-effort and may return nothing — prefer `query` there (it has progressive fallback)."New value: +"Raw Microsoft Graph `$search` expression for advanced server-side search, e.g. `subject:\"invoice\"`, `from:github.com`, or `foo OR bar`. Quote your own phrases; a single bare token is auto-quoted. Pair with `searchAllFolders: true` for cross-folder search. Bypasses other search params. NOTE: personal Outlook.com accounts reject field-scoped `$search` outright; since v3.10.0 recognised `from:`/`to:`/`subject:` expressions are translated into the closest equivalent OData filters and retried automatically (a `subject:` term becomes a substring match, so it is close but not identical) (reported as strategy `raw-kql-translated`). Expressions that cannot be translated exactly — free text, `AND`/`OR`, unknown prefixes — are not retried, so use `query` for those there. RELEVANCE, NOT RECENCY: an untranslated expression is answered by Graph `$search` over the whole message including the body, ranked by relevance and not sorted by date, so top hits can look unrelated to a caller expecting a subject match. `query` is the more predictable choice for a term you expect in a subject line; `searchExpression` is the one that reaches body text." - changed
Input schema / properties / to / descriptionPrevious value: -"Filter by recipient email/name"New value: +"Filter by recipient email/name. Personal Outlook.com accounts reject the server-side recipient filter, in which case this is matched locally over the 500 most recent messages only (raise with `OUTLOOK_SEARCH_SCAN_LIMIT`). On a large archive, pair `to` with `receivedAfter`/`receivedBefore` to reach older mail; the response says so when the scan was truncated."
10 tool updates
v3.9.1- Added
apply-category - Added
auth - Added
find-meeting-rooms - Added
folders - Added
get-mail-tips - Added
mailbox-settings - Added
manage-contact - Added
manage-event - Added
manage-focused-inbox - Added
search-emails
12 tool updates
v3.9.0- Removed
apply-category - Changed
attachments1 field changed- changed
Input schema / properties / savePath / descriptionPrevious value: -"DEPRECATED alias for `outputDir`. Will be removed in v3.8.0."New value: +"DEPRECATED alias for `outputDir`. Will be removed in a future release."
- Removed
auth - Removed
find-meeting-rooms - Removed
folders - Removed
get-mail-tips - Removed
mailbox-settings - Changed
manage-category1 field changed- changed
Input schema / properties / categoryId / descriptionPrevious value: -"DEPRECATED: alias for `id`. Will be removed in v3.8.0."New value: +"DEPRECATED: alias for `id`. Will be removed in a future release."
- Removed
manage-contact - Removed
manage-event - Removed
manage-focused-inbox - Removed
search-emails
1 tool update
v3.8.0- Changed
manage-event14 fields changed- changed
Input schema / properties / action / enumPrevious value: -[ - "decline", - "cancel", - "delete" -]New value: +[ + "update", + "decline", + "cancel", + "delete" +] - added
Input schema / properties / attendeesAdded value: +{ + "description": "Full replacement attendee list — pass complete desired list, or [] to clear (action=update only)", + "items": { + "type": "string" + }, + "type": "array" +} - added
Input schema / properties / bodyAdded value: +{ + "description": "New body content (action=update only)", + "type": "string" +} - added
Input schema / properties / categoriesAdded value: +{ + "description": "Full replacement category list — pass [] to clear (action=update only)", + "items": { + "type": "string" + }, + "type": "array" +} - added
Input schema / properties / dryRunAdded value: +{ + "description": "Preview the PATCH without applying it (action=update only). Returns the body that would be sent to Graph.", + "type": "boolean" +} - added
Input schema / properties / endAdded value: +{ + "description": "New end time as ISO 8601 string or {dateTime, timeZone} object (action=update only)", + "oneOf": [ + { + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "dateTime": { + "type": "string" + }, + "timeZone": { + "type": "string" + } + }, + "required": [ + "dateTime" + ], + "type": "object" + } + ] +} - added
Input schema / properties / importanceAdded value: +{ + "description": "Event importance flag (action=update only)", + "enum": [ + "low", + "normal", + "high" + ], + "type": "string" +} - added
Input schema / properties / isOnlineMeetingAdded value: +{ + "description": "Toggle online meeting flag (action=update only)", + "type": "boolean" +} - added
Input schema / properties / locationAdded value: +{ + "description": "New location display name (action=update only)", + "type": "string" +} - added
Input schema / properties / reminderMinutesBeforeStartAdded value: +{ + "description": "Minutes before start to fire the reminder (action=update only)", + "type": "number" +} - added
Input schema / properties / sensitivityAdded value: +{ + "description": "Event sensitivity classification (action=update only)", + "enum": [ + "normal", + "personal", + "private", + "confidential" + ], + "type": "string" +} - added
Input schema / properties / showAsAdded value: +{ + "description": "Free/busy status shown to others (action=update only)", + "enum": [ + "free", + "tentative", + "busy", + "oof", + "workingElsewhere", + "unknown" + ], + "type": "string" +} - added
Input schema / properties / startAdded value: +{ + "description": "New start time as ISO 8601 string or {dateTime, timeZone} object (action=update only)", + "oneOf": [ + { + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "dateTime": { + "type": "string" + }, + "timeZone": { + "type": "string" + } + }, + "required": [ + "dateTime" + ], + "type": "object" + } + ] +} - added
Input schema / properties / subjectAdded value: +{ + "description": "New subject (action=update only)", + "type": "string" +}
22 tool updates
v3.7.4- Changed
access-shared-mailbox3 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / emailAdded value: +{ + "description": "Alias for `sharedMailbox` (more intuitive name for the same value).", + "type": "string" +} - changed
Input schema / requiredPrevious value: -[ - "sharedMailbox" -]New value: +[]
- Changed
apply-category1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
attachments3 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / outputDirAdded value: +{ + "description": "Directory to save file (action=download, default: system tmpdir). Auto-created if missing.", + "type": "string" +} - changed
Input schema / properties / savePath / descriptionPrevious value: -"Directory to save file (action=download, default: current directory)"New value: +"DEPRECATED alias for `outputDir`. Will be removed in v3.8.0."
- Changed
auth3 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / action / enumPrevious value: -[ - "status", - "authenticate", - "about" -]New value: +[ + "status", + "authenticate", + "device-code-complete", + "about" +] - added
Input schema / properties / methodAdded value: +{ + "description": "Auth method for action=authenticate. device-code (default): no auth server needed, works remotely. browser: traditional OAuth redirect via port 3333.", + "enum": [ + "device-code", + "browser" + ], + "type": "string" +}
- Changed
create-event1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Added
draft - Changed
export3 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / format / descriptionPrevious value: -"Export format (target=message: mime/eml/markdown/json/csv, target=conversation: eml/mbox/markdown/json/html/csv)"New value: +"Export format. Valid values vary by target: target=message accepts mime/eml/markdown/json/csv (mbox and html are conversation-only). target=conversation accepts eml/mbox/markdown/json/html/csv. target=messages (batch) accepts markdown/json/csv. mime is an alias for eml (same RFC822 bytes, .eml extension on disk)." - added
Input schema / properties / queryAdded value: +{ + "description": "Free-text search shortcut (target=messages). Equivalent to passing searchQuery: { subject: <query> }. Convenience alias for callers used to search-emails.", + "type": "string" +}
- Changed
find-meeting-rooms1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
folders1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Added
get-mail-tips - Changed
list-events1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
mailbox-settings1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
manage-category4 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / action / descriptionPrevious value: -"Action to perform (default: list)"New value: +"Action to perform (default: list). 'set' is a deprecated alias for 'update'." - changed
Input schema / properties / action / enumPrevious value: -[ - "list", - "create", - "update", - "delete" -]New value: +[ + "list", + "create", + "update", + "set", + "delete" +] - added
Input schema / properties / categoryIdAdded value: +{ + "description": "DEPRECATED: alias for `id`. Will be removed in v3.8.0.", + "type": "string" +}
- Changed
manage-contact5 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / emailsAdded value: +{ + "description": "Multiple email addresses (action=create/update). First entry is primary.", + "items": { + "type": "string" + }, + "type": "array" +} - added
Input schema / properties / firstNameAdded value: +{ + "description": "Given name (action=create/update). Maps to Graph `givenName`. If displayName not provided, will be combined with lastName.", + "type": "string" +} - added
Input schema / properties / lastNameAdded value: +{ + "description": "Surname (action=create/update). Maps to Graph `surname`.", + "type": "string" +} - added
Input schema / properties / skipAdded value: +{ + "description": "Pagination offset for action=list (default: 0). Use the value suggested by the previous page response.", + "type": "integer" +}
- Changed
manage-event3 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / idAdded value: +{ + "description": "Alias for `eventId` (canonical per the v3.7.3 alias pass).", + "type": "string" +} - changed
Input schema / requiredPrevious value: -[ - "action", - "eventId" -]New value: +[ + "action" +]
- Changed
manage-focused-inbox1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
manage-rules38 fields changed- added
Input schema / additionalPropertiesAdded value: +false - changed
Input schema / properties / action / enumPrevious value: -[ - "list", - "create", - "reorder", - "delete" -]New value: +[ + "list", + "create", + "update", + "reorder", + "delete" +] - added
Input schema / properties / assignCategoriesAdded value: +{ + "description": "Comma-separated Outlook categories to assign (action=create/update)", + "type": "string" +} - added
Input schema / properties / bodyContainsAdded value: +{ + "description": "Comma-separated body text keywords (OR logic) (action=create/update)", + "type": "string" +} - added
Input schema / properties / bodyOrSubjectContainsAdded value: +{ + "description": "Comma-separated keywords matching body OR subject (OR logic) (action=create/update)", + "type": "string" +} - changed
Input schema / properties / containsSubject / descriptionPrevious value: -"Subject text the email must contain (action=create)"New value: +"Comma-separated subject keywords (OR logic). e.g. \"invoice, receipt, payment\" (action=create/update)" - added
Input schema / properties / copyToFolderAdded value: +{ + "description": "Folder name to copy matching emails to (action=create/update)", + "type": "string" +} - added
Input schema / properties / deleteMessageAdded value: +{ + "description": "Move matching emails to Deleted Items (action=create/update)", + "type": "boolean" +} - added
Input schema / properties / displayNameAdded value: +{ + "description": "Alias for `name` (matches Graph's own `displayName` field).", + "type": "string" +} - added
Input schema / properties / dryRunAdded value: +{ + "description": "Preview rule without creating/updating (action=create, action=update)", + "type": "boolean" +} - added
Input schema / properties / exceptBodyContainsAdded value: +{ + "description": "Comma-separated body keywords to exclude (action=create/update)", + "type": "string" +} - added
Input schema / properties / exceptFromAddressesAdded value: +{ + "description": "Comma-separated sender emails to exclude (action=create/update)", + "type": "string" +} - added
Input schema / properties / exceptHasAttachmentsAdded value: +{ + "description": "Exclude emails with attachments (action=create/update)", + "type": "boolean" +} - added
Input schema / properties / exceptSenderContainsAdded value: +{ + "description": "Comma-separated partial sender matches to exclude (action=create/update)", + "type": "string" +} - added
Input schema / properties / exceptSubjectContainsAdded value: +{ + "description": "Comma-separated subject keywords to exclude (action=create/update)", + "type": "string" +} - added
Input schema / properties / forwardToAdded value: +{ + "description": "Comma-separated emails to forward matching messages to (action=create/update)", + "type": "string" +} - changed
Input schema / properties / fromAddresses / descriptionPrevious value: -"Comma-separated sender email addresses (action=create)"New value: +"Comma-separated sender emails to match (action=create/update)" - changed
Input schema / properties / hasAttachments / descriptionPrevious value: -"Apply to emails with attachments (action=create)"New value: +"Match emails with attachments (action=create/update)" - added
Input schema / properties / importanceAdded value: +{ + "description": "Match emails with this importance (action=create/update)", + "enum": [ + "low", + "normal", + "high" + ], + "type": "string" +} - changed
Input schema / properties / includeDetails / descriptionPrevious value: -"Include detailed rule conditions and actions (action=list)"New value: +"Include detailed conditions, actions, and exceptions (action=list)" - added
Input schema / properties / isAutomaticReplyAdded value: +{ + "description": "Match automatic reply emails (action=create/update)", + "type": "boolean" +} - changed
Input schema / properties / isEnabled / descriptionPrevious value: -"Enable rule after creation, default: true (action=create)"New value: +"Enable/disable rule (action=create default: true, action=update)" - changed
Input schema / properties / markAsRead / descriptionPrevious value: -"Mark matching emails as read (action=create)"New value: +"Mark matching emails as read (action=create/update)" - added
Input schema / properties / markImportanceAdded value: +{ + "description": "Set importance on matching emails (action=create/update)", + "enum": [ + "low", + "normal", + "high" + ], + "type": "string" +} - changed
Input schema / properties / moveToFolder / descriptionPrevious value: -"Folder to move matching emails to (action=create)"New value: +"Folder name to move matching emails to (action=create/update)" - changed
Input schema / properties / name / descriptionPrevious value: -"Name of the rule to create (action=create, required)"New value: +"Rule name (action=create required, action=update to rename)" - added
Input schema / properties / recipientContainsAdded value: +{ + "description": "Comma-separated partial recipient matches (action=create/update)", + "type": "string" +} - added
Input schema / properties / redirectToAdded value: +{ + "description": "Comma-separated emails to redirect matching messages to (action=create/update)", + "type": "string" +} - changed
Input schema / properties / ruleId / descriptionPrevious value: -"ID of the rule to delete (action=delete)"New value: +"ID of existing rule (action=update/delete)" - changed
Input schema / properties / ruleName / descriptionPrevious value: -"Name of the rule (action=reorder required, action=delete alternative to ruleId)"New value: +"Name of existing rule (action=update/reorder/delete)" - added
Input schema / properties / senderContainsAdded value: +{ + "description": "Comma-separated partial sender matches (action=create/update)", + "type": "string" +} - added
Input schema / properties / sensitivityAdded value: +{ + "description": "Match emails with this sensitivity (action=create/update)", + "enum": [ + "normal", + "personal", + "private", + "confidential" + ], + "type": "string" +} - added
Input schema / properties / sentCcMeAdded value: +{ + "description": "Match emails where I am in CC (action=create/update)", + "type": "boolean" +} - added
Input schema / properties / sentOnlyToMeAdded value: +{ + "description": "Match emails where I am the only recipient (action=create/update)", + "type": "boolean" +} - added
Input schema / properties / sentToAddressesAdded value: +{ + "description": "Comma-separated recipient emails to match (action=create/update)", + "type": "string" +} - added
Input schema / properties / sentToMeAdded value: +{ + "description": "Match emails sent to me (action=create/update)", + "type": "boolean" +} - changed
Input schema / properties / sequence / descriptionPrevious value: -"Execution order, lower numbers run first (action=create default: 100, action=reorder required)"New value: +"Execution order, lower = higher priority (action=create default: auto, action=reorder required)" - added
Input schema / properties / stopProcessingRulesAdded value: +{ + "description": "Stop evaluating subsequent rules (action=create/update)", + "type": "boolean" +}
- Changed
read-email1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
search-emails1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
search-people1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Changed
send-email2 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / checkRecipientsAdded value: +{ + "description": "Check recipients for out-of-office, mailbox full, delivery restrictions before sending (default: false). Combine with dryRun=true for pre-send review.", + "type": "boolean" +}
- Changed
update-email1 field changed- added
Input schema / additionalPropertiesAdded value: +false
20 tool updates
v3.4.1- First observed
access-shared-mailbox - First observed
apply-category - First observed
attachments - First observed
auth - First observed
create-event - First observed
export - First observed
find-meeting-rooms - First observed
folders - First observed
list-events - First observed
mailbox-settings - First observed
manage-category - First observed
manage-contact - First observed
manage-event - First observed
manage-focused-inbox - First observed
manage-rules - First observed
read-email - First observed
search-emails - First observed
search-people - First observed
send-email - First observed
update-email
TDQS
Scored across 22 tools
Each tool targets a fairly distinct resource+action (email read/search/send, calendar, contacts, rules, categories, settings). Minor overlap exists between search-emails and access-shared-mailbox (both list emails, and search-emails also accepts sharedMailbox), and between manage-contact search and search-people, but descriptions clearly demarcate them.
Most names are snake_case verb_noun (search-emails, create-event, manage-rules, apply-category), but several are bare nouns (auth, draft, attachments, export, folders), mixing conventions. Still readable, but not a single predictable pattern.
22 tools is on the heavier side but justified by a genuinely broad domain spanning mail, calendar, contacts, categories, rules, settings and shared mailboxes. Each tool earns its place with clear scope; none appears redundant.
Very thorough lifecycle coverage: search/read/send/draft/update, attachments, export, folders, rules, contacts CRUD, categories, focused inbox, settings, shared mailboxes and rooms. Minor gaps: no direct delete-message tool and no event-accept action (noted as a Graph limitation), but core workflows are covered.
Maintenance
Related MCP Connectors
Manage Microsoft 365 email, calendar, contacts and inbox rules via the Graph API with OAuth 2.0.
Hosted MCP server with managed OAuth for 15+ toolkits: Google Workspace, Fitbit, Oura, Kalshi, etc.
Hosted multi-user MCP for Microsoft Exchange - on-premises, hybrid and Online, per-user rights
Hosted MCP catalog with 30 tenant-isolated browser, RAG, AI, mail and media tools.
Related MCP Servers
- AlicenseCqualityAmaintenanceA Model Context Protocol server that enables interaction with Microsoft 365 services (Excel, Calendar, Mail, OneDrive, Teams, etc.) through the Graph API, allowing AI assistants to manage Microsoft 365 resources via natural language.18859,677 npm1,007MIT
- AlicenseNot gradedqualityDmaintenanceA modular collection of MCP servers for automating virtual secretary tasks within Microsoft Outlook, including email management, calendar operations, contacts, tasks, and mailbox settings through Microsoft Graph API.5MIT
- AlicenseBqualityAmaintenanceMCP server that provides 62 tools to manage Outlook mail, calendar, contacts, and tasks for personal Microsoft accounts via Microsoft Graph API.681,346 PyPI37MIT
- AlicenseNot gradedqualityAmaintenanceEnables MCP-aware agents to interact with the classic Outlook desktop client for mail, calendar, contacts, tasks, and Out-of-Office settings via the COM API, without Azure or OAuth.209 PyPI30MIT