ms-365-mcp-server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ms-365-mcp-server What meetings do I have on my calendar tomorrow?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ms-365-mcp-server
Microsoft 365 MCP Server
A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Microsoft Office services through the Graph API.
Note: This is A-Impact's internal build and it is not published to npm. The
npx @a-impact/ms365-mcpcommands below apply only if you publish the package under that scope yourself. To run it from this repository instead, usenpm install && npm run buildand point your MCP client atdist/index.js, as described in Local Development.
Supported Clouds
This server supports multiple Microsoft cloud environments:
Cloud | Description | Auth Endpoint | Graph API Endpoint |
Global (default) | International Microsoft 365 | login.microsoftonline.com | graph.microsoft.com |
China (21Vianet) | Microsoft 365 operated by 21Vianet | login.chinacloudapi.cn | microsoftgraph.chinacloudapi.cn |
Related MCP server: Microsoft Graph MCP
Prerequisites
Node.js >= 20 (recommended)
Node.js 14+ may work with dependency warnings
Features
Authentication via Microsoft Authentication Library (MSAL)
Comprehensive Microsoft 365 service integration
Read-only mode support for safe operations
Tool filtering for granular access control
Tool presets and dynamic discovery to shrink the tool surface and token usage
Output Format: JSON vs TOON
The server supports two output formats that can be configured globally:
JSON Format (Default)
Standard JSON output with pretty-printing:
{
"value": [
{
"id": "1",
"displayName": "Alice Johnson",
"mail": "alice@example.com",
"jobTitle": "Software Engineer"
}
]
}(experimental) TOON Format
Token-Oriented Object Notation for efficient LLM token usage:
value[1]{id,displayName,mail,jobTitle}:
"1",Alice Johnson,alice@example.com,Software EngineerBenefits:
30-60% fewer tokens vs JSON
Best for uniform array data (lists of emails, calendar events, files, etc.)
Ideal for cost-sensitive applications at scale
Usage: (experimental) Enable TOON format globally:
Via CLI flag:
npx @a-impact/ms365-mcp --toonVia Claude Desktop configuration:
{
"mcpServers": {
"ms365": {
"command": "npx",
"args": ["-y", "@a-impact/ms365-mcp", "--toon"]
}
}
}Via environment variable:
MS365_MCP_OUTPUT_FORMAT=toon npx @a-impact/ms365-mcpSupported Services & Tools
The server provides 300+ tools covering most of the Microsoft Graph API surface. Each tool maps 1-to-1 to a Graph API endpoint and is defined declaratively in src/endpoints.json.
Personal Account Tools (Available by default)
Email (Outlook), Calendar, OneDrive Files, Excel, OneNote, To Do Tasks, Planner, Contacts, User Profile, Search
Organization Account Tools (Requires --org-mode flag)
Teams & Chats, Online Meetings, Transcripts & Recordings, Attendance Reports, SharePoint Sites & Lists, Shared Mailboxes & Calendars, User Management, Presence, Virtual Events
Required Graph API Permissions
Permissions are requested dynamically based on which tools are enabled. Use --list-permissions to see the exact permissions for your configuration:
# Personal mode (default)
npx @a-impact/ms365-mcp --list-permissions
# Organization mode (includes Teams, SharePoint, etc.)
npx @a-impact/ms365-mcp --org-mode --list-permissions
# Filtered by preset
npx @a-impact/ms365-mcp --preset mail --list-permissionsThis is useful for enterprise environments where Graph API permissions must be pre-approved and admin-consented before deploying a new version.
The --list-permissions JSON includes:
toolPermissions: permissions implied by the tool surface before--allowed-scopesfilteringeffectivePermissions: permissions implied by the tools that remain enabled after--allowed-scopespermissions: legacy alias foreffectivePermissions, kept for compatibility with existing scriptsallowedScopes: the configured scope allowlist, when provideddisabledTools: tools hidden because their required Graph scopes are not covered byallowedScopesmissingAllowedScopesForTools: unique missing scopes across disabled toolsextraAllowedScopesNotUsedByTools: allowed scopes that are not used by the current tool surface
Allowed Scopes
By default, MSAL requests the scopes implied by the enabled tools, and the tool surface is controlled by --enabled-tools, --preset, --org-mode, and --read-only.
Enterprise and headless deployments can add a scope boundary with --allowed-scopes or MS365_MCP_ALLOWED_SCOPES. When configured, the server first computes the normal tool surface, then hides Graph tools whose required scopes are not covered by the allowlist. OAuth metadata and login flows request only the effective permissions for the tools that remain enabled.
npx @a-impact/ms365-mcp \
--org-mode \
--enabled-tools '^(list-mail-messages|get-mail-message|list-drives|get-drive-item|download-bytes)$' \
--allowed-scopes 'User.Read Mail.Read Files.Read'CLI value takes precedence over MS365_MCP_ALLOWED_SCOPES; if neither is set, the default tool-derived scope behavior is unchanged. Supplying an empty value fails at startup so deployments do not accidentally fall back to a wider tool surface.
Scope coverage is hierarchy-aware: for example, Mail.ReadWrite covers tools that require Mail.Read, and Files.ReadWrite.All covers tools that require Files.Read.
SharePoint supports two enterprise permission models:
Broad tenant scopes such as
Sites.Read.All,Sites.ReadWrite.All, andSites.Manage.All.Microsoft Graph
Sites.Selected, where SharePoint site access is granted to the app on specific site collections and Graph evaluates the signed-in user's own permissions at request time.
The default org-mode behavior continues to request the broad SharePoint scopes used by existing deployments. Enterprises that want selected-site SharePoint access can set an allowlist containing Sites.Selected instead of broad Sites.*.All scopes. Direct site/list/item tools that target an explicit SharePoint site can run with Sites.Selected; tenant-wide SharePoint discovery and search tools still require broad SharePoint scopes.
npx @a-impact/ms365-mcp \
--org-mode \
--read-only \
--enabled-tools 'sharepoint|site|drive|planner' \
--allowed-scopes 'User.Read Files.Read Notes.Read Tasks.Read Sites.Selected'In HTTP mode, OAuth discovery advertises the effective filtered permissions so clients request the same consent surface. On-Behalf-Of mode (--obo) still advertises api://<clientId>/access_as_user for protected-resource metadata; --allowed-scopes does not override OBO.
Requesting extra scopes
--allowed-scopes only ever narrows the token request. To request a Graph scope that no bundled tool needs — for example to drive an endpoint via graph-batch — use --extra-scopes (or MS365_MCP_EXTRA_SCOPES). These scopes are appended verbatim to the token request, on top of the tool-derived scopes.
npx @a-impact/ms365-mcp \
--org-mode \
--extra-scopes 'CopilotPackages.ReadWrite.All'This is for use with your own Azure app registration (MS365_MCP_CLIENT_ID / MS365_MCP_CLIENT_SECRET): the default upstream app only declares a lean, fixed permission set, so request additional scopes against an app you control (your tenant admin consents to them there). CLI value takes precedence over the env var; an empty value fails at startup.
Organization/Work Mode
To access work/school features (Teams, SharePoint, etc.), enable organization mode using any of these flags:
{
"mcpServers": {
"ms365": {
"command": "npx",
"args": ["-y", "@a-impact/ms365-mcp", "--org-mode"]
}
}
}Organization mode must be enabled from the start to access work account features. Without this flag, only personal account features (email, calendar, OneDrive, etc.) are available.
Shared Mailbox Access
To access shared mailboxes, you need:
Organization mode: Shared mailbox tools require
--org-modeflag (work/school accounts only)Delegated permissions:
Mail.Read.Sharedto read,Mail.ReadWrite.Sharedto create, update or move messages,Mail.Send.Sharedto send, reply or forward, andCalendars.Read.Sharedfor the shared calendar toolsExchange permissions: The signed-in user must have been granted access to the shared mailbox
Usage: Use the shared mailbox's email address as the
user-idparameter in the shared mailbox tools
Finding shared mailboxes: Use the list-users tool to discover available users and shared mailboxes in your
organization.
Example: list-shared-mailbox-messages with user-id set to shared-mailbox@company.com
Quick Start Example
Test login in Claude Desktop:
Examples
Integration
Claude Desktop
To add this MCP server to Claude Desktop, edit the config file under Settings > Developer.
Personal Account (MSA)
{
"mcpServers": {
"ms365": {
"command": "npx",
"args": ["-y", "@a-impact/ms365-mcp"]
}
}
}Work/School Account (Global)
{
"mcpServers": {
"ms365": {
"command": "npx",
"args": ["-y", "@a-impact/ms365-mcp", "--org-mode"]
}
}
}Work/School Account (China 21Vianet)
{
"mcpServers": {
"ms365-china": {
"command": "npx",
"args": ["-y", "@a-impact/ms365-mcp", "--org-mode", "--cloud", "china"]
}
}
}Claude Code CLI
Personal Account (MSA)
claude mcp add ms365 -- npx -y @a-impact/ms365-mcpWork/School Account (Global)
# macOS/Linux
claude mcp add ms365 -- npx -y @a-impact/ms365-mcp --org-mode
# Windows (use cmd /c wrapper)
claude mcp add ms365 -s user -- cmd /c "npx -y @a-impact/ms365-mcp --org-mode"Work/School Account (China 21Vianet)
# macOS/Linux
claude mcp add ms365-china -- npx -y @a-impact/ms365-mcp --org-mode --cloud china
# Windows (use cmd /c wrapper)
claude mcp add ms365-china -s user -- cmd /c "npx -y @a-impact/ms365-mcp --org-mode --cloud china"For other interfaces that support MCPs, please refer to their respective documentation for the correct integration method.
Open WebUI
Open WebUI supports MCP servers via HTTP transport with OAuth 2.1.
Start the server with HTTP mode:
npx @a-impact/ms365-mcp --httpIn Open WebUI, go to Admin Settings → Tools (
/admin/settings/tools) → Add Connection:Type: MCP Streamable HTTP
URL: Your MCP server URL with
/mcppathAuth: OAuth 2.1
Click Register Client.
Note: Dynamic client registration is enabled by default in HTTP mode. Use
--no-dynamic-registration(or setMS365_MCP_DISABLE_DCR=true) to disable it. If using a custom Azure Entra app, the platform type for your redirect URI depends on whether the app has a client secret: with a secret use "Web", without one use "Mobile and desktop applications" (never "Single-page application").
Quick test setup using the default Azure app (ID ms-365 and localhost:8080 are pre-configured):
docker run -d -p 8080:8080 \
-e WEBUI_AUTH=false \
-e OPENAI_API_KEY \
ghcr.io/open-webui/open-webui:main
npx @a-impact/ms365-mcp --httpThen add connection with URL http://localhost:3000/mcp and ID ms-365.
Running in Docker behind a reverse proxy? Set
--public-url https://your-domain.comso the OAuth authorize URL handed to the user's browser is reachable from outside the container network. See docs/deployment.md for the full guide.
Local Development
For local development or testing:
# From the project directory
claude mcp add ms -- npx tsx src/index.ts --org-modeOr configure Claude Desktop manually:
{
"mcpServers": {
"ms365": {
"command": "node",
"args": ["/absolute/path/to/ms-365-mcp-server/dist/index.js", "--org-mode"]
}
}
}Note: Run
npm run buildafter code changes to update thedist/folder.
Authentication
⚠️ You must authenticate before using tools.
The server supports three authentication methods:
1. Device Code Flow (Default)
For interactive authentication via device code:
MCP client login:
Call the
logintool (auto-checks existing token)If needed, get URL+code, visit in browser
Use
verify-logintool to confirm
CLI login:
npx @a-impact/ms365-mcp --loginFollow the URL and code prompt in the terminal.
Tokens are cached securely in your OS credential store (fallback to file).
2. OAuth Authorization Code Flow (HTTP mode only)
When running with --http, the server requires OAuth authentication:
npx @a-impact/ms365-mcp --http 3000This mode:
Advertises OAuth capabilities to MCP clients
Provides OAuth endpoints at
/auth/*(authorize, token, metadata)Requires
Authorization: Bearer <token>for all MCP requestsValidates tokens with Microsoft Graph API
Disables login/logout tools by default (use
--enable-auth-toolsto enable them)
MCP clients will automatically handle the OAuth flow when they see the advertised capabilities.
Setting up Azure AD for OAuth Testing
To use OAuth mode with custom Azure credentials (recommended for production), you'll need to set up an Azure AD app registration:
Create Azure AD App Registration:
Go to Azure Portal
Navigate to Azure Active Directory → App registrations → New registration
Set name: "MS365 MCP Server"
Configure Redirect URIs:
Configure the OAuth callback URI: Go to your app registration and on the left side, go to Authentication.
Under Platform configurations:
Click Add a platform (if you don’t already see one for "Mobile and desktop applications" / "Public client").
Choose Mobile and desktop applications or Public client/native (mobile & desktop) (label depends on portal version).
Testing with MCP Inspector (
npm run inspector):
Go to your app registration and on the left side, go to Authentication.
Under Platform configurations:
Click Add a platform (if you don’t already see one for "Web").
Choose Web.
Configure the following redirect URIs
http://localhost:6274/oauth/callbackhttp://localhost:6274/oauth/callback/debughttp://localhost:3000/callback(optional, for server callback)
Get Credentials:
Copy the Application (client) ID from Overview page
Go to Certificates & secrets → New client secret → Copy the secret value (optional for public apps)
Configure Environment Variables: Create a
.envfile in your project root:MS365_MCP_CLIENT_ID=your-azure-ad-app-client-id-here MS365_MCP_CLIENT_SECRET=your-secret-here # Optional for public apps MS365_MCP_TENANT_ID=common
With these configured, the server will use your custom Azure app instead of the built-in one.
Note:
.envis read from the directory the server is started in, and the MCP client decides what that is. OnlyMS365_MCP_CLIENT_ID,MS365_MCP_CLIENT_SECRET,MS365_MCP_TENANT_IDandMS365_MCP_CLOUD_TYPEare read from it. Every other variable listed above must be set in your shell or MCP client config; anything else found in a.envis ignored with a warning on stderr.
3. Bring Your Own Token (BYOT)
If you are running ms-365-mcp-server as part of a larger system that manages Microsoft OAuth tokens externally, you can provide an access token directly to this MCP server:
MS365_MCP_OAUTH_TOKEN=your_oauth_token npx @a-impact/ms365-mcpThis method:
Bypasses the interactive authentication flows
Use your pre-existing OAuth token for Microsoft Graph API requests
Does not handle token refresh (token lifecycle management is your responsibility)
Note: HTTP mode requires authentication. For unauthenticated testing, use stdio mode with device code flow.
Authentication Tools: In HTTP mode, login/logout tools are disabled by default since OAuth handles authentication. Use
--enable-auth-toolsif you need them available.
Multi-Account Support
Use a single server instance to serve multiple Microsoft accounts. When more than one account is logged in, an account parameter is automatically injected into every tool, allowing you to specify which account to use per tool call.
Login multiple accounts (one-time per account):
# Login first account (device code flow)
npx @a-impact/ms365-mcp --login
# Follow the device code prompt, sign in as personal@outlook.com
# Login second account
npx @a-impact/ms365-mcp --login
# Follow the device code prompt, sign in as work@company.comList configured accounts:
npx @a-impact/ms365-mcp --list-accountsUse in tool calls: Pass "account": "work@company.com" in any tool request:
{ "tool": "list-mail-messages", "arguments": { "account": "work@company.com" } }Behavior:
With a single account configured, it auto-selects (no
accountparameter needed).With multiple accounts and no
accountparameter, the server uses the selected default or returns a helpful error listing available accounts.100% backward compatible: existing single-account setups work unchanged.
The
accountparameter accepts email address (e.g.user@outlook.com) or MSALhomeAccountId.
Strict Account Pinning
Headless stdio deployments can pin the local MSAL cache to one expected Microsoft account:
# Username matching is case-insensitive
MS365_MCP_EXPECTED_USERNAME=work@company.com npx @a-impact/ms365-mcp --login
# Or pin the exact MSAL homeAccountId shown by --list-accounts
npx @a-impact/ms365-mcp --expected-home-account-id <homeAccountId> --loginUse --list-accounts to discover homeAccountId values. The MCP list-accounts tool intentionally hides account IDs, so use the CLI for exact ID pinning.
Pinning is opt-in and local-MSAL only:
CLI values (
--expected-username,--expected-home-account-id) take precedence overMS365_MCP_EXPECTED_USERNAMEandMS365_MCP_EXPECTED_HOME_ACCOUNT_ID.Supplying an empty pin value fails at startup instead of being ignored.
Username pins are compared case-insensitively;
homeAccountIdpins are exact.If both pins are set, they must resolve to the same cached account.
Local stdio startup fails fast when the expected account is not in the token cache. Bootstrap by setting the pin, running
--login, then starting the headless server.Device-code and browser logins reject a missing or mismatched account before persisting the selected account or token cache.
Pinning collapses the effective MCP mode to single-account: the server does not advertise an
accountparameter and MCP instructions do not suggest account switching.--http,--obo, andMS365_MCP_OAUTH_TOKENuse request-provided tokens for Graph calls, so account pins are warning-only in those modes. If HTTP auth tools are enabled, the pin still applies to those local MSAL helper flows.--logoutclears all cached accounts, including the pinned account. For surgical cleanup, prefer--remove-account <id>.
For MCP multiplexers (Legate, Governor): Multi-account mode replaces the N-process pattern. Instead of spawning one server per account, a single instance handles all accounts via the
accountparameter, reducing tool duplication from N×110 to 110.
Tool Presets
To reduce initial connection overhead and token usage, use preset tool categories instead of loading the full tool set:
npx @a-impact/ms365-mcp --preset mail
npx @a-impact/ms365-mcp --list-presets # See all available presetsAvailable presets: mail, calendar, files, personal, work, excel, contacts, tasks, onenote, search, users, outlook, onedrive, teams, teams-write, all
Each endpoint in endpoints.json declares which presets it belongs to via a presets array, so every preset is an exact tool-name allow-list that never over-matches across apps (e.g. mail does not include shared-mailbox tools; those are in work). The universal binary reader download-bytes is included in every preset except teams-write, so whatever an app returns (a file, an attachment, a photo, a recording) can always be fetched; get-download-url (a pre-authenticated URL for drive/SharePoint files) rides with the drive-backed presets. So a preset that can find a file can always read its bytes.
The outlook, onedrive and teams presets are app-scoped: they expose exactly one Microsoft app. Use these for "expose exactly one app" deployments:
# Outlook only (mail + calendar + contacts; no shared mailboxes, no files)
npx @a-impact/ms365-mcp --preset outlook
# Teams only (requires --org-mode)
npx @a-impact/ms365-mcp --org-mode --preset teamsThe teams-write preset is the send-only counterpart to --read-only: send in chats, send/reply in channels, list chats/teams/channels by name, and activity notifications - no message reading and no byte downloaders. The requested token is minimal by construction (Chat.ReadBasic, the *.Send scopes, and basic team/channel listing - nothing that can read message content):
npx @a-impact/ms365-mcp --org-mode --preset teams-writeDynamic Tool Discovery
Instead of loading every tool upfront, use dynamic discovery so the LLM finds and loads tools only when it needs them:
npx @a-impact/ms365-mcp --discoveryKeeps the initial context small and cuts token usage, especially useful for long sessions or cost-sensitive setups (e.g. Open WebUI running against a paid API).
CLI Options
The following options can be used when running ms-365-mcp-server directly from the command line:
--login Login using device code flow
--logout Log out and clear saved credentials
--verify-login Verify login without starting the server
--list-permissions List required Graph API permissions and exit (respects --org-mode, --preset, --enabled-tools, --allowed-scopes)
--org-mode Enable organization/work mode from start (includes Teams, SharePoint, etc.)
--work-mode Alias for --org-mode
--force-work-scopes Backwards compatibility alias for --org-mode (deprecated)
--cloud <type> Microsoft cloud environment: global (default) or china (21Vianet)
--allowed-scopes <scopes> Limit exposed tools to Graph scopes covered by this allowlist
--extra-scopes <scopes> Append additional Graph scopes to the token request (for use with your own app registration + graph-batch)
--expected-username <username> Require local MSAL auth to use this account username
--expected-home-account-id <id> Require local MSAL auth to use this exact homeAccountIdServer Options
When running as an MCP server, the following options can be used:
-v Enable verbose logging
--read-only Start server in read-only mode, disabling write operations
--http [port] Use Streamable HTTP transport instead of stdio (optionally specify port, default: 3000)
Starts Express.js server with MCP endpoint at /mcp
--enable-auth-tools Enable login/logout tools when using HTTP mode (disabled by default in HTTP mode)
--no-dynamic-registration Disable OAuth Dynamic Client Registration (enabled by default in HTTP mode)
--enabled-tools <pattern> Filter tools using regex pattern (e.g., "excel|contact" to enable Excel and Contact tools)
--preset <names> Use preset tool categories (comma-separated). See "Tool Presets" section above
--list-presets List all available presets and exit
--toon (experimental) Enable TOON output format for 30-60% token reduction
--discovery Dynamic tool discovery: loads tools on demand to reduce initial token usage (see "Dynamic Tool Discovery" above)
--public-url <url> Public base URL for OAuth when behind a reverse proxy (see Open WebUI section and docs/deployment.md)Environment variables:
READ_ONLY=true|1: Alternative to --read-only flagENABLED_TOOLS: Filter tools using a regex pattern (alternative to --enabled-tools flag)MS365_MCP_ORG_MODE=true|1: Enable organization/work mode (alternative to --org-mode flag)MS365_MCP_FORCE_WORK_SCOPES=true|1: Backwards compatibility for MS365_MCP_ORG_MODEMS365_MCP_OUTPUT_FORMAT=toon: Enable TOON output format (alternative to --toon flag)MS365_MCP_MAX_TOP=<n>: Hard cap for Graph$top/topon list requests (positive integer). When the model passes a larger value, the server clamps it tonso responses stay smaller. Example:MS365_MCP_MAX_TOP=15MS365_MCP_MAX_PAGES=<n>: Maximum number of pages followed when a tool is called withfetchAllPages: true(positive integer, default100). Bounds memory and latency for large result sets.MS365_MCP_MAX_ITEMS=<n>: Maximum number of items accumulated whenfetchAllPages: true(positive integer, default10000). Pagination stops and the response is truncated once this many items are collected.MS365_MCP_ALLOW_PAGINATION=0|false|no: Disable multi-page following entirely. When set, thefetchAllPagesparameter is not advertised on tools, and any request that still passes it returns only the first page (default: pagination enabled).MS365_MCP_BODY_FORMAT=html: Return email bodies as HTML instead of plain text (default: text)MS365_MCP_MESSAGE_SIGNOFF_PREFIX=<text>: Signoff prepended to outgoing messages so recipients can tell they were agent-sent, e.g.🤖. Default: none. CLI equivalent:--message-signoff-prefix <text>(see Message Signoff below)MS365_MCP_MESSAGE_SIGNOFF_SUFFIX=<text>: Signoff appended to outgoing messages. Default: none. CLI equivalent:--message-signoff-suffix <text>.--no-message-signoffdisables both (see Message Signoff below)MS365_MCP_RATE_LIMIT_DISABLED=true|1: Disable per-IP rate limiting in HTTP mode (default: enabled — 30 req/min on/authorize,/token,/register; 120 req/min on/mcp)MS365_MCP_TRUST_PROXY_HOPS=<n>: Number of trusted reverse-proxy hops in HTTP mode (default1). Accurate per-IP rate limiting depends on this matching your deployment — set to the number of proxies in front of the server,0to use the raw socket peer IP, or a comma-separated subnet listMS365_MCP_CLOUD_TYPE=global|china: Microsoft cloud environment (alternative to --cloud flag)LOG_LEVEL: Set logging level (default: 'info')SILENT=true|1: Disable console outputMS365_MCP_REDACT_PII=false|0: Disable scrubbing of JWTs, Bearer headers, OAuth token fields, and email addresses from log messages (default: enabled). The server handles live Graph bearer tokens, so redaction is on unless you opt out for fully verbose local debugging.MS365_MCP_CLIENT_ID: Custom Azure app client ID (defaults to built-in app)MS365_MCP_TENANT_ID: Custom tenant ID (defaults to 'common' for multi-tenant). Personal Microsoft accounts should set this toconsumers- as of June 2026, refresh tokens issued via the default 'common' authority are rejected at the first refresh, so sessions die roughly an hour after loginMS365_MCP_OAUTH_TOKEN: Pre-existing OAuth token for Microsoft Graph API (BYOT method)MS365_MCP_KEYVAULT_URL: Azure Key Vault URL for secrets management (see Azure Key Vault section)MS365_MCP_TOKEN_CACHE_PATH: Custom file path for MSAL token cache (see Token Storage below)MS365_MCP_SELECTED_ACCOUNT_PATH: Custom file path for selected account metadata (see Token Storage below)MS365_MCP_AUTH_CACHE_COMMAND: External executable wrapper for provider-neutral auth-cache storage (see Token Storage below)MS365_MCP_AUTH_CACHE_COMMAND_TIMEOUT_MS: Per-invocation timeout forMS365_MCP_AUTH_CACHE_COMMAND(default:10000)MS365_MCP_EXPECTED_USERNAME: Require local MSAL auth to use this Microsoft account username (case-insensitive; CLI flag takes precedence)MS365_MCP_EXPECTED_HOME_ACCOUNT_ID: Require local MSAL auth to use this exact MSAL homeAccountId (CLI flag takes precedence)
Token Storage
Authentication tokens are stored in an encrypted file (AES-256-GCM). Only the 32-byte encryption key goes to the OS credential store via keytar.
The cache itself is too big for some credential stores to hold - a Windows Credential Manager blob caps out at 2560 bytes and a real token cache is several times that, so on Windows the write could never succeed. A key is 32 bytes regardless of how many accounts are signed in, so this works the same way on every platform.
Default paths are in the per-user config directory:
Platform | Location |
Windows |
|
macOS |
|
Linux |
|
Earlier versions defaulted to a path inside the installed package, which under npx resolves to a content-hashed cache directory that npm cache clean or a version bump throws away. A cache still sitting in the package directory is moved to the new location on first run.
That covers global and local installs, and npx when the hash has not changed. It cannot reach a cache left behind in a previous npx hash directory, so upgrading an npx install one last time means signing in again. Adopting a cache from another directory would mean trusting a directory this package cannot prove it wrote, which is not worth one saved sign-in.
Override the paths if you need to:
export MS365_MCP_TOKEN_CACHE_PATH="$HOME/.config/ms365-mcp/.token-cache.json"
export MS365_MCP_SELECTED_ACCOUNT_PATH="$HOME/.config/ms365-mcp/.selected-account.json"Parent directories are created automatically. Files are written with 0600 permissions.
Without a credential store (headless Linux, most containers) the key is written to .cache-key next to the cache file, with 0600 permissions. That stops the tokens showing up in a stray cat, a backup or an accidental commit. It does not protect against anyone who can already read the directory - the key is right there. Use MS365_MCP_AUTH_CACHE_COMMAND below if you need the cache in a real secret store.
If the cache cannot be decrypted - key lost, keychain locked, file modified - you are asked to sign in again rather than the server failing to start. The cache file is left exactly as it was: not deleted, and not overwritten by that new sign-in either. A keychain that is merely locked usually reads fine on the next start, and the cache is still there when it does.
The cost is that the new session is not saved while this lasts, so each start asks you to sign in again. If the key is genuinely gone and the cache will never open, delete .token-cache.json to start over - the log says so, and names the path.
Hosted/sandboxed environments (e.g. Anthropic Cowork): Set
MS365_MCP_TOKEN_CACHE_PATHandMS365_MCP_SELECTED_ACCOUNT_PATHto a persistent mount so tokens survive between sessions.
External auth-cache command
Headless local-MSAL deployments can replace the built-in keytar/file storage with a provider-neutral external command:
export MS365_MCP_AUTH_CACHE_COMMAND="/path/to/ms365-auth-cache-store"
export MS365_MCP_AUTH_CACHE_COMMAND_TIMEOUT_MS=10000When MS365_MCP_AUTH_CACHE_COMMAND is set for a local auth flow, the server uses only that command for the MSAL token cache and selected-account metadata. It does not fall back to keytar or local files. If the command path is missing, not executable on POSIX, exits non-zero, times out, or returns malformed data, auth-cache operations fail closed with a sanitized error message.
The value must be a real executable wrapper path. It is not a shell command string, and there is no companion args environment variable. Put any interpreter, region, profile, or provider-specific settings inside the wrapper. Windows users should point the variable at a wrapper executable or script that can be launched directly by Node without shell parsing.
The server invokes the wrapper with:
$MS365_MCP_AUTH_CACHE_COMMAND load token-cache
$MS365_MCP_AUTH_CACHE_COMMAND save token-cache
$MS365_MCP_AUTH_CACHE_COMMAND delete token-cache
$MS365_MCP_AUTH_CACHE_COMMAND load selected-account
$MS365_MCP_AUTH_CACHE_COMMAND save selected-account
$MS365_MCP_AUTH_CACHE_COMMAND delete selected-accountProtocol v1:
load <key>reads no stdin. Exit0with{"found":true,"value":"<stored envelope string>"}when present. A miss is exit0with{"found":false}or empty stdout.save <key>receives{"value":"<stamped envelope string>"}on stdin and must exit0only after the value is durably committed. There are no fire-and-forget or coalesced saves in v1.delete <key>reads no stdin and exits0whether the key existed or not.<key>istoken-cacheorselected-account.Any non-zero exit is a storage error. Do not use exit code
2for cache misses.Stderr is captured and truncated in sanitized errors. Stdin and stdout payloads are never logged by the server.
Token-cache payloads can be large; wrappers should handle at least 256 KB values.
Normal stateless HTTP Graph requests do not use local auth-cache storage. In HTTP mode, command storage is skipped at startup and per request unless local auth tools are explicitly enabled or a local account command such as --login, --verify-login, --list-accounts, --select-account, or --logout is used.
Azure Key Vault Integration
For production deployments, you can store secrets in Azure Key Vault instead of environment variables. This is particularly useful for Azure Container Apps with managed identity.
Setup
Create a Key Vault (if you don't have one):
az keyvault create --name your-keyvault-name --resource-group your-rg --location eastusAdd secrets to Key Vault:
az keyvault secret set --vault-name your-keyvault-name --name ms365-mcp-client-id --value "your-client-id" az keyvault secret set --vault-name your-keyvault-name --name ms365-mcp-tenant-id --value "your-tenant-id" # Optional: if using confidential client flow az keyvault secret set --vault-name your-keyvault-name --name ms365-mcp-client-secret --value "your-secret"Grant access to Key Vault:
For Azure Container Apps with managed identity:
# Get the managed identity principal ID PRINCIPAL_ID=$(az containerapp show --name your-app --resource-group your-rg --query identity.principalId -o tsv) # Grant access to Key Vault secrets az keyvault set-policy --name your-keyvault-name --object-id $PRINCIPAL_ID --secret-permissions get listFor local development with Azure CLI:
# Your Azure CLI identity already has access if you have appropriate RBAC roles az loginConfigure the server:
MS365_MCP_KEYVAULT_URL=https://your-keyvault-name.vault.azure.net npx @a-impact/ms365-mcp
Secret Name Mapping
Key Vault Secret Name | Environment Variable | Required |
ms365-mcp-client-id | MS365_MCP_CLIENT_ID | Yes |
ms365-mcp-tenant-id | MS365_MCP_TENANT_ID | No (defaults to 'common') |
ms365-mcp-client-secret | MS365_MCP_CLIENT_SECRET | No |
Authentication
The Key Vault integration uses DefaultAzureCredential from the Azure Identity SDK, which automatically tries multiple authentication methods in order:
Environment variables (AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID)
Managed Identity (recommended for Azure Container Apps)
Azure CLI credentials (for local development)
Visual Studio Code credentials
Azure PowerShell credentials
Optional Dependencies
The Azure Key Vault packages (@azure/identity and @azure/keyvault-secrets) are optional dependencies. They are only loaded when MS365_MCP_KEYVAULT_URL is configured. If you don't use Key Vault, these packages are not required.
Message Signoff
Outgoing messages can be wrapped in a configurable signoff (e.g. a 🤖 prefix) so recipients can tell agent-sent messages from ones you typed yourself. Off by default — enable it with --message-signoff-prefix / --message-signoff-suffix (env: MS365_MCP_MESSAGE_SIGNOFF_PREFIX / MS365_MCP_MESSAGE_SIGNOFF_SUFFIX); --no-message-signoff or an empty env value turns it back off.
Once configured, it applies to all Teams messages (sends, replies and edits, including via graph-batch), to direct mail sends (send-mail, reply/forward, their shared-mailbox variants, and group thread replies), and to mail drafts as their content is written — send-draft-message sends a draft as-is, so a draft you wrote yourself goes out untouched. A message that already carries the marker is not signed twice, and a send whose body cannot take the signoff is refused rather than sent unsigned.
Markers may contain markup (e.g. a coloured <span>) as long as it renders visible text. Note that the signoff is a guardrail against an agent misusing the tools it was given, not a hard security boundary — an agent with shell access on the same machine could simply restart the server without it.
Production Deployment
See docs/deployment.md for a full guide to hosting the server for organization-wide access, including Docker, Azure Container Apps, Azure App Service, Azure AD app registration, reverse proxy setup, client configuration, and exposed endpoints.
Contributing
We welcome contributions! Before submitting a pull request, please ensure your changes meet our quality standards.
Run the verification script to check all code quality requirements:
npm run verifyFor Developers
After cloning the repository, you may need to generate the client code from the Microsoft Graph OpenAPI specification:
npm run generateRelated Projects
ms-365-admin-mcp-server by @okapi-ca: companion server for admin/daemon scenarios using application permissions (client credentials flow), covering security alerts, audit logs, service health, and usage reports.
Support
If you're having problems or need help:
Create an issue
Start a discussion
Email: eirikb@eirikb.no
Discord: https://discord.gg/WvGVNScrAZ or @eirikb
License
MIT © 2026 A-Impact
Available Tools
188 toolsaccept-calendar-eventaccept-calendar-eventADestructive
Accept the specified event in a user calendar.
💡 TIP: Accepts a meeting invitation. Optional body: { sendResponse: true, comment: 'I will attend.' }. Set sendResponse to false to accept silently without notifying the organizer.
Inputs: body (required), eventId (required), confirm, includeHeaders, +1 more.
add-excel-table-rowsadd-excel-table-rowsADestructive
Add Excel table rows.
💡 TIP: Adds rows to a table. Body: { values: [['col1val', 'col2val', 'col3val'], ['row2col1', 'row2col2', 'row2col3']] }. Each inner array is one row. Values must match the number of columns in the table.
Inputs: body (required), driveId (required), driveItemId (required), workbookTableId (required), +3 more.
add-mail-attachmentadd-mail-attachmentADestructive
Use this API to create a new Attachment. An attachment can be one of the following types: All these types of attachment resources are derived from the attachment resource.
💡 TIP: The only path for attachments under 3MB. contentBytes must carry the complete base64 verbatim; a truncated argument fails with 400 UnableToDeserializePostBody. At 3MB and above use create-mail-attachment-upload-session, which rejects anything smaller. Body requires @odata.type: {"@odata.type": "#microsoft.graph.fileAttachment", "name": "file.pdf", "contentBytes": ""}.
Inputs: body (required), messageId (required), confirm, includeHeaders, +1 more.
cancel-calendar-eventcancel-calendar-eventADestructive
This action allows the organizer of a meeting to send a cancellation message and cancel the event. The action moves the event to the Deleted Items folder. The organizer can also cancel an occurrence of a recurring meeting by providing the occurrence event ID. An attendee calling this action gets an error (HTTP 400 Bad Request), with the following error message: 'Your request can't be completed. You need to be an organizer to cancel a meeting.' This action differs from Delete in that Cancel is available to only the organizer, and lets the organizer send a custom message to the attendees about the cancellation.
💡 TIP: Cancels a meeting (organizer only) and sends a cancellation message to all attendees. Body: { Comment (optional string, custom message) }. Use this instead of delete-calendar-event when you want attendees to see 'Canceled' in their calendar. Attendees calling this get HTTP 400 — they should use decline-calendar-event instead.
Inputs: body (required), eventId (required), confirm, includeHeaders, +1 more.
clear-excel-rangeclear-excel-rangeADestructive
Clear an Excel range.
💡 TIP: Clear cell contents and/or formatting on the given range. Body: { applyTo: 'All' | 'Formats' | 'Contents' }. 'Contents' wipes values but keeps formatting; 'Formats' resets styling but keeps values; 'All' wipes both. Use this to reset a worksheet section before a fresh write rather than overwriting cell-by-cell.
Inputs: body (required), address (required), driveId (required), driveItemId (required), +4 more.
copy-drive-itemcopy-drive-itemADestructive
Copy a drive item.
💡 TIP: Asynchronously copy a file or folder to a new location and/or name. Body: { parentReference: { driveId: '...', id: '...' }, name?: 'New Name.xlsx' }. Returns 202 Accepted with a Location header pointing at a monitor URL for the async job. Ideal for duplicating templates (e.g. clone an Armhr Census Template per prospect), bulk file provisioning, or preserving an immutable snapshot of a working file.
Inputs: body (required), driveId (required), driveItemId (required), confirm, +2 more.
copy-mail-messagecopy-mail-messageADestructive
Copy a message to a folder within the user's mailbox.
💡 TIP: Copies a message to another mail folder. Body: { DestinationId: '<mailFolder-id or well-known name like inbox, archive, junkemail>' }. Returns the newly created message (with a new id) in the destination folder. For moving instead of copying, use move-mail-message.
Inputs: body (required), messageId (required), confirm, includeHeaders, +1 more.
create-calendarcreate-calendarADestructive
Create a new calendar for a user.
💡 TIP: Creates a new personal calendar. Body: { name: 'My Calendar', color: 'auto' }. Available colors: auto, lightBlue, lightGreen, lightOrange, lightGray, lightYellow, lightTeal, lightPink, lightBrown, lightRed, maxColor.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-calendar-eventcreate-calendar-eventBDestructive
Create (schedule) a new calendar event — a meeting or appointment — on the user's calendar. Set subject, start/end times, time zone, location, body, and attendees; supports online meetings and recurrence.
💡 TIP: CRITICAL: Do not try to guess the email address of the recipients. Use the list-users tool to find the email address of the recipients.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-contact-child-foldercreate-contact-child-folderADestructive
Create a new contactFolder as a child of a specified folder. You can also create a new contactFolder under the user's default contact folder.
💡 TIP: Creates a sub-folder under an existing contact folder. Body: { displayName: 'Sub-folder name' }. Use list-contact-folders to discover the parent id. The returned contactFolder has its own id usable with update-contact-folder, delete-contact-folder, list-contact-folder-contacts, and create-contact-in-folder — contactFolder ids are mailbox-unique regardless of nesting depth.
Inputs: body (required), contactFolderId (required), confirm, includeHeaders, +1 more.
create-contact-foldercreate-contact-folderCDestructive
Create a new contactFolder under the user's default contacts folder. You can also create a new contactfolder as a child of any specified contact folder.
💡 TIP: Creates a new contact folder under the user's mailbox root. Body: { displayName: 'Family' }. Returns the created contactFolder with its id. To create a sub-folder under an existing folder, use create-contact-child-folder.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-contact-in-foldercreate-contact-in-folderADestructive
Add a contact to the root Contacts folder or to the contacts endpoint of another contact folder.
💡 TIP: Creates a contact inside a specific folder (instead of the default Contacts folder). Body is a contact resource: { givenName, surname, displayName, emailAddresses: [{ address, name }], businessPhones: [], mobilePhone, jobTitle, companyName, ... }. The existing create-outlook-contact (POST /me/contacts) writes to the default folder only; use this when organizing contacts into named folders. Get the folder id via list-contact-folders.
Inputs: body (required), contactFolderId (required), confirm, includeHeaders, +1 more.
create-draft-emailcreate-draft-emailADestructive
Create a draft Outlook email message in the signed-in user's Drafts folder. Set subject, body, toRecipients, ccRecipients, and importance. The draft is saved, not sent — use send-mail to send a message directly, or send the draft afterwards.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-drive-item-previewcreate-drive-item-previewADestructive
Create a drive item preview.
💡 TIP: Generate a short-lived embeddable preview URL for a file (Office docs, PDFs, images). Body: { page?: number | string, zoom?: number, viewer?: 'onedrive' | 'office' }. Returns getUrl (interactive) and postUrl (form-post). Useful for surfacing inline previews in summary emails or chat messages without needing the recipient to open the file.
Inputs: body (required), driveId (required), driveItemId (required), confirm, +2 more.
create-excel-chartcreate-excel-chartCDestructive
Creates a new chart.
Inputs: body (required), driveId (required), driveItemId (required), workbookWorksheetId (required), +3 more.
create-excel-tablecreate-excel-tableADestructive
Create a new table. The range source address determines the worksheet under which the table will be added. If the table can't be added (for example, because the address is invalid, or the table would overlap with another table), an error is generated.
💡 TIP: Convert a worksheet range into a formal Excel table. Body: { address: 'A1:H171', hasHeaders: true }. Required before using add-excel-table-rows / update-excel-table-row / delete-excel-table-row on a plain-cells sheet.
Inputs: body (required), driveId (required), driveItemId (required), workbookWorksheetId (required), +3 more.
create-focused-inbox-overridecreate-focused-inbox-overrideADestructive
Create an override for a sender identified by an SMTP address. Future messages from that SMTP address will be consistently classified as specified in the override. Note
💡 TIP: Creates a Focused Inbox override for a sender. Body: { classifyAs: 'focused', senderEmailAddress: { name: 'Display Name', address: 'sender@example.com' } }. classifyAs must be 'focused' or 'other'. If an override already exists for that SMTP address, POST updates the existing override's name and classifyAs (use this to rename a sender). Resolve the sender's address with list-users or by reading a recent mail header — do not invent SMTP addresses.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-forward-draftcreate-forward-draftADestructive
Create a forward draft.
💡 TIP: Create a forward draft (does not send). Useful when user wants to review before sending.
Inputs: body (required), messageId (required), confirm, includeHeaders, +1 more.
create-mail-attachment-upload-sessioncreate-mail-attachment-upload-sessionADestructive
Create an upload session that allows an app to iteratively upload ranges of a file, so as to attach the file to the specified Outlook item. The item can be a message or event. Use this approach to attach a file if the file size is between 3 MB and 150 MB. To attach a file that's smaller than 3 MB, do a POST operation on the attachments navigation property of the Outlook item; see how to do this for a message or for an event. As part of the response, this action returns an upload URL that you can use in subsequent sequential PUT queries. Request headers for each PUT operation let you specify the exact range of bytes to be uploaded. This allows transfer to be resumed, in case the network connection is dropped during upload. The following are the steps to attach a file to an Outlook item using an upload session: See attach large files to Outlook messages or events for an example.
💡 TIP: For attachments 3MB to 150MB. Graph rejects smaller files with ErrorAttachmentSizeShouldNotBeLessThanMinimumSize, so under 3MB use add-mail-attachment instead. Body: { AttachmentItem: { attachmentType: 'file', name: 'report.pdf', size: 5000000 } }. Returns a pre-authenticated uploadUrl; the caller PUTs the bytes there itself in ranges up to 4MB. This server does not perform the PUT.
Inputs: body (required), messageId (required), confirm, includeHeaders, +1 more.
create-mail-child-foldercreate-mail-child-folderADestructive
Use this API to create a new child mailFolder. If you intend a new folder to be hidden, you must set the isHidden property to true on creation.
💡 TIP: Creates a subfolder inside an existing mail folder. Use list-mail-folders or list-mail-child-folders to find the parent folder ID.
Inputs: body (required), mailFolderId (required), confirm, includeHeaders, +1 more.
create-mail-foldercreate-mail-folderADestructive
Use this API to create a new mail folder in the root folder of the user's mailbox. If you intend a new folder to be hidden, you must set the isHidden property to true on creation.
💡 TIP: Creates a top-level mail folder. Use create-mail-child-folder to create a subfolder inside an existing folder. Use list-mail-folders to find existing folder IDs.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-mail-rulecreate-mail-ruleADestructive
Create a messageRule object by specifying a set of conditions and actions. Outlook carries out those actions if an incoming message in the user's Inbox meets the specified conditions.
💡 TIP: Creates a message rule for a mail folder. Use the Inbox folder ID (get it from list-mail-folders) for inbox rules. Body: { displayName: 'Rule name', sequence: 1, isEnabled: true, conditions: { fromAddresses: [{ emailAddress: { address: 'user@example.com' } }] }, actions: { moveToFolder: 'folder-id' } }. Actions: moveToFolder, copyToFolder, forwardTo, forwardAsAttachmentTo, delete, markAsRead, markImportance, stopProcessingRules.
Inputs: body (required), mailFolderId (required), confirm, includeHeaders, +1 more.
create-my-calendar-permissioncreate-my-calendar-permissionADestructive
Create a calendarPermission resource to specify the identity and role of the user with whom the specified calendar is being shared or delegated.
💡 TIP: Shares the user's primary calendar with another user (or sets up a delegate). Body: { emailAddress: { name: 'Adele Vance', address: 'adele@contoso.com' }, role: 'read' | 'write' | 'delegateWithoutPrivateEventAccess' | 'delegateWithPrivateEventAccess', isInsideOrganization: true, isRemovable: true }. Use list-users to resolve the recipient SMTP. Returns the created calendarPermission with its id (used by update-my-calendar-permission and delete-my-calendar-permission).
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-onedrive-foldercreate-onedrive-folderADestructive
Create a OneDrive folder.
💡 TIP: Creates a new folder inside the specified drive item. Body must include name (string) and folder ({}) fields. Use @microsoft.graph.conflictBehavior to control behavior on name conflict: 'rename' (default), 'replace', or 'fail'.
Inputs: body (required), driveId (required), driveItemId (required), confirm, +2 more.
create-onenote-notebookcreate-onenote-notebookBDestructive
Create a new OneNote notebook.
💡 TIP: Creates a new OneNote notebook. Body: { displayName: 'Notebook Name' }. The name must be unique across the user's notebooks.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-onenote-pagecreate-onenote-pageADestructive
Create a new OneNote page in the default section of the default notebook. To create a page in a different section in the default notebook, you can use the sectionName query parameter. Example: ../onenote/pages?sectionName=My%20section The POST /onenote/pages operation is used only to create pages in the current user's default notebook. If you're targeting other notebooks, you can create pages in a specified section.
💡 TIP: Body must be a full HTML document (with ......). Partial HTML or plain text fails silently or creates malformed pages.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-onenote-sectioncreate-onenote-sectionADestructive
Create a new onenoteSection in the specified notebook.
💡 TIP: Creates a new section in a notebook. Body: { displayName: 'Section Name' }.
Inputs: body (required), notebookId (required), confirm, includeHeaders, +1 more.
create-onenote-section-pagecreate-onenote-section-pageBDestructive
Create a new page in the specified section.
💡 TIP: Body must be a full HTML document (with ......). Partial HTML fails silently.
Inputs: body (required), onenoteSectionId (required), confirm, includeHeaders, +1 more.
create-outlook-categorycreate-outlook-categoryADestructive
Create an outlookCategory object in the user's master list of categories.
💡 TIP: Creates a new Outlook category. Body: { displayName (unique), color (one of: none, preset0 … preset24 — maps to red, orange, yellow, green, teal, olive, blue, purple, cranberry, steel, dark-steel, gray, dark-gray, black, dark-red, dark-orange, dark-yellow, dark-green, dark-teal, dark-olive, dark-blue, dark-purple, dark-cranberry) }. Category names are case-sensitive when applied to messages/events.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-outlook-contactcreate-outlook-contactBDestructive
Add a contact to the root Contacts folder or to the contacts endpoint of another contact folder.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-planner-bucketcreate-planner-bucketCDestructive
Create a new plannerBucket object.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-planner-taskcreate-planner-taskCDestructive
Create a new plannerTask.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-planner-task-messagecreate-planner-task-messageADestructive
[beta] Create a new plannerTaskChatMessage on a plannerTask.
💡 TIP: Posts a message to a Planner task's chat (the modern 'task chat', not the legacy conversationThreadId comment). Microsoft is retiring the classic task comments experience and hiding it from the task (Planner 2026 update), so task chat is the current supported way to post per-task updates teammates will see, with @mentions. Body: { content: 'plain text or sanitized HTML', mentions?: [{ mentioned: 'user-id', position: 0, mentionType: 'user' }] } (mentions optional). No ETag/If-Match required. BETA Graph API: subject to change; delegated work/school accounts only — no application permissions, no personal Microsoft accounts, global cloud only (not GCC/DoD/21Vianet).
Inputs: body (required), plannerTaskId (required), confirm, includeHeaders, +1 more.
create-reply-all-draftcreate-reply-all-draftADestructive
Create a draft to reply to the sender and all recipients of a message in either JSON or MIME format. When using JSON format:
Specify either a comment or the body property of the message parameter. Specifying both will return an HTTP 400 Bad Request error.
If the original message specifies a recipient in the replyTo property, per Internet Message Format (RFC 2822), you should send the reply to the recipients in the replyTo and toRecipients properties, and not the recipients in the from and toRecipients properties.
You can update the draft later to add reply content to the body or change other message properties. When using MIME format:
Provide the applicable Internet message headers and the MIME content, all encoded in base64 format in the request body.
Add any attachments and S/MIME properties to the MIME content. Send the draft message in a subsequent operation. Alternatively, reply-all to a message in a single action.
💡 TIP: For HTML replies pass Message.body.contentType: 'html' with Message.body.content as HTML. Note: supplying Message.body replaces the whole draft body, so the original quoted history is not included. Specifying both 'comment' and Message.body returns 400. Signatures are added by the Outlook client only, not via Graph.
Inputs: body (required), messageId (required), confirm, includeHeaders, +1 more.
create-reply-draftcreate-reply-draftADestructive
Create a draft to reply to the sender of a message in either JSON or MIME format. When using JSON format:
Specify either a comment or the body property of the message parameter. Specifying both will return an HTTP 400 Bad Request error.
If replyTo is specified in the original message, per Internet Message Format (RFC 2822), you should send the reply to the recipients in replyTo, and not the recipients in from.
You can update the draft later to add reply content to the body or change other message properties. When using MIME format:
Provide the applicable Internet message headers and the MIME content, all encoded in base64 format in the request body.
Add any attachments and S/MIME properties to the MIME content. Send the draft message in a subsequent operation. Alternatively, reply to a message in a single operation.
💡 TIP: For HTML replies pass Message.body.contentType: 'html' with Message.body.content as HTML. Note: supplying Message.body replaces the whole draft body, so the original quoted history is not included. Specifying both 'comment' and Message.body returns 400. Signatures are added by the Outlook client only, not via Graph.
Inputs: body (required), messageId (required), confirm, includeHeaders, +1 more.
create-specific-calendar-eventcreate-specific-calendar-eventCDestructive
Use this API to create a new event in a calendar. The calendar can be one for a user, or the default calendar of a Microsoft 365 group.
💡 TIP: CRITICAL: Do not try to guess the email address of the recipients. Use the list-users tool to find the email address of the recipients.
Inputs: body (required), calendarId (required), confirm, includeHeaders, +1 more.
create-subscriptioncreate-subscriptionADestructive
Subscribes a listener application to receive change notifications when the requested type of changes occur to the specified resource in Microsoft Graph. To identify the resources for which you can create subscriptions and the limitations on subscriptions, see Set up notifications for changes in resource data: Supported resources. Some resources support rich notifications, that is, notifications that include resource data. For more information about these resources, see Set up change notifications that include resource data: Supported resources.
💡 TIP: Creates a webhook subscription for change notifications. Required body: { changeType (comma-separated: 'created,updated,deleted'), notificationUrl (HTTPS, must validate with token echo), resource (e.g. '/me/mailFolders/inbox/messages', '/users/{id}/events', '/teams/{id}/channels/{id}/messages'), expirationDateTime (ISO 8601, max varies by resource type — 1 hour for calls, 24h for messages, 3 days for mail), clientState (opaque string returned in notifications, for validation) }. Optional: includeResourceData (true enables rich notifications, requires encryptionCertificate + encryptionCertificateId). No dedicated scope — caller must have a read permission for the target resource (e.g. Mail.Read, Calendars.Read, ChannelMessage.Read.All, Files.Read.All).
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-todo-linked-resourcecreate-todo-linked-resourceADestructive
Create a linkedResource object to associate a specified task with an item in a partner application. For example, you can associate a task with an email item in Outlook that spurred the task, and you can create a linkedResource object to track its association. You can also create a linkedResource object while creating a task.
💡 TIP: Links a resource to a To Do task. Body: { webUrl: 'https://...', applicationName: 'Mail', displayName: 'Related email', externalId: 'optional-id' }. Use to link tasks to emails, files, or web pages for context.
Inputs: body (required), todoTaskId (required), todoTaskListId (required), confirm, +2 more.
create-todo-taskcreate-todo-taskADestructive
Create a new task object in a specified todoTaskList.
💡 TIP: Creates a new task in a Microsoft To Do list. Body: { title: "..." }; optional dueDateTime, reminderDateTime, importance, body (notes), recurrence, categories. Requires todoTaskListId from list-todo-task-lists.
Inputs: body (required), todoTaskListId (required), confirm, includeHeaders, +1 more.
create-todo-task-listcreate-todo-task-listADestructive
Create a new lists object.
💡 TIP: Creates a new Microsoft To Do task list (the named buckets shown in the To Do app sidebar). Body: { displayName: 'My new list' }. Returns the created todoTaskList with its id, displayName, isOwner, isShared, and wellknownListName ('none' for user-created lists). The built-in lists ('Tasks', 'Flagged emails') already exist and cannot be re-created. Pair with create-todo-task to populate it.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
create-upload-sessioncreate-upload-sessionADestructive
Create an upload session.
💡 TIP: For large file uploads (no size limit, and no minimum unlike the Outlook attachment session). Returns a pre-authenticated uploadUrl; the caller PUTs the bytes there itself. This server does not perform the PUT. For new files use path: /items/{parentId}:/{fileName}:/createUploadSession. Body (optional): { item: { '@microsoft.graph.conflictBehavior': 'rename' } }.
Inputs: body (required), driveId (required), driveItemId (required), confirm, +2 more.
decline-calendar-eventdecline-calendar-eventADestructive
Decline invitation to the specified event in a user calendar. If the event allows proposals for new times, on declining the event, an invitee can choose to suggest an alternative time by including the proposedNewTime parameter. For more information on how to propose a time, and how to receive and accept a new time proposal, see Propose new meeting times.
💡 TIP: Declines a meeting invitation. Optional body: { sendResponse: true, comment: 'Cannot attend, conflict.' }. The event remains in the calendar as declined unless the user deletes it.
Inputs: body (required), eventId (required), confirm, includeHeaders, +1 more.
delete-calendardelete-calendarADestructive
Delete a calendar other than the default calendar.
💡 TIP: Deletes a calendar and all its events. The default calendar cannot be deleted. This action cannot be undone.
Inputs: calendarId (required), confirm, If-Match, includeHeaders, +1 more.
delete-calendar-eventdelete-calendar-eventADestructive
Removes the specified event from the containing calendar. If the event is a meeting, deleting the event on the organizer's calendar sends a cancellation message to the meeting attendees.
💡 TIP: Deleting a seriesMaster deletes ALL occurrences of the recurring event. To cancel a single occurrence, delete that specific instance ID from list-calendar-event-instances.
Inputs: eventId (required), confirm, If-Match, includeHeaders, +1 more.
delete-contact-folderdelete-contact-folderADestructive
Delete contactFolder other than the default contactFolder.
💡 TIP: Deletes a contact folder. The default 'Contacts' folder cannot be deleted — Graph returns an error. The folder (and its contents) typically lands in Deleted Items rather than being permanently removed. Get the folder id via list-contact-folders.
Inputs: contactFolderId (required), confirm, If-Match, includeHeaders, +1 more.
delete-drive-item-permissiondelete-drive-item-permissionADestructive
Delete a drive item permission.
💡 TIP: Removes a specific permission from a file or folder. Only permissions that are not inherited can be deleted. Use list-drive-item-permissions first to find the permission ID.
Inputs: driveId (required), driveItemId (required), permissionId (required), confirm, +3 more.
delete-excel-rangedelete-excel-rangeADestructive
Delete an Excel range.
💡 TIP: Delete cells at the given range, shifting remaining content. Body: { shift: 'Up' } or { shift: 'Left' }. Use 'Up' to delete entire rows.
Inputs: body (required), address (required), driveId (required), driveItemId (required), +4 more.
delete-excel-table-rowdelete-excel-table-rowADestructive
Delete an Excel table row.
💡 TIP: Delete a single row from a formal Excel table by zero-based row index.
Inputs: index (required), driveId (required), driveItemId (required), workbookTableId (required), +3 more.
delete-focused-inbox-overridedelete-focused-inbox-overrideADestructive
Delete an override specified by its ID.
💡 TIP: Deletes a Focused Inbox override. Future messages from that sender revert to the Outlook ML classifier's default behavior. Use list-focused-inbox-overrides to find the ID first.
Inputs: inferenceClassificationOverrideId (required), confirm, If-Match, includeHeaders, +1 more.
delete-mail-attachmentdelete-mail-attachmentBDestructive
Delete a mail attachment.
Inputs: messageId (required), attachmentId (required), confirm, If-Match, +2 more.
delete-mail-folderdelete-mail-folderADestructive
Delete the specified mailFolder. The folder can be a mailSearchFolder. You can specify a mail folder by its folder ID, or by its well-known folder name, if one exists.
💡 TIP: Deletes a mail folder and all its contents. This action is irreversible. Use list-mail-folders to find the folder ID.
Inputs: mailFolderId (required), confirm, If-Match, includeHeaders, +1 more.
delete-mail-messagedelete-mail-messageADestructive
Delete an Outlook email message by its message ID. This is a soft delete that moves the message to Deleted Items.
💡 TIP: Soft delete — moves to Deleted Items. To permanently delete, delete again from Deleted Items.
Inputs: messageId (required), confirm, If-Match, includeHeaders, +1 more.
delete-mail-ruledelete-mail-ruleADestructive
Delete the specified messageRule object.
💡 TIP: Deletes a message rule permanently. Use the Inbox folder ID (get it from list-mail-folders) for inbox rules.
Inputs: mailFolderId (required), messageRuleId (required), confirm, If-Match, +2 more.
delete-my-calendar-permissiondelete-my-calendar-permissionADestructive
Delete my calendar permission.
💡 TIP: Revokes a calendar share or delegate access. Get the permission id via list-my-calendar-permissions. Permissions where isRemovable=false (e.g. the implicit 'My Organization' default) cannot be deleted — Graph returns an error.
Inputs: calendarPermissionId (required), confirm, If-Match, includeHeaders, +1 more.
delete-onedrive-filedelete-onedrive-fileADestructive
Delete a OneDrive file.
Inputs: driveId (required), driveItemId (required), confirm, If-Match, +2 more.
delete-onenote-pagedelete-onenote-pageADestructive
Delete a OneNote page.
💡 TIP: Deletes a OneNote page permanently. This cannot be undone.
Inputs: onenotePageId (required), confirm, If-Match, includeHeaders, +1 more.
delete-outlook-contactdelete-outlook-contactBDestructive
Delete a contact.
Inputs: contactId (required), confirm, If-Match, includeHeaders, +1 more.
delete-planner-bucketdelete-planner-bucketADestructive
Delete plannerBucket.
💡 TIP: CRITICAL: Requires If-Match header with ETag from get-planner-bucket (use includeHeaders=true).
Inputs: plannerBucketId (required), confirm, If-Match, includeHeaders, +1 more.
delete-planner-task-messagedelete-planner-task-messageADestructive
[beta] Delete a plannerTaskChatMessage object.
💡 TIP: Deletes a message from a Planner task's chat. No request body; If-Match is optional if you want conditional deletion; returns 204. BETA Graph API: subject to change; delegated work/school accounts only — no application permissions, no personal Microsoft accounts, global cloud only (not GCC/DoD/21Vianet).
Inputs: plannerTaskId (required), plannerTaskChatMessageId (required), confirm, If-Match, +2 more.
delete-specific-calendar-eventdelete-specific-calendar-eventADestructive
Delete a specific calendar event.
💡 TIP: Deleting a seriesMaster deletes ALL occurrences. To cancel a single occurrence, use the specific instance ID.
Inputs: eventId (required), calendarId (required), confirm, If-Match, +2 more.
delete-subscriptiondelete-subscriptionADestructive
Delete a subscription. For the list of resources that support subscribing to change notifications, see the table in the Permissions section.
💡 TIP: Deletes a webhook subscription. No further change notifications will be sent. Use this to clean up stale subscriptions or stop receiving notifications. Use list-subscriptions to find the id.
Inputs: subscriptionId (required), confirm, If-Match, includeHeaders, +1 more.
delete-todo-linked-resourcedelete-todo-linked-resourceCDestructive
Delete a linkedResource object.
💡 TIP: Removes a linked resource from a To Do task.
Inputs: todoTaskId (required), todoTaskListId (required), linkedResourceId (required), confirm, +3 more.
delete-todo-taskdelete-todo-taskCDestructive
Delete a todoTask object.
Inputs: todoTaskId (required), todoTaskListId (required), confirm, If-Match, +2 more.
delete-todo-task-listdelete-todo-task-listADestructive
Deletes a todoTaskList object.
💡 TIP: Deletes a Microsoft To Do task list. Built-in lists (Flagged emails, the default Tasks list) cannot be deleted — the API returns an error for those. Get list ids via list-todo-task-lists.
Inputs: todoTaskListId (required), confirm, If-Match, includeHeaders, +1 more.
dismiss-calendar-event-reminderdismiss-calendar-event-reminderADestructive
Dismiss a reminder that has been triggered for an event in a user calendar.
💡 TIP: Dismisses a triggered event reminder so it won't re-fire. No request body required. Pair with list-calendar-events or get-schedule to find active reminders.
Inputs: eventId (required), confirm, includeHeaders, excludeResponse.
download-bytesARead-only
Download binary content from Microsoft Graph and return it as base64. Single tool for any binary read: drive file content, mail attachment, profile photo, Teams hosted content, meeting recording. Returns { contentType, encoding: "base64", contentLength, contentBytes }. For large drive/SharePoint file content, prefer get-download-url, which returns a pre-authenticated URL to stream bytes out-of-band instead of base64 through the agent context.
Inputs: target (required).
download-bytes-to-fileARead-only
Write authenticated Microsoft Graph byte content to a local file on the server, returning { path, contentType, bytesWritten } instead of base64. The only out-of-band way to save mail attachments and meeting recordings, whose bytes are exposed solely through authenticated endpoints. Also handles profile photos and Teams hosted content. Writes to an absolute outputPath and never overwrites an existing file. stdio mode only: not available over HTTP. For OneDrive or SharePoint file content, get-download-url is preferred — it returns a pre-authenticated URL for fully out-of-band download without the server fetching the bytes.
Inputs: target (required), outputPath (required).
format-excel-rangeformat-excel-rangeADestructive
Format an Excel range.
💡 TIP: Apply rangeFormat properties to a specific range. Required path param 'address' (e.g. 'A1:E5' or 'Sheet1!A1:E5'). Body: { horizontalAlignment, verticalAlignment, wrapText, columnWidth, rowHeight }. Font, fill, and borders are NOT set here; use format-excel-range-font, format-excel-range-fill, and format-excel-range-border for those.
Inputs: body (required), address (required), driveId (required), driveItemId (required), +4 more.
format-excel-range-borderformat-excel-range-borderADestructive
Format an Excel range border.
💡 TIP: Sets one border side. The {sideIndex} path param selects which side: EdgeTop, EdgeBottom, EdgeLeft, EdgeRight, InsideVertical, InsideHorizontal, DiagonalDown, or DiagonalUp. To outline all four edges, call once per side.
Inputs: body (required), address (required), driveId (required), sideIndex (required), +5 more.
format-excel-range-fillformat-excel-range-fillCDestructive
Format an Excel range fill.
💡 TIP: Set the background fill color of a range's cells.
Inputs: body (required), address (required), driveId (required), driveItemId (required), +4 more.
format-excel-range-fontformat-excel-range-fontBDestructive
Format an Excel range font.
💡 TIP: Set font formatting on a range: bold, italic, underline, size, color, and font name.
Inputs: body (required), address (required), driveId (required), driveItemId (required), +4 more.
forward-calendar-eventforward-calendar-eventADestructive
This action allows the organizer or attendee of a meeting event to forward the meeting request to a new recipient. If the meeting event is forwarded from an attendee's Microsoft 365 mailbox to another recipient, this action also sends a message to notify the organizer of the forwarding, and adds the recipient to the organizer's copy of the meeting event. This convenience is not available when forwarding from an Outlook.com account.
💡 TIP: Forwards a meeting invitation to additional recipients. Body: { ToRecipients: [{ emailAddress: { address, name } }], Comment (optional) }. If the forwarder is an attendee (not organizer), the organizer is also notified and the new recipient is added to the organizer's attendee list.
Inputs: body (required), eventId (required), confirm, includeHeaders, +1 more.
forward-mail-messageforward-mail-messageADestructive
Forward a message using either JSON or MIME format. When using JSON format, you can:
Specify either a comment or the body property of the message parameter. Specifying both will return an HTTP 400 Bad Request error.
Specify either the toRecipients parameter or the toRecipients property of the message parameter. Specifying both or specifying neither will return an HTTP 400 Bad Request error. When using MIME format:
Provide the applicable Internet message headers and the MIME content, all encoded in base64 format in the request body.
Add any attachments and S/MIME properties to the MIME content. This method saves the message in the Sent Items folder. Alternatively, create a draft to forward a message, and send it later.
💡 TIP: Forward an email preserving full HTML formatting and attachments. The 'comment' field adds text above the forwarded content. toRecipients is required. Do NOT reconstruct the email manually - this endpoint handles everything server-side.
Inputs: body (required), messageId (required), confirm, includeHeaders, +1 more.
get-calendar-eventget-calendar-eventBRead-only
Get the properties and relationships of the specified event object. Currently, this operation returns event bodies in only HTML format. There are two scenarios where an app can get an event in another user's calendar: Since the event resource supports extensions, you can also use the GET operation to get custom properties and extension data in an event instance.
Inputs: eventId (required), expand, select, timezone, +4 more.
get-calendar-viewget-calendar-viewARead-only
Get the occurrences, exceptions, and single instances of events in a calendar view defined by a time range, from the user's default calendar, or from some other calendar of the user.
💡 TIP: Returns expanded recurring event instances (not just seriesMaster) within a date range for the default calendar. Requires startDateTime and endDateTime query parameters in ISO 8601 format (e.g., 2024-01-01T00:00:00Z). Use get-specific-calendar-view if you need a non-default calendar. To find Teams meetings, use $filter=isOnlineMeeting eq true. To search by subject, use $filter=contains(subject,'keyword'). Teams meetings include a joinWebUrl property needed for transcript access via list-online-meetings.
Inputs: endDateTime (required), startDateTime (required), top, skip, +11 more.
get-current-userget-current-userARead-only
Retrieve the properties and relationships of user object. This operation returns by default only a subset of the more commonly used properties for each user. These default properties are noted in the Properties section. To get properties that are not returned by default, do a GET operation for the user and specify the properties in a $select OData query option. Because the user resource supports extensions, you can also use the GET operation to get custom properties and extension data in a user instance. Customers through Microsoft Entra ID for customers can also use this API operation to retrieve their details.
Inputs: expand, select, fetchAllPages, includeHeaders, +2 more.
get-download-urlARead-only
Resolve a short-lived, pre-authenticated download URL for Microsoft Graph binary content that exposes one (drive/SharePoint file content). The returned URL streams the bytes with NO Authorization header, so the client can fetch it straight to disk (e.g. curl) without round-tripping base64 through the agent context. Prefer this over download-bytes for any file above a few KB or any bulk download. Returns { downloadUrl, name?, size?, contentType? }. NOTE: mail file attachments (/messages/{id}/attachments/{id}/$value) and meeting recordings do NOT expose a pre-authenticated URL — Graph offers no such link for them; use download-bytes for small ones.
Inputs: target (required).
get-drive-deltaget-drive-deltaARead-only
Track changes in a driveItem and its children over time. Your app begins by calling delta without any parameters. The service starts enumerating the drive's hierarchy, returning pages of items and either an @odata.nextLink or an @odata.deltaLink, as described below. Your app should continue calling with the @odata.nextLink until you no longer see an @odata.nextLink returned, or you see a response with an empty set of changes. After you have finished receiving all the changes, you may apply them to your local state. To check for changes in the future, call delta again with the @odata.deltaLink from the previous response. Deleted items are returned with the deleted facet. Items with this property set should be removed from your local state.
💡 TIP: Tracks changes to a driveItem and its children over time. Returns a collection of driveItems that have been created, modified, or deleted. Use get-drive-root-item first to get the root driveItem-id, then pass it here. Supports $select and delta tokens for incremental sync via @odata.deltaLink.
Inputs: driveId (required), driveItemId (required), top, skip, +9 more.
get-drive-itemget-drive-itemBRead-only
All items contained in the drive. Read-only. Nullable.
💡 TIP: Gets metadata for a file or folder: name, size, lastModifiedDateTime, createdBy, webUrl, file (mimeType, hashes), folder (childCount), parentReference, and @microsoft.graph.downloadUrl. For large drive/SharePoint files, call get-download-url with target=/drives/{drive-id}/items/{driveItem-id}/content to fetch out-of-band with no Authorization header. For small files where base64 in the tool response is acceptable, call download-bytes with the same /content target.
Inputs: driveId (required), driveItemId (required), expand, select, +3 more.
get-drive-root-itemget-drive-root-itemDRead-only
The root folder of the drive. Read-only.
Inputs: driveId (required), expand, select, fetchAllPages, +2 more.
get-excel-rangeget-excel-rangeCRead-only
Get an Excel range.
Inputs: address (required), driveId (required), driveItemId (required), workbookWorksheetId (required), +3 more.
get-excel-range-formatget-excel-range-formatARead-only
Returns a format object, encapsulating the range's font, fill, borders, alignment, and other properties. Read-only.
💡 TIP: Reads a range's format: alignment, wrapText, columnWidth, rowHeight. Font, fill, and borders are nested and omitted by default; add font, fill, and borders to the $expand parameter to include them in one call. Cell styling often encodes meaning (a fill or font color flagging status such as tentative or reconciled), so expand these to interpret what the cells mean.
Inputs: address (required), driveId (required), driveItemId (required), workbookWorksheetId (required), +5 more.
get-excel-tableget-excel-tableBRead-only
Represents a collection of tables associated with the workbook. Read-only.
💡 TIP: Gets a specific table by name or ID. Returns table properties including columns, showHeaders, showTotals, and style.
Inputs: driveId (required), driveItemId (required), workbookTableId (required), expand, +4 more.
get-excel-used-rangeget-excel-used-rangeARead-only
Get an Excel used range.
💡 TIP: Get the smallest range that encompasses any cells with values or formatting on the worksheet. Returns address, values, formulas, numberFormat, rowCount, columnCount. Use this to discover the populated bounds of a sheet before reading or appending — avoids guessing how far data extends. Optional $select to trim the response.
Inputs: driveId (required), driveItemId (required), workbookWorksheetId (required), fetchAllPages, +2 more.
get-mailbox-settingsget-mailbox-settingsARead-only
Get the user's mailboxSettings. You can view all mailbox settings, or get specific settings. Users can set the following settings for their mailboxes through an Outlook client: Users can set their preferred date and time formats using Outlook on the web. Users can choose one of the supported short date or short time formats. This GET operation returns the format the user has chosen. Users can set the time zone they prefer on any Outlook client, by choosing from the supported time zones that their administrator has set up for their mailbox server. The administrator can set up time zones in the Windows time zone format or Internet Assigned Numbers Authority (IANA) time zone (also known as Olson time zone) format. The Windows format is the default. This GET operation returns the user's preferred time zone in the format that the administrator has set up. If you want that time zone to be in a specific format (Windows or IANA), you can first update the preferred time zone in that format as a mailbox setting. Subsequently you will be able to get the time zone in that format. Alternatively, you can manage the format conversion separately in your app.
💡 TIP: Gets the current user's mailbox settings including automaticRepliesSetting (out-of-office status, message, scheduledStartDateTime/EndDateTime, externalAudience), language, timeZone, dateFormat, timeFormat, delegateMeetingMessageDeliveryOptions, and userPurpose.
Inputs: expand, select, fetchAllPages, includeHeaders, +1 more.
get-mail-messageget-mail-messageARead-only
Get a single Outlook email message by its message ID, including full subject, sender, recipients, body, and attachment flags. Use list-mail-messages first to obtain the message ID.
Inputs: messageId (required), expand, select, fetchAllPages, +2 more.
get-mail-message-mimeget-mail-message-mimeARead-only
Download the raw MIME source (RFC 822 .eml content) of an Outlook email message by its message ID. Returns the complete original message including headers and encoded attachments.
💡 TIP: Download an email message as raw RFC 5322 MIME content (.eml format). Use this when archiving an email to disk preserving all original headers, body, and inline-encoded attachments. Returns the MIME stream as text. Find the message id with list-mail-messages first.
Inputs: messageId (required), fetchAllPages, includeHeaders, excludeResponse, +1 more.
get-mail-tipsget-mail-tipsARead-only
Get the MailTips of one or more recipients as available to the signed-in user. Note that by making a POST call to the getMailTips action, you can request specific types of MailTips to be returned for more than one recipient at one time. The requested MailTips are returned in a mailTips collection.
💡 TIP: Looks up MailTips for one or more recipients before sending an email — answers 'is this person on auto-reply / OOF?', 'will my email exceed their mailbox quota?', 'are they an external recipient?', 'is this a mailbox or distribution list?'. Body: { EmailAddresses: ['user@contoso.com', ...], MailTipsOptions: 'automaticReplies, mailboxFullStatus, customMailTip, externalMemberCount, totalMemberCount, maxMessageSize, deliveryRestriction, moderationStatus, recipientScope, recipientSuggestions' (comma-separated subset) }. Returns mailTips per recipient with the requested fields populated. Use this to short-circuit urgent emails when a recipient is OOF, or to warn before fanning out to a large DL.
Inputs: body (required), includeHeaders, excludeResponse.
get-my-profileget-my-profileARead-only
[beta] Retrieve the properties and relationships of a profile object for a given user. The profile resource exposes various rich properties that are descriptive of the user as relationships, for example, anniversaries and education activities. To get one of these navigation properties, use the corresponding GET method on that property. See the methods exposed by profile.
💡 TIP: Retrieves the signed-in user's rich profile - a richer object than get-current-user, exposing relationships like skills, projects, languages, education and work positions. Use $expand to pull related collections (e.g. $expand=skills,projects).
Inputs: expand, select, fetchAllPages, includeHeaders, +1 more.
get-onenote-page-contentget-onenote-page-contentDRead-only
The page's HTML content.
Inputs: onenotePageId (required), fetchAllPages, includeHeaders, excludeResponse.
get-outlook-contactget-outlook-contactCRead-only
Retrieve the properties and relationships of a contact object. There are two scenarios where an app can get a contact in another user's contact folder:
Inputs: contactId (required), expand, select, fetchAllPages, +2 more.
get-planner-bucketget-planner-bucketARead-only
Retrieve the properties and relationships of a plannerBucket object.
💡 TIP: Response includes @odata.etag — required as If-Match for update-planner-bucket and delete-planner-bucket. Use includeHeaders=true.
Inputs: plannerBucketId (required), expand, select, fetchAllPages, +2 more.
get-planner-planget-planner-planBRead-only
Retrieve the properties and relationships of a plannerplan object.
Inputs: plannerPlanId (required), expand, select, fetchAllPages, +2 more.
get-planner-taskget-planner-taskARead-only
Retrieve the properties and relationships of plannerTask object.
💡 TIP: Response includes @odata.etag — save it, required as If-Match header for update-planner-task. Use includeHeaders=true to capture it.
Inputs: plannerTaskId (required), expand, select, fetchAllPages, +2 more.
get-planner-task-detailsget-planner-task-detailsARead-only
Retrieve the properties and relationships of a plannerTaskDetails object.
💡 TIP: Response includes @odata.etag — required for update-planner-task-details. Use includeHeaders=true.
Inputs: plannerTaskId (required), expand, select, fetchAllPages, +2 more.
get-specific-calendar-eventget-specific-calendar-eventARead-only
Get a single event from one of the signed-in user's calendars, addressed by calendar ID and event ID. Any calendar can be addressed this way, including the default one.
Inputs: eventId (required), calendarId (required), expand, select, +5 more.
get-specific-calendar-viewget-specific-calendar-viewARead-only
List the occurrences, exceptions, and single instances of events over a time range, from one of the signed-in user's calendars addressed by calendar ID.
💡 TIP: Returns expanded recurring event instances (not just seriesMaster) within a date range for a specific calendar. Requires startDateTime and endDateTime query parameters in ISO 8601 format (e.g., 2024-01-01T00:00:00Z). Each instance includes seriesMasterId and type (occurrence/exception) fields for recurring event linkage. Use fetchAllPages=true to retrieve all results when there are many events. To find Teams meetings, use $filter=isOnlineMeeting eq true. Teams meetings include a joinWebUrl property needed for transcript access via list-online-meetings.
Inputs: calendarId (required), endDateTime (required), startDateTime (required), top, +12 more.
get-subscriptionget-subscriptionARead-only
Retrieve the properties and relationships of a subscription. See the table in the Permissions section for the list of resources that support subscribing to change notifications.
💡 TIP: Gets a specific webhook subscription by id. Use list-subscriptions to find the id. Returns full subscription details including resource, changeType, notificationUrl, expirationDateTime, applicationId.
Inputs: subscriptionId (required), select, fetchAllPages, includeHeaders, +1 more.
get-todo-taskget-todo-taskARead-only
Read the properties and relationships of a todoTask object.
💡 TIP: Returns a single To Do task. NOTE: $select is NOT supported — do not pass select parameter, Graph returns RequestBroker--ParseUri (400). Use $expand=linkedResources to include linked email/resource. Returns body content (HTML format), checklist items, and linked resources.
Inputs: todoTaskId (required), todoTaskListId (required), expand, select, +3 more.
graph-batchgraph-batchADestructive
Combine up to 20 Graph requests into a single HTTP call. Body: { requests: [{ id: '1', method: 'GET'|'POST'|'PATCH'|'DELETE', url: '/me/messages?$top=5', headers?: {...}, body?: {...}, dependsOn?: ['1'] }, ...] }. Returns { responses: [{ id, status, body, headers }] } in arbitrary order — match by id. Use cases: (1) parallelize many small reads (e.g. fetch 15 mail messages by id in one round-trip); (2) sequence dependent writes via dependsOn; (3) batch many Excel range writes into one call to dramatically reduce latency on large workbook builds. Note: each sub-request URL is relative to the Graph version root (/me/..., /drives/..., NOT https://graph.microsoft.com/v1.0/...).
💡 TIP: Combine up to 20 Graph requests into a single HTTP call. Body: { requests: [{ id: '1', method: 'GET'|'POST'|'PATCH'|'DELETE', url: '/me/messages?$top=5', headers?: {...}, body?: {...}, dependsOn?: ['1'] }, ...] }. Returns { responses: [{ id, status, body, headers }] } in arbitrary order — match by id. Use cases: (1) parallelize many small reads (e.g. fetch 15 mail messages by id in one round-trip); (2) sequence dependent writes via dependsOn; (3) batch many Excel range writes into one call to dramatically reduce latency on large workbook builds. Note: each sub-request URL is relative to the Graph version root (/me/..., /drives/..., NOT https://graph.microsoft.com/v1.0/...).
Inputs: body (required), confirm, includeHeaders, excludeResponse.
insert-excel-rangeinsert-excel-rangeADestructive
Insert an Excel range.
💡 TIP: Insert blank cells at the given range, shifting existing content. Body: { shift: 'Down' } or { shift: 'Right' }. Use 'Down' to insert blank rows above existing data.
Inputs: body (required), address (required), driveId (required), driveItemId (required), +4 more.
list-accountsARead-only
List all Microsoft accounts configured in this server. Use this to discover available account emails before making tool calls. Reflects accounts added mid-session via --login.
Inputs: no named parameters.
list-all-onenote-sectionslist-all-onenote-sectionsARead-only
Retrieve a list of onenoteSection objects.
💡 TIP: Lists all sections across all notebooks. Use list-onenote-notebook-sections to list sections within a specific notebook instead.
Inputs: top, skip, count, expand, +7 more.
list-calendar-event-instanceslist-calendar-event-instancesARead-only
The occurrences of a recurring series, if the event is a series master. This property includes occurrences that are part of the recurrence pattern, and exceptions modified, but doesn't include occurrences canceled from the series. Navigation property. Read-only. Nullable.
💡 TIP: Expand a recurring event into individual instances within a date range. Requires startDateTime and endDateTime query parameters in ISO 8601 format (e.g., 2024-01-01T00:00:00Z). Use this to see all occurrences of a recurring event.
Inputs: eventId (required), calendarId (required), endDateTime (required), startDateTime (required), +13 more.
list-calendar-eventslist-calendar-eventsARead-only
Get a list of event objects in the user's mailbox. The list contains single instance meetings and series masters. To get expanded event instances, you can get the calendar view, or get the instances of an event. Currently, this operation returns event bodies in only HTML format. There are two scenarios where an app can get events in another user's calendar:
💡 TIP: WARNING: Does NOT expand recurring events — only returns seriesMaster. Use get-calendar-view instead to see individual occurrences within a date range.
Inputs: top, skip, count, expand, +9 more.
list-calendar-events-deltalist-calendar-events-deltaARead-only
Get a set of event resources that have been added, deleted, or updated in a calendarView (a range of events defined by start and end dates) of the user's primary calendar. Typically, synchronizing events in a calendarView in a local store entails a round of multiple delta function calls. The initial call is a full synchronization, and every subsequent delta call in the same round gets the incremental changes (additions, deletions, or updates). This allows you to maintain and synchronize a local store of events in the specified calendarView, without having to fetch all the events of that calendar from the server every time.
💡 TIP: Incremental sync of events across the default calendar. First call returns all events plus @odata.deltaLink. Subsequent calls with that link return only additions/updates/removals. Use $select to limit fields. Deltas expire after ~30 days — start over if the server returns 410 Gone. For a time-bounded view with delta semantics, use list-calendar-view-delta instead.
Inputs: endDateTime (required), startDateTime (required), skip, count, +8 more.
list-calendarslist-calendarsARead-only
Get all the user's calendars (/calendars navigation property), get the calendars from the default calendar group or from a specific calendar group.
Inputs: top, skip, count, expand, +7 more.
list-calendar-view-deltalist-calendar-view-deltaARead-only
Get a set of event resources that have been added, deleted, or updated in a calendarView (a range of events defined by start and end dates) of the user's primary calendar. Typically, synchronizing events in a calendarView in a local store entails a round of multiple delta function calls. The initial call is a full synchronization, and every subsequent delta call in the same round gets the incremental changes (additions, deletions, or updates). This allows you to maintain and synchronize a local store of events in the specified calendarView, without having to fetch all the events of that calendar from the server every time.
💡 TIP: Incremental sync of events within a time window. Required query params on first call: startDateTime, endDateTime (ISO 8601). Returns events in the window plus @odata.deltaLink; subsequent calls with that link return only changes. Expands recurring events to individual occurrences (unlike list-calendar-events-delta which returns the series master). Use this for calendar UIs showing a week/month view.
Inputs: endDateTime (required), startDateTime (required), skip, count, +8 more.
list-contact-folder-child-folderslist-contact-folder-child-foldersARead-only
Get a collection of child folders under the specified contact folder.
💡 TIP: Lists immediate sub-folders under a given contact folder. Returns id, displayName, parentFolderId. Use list-contact-folders to discover top-level folders, then this tool to traverse one level deeper. Supports $filter, $top, $orderby. Note: contact folders are typically a flat list in Outlook clients, but Graph allows nesting via this endpoint.
Inputs: contactFolderId (required), top, skip, count, +8 more.
list-contact-folder-contactslist-contact-folder-contactsARead-only
Get a contact collection from the default Contacts folder of the signed-in user (.../me/contacts), or from the specified contact folder.
💡 TIP: Lists contacts inside a specific folder. Pair with list-contact-folders to discover the folder id. Note: the existing list-outlook-contacts (GET /me/contacts) only returns contacts from the default folder — use this tool to read contacts from any folder. Supports $filter, $search='query', $orderby, $top, $select.
Inputs: contactFolderId (required), top, skip, count, +8 more.
list-contact-folderslist-contact-foldersARead-only
Get the contact folder collection in the default Contacts folder of the signed-in user.
💡 TIP: Lists the user's Outlook contact folders (the named buckets that organize contacts). Always includes the built-in 'Contacts' folder; user-created folders also appear. Returns id, displayName, and parentFolderId. To identify the default folder, match displayName === 'Contacts'. Use this before list-contact-folder-contacts or create-contact-in-folder to discover folder ids. Supports OData query parameters.
Inputs: top, skip, count, expand, +7 more.
list-drive-item-permissionslist-drive-item-permissionsCRead-only
The set of permissions for the item. Read-only. Nullable.
💡 TIP: Lists all permissions (sharing links, direct access, inherited) on a file or folder. Each permission has roles, grantedTo (user), link (sharing URL), and inheritedFrom.
Inputs: driveId (required), driveItemId (required), top, skip, +9 more.
list-drive-item-thumbnailslist-drive-item-thumbnailsARead-only
Collection of thumbnailSet objects associated with the item. For more information, see getting thumbnails. Read-only. Nullable.
💡 TIP: Lists thumbnail sets for a file. Each set contains small (96px), medium (176px), large (800px) thumbnails with url and dimensions. Returns empty for unsupported types (text docs). Use $select=small,medium,large or $expand=small($select=url) to fetch specific sizes. The returned URLs are short-lived — fetch the bytes immediately.
Inputs: driveId (required), driveItemId (required), top, skip, +9 more.
list-drive-item-versionslist-drive-item-versionsBRead-only
The list of previous versions of the item. For more info, see getting previous versions. Read-only. Nullable.
💡 TIP: Lists version history of a file. Each version has id, lastModifiedDateTime, lastModifiedBy, and size. Use the version id with /versions/{id}/content to download a specific version.
Inputs: driveId (required), driveItemId (required), top, skip, +9 more.
list-driveslist-drivesARead-only
Retrieve the list of Drive resources available for a target User, Group, or Site.
Inputs: top, skip, count, expand, +7 more.
list-excel-table-rowslist-excel-table-rowsARead-only
The list of all the rows in the table. Read-only.
💡 TIP: Lists all rows in a table. Each row has index and values (array of cell values). Use $top and $skip for pagination on large tables.
Inputs: driveId (required), driveItemId (required), workbookTableId (required), top, +10 more.
list-excel-tableslist-excel-tablesARead-only
Represents a collection of tables associated with the workbook. Read-only.
💡 TIP: Lists all named tables in a workbook. Each table has id, name, showHeaders, showTotals, columns, and style. Use the table name or id with other table endpoints.
Inputs: driveId (required), driveItemId (required), top, skip, +9 more.
list-excel-worksheetslist-excel-worksheetsDRead-only
Represents a collection of worksheets associated with the workbook. Read-only.
Inputs: driveId (required), driveItemId (required), top, skip, +9 more.
list-focused-inbox-overrideslist-focused-inbox-overridesARead-only
Get the overrides that a user has set up to always classify messages from certain senders in specific ways. Each override corresponds to an SMTP address of a sender. Initially, a user doesn't have any overrides.
💡 TIP: Lists Focused Inbox classification overrides — explicit rules that force messages from a given sender (by SMTP address) into either the Focused or Other tab, regardless of what the Outlook ML classifier would predict. Each override has id, classifyAs ('focused' or 'other'), and senderEmailAddress {name, address}. Returns an empty collection if the user has never set an override.
Inputs: top, skip, count, expand, +7 more.
list-folder-fileslist-folder-filesARead-only
Return a collection of DriveItems in the children relationship of a DriveItem. DriveItems with a non-null folder or package facet can have one or more child DriveItems.
Inputs: driveId (required), driveItemId (required), top, skip, +9 more.
list-mail-attachmentslist-mail-attachmentsARead-only
Retrieve a list of attachment objects.
💡 TIP: Lists attachments on a message: id, name, contentType, size, isInline. To download the bytes, call download-bytes with target=/me/messages/{message-id}/attachments/{attachment-id}/$value (the /$value suffix returns raw bytes; the bare attachment URL embeds contentBytes in JSON which can truncate large files).
Inputs: messageId (required), top, skip, count, +8 more.
list-mail-child-folderslist-mail-child-foldersARead-only
Get the folder collection under the specified folder. You can use the .../me/mailFolders shortcut to get the top-level folder collection and navigate to another folder. By default, this operation doesn't return hidden folders. Use a query parameter includeHiddenFolders to include them in the response.
Inputs: mailFolderId (required), top, skip, count, +9 more.
list-mail-folder-messageslist-mail-folder-messagesARead-only
Get all the messages in the specified user's mailbox, or those messages in a specified folder in the mailbox.
💡 TIP: List read search Outlook emails within a specific mail folder. CRITICAL: When searching emails, the $search parameter value MUST be wrapped in double quotes. Format: $search="your search query here". Use KQL (Keyword Query Language) syntax to search specific properties: 'from:', 'subject:', 'body:', 'to:', 'cc:', 'bcc:', 'attachment:', 'hasAttachments:', 'importance:', 'received:', 'sent:'. Examples: $search="from:john@example.com" | $search="subject:meeting AND hasAttachments:true" | $search="body:urgent AND received>=2024-01-01" | $search="from:alice AND importance:high". Remember: ALWAYS wrap the entire search expression in double quotes! Reference: https://learn.microsoft.com/en-us/graph/search-query-parameter IMPORTANT: Always use $select to limit returned fields and reduce response size. Recommended default: $select=id,subject,from,toRecipients,receivedDateTime,bodyPreview,isRead,hasAttachments. Use bodyPreview instead of body for listings. To read the full email body, use get-mail-message with the specific message id.
Inputs: mailFolderId (required), top, skip, count, +8 more.
list-mail-folder-messages-deltalist-mail-folder-messages-deltaARead-only
Get a set of messages added, deleted, or updated in a specified folder. A delta function call for messages in a folder is similar to a GET request, except that by appropriately applying state tokens in one or more of these calls, you can query for incremental changes in the messages in that folder. It allows you to maintain and synchronize a local store of a user's messages without having to fetch the entire set of messages from the server every time.
💡 TIP: Incremental sync of messages within a mail folder. Graph only supports delta scoped to a folder — use mailFolder-id = 'inbox' for the well-known inbox, or another folder id from list-mail-folders. First call returns all messages plus @odata.deltaLink; subsequent calls with that link return only changes (created/updated/deleted). @odata.nextLink paginates within a single delta window. Deltas expire after ~30 days of inactivity — start over if the server returns 410. Prefer this over full re-list for polling.
Inputs: mailFolderId (required), top, skip, count, +9 more.
list-mail-folderslist-mail-foldersARead-only
Get the mail folder collection directly under the root folder of the signed-in user. The returned collection includes any mail search folders directly under the root. By default, this operation doesn't return hidden folders. Use a query parameter includeHiddenFolders to include them in the response. This operation doesn't return all mail folders in a mailbox, only the child folders of the root folder. To return all mail folders in a mailbox, each child folder must be traversed separately.
Inputs: top, skip, count, expand, +8 more.
list-mail-messageslist-mail-messagesARead-only
List, search, and filter Outlook email messages in the signed-in user's mailbox across all folders. Returns message metadata (subject, from, receivedDateTime, isRead, hasAttachments) plus a body preview. Use $search for keyword queries, $filter to narrow by sender/read state/date, $top to limit page size, and $select to trim fields.
💡 TIP: List read search my Outlook emails across folders. CRITICAL: When searching emails, the $search parameter value MUST be wrapped in double quotes. Format: $search="your search query here". Use KQL (Keyword Query Language) syntax to search specific properties: 'from:', 'subject:', 'body:', 'to:', 'cc:', 'bcc:', 'attachment:', 'hasAttachments:', 'importance:', 'received:', 'sent:'. Examples: $search="from:john@example.com" | $search="subject:meeting AND hasAttachments:true" | $search="body:urgent AND received>=2024-01-01" | $search="from:john AND importance:high". Remember: ALWAYS wrap the entire search expression in double quotes! Reference: https://learn.microsoft.com/en-us/graph/search-query-parameter IMPORTANT: Always use $select to limit returned fields and reduce response size. Recommended default: $select=id,subject,from,toRecipients,receivedDateTime,bodyPreview,isRead,hasAttachments. Use bodyPreview instead of body for listings. To read the full email body, use get-mail-message with the specific message id.
Inputs: top, skip, count, expand, +8 more.
list-mail-ruleslist-mail-rulesARead-only
Get all the messageRule objects defined for the user's inbox.
💡 TIP: Lists all message rules for a mail folder. Use the Inbox folder ID (get it from list-mail-folders) for inbox rules. Each rule has displayName, sequence, isEnabled, conditions (fromAddresses, subjectContains, etc.), actions (moveToFolder, forwardTo, delete, etc.), and exceptions.
Inputs: mailFolderId (required), top, skip, count, +8 more.
list-my-calendar-permissionslist-my-calendar-permissionsARead-only
The permissions of the users with whom the calendar is shared.
💡 TIP: Lists share recipients and delegates on the user's primary calendar. Returns calendarPermission objects with id, role ('none' | 'freeBusyRead' | 'limitedRead' | 'read' | 'write' | 'delegateWithoutPrivateEventAccess' | 'delegateWithPrivateEventAccess' | 'custom'), emailAddress { name, address }, isInsideOrganization, isRemovable, allowedRoles. Returns an empty collection when called by a delegate or share recipient (only the calendar owner sees the full list). For a non-primary calendar, use /me/calendars/{calendar-id}/calendarPermissions — not currently exposed.
Inputs: top, skip, count, expand, +7 more.
list-onenote-notebookslist-onenote-notebooksCRead-only
Retrieve a list of notebook objects.
Inputs: top, skip, count, expand, +7 more.
list-onenote-notebook-sectionslist-onenote-notebook-sectionsARead-only
Retrieve a list of onenoteSection objects from the specified notebook.
Inputs: notebookId (required), top, skip, count, +8 more.
list-onenote-pageslist-onenote-pagesARead-only
Retrieve a list of page objects.
💡 TIP: Lists all OneNote pages across every notebook and section the user has access to — transverse alternative to walking notebooks → sections → pages. Default returns top 20 ordered by lastModifiedTime desc. Supports $filter (e.g. lastModifiedTime gt 2026-01-01, or contains(tolower(title), 'topic') for title search), $top (max 100), $select, and $expand=parentNotebook,parentSection. Use this instead of bouncing through list-onenote-notebooks / list-all-onenote-sections / list-onenote-section-pages when you have a topic in mind.
Inputs: top, skip, count, expand, +7 more.
list-onenote-section-groupslist-onenote-section-groupsARead-only
Retrieve a list of sectionGroup objects.
💡 TIP: Lists all OneNote section groups (subfolders inside notebooks that contain their own sections and nested section groups) for the user. A section group is a folder-like container — many notebooks use them to organize sections by theme. Default sort is name asc. Supports $expand=sections,sectionGroups,parentNotebook,parentSectionGroup to traverse the full hierarchy. Pair with list-onenote-notebooks for a complete picture of the user's notebook structure.
Inputs: top, skip, count, expand, +7 more.
list-onenote-section-pageslist-onenote-section-pagesCRead-only
Retrieve a list of page objects from the specified section.
Inputs: onenoteSectionId (required), top, skip, count, +8 more.
list-outlook-categorieslist-outlook-categoriesARead-only
Get all the categories that have been defined for a user.
💡 TIP: Lists the user's Outlook categories (colored labels) used to tag messages, events, contacts, and tasks. Each category has displayName and color (preset0 through preset24, or 'none'). Use this to show available tags before applying via update-mail-message or update-calendar-event with body { categories: ['Category Name'] }.
Inputs: top, skip, count, expand, +7 more.
list-outlook-contactslist-outlook-contactsARead-only
Get a contact collection from the default contacts folder of the signed-in user. There are two scenarios where an app can get contacts in another user's contact folder:
💡 TIP: $filter only supports startswith() — contains() and eq on emailAddresses do not work. Use $search as alternative for broader matching.
Inputs: top, skip, count, expand, +7 more.
list-plan-bucketslist-plan-bucketsARead-only
Retrieve a list of plannerBucket objects contained by a plannerPlan object.
Inputs: plannerPlanId (required), top, skip, count, +8 more.
list-planner-task-messageslist-planner-task-messagesARead-only
[beta] Retrieve a list of plannerTaskChatMessage objects associated with a plannerTask.
💡 TIP: Lists messages in a Planner task's chat — the modern Planner 'task chat', distinct from the legacy conversationThreadId comments (which live in the M365 group conversation thread). Each message has id, content (HTML), createdBy, createdDateTime, mentions, reactions. BETA Graph API: subject to change; delegated work/school accounts only — no application permissions, no personal Microsoft accounts, global cloud only (not GCC/DoD/21Vianet).
Inputs: plannerTaskId (required), top, skip, count, +8 more.
list-planner-taskslist-planner-tasksARead-only
Retrieve a list of plannertask objects assigned to a User.
💡 TIP: Priority values: 0=Urgent, 1=Important, 3=Medium, 5=Low, 9=unset.
Inputs: top, skip, count, expand, +7 more.
list-plan-taskslist-plan-tasksARead-only
Retrieve a list of plannerTask objects associated with a plannerPlan object.
💡 TIP: Priority values: 0=Urgent, 1=Important, 3=Medium, 5=Low, 9=unset.
Inputs: plannerPlanId (required), top, skip, count, +8 more.
list-specific-calendar-eventslist-specific-calendar-eventsARead-only
List events from one of the signed-in user's calendars, addressed by calendar ID.
💡 TIP: WARNING: Does NOT expand recurring events — only returns seriesMaster. Use get-specific-calendar-view instead.
Inputs: calendarId (required), top, skip, count, +10 more.
list-subscriptionslist-subscriptionsARead-only
Retrieve the properties and relationships of webhook subscriptions, based on the app ID, the user, and the user's role with a tenant. The content of the response depends on the context in which the app is calling; for details, see the scenarios in the Permissions section.
💡 TIP: Lists webhook subscriptions owned by the current app/user. Returns id, resource, changeType, notificationUrl, expirationDateTime, clientState. Use $filter=resource eq '/me/messages' to find subscriptions for a specific resource. No dedicated 'Subscription.*' scope exists — the caller must already have a read permission for the subscribed resource (e.g. Mail.Read for /me/messages), which is supplied by the tool that reads that resource.
Inputs: top, skip, count, expand, +7 more.
list-supported-languageslist-supported-languagesARead-only
Get the list of locales and languages that are supported for the user, as configured on the user's mailbox server. When setting up an Outlook client, the user selects the preferred language from this supported list. You can subsequently get the preferred language by getting the user's mailbox settings.
💡 TIP: Lists locales and languages the user's mailbox server supports for the Outlook UI and message rendering. Returns localeInfo objects with locale (e.g. 'en-US') and displayName ('English (United States)'). Use this to validate the locale value before calling update-mailbox-settings to change the user's preferred language.
Inputs: top, skip, count, filter, +4 more.
list-supported-time-zoneslist-supported-time-zonesARead-only
Get the list of time zones that are supported for the user, as configured on the user's mailbox server. You can explicitly specify to have time zones returned in the Windows time zone format or Internet Assigned Numbers Authority (IANA) time zone (also known as Olson time zone) format. The Windows format is the default. When setting up an Outlook client, the user selects the preferred time zone from this supported list. You can subsequently get the preferred time zone by getting the user's mailbox settings.
💡 TIP: Lists time zones the user's mailbox server supports. TimeZoneStandard path parameter must be one of: Windows (default — Windows time zone names like 'Pacific Standard Time'), or Iana (IANA / Olson names like 'America/Los_Angeles'). Note the PascalCase — the values are case-sensitive enums, not lowercase strings. Returns timeZoneInformation objects with alias and displayName. Use the result to validate or look up the value before calling update-mailbox-settings to change the user's preferred timeZone — the format must match what the server expects.
Inputs: TimeZoneStandard (required), top, skip, count, +5 more.
list-todo-linked-resourceslist-todo-linked-resourcesARead-only
Get information of one or more items in a partner application, based on which a specified task was created. The information is represented in a linkedResource object for each item. It includes an external ID for the item in the partner application, and if applicable, a deep link to that item in the application.
💡 TIP: Lists resources linked to a To Do task (emails, URLs, etc.). Each linked resource has displayName, webUrl, applicationName, and externalId.
Inputs: todoTaskId (required), todoTaskListId (required), top, skip, +9 more.
list-todo-task-listslist-todo-task-listsARead-only
Get a list of the todoTaskList objects and their properties.
💡 TIP: Lists all To Do task lists. Returns todoTaskList-id needed for all task operations. The default list is typically called 'Tasks'. NOTE: $select is NOT supported by this endpoint — do not pass select parameter, Graph returns 400.
Inputs: top, skip, count, expand, +7 more.
list-todo-taskslist-todo-tasksARead-only
Get the todoTask resources from the tasks navigation property of a specified todoTaskList.
💡 TIP: Lists tasks in a To Do list. Requires todoTaskList-id — use list-todo-task-lists to find it. NOTE: $select is NOT supported — do not pass select, Graph returns 400. Use $filter=status eq 'notStarted' or $filter=status eq 'completed' to filter by status. Use $top to limit results. Status values: 'notStarted', 'inProgress', 'completed', 'waitingOnOthers', 'deferred'.
Inputs: todoTaskListId (required), top, skip, count, +8 more.
loginC
Authenticate with Microsoft account
Inputs: force.
logoutB
Log out from Microsoft account
Inputs: no named parameters.
merge-excel-rangemerge-excel-rangeADestructive
Merge an Excel range.
💡 TIP: Merge the cells in the given range into a single cell. Body: { across: false } merges the entire range into one cell; { across: true } merges each row separately. Useful for building styled headers, banner rows, and report layouts.
Inputs: body (required), address (required), driveId (required), driveItemId (required), +4 more.
move-mail-messagemove-mail-messageADestructive
Move a message to another folder within the specified user's mailbox. This creates a new copy of the message in the destination folder and removes the original message.
💡 TIP: destinationId accepts folder ID or well-known name (inbox, drafts, sentitems, deleteditems, junkemail, archive).
Inputs: body (required), messageId (required), confirm, includeHeaders, +1 more.
move-rename-onedrive-itemmove-rename-onedrive-itemADestructive
Move or rename a OneDrive item.
💡 TIP: Move and/or rename a file or folder. To move, provide parentReference with the target folder's id. To rename, provide a new name. Both can be done in a single request.
Inputs: body (required), driveId (required), driveItemId (required), confirm, +2 more.
parse-teams-urlARead-only
Converts any Teams meeting URL format (short /meet/, full /meetup-join/, or recap ?threadId=) into a standard joinWebUrl. Use this before list-online-meetings when the user provides a recap or short URL.
Inputs: url (required).
reauthorize-subscriptionreauthorize-subscriptionADestructive
Reauthorize a subscription when you receive a reauthorizationRequired challenge.
💡 TIP: Reauthorizes a subscription after receiving a 'reauthorizationRequired' lifecycle notification from Microsoft Graph. No body required. Must be called within the reauthorizationRequiredDateTime window (typically 48h) to avoid subscription expiry.
Inputs: subscriptionId (required), confirm, includeHeaders, excludeResponse.
remove-accountA
Remove a Microsoft account from the cache. Accepts email address (e.g. user@outlook.com) or account ID. Use list-accounts to discover available accounts.
Inputs: account (required).
reply-all-mail-messagereply-all-mail-messageADestructive
Reply to all recipients of a message using either JSON or MIME format. When using JSON format:
Specify either a comment or the body property of the message parameter. Specifying both will return an HTTP 400 Bad Request error.
If the original message specifies a recipient in the replyTo property, per Internet Message Format (RFC 2822), send the reply to the recipients in replyTo and not the recipient in the from property. When using MIME format:
Provide the applicable Internet message headers and the MIME content, all encoded in base64 format in the request body.
Add any attachments and S/MIME properties to the MIME content. This method saves the message in the Sent Items folder. Alternatively, create a draft to reply-all to a message and send it later.
💡 TIP: Reply-all preserving full HTML formatting. The 'comment' field is your reply text.
Inputs: body (required), messageId (required), confirm, includeHeaders, +1 more.
reply-mail-messagereply-mail-messageADestructive
Reply to the sender of a message using either JSON or MIME format. When using JSON format:
Specify either a comment or the body property of the message parameter. Specifying both will return an HTTP 400 Bad Request error.
If the original message specifies a recipient in the replyTo property, per Internet Message Format (RFC 2822), send the reply to the recipients in replyTo and not the recipient in the from property. When using MIME format:
Provide the applicable Internet message headers and the MIME content, all encoded in base64 format in the request body.
Add any attachments and S/MIME properties to the MIME content. This method saves the message in the Sent Items folder. Alternatively, create a draft to reply to an existing message and send it later.
💡 TIP: Reply to an email preserving full HTML formatting. The 'comment' field is your reply text. Do NOT reconstruct the email manually.
Inputs: body (required), messageId (required), confirm, includeHeaders, +1 more.
search-onedrive-filessearch-onedrive-filesARead-only
Search the hierarchy of items for items matching a query. You can search within a folder hierarchy, a whole drive, or files shared with the current user.
💡 TIP: Searches for files in a drive by name or content. The q parameter searches file names, metadata, and file content. Returns matching driveItems with id, name, webUrl, size, lastModifiedDateTime. Use list-drives first to get the drive-id.
Inputs: q (required), driveId (required), top, skip, +9 more.
select-accountA
Select a Microsoft account as the default. Accepts email address (e.g. user@outlook.com) or account ID. Use list-accounts to discover available accounts.
Inputs: account (required).
send-draft-messagesend-draft-messageBDestructive
Send an existing draft message. The draft message can be a new message draft, reply draft, reply-all draft, or a forward draft. This method saves the message in the Sent Items folder. Alternatively, send a new message in a single operation.
💡 TIP: No request body needed — just call with the message ID. Draft must exist in Drafts folder.
Inputs: messageId (required), confirm, includeHeaders, excludeResponse.
send-mailsend-mailADestructive
Send the message specified in the request body using either JSON or MIME format. When using JSON format, you can include a file attachment in the same sendMail action call. When using MIME format: This method saves the message in the Sent Items folder. Alternatively, create a draft message to send later. To learn more about the steps involved in the backend before a mail is delivered to recipients, see here.
💡 TIP: CRITICAL: Do not try to guess the email address of the recipients. Use the list-users tool to find the email address of the recipients.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
snooze-calendar-event-remindersnooze-calendar-event-reminderADestructive
Postpone a reminder for an event in a user calendar until a new time.
💡 TIP: Postpones a triggered event reminder. Body: { NewReminderTime: { dateTime (ISO 8601), timeZone (IANA or Windows, e.g. 'Pacific Standard Time') } }. The reminder will re-fire at the new time.
Inputs: body (required), eventId (required), confirm, includeHeaders, +1 more.
sort-excel-rangesort-excel-rangeCDestructive
Sort an Excel range.
Inputs: body (required), driveId (required), driveItemId (required), workbookWorksheetId (required), +3 more.
tentatively-accept-calendar-eventtentatively-accept-calendar-eventADestructive
Tentatively accept the specified event in a user calendar. If the event allows proposals for new times, on responding tentative to the event, an invitee can choose to suggest an alternative time by including the proposedNewTime parameter. For more information on how to propose a time, and how to receive and accept a new time proposal, see Propose new meeting times.
💡 TIP: Tentatively accepts a meeting invitation. Optional body: { sendResponse: true, comment: 'I might be able to attend.' }. Use proposedNewTime to suggest an alternative: { proposedNewTime: { start: { dateTime, timeZone }, end: { dateTime, timeZone } } }.
Inputs: body (required), eventId (required), confirm, includeHeaders, +1 more.
unmerge-excel-rangeunmerge-excel-rangeADestructive
Unmerge an Excel range.
💡 TIP: Unmerge any merged cells within the given range back into individual cells. No request body. Inverse of merge-excel-range.
Inputs: address (required), driveId (required), driveItemId (required), workbookWorksheetId (required), +3 more.
update-calendarupdate-calendarBDestructive
Update a calendar.
💡 TIP: Updates a calendar's properties. Body: { name: 'New Name', color: 'lightBlue' }. Cannot update the default calendar's name.
Inputs: body (required), calendarId (required), confirm, includeHeaders, +1 more.
update-calendar-eventupdate-calendar-eventADestructive
Update the properties of the event object.
💡 TIP: CRITICAL: Do not try to guess the email address of the recipients. Use the list-users tool to find the email address of the recipients. WARNING: Setting attendees replaces the entire attendee list — include all attendees, not just new ones.
Inputs: body (required), eventId (required), confirm, includeHeaders, +1 more.
update-contact-folderupdate-contact-folderADestructive
Update the properties of contactfolder object.
💡 TIP: Updates a contact folder. Body: { displayName?: 'New name', parentFolderId?: '' } — both displayName (rename) and parentFolderId (move) are writable. The default 'Contacts' folder may not be renameable. Get the folder id via list-contact-folders.
Inputs: body (required), contactFolderId (required), confirm, includeHeaders, +1 more.
update-excel-rangeupdate-excel-rangeADestructive
Update an Excel range.
💡 TIP: Set cell values, formulas, or number format on any range — does NOT require the worksheet to be a formal Excel table. Body: { values: [['v1','v2','v3']] } for a single row, or [['a','b'],['c','d']] for multi-row. Use this for append (target the next empty row's address, e.g. 'A172:H172'), update (target a single cell like 'H42'), or prepend-style edits (read existing, concatenate, write back). Number of inner-array values must match the column count of the address.
Inputs: body (required), address (required), driveId (required), driveItemId (required), +4 more.
update-excel-table-rowupdate-excel-table-rowADestructive
Update an Excel table row.
💡 TIP: Update a single row in a formal Excel table by zero-based row index. Body: { values: [[...]] } with one inner array matching the column count.
Inputs: body (required), index (required), driveId (required), driveItemId (required), +4 more.
update-focused-inbox-overrideupdate-focused-inbox-overrideADestructive
Change the classifyAs field of an override as specified. You cannot use PATCH to change any other fields in an inferenceClassificationOverride instance. If an override exists for a sender and the sender changes his/her display name, you can use POST to force an update to the name field in the existing override. If an override exists for a sender and the sender changes his/her SMTP address, deleting the existing override and creating a new one with the new SMTP address is the only way to 'update' the override for this sender.
💡 TIP: Updates the classifyAs field of an existing override. Body: { classifyAs: 'focused' } or { classifyAs: 'other' }. Per Graph API, PATCH cannot change senderEmailAddress — to change the SMTP address, delete and recreate the override. To rename the display name only, POST a new override with the same SMTP address (it will overwrite the name).
Inputs: body (required), inferenceClassificationOverrideId (required), confirm, includeHeaders, +1 more.
update-mailbox-settingsupdate-mailbox-settingsCDestructive
Enable, configure, or disable one or more of the following settings as part of a user's mailboxSettings: When updating the preferred date or time format for a user, specify it in respectively, the short date or short time format. When updating the preferred time zone for a user, specify it in the Windows or Internet Assigned Numbers Authority (IANA) time zone (also known as Olson time zone) format. You can also further customize the time zone as shown in example 2 below.
💡 TIP: Updates mailbox settings. Common use: configure Out-of-Office (automatic replies). Body example: { automaticRepliesSetting: { status: 'scheduled', scheduledStartDateTime: { dateTime: '2026-03-28T17:00:00', timeZone: 'Eastern Standard Time' }, scheduledEndDateTime: { dateTime: '2026-04-01T08:00:00', timeZone: 'Eastern Standard Time' }, internalReplyMessage: 'I am OOO.', externalReplyMessage: 'I am out of office.' } }. Status values: disabled, alwaysEnabled, scheduled.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
update-mail-folderupdate-mail-folderADestructive
Update the properties of mailfolder object.
💡 TIP: Renames a mail folder by updating its displayName. Use list-mail-folders to find the folder ID.
Inputs: body (required), mailFolderId (required), confirm, includeHeaders, +1 more.
update-mail-messageupdate-mail-messageADestructive
Update an existing Outlook email message by its message ID — for example mark it read or unread (isRead), flag it (flag), change its categories, importance, or edit a draft's subject, body, or recipients.
Inputs: body (required), messageId (required), confirm, includeHeaders, +1 more.
update-mail-ruleupdate-mail-ruleADestructive
Change writable properties on a messageRule object and save the changes.
💡 TIP: Updates an existing message rule. Use the Inbox folder ID (get it from list-mail-folders) for inbox rules. Send only the properties to change. Common use: { isEnabled: false } to disable a rule, or update conditions/actions.
Inputs: body (required), mailFolderId (required), messageRuleId (required), confirm, +2 more.
update-my-calendar-permissionupdate-my-calendar-permissionADestructive
Update my calendar permission.
💡 TIP: Changes the role (permission level) granted to an existing share recipient or delegate. Body: { role: 'read' | 'write' | 'delegateWithoutPrivateEventAccess' | 'delegateWithPrivateEventAccess' }. Only the role property is writable — to change the recipient's email or other properties, delete and recreate via delete-my-calendar-permission + create-my-calendar-permission. Get the permission id via list-my-calendar-permissions.
Inputs: body (required), calendarPermissionId (required), confirm, includeHeaders, +1 more.
update-outlook-contactupdate-outlook-contactBDestructive
Update the properties of a contact object.
💡 TIP: emailAddresses array is replaced entirely — include all addresses, not just new ones.
Inputs: body (required), contactId (required), confirm, includeHeaders, +1 more.
update-planner-bucketupdate-planner-bucketADestructive
Update the properties of plannerbucket object.
💡 TIP: CRITICAL: Requires If-Match header with ETag from get-planner-bucket (use includeHeaders=true).
Inputs: body (required), If-Match (required), plannerBucketId (required), confirm, +2 more.
update-planner-taskupdate-planner-taskADestructive
Update the properties of plannerTask object.
💡 TIP: CRITICAL: Requires If-Match header with the task's @odata.etag value, otherwise returns 412 Precondition Failed. Get the ETag from get-planner-task with includeHeaders=true. Priority values: 0=Urgent, 1=Important, 3=Medium, 5=Low, 9=unset.
Inputs: body (required), If-Match (required), plannerTaskId (required), confirm, +2 more.
update-planner-task-detailsupdate-planner-task-detailsADestructive
Update the properties of plannerTaskDetails object.
💡 TIP: CRITICAL: Requires If-Match header with ETag from get-planner-task-details (use includeHeaders=true). Checklist items use GUID keys: {"checklist": {"": {"title": "...", "isChecked": false}}}.
Inputs: body (required), If-Match (required), plannerTaskId (required), confirm, +2 more.
update-specific-calendar-eventupdate-specific-calendar-eventADestructive
Update a specific calendar event.
💡 TIP: CRITICAL: Do not try to guess the email address of the recipients. Use the list-users tool to find the email address of the recipients. WARNING: Setting attendees replaces the entire attendee list — include all attendees, not just new ones.
Inputs: body (required), eventId (required), calendarId (required), confirm, +2 more.
update-subscriptionupdate-subscriptionADestructive
Renew a subscription by extending its expiry time. The table in the Permissions section lists the resources that support subscribing to change notifications. Subscriptions expire after a length of time that varies by resource type. In order to avoid missing change notifications, an app should renew its subscriptions well in advance of their expiry date. See subscription for maximum length of a subscription for each resource type.
💡 TIP: Renews a webhook subscription by extending its expiration. Body: { expirationDateTime (ISO 8601, new expiry) }. Call before the current expirationDateTime to avoid missing notifications. Max extension varies by resource type — check Microsoft Graph docs for subscription limits.
Inputs: body (required), subscriptionId (required), confirm, includeHeaders, +1 more.
update-todo-taskupdate-todo-taskADestructive
Update the properties of a todoTask object.
💡 TIP: Updates a Microsoft To Do task. Use this to mark a to-do item complete or done (body: { status: "completed" }), reopen it (status: "notStarted"), rename it (title), or change its due date (dueDateTime), reminder (reminderDateTime), importance, or notes (body). Requires todoTaskListId from list-todo-task-lists and todoTaskId from list-todo-tasks.
Inputs: body (required), todoTaskId (required), todoTaskListId (required), confirm, +2 more.
update-todo-task-listupdate-todo-task-listADestructive
Update the properties of a todoTaskList object.
💡 TIP: Renames a Microsoft To Do task list. Body: { displayName: 'New name' }. Only displayName is writable. Built-in lists (Flagged emails, the default Tasks list) cannot be renamed — the API returns an error. Get list ids via list-todo-task-lists.
Inputs: body (required), todoTaskListId (required), confirm, includeHeaders, +1 more.
upload-file-contentupload-file-contentADestructive
The content stream, if the item represents a file.
💡 TIP: Body is a base64-encoded string of the file bytes; the server decodes it before PUT. Graph accepts up to 250MB here, but the whole string travels as a tool argument and a truncated one decodes to a truncated file with no error, so use create-upload-session rather than emitting a large base64 string. For new files use path format: /items/root:/path/to/file.txt:/content. Overwrites existing files without warning.
Inputs: body (required), driveId (required), driveItemId (required), confirm, +2 more.
upload-my-profile-photoupload-my-profile-photoADestructive
Update the photo for the specified contact, group, team, or user in a tenant. The size of the photo you can update to is limited to 4 MB. You can use either PATCH or PUT for this operation.
💡 TIP: Uploads a new profile photo for the signed-in user. Body is a base64-encoded string of the image bytes (the server decodes before PUT). Photo must be JPEG, max 4 MB; the base64 travels as a tool argument and a truncated one is written without error, so resize before encoding instead of emitting a large string. Microsoft 365 generates HD downsized variants automatically (48x48, 64x64, 96x96, 120x120, 240x240, 360x360, 432x432, 504x504, 648x648). For work or school accounts, ProfilePhoto.ReadWrite.All is the more granular alternative permission. Use download-bytes with target=/me/photo/$value to retrieve the current photo.
Inputs: body (required), confirm, includeHeaders, excludeResponse.
verify-loginA
Check current Microsoft authentication status
Inputs: no named parameters.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 188 tools
With 188 tools, many have unclear or overlapping boundaries: calendar listing alone has list-calendar-events, list-specific-calendar-events, get-calendar-view, get-specific-calendar-view, list-calendar-view-delta, and list-calendar-events-delta. Download tools (download-bytes, download-bytes-to-file, get-download-url) and OneNote page creators (create-onenote-page vs create-onenote-section-page) are also easily confused, and list-excel-tables/get-excel-table share identical descriptions.
Most tools follow a readable kebab-case verb_noun pattern (list-, get-, create-, update-, delete-), but object terminology is inconsistent: drive vs onedrive (get-drive-item vs move-rename-onedrive-item), planner vs plan (get-planner-plan vs list-plan-buckets), and parallel actions diverge (create-draft-email vs create-reply-draft; add-excel-table-rows vs create-excel-table). The pattern is recognizable but not predictable enough for a 188-tool surface.
188 tools is an extreme count, far exceeding the 50+ threshold for a 1. Even for a broad Microsoft Graph wrapper, this is unwieldy and would be better split into purpose-specific servers for mail, calendar, files, Excel, OneNote, Planner, and To Do.
Despite the massive surface, the server has significant gaps: list-users, list-online-meetings, and get-schedule are all referenced in tool tips but are not actual tools, creating dead ends. Within covered domains, CRUD is incomplete (no delete-planner-task, no delete-onenote-section, no create/update/delete planner plan, no Outlook category update/delete), and major M365 areas like Teams, SharePoint sites, and admin are essentially absent.
Maintenance
Related MCP Connectors
Permissioned access to Outlook, OneDrive and Teams via the user's own Microsoft account
Gmail, Outlook, Drive, OneDrive and calendars for AI agents. Many accounts, one endpoint, audit log.
Connect Apple iCloud and Microsoft 365 mail, calendars, contacts and tasks to AI.
Email, calendar and contacts for AI agents: Gmail, Microsoft 365 and IMAP accounts behind one API.
Related MCP Servers
- AlicenseNot gradedqualityFmaintenanceEnables AI assistants to interact with Microsoft 365 services (users, mail, calendar, files) via Microsoft Graph API.55 npm1MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to interact with Microsoft 365 through the Microsoft Graph API, including searching Teams messages, managing chats, and sending messages.77 npmMIT
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to seamlessly interact with Microsoft 365 services through the Graph API, featuring super tools, unified search, and intelligent learning.-
- AlicenseNot gradedqualityBmaintenanceEnables authenticated interaction with Microsoft 365 and Office services through the Graph API, covering email, calendar, files, Teams, SharePoint, and more. Supports read-only mode, tool filtering, presets, and multiple cloud environments for safe, controlled access.107,765 npmMIT