Microsoft 365 logo

Microsoft 365

Organization
Softeria

A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Microsoft Office services through the Graph API

PublisherSofteria
Repositoryms-365-mcp-server
LanguageTypeScript
Forks
372
Stars
988
Available tools
118
Transport typestdio
Categories
LicenseMIT
Links
  • Connect tools to AI workflows

    Microsoft 365 exposes MCP capabilities that can be used by compatible AI clients and agents.

  • 118 available tools

    Browse the callable actions below, including names and descriptions when provided by the server.

  • Ready-to-copy setup

    Use the installation snippets to configure this server in your preferred MCP client.

  • Open source signals

    988 stars and 372 forks from the linked repository.

ms-365-mcp-server

npm version build status license

Microsoft 365 MCP Server

A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Microsoft Office services through the Graph API.

Supported Clouds

This server supports multiple Microsoft cloud environments:

CloudDescriptionAuth EndpointGraph API Endpoint
Global (default)International Microsoft 365login.microsoftonline.comgraph.microsoft.com
China (21Vianet)Microsoft 365 operated by 21Vianetlogin.chinacloudapi.cnmicrosoftgraph.chinacloudapi.cn

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:

json
{
  "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 Engineer

Benefits:

  • 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:

bash
npx @softeria/ms-365-mcp-server --toon

Via Claude Desktop configuration:

json
{
  "mcpServers": {
    "ms365": {
      "command": "npx",
      "args": ["-y", "@softeria/ms-365-mcp-server", "--toon"]
    }
  }
}

Via environment variable:

bash
MS365_MCP_OUTPUT_FORMAT=toon npx @softeria/ms-365-mcp-server

Supported 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:

bash
# Personal mode (default)
npx @softeria/ms-365-mcp-server --list-permissions

# Organization mode (includes Teams, SharePoint, etc.)
npx @softeria/ms-365-mcp-server --org-mode --list-permissions

# Filtered by preset
npx @softeria/ms-365-mcp-server --preset mail --list-permissions

This 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-scopes filtering
  • effectivePermissions: permissions implied by the tools that remain enabled after --allowed-scopes
  • permissions: legacy alias for effectivePermissions, kept for compatibility with existing scripts
  • allowedScopes: the configured scope allowlist, when provided
  • disabledTools: tools hidden because their required Graph scopes are not covered by allowedScopes
  • missingAllowedScopesForTools: unique missing scopes across disabled tools
  • extraAllowedScopesNotUsedByTools: 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.

bash
npx @softeria/ms-365-mcp-server \
  --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, and Sites.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, and the /drives/{drive-id}/... item tools (list, get, upload, folder, move/rename, copy, versions) for drives of a granted site, can run with Sites.Selected; tenant-wide SharePoint discovery and search tools still require broad SharePoint scopes.

bash
npx @softeria/ms-365-mcp-server \
  --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.

bash
npx @softeria/ms-365-mcp-server \
  --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 Softeria 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:

json
{
  "mcpServers": {
    "ms365": {
      "command": "npx",
      "args": ["-y", "@softeria/ms-365-mcp-server", "--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:

  1. Organization mode: Shared mailbox tools require --org-mode flag (work/school accounts only)
  2. Delegated permissions: Mail.Read.Shared to read, Mail.ReadWrite.Shared to create, update or move messages, Mail.Send.Shared to send, reply or forward, and Calendars.Read.Shared for the shared calendar tools
  3. Exchange permissions: The signed-in user must have been granted access to the shared mailbox
  4. Usage: Use the shared mailbox's email address as the user-id parameter 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:

Login example

Examples

Image

Integration

Claude Desktop

To add this MCP server to Claude Desktop, edit the config file under Settings > Developer.

Personal Account (MSA)

json
{
  "mcpServers": {
    "ms365": {
      "command": "npx",
      "args": ["-y", "@softeria/ms-365-mcp-server"]
    }
  }
}

Work/School Account (Global)

json
{
  "mcpServers": {
    "ms365": {
      "command": "npx",
      "args": ["-y", "@softeria/ms-365-mcp-server", "--org-mode"]
    }
  }
}

Work/School Account (China 21Vianet)

json
{
  "mcpServers": {
    "ms365-china": {
      "command": "npx",
      "args": ["-y", "@softeria/ms-365-mcp-server", "--org-mode", "--cloud", "china"]
    }
  }
}

Claude Code CLI

Personal Account (MSA)

bash
claude mcp add ms365 -- npx -y @softeria/ms-365-mcp-server

Work/School Account (Global)

bash
# macOS/Linux
claude mcp add ms365 -- npx -y @softeria/ms-365-mcp-server --org-mode

# Windows (use cmd /c wrapper)
claude mcp add ms365 -s user -- cmd /c "npx -y @softeria/ms-365-mcp-server --org-mode"

Work/School Account (China 21Vianet)

bash
# macOS/Linux
claude mcp add ms365-china -- npx -y @softeria/ms-365-mcp-server --org-mode --cloud china

# Windows (use cmd /c wrapper)
claude mcp add ms365-china -s user -- cmd /c "npx -y @softeria/ms-365-mcp-server --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.

  1. Start the server with HTTP mode:

    bash
    npx @softeria/ms-365-mcp-server --http
  2. In Open WebUI, go to Admin Settings β†’ Tools (/admin/settings/tools) β†’ Add Connection:

    • Type: MCP Streamable HTTP
    • URL: Your MCP server URL with /mcp path
    • Auth: OAuth 2.1
  3. Click Register Client.

Note: Dynamic client registration is enabled by default in HTTP mode. Use --no-dynamic-registration (or set MS365_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):

bash
docker run -d -p 8080:8080 \
  -e WEBUI_AUTH=false \
  -e OPENAI_API_KEY \
  ghcr.io/open-webui/open-webui:main

npx @softeria/ms-365-mcp-server --http

Then add connection with URL http://localhost:3000/mcp and ID ms-365.

Open WebUI MCP Connection

Running in Docker behind a reverse proxy? Set --public-url https://your-domain.com so 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:

bash
# From the project directory
claude mcp add ms -- npx tsx src/index.ts --org-mode

Or configure Claude Desktop manually:

json
{
  "mcpServers": {
    "ms365": {
      "command": "node",
      "args": ["/absolute/path/to/ms-365-mcp-server/dist/index.js", "--org-mode"]
    }
  }
}

Note: Run npm run build after code changes to update the dist/ 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 login tool (auto-checks existing token)
    • If needed, get URL+code, visit in browser
    • Use verify-login tool to confirm
  • CLI login:
    bash
    npx @softeria/ms-365-mcp-server --login
    Follow 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:

bash
npx @softeria/ms-365-mcp-server --http 3000

This mode:

  • Advertises OAuth capabilities to MCP clients
  • Provides OAuth endpoints at /auth/* (authorize, token, metadata)
  • Requires Authorization: Bearer <token> for all MCP requests
  • Validates tokens with Microsoft Graph API
  • Disables login/logout tools by default (use --enable-auth-tools to 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:

  1. Create Azure AD App Registration:
  • Go to Azure Portal
  • Navigate to Azure Active Directory β†’ App registrations β†’ New registration
  • Set name: "MS365 MCP Server"
  1. 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).
  1. 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/callback
      • http://localhost:6274/oauth/callback/debug
      • http://localhost:3000/callback (optional, for server callback)
  1. 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)
  1. Configure Environment Variables: Create a .env file in your project root:
    env
    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: .env is read from the directory the server is started in, and the MCP client decides what that is. Only MS365_MCP_CLIENT_ID, MS365_MCP_CLIENT_SECRET, MS365_MCP_TENANT_ID and MS365_MCP_CLOUD_TYPE are read from it. Every other variable listed above must be set in your shell or MCP client config; anything else found in a .env is 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:

bash
MS365_MCP_OAUTH_TOKEN=your_oauth_token npx @softeria/ms-365-mcp-server

This 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-tools if 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):

bash
# Login first account (device code flow)
npx @softeria/ms-365-mcp-server --login
# Follow the device code prompt, sign in as personal@outlook.com

# Login second account
npx @softeria/ms-365-mcp-server --login
# Follow the device code prompt, sign in as work@company.com

List configured accounts:

bash
npx @softeria/ms-365-mcp-server --list-accounts

Use in tool calls: Pass "account": "work@company.com" in any tool request:

json
{ "tool": "list-mail-messages", "arguments": { "account": "work@company.com" } }

Behavior:

  • With a single account configured, it auto-selects (no account parameter needed).
  • With multiple accounts and no account parameter, the server uses the selected default or returns a helpful error listing available accounts.
  • 100% backward compatible: existing single-account setups work unchanged.
  • The account parameter accepts email address (e.g. user@outlook.com) or MSAL homeAccountId.

Strict Account Pinning

Headless stdio deployments can pin the local MSAL cache to one expected Microsoft account:

bash
# Username matching is case-insensitive
MS365_MCP_EXPECTED_USERNAME=work@company.com npx @softeria/ms-365-mcp-server --login

# Or pin the exact MSAL homeAccountId shown by --list-accounts
npx @softeria/ms-365-mcp-server --expected-home-account-id <homeAccountId> --login

Use --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 over MS365_MCP_EXPECTED_USERNAME and MS365_MCP_EXPECTED_HOME_ACCOUNT_ID.
  • Supplying an empty pin value fails at startup instead of being ignored.
  • Username pins are compared case-insensitively; homeAccountId pins 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 account parameter and MCP instructions do not suggest account switching.
  • --http, --obo, and MS365_MCP_OAUTH_TOKEN use 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.
  • --logout clears 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 account parameter, 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:

bash
npx @softeria/ms-365-mcp-server --preset mail
npx @softeria/ms-365-mcp-server --list-presets  # See all available presets

Available 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:

bash
# Outlook only (mail + calendar + contacts; no shared mailboxes, no files)
npx @softeria/ms-365-mcp-server --preset outlook

# Teams only (requires --org-mode)
npx @softeria/ms-365-mcp-server --org-mode --preset teams

The 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):

bash
npx @softeria/ms-365-mcp-server --org-mode --preset teams-write

Dynamic Tool Discovery

Instead of loading every tool upfront, use dynamic discovery so the LLM finds and loads tools only when it needs them:

bash
npx @softeria/ms-365-mcp-server --discovery

Keeps 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 homeAccountId

Server 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)
--enable-attachment-urls Let get-download-url mint a server-served URL for byte resources Graph
                  exposes no pre-authenticated URL for (see "Server-Minted Attachment URLs")
--attachment-port <port> Serve /attachment on its own listener on this port instead of on the
                  MCP app, so a fetcher that can read attachments cannot also reach /mcp
                  (requires --enable-attachment-urls; see "Splitting the attachment listener")
--attachment-host <host> Interface the --attachment-port listener binds. Defaults to whatever
                  --http bound, which with a wildcard --http leaves BOTH ports on every
                  interface and so isolates nothing β€” set this to make the split real
                  (requires --attachment-port; see "Splitting the attachment listener")
--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 flag
  • ENABLED_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_MODE
  • MS365_MCP_OUTPUT_FORMAT=toon: Enable TOON output format (alternative to --toon flag)
  • MS365_MCP_MAX_TOP=<n>: Hard cap for Graph $top / top on list requests (positive integer). When the model passes a larger value, the server clamps it to n so responses stay smaller. Example: MS365_MCP_MAX_TOP=15
  • MS365_MCP_MAX_PAGES=<n>: Maximum number of pages followed when a tool is called with fetchAllPages: true (positive integer, default 100). Bounds memory and latency for large result sets.
  • MS365_MCP_MAX_ITEMS=<n>: Maximum number of items accumulated when fetchAllPages: true (positive integer, default 10000). 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, the fetchAllPages parameter 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-signoff disables 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 (default 1). Accurate per-IP rate limiting depends on this matching your deployment β€” set to the number of proxies in front of the server, 0 to use the raw socket peer IP, or a comma-separated subnet list
  • MS365_MCP_ATTACHMENT_PORT=<port>: Serve the attachment route on its own listener on this port (alternative to --attachment-port; requires --enable-attachment-urls)
  • MS365_MCP_ATTACHMENT_HOST=<host>: Interface the MS365_MCP_ATTACHMENT_PORT listener binds (alternative to --attachment-host; requires --attachment-port). Defaults to the host --http bound β€” which for a wildcard --http means both ports answer everywhere and the port split isolates nothing. See "Splitting the attachment listener"
  • MS365_MCP_CLOUD_TYPE=global|china: Microsoft cloud environment (alternative to --cloud flag)
  • LOG_LEVEL: Set logging level (default: 'info')
  • SILENT=true|1: Disable console output
  • MS365_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 to consumers - 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 login
  • MS365_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 for MS365_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)

Server-Minted Attachment URLs

get-download-url returns Microsoft's own pre-authenticated @microsoft.graph.downloadUrl for OneDrive and SharePoint items. Graph publishes no such URL for mail and calendar attachments, meeting recordings, or any other /$value byte endpoint β€” for those, the only way to read the bytes has been download-bytes, which returns base64 into the agent's context. A 73 KB, 3-page PDF costs about 24,500 tokens that way, and the model cannot parse them anyway.

--enable-attachment-urls (HTTP mode, off by default) closes that gap. When Graph has no URL of its own, get-download-url mints one this server serves:

GET /attachment?t=<ticket>&dgk=<key-id>&dgx=<expiry>&dgs=<signature>

The ticket is 32 bytes of CSPRNG output, single-use, memory-only, and expires after MS365_MCP_ATTACHMENT_URL_TTL_S seconds. Redeeming it streams the Graph bytes with this server's own token; the fetcher sends no Authorization header and holds no Microsoft credential.

This grants no authority the calling agent did not already have. Every target that can be minted is one download-bytes would fetch for the same caller on the same account. The ticket only moves those bytes out of the context window and into a direct transfer.

Configuration

MS365_MCP_ATTACHMENT_URL_BASE=http://m365-mcp:3000   # required
MS365_MCP_ATTACHMENT_URL_KEY=...                     # required (or _KEY_FILE=/path)
MS365_MCP_ATTACHMENT_URL_KEY_ID=1                    # optional, default 1
MS365_MCP_ATTACHMENT_URL_TTL_S=120                   # optional, default 120, max 300

MS365_MCP_ATTACHMENT_URL_BASE is deliberately not MS365_MCP_PUBLIC_URL: that one is browser-facing, for OAuth redirects, while this is fetched server-to-server and is commonly a container address. A missing or malformed setting fails at startup rather than per-request β€” a signing feature that comes up without a key would mint URLs nothing can verify, silently.

Splitting the attachment listener

By default /attachment is served by the same Express app, on the same port, as /mcp. That is fine when callers are authenticated by a bearer token, and it is a problem when they are not. Under --trust-proxy-auth the MCP endpoint reads no Authorization header at all β€” reachability is the authentication β€” so one shared port means the sidecar you allowed through in order to fetch a PDF can also call every tool on the server.

--attachment-port <port> (or MS365_MCP_ATTACHMENT_PORT) moves the route onto a listener of its own, and --attachment-host <host> (or MS365_MCP_ATTACHMENT_HOST) says which interface that listener binds:

ms-365-mcp-server --http 10.89.0.2:3000 --trust-proxy-auth \
                  --enable-attachment-urls \
                  --attachment-port 3001 --attachment-host 10.89.1.2
MS365_MCP_ATTACHMENT_URL_BASE=http://m365-mcp:3001   # note: the attachment port
  • GET /attachment on 3001 works; on 3000 it is 404 β€” the MCP app never mounts it.
  • /mcp on 3001 is 404, as is everything else: the second app has the attachment route and nothing more. No OAuth router, no body parsers, no CORS, no health check.
  • The 60 req/min limiter that guards the route follows it onto the new listener.
  • trust proxy is off on the attachment listener (and MS365_MCP_TRUST_PROXY_HOPS is not read for it), unlike the MCP listener, which trusts one hop. This port is meant to be dialled directly on a container network; honouring X-Forwarded-For on the server's one uncredentialed surface would let a caller choose its own rate-limit bucket.

The flag requires --enable-attachment-urls and refuses to start without it β€” on its own it would open a port with nothing on it while the operator believed the surfaces were separated. In stdio mode it warns and is ignored, like the flag it depends on. --attachment-host likewise requires --attachment-port: alone it would name an interface for a listener that does not exist.

Two ports are not two surfaces unless they bind two interfaces

This is the part that decides whether any of the above is worth anything. Read it before you deploy the split.

--attachment-port on its own separates the two surfaces inside the process. It does not separate them on the network. Without --attachment-host the attachment listener inherits whatever host --http bound β€” and --http 3000, the common form, names no host at all, so Node binds the wildcard and both ports answer on every interface:

ms-365-mcp-server --http 3000 --trust-proxy-auth \
                  --enable-attachment-urls --attachment-port 3001   # NOT isolated

Container networks grant a peer every port on a container, not one port. Put a document-conversion sidecar on a shared bridge so it can fetch /attachment on 3001, and that same sidecar can dial :3000/mcp β€” which under --trust-proxy-auth reads no Authorization header at all and hands back the full tool catalogue. Nothing fails, nothing is logged as an error, and the config looks exactly like the isolated one.

To make it real, give the two listeners different addresses, and put only the attachment address on the network the fetcher is on:

yaml
# docker compose β€” the MCP port on the agent's own bridge, the attachment port on the
# bridge shared with the converter. The converter can reach 3001 and cannot route to 3000.
services:
  m365-mcp:
    networks: { agent-net: { ipv4_address: 10.89.0.2 }, convert-net: { ipv4_address: 10.89.1.2 } }
    command: >
      --http 10.89.0.2:3000 --trust-proxy-auth
      --enable-attachment-urls
      --attachment-port 3001 --attachment-host 10.89.1.2
  docglean:
    networks: [convert-net]

The MCP port is then unreachable from convert-net by binding β€” there is no socket listening on that interface β€” rather than by a firewall rule that has to keep matching.

The server warns at startup if you run --trust-proxy-auth with --attachment-port while both listeners still answer on a common interface (either sharing an address, or either one on the wildcard). Both bound addresses are logged, read back from the socket rather than from the flags, so Server listening on … and Attachment listener on … can be compared directly.

--attachment-host takes a bare IPv4 address, IPv6 address (bracketed [::1] or bare ::1) or hostname. It is refused rather than coerced β€” --attachment-host 10.0.0.5:3001 is an error naming --attachment-port, not a bind to something else. Note that MS365_MCP_ATTACHMENT_URL_BASE still must not be an IPv6 literal (the URL signature covers the host and the two implementations normalise IPv6 differently); if you bind the listener to an IPv6 address, name it in the base by hostname.

Point MS365_MCP_ATTACHMENT_URL_BASE at the attachment port. The server cannot check this for you: the base is usually a container name on a network this process cannot resolve, so a wrong port here shows up as a fetch failure in the sidecar, not an error here. Both the base and the bound port are logged at startup, one line apart, for exactly that comparison.

The signature, and who checks what

dgk/dgx/dgs are not checked by this server on redemption, and that is deliberate. They exist for the fetcher: a document-conversion sidecar that refuses to dial a private address unless the URL carries a valid HMAC from an origin it has been configured to trust. What authorises redemption here is the ticket. Verifying the signature on the way back in would prove only that we minted the URL β€” which the ticket already proves β€” while coupling redemption to the sidecar's clock and to the key surviving a restart.

The wire format is docglean-mcp's signing.py (canonical_string), and src/lib/url-signing.ts is a port of it. The canonical string is \n-joined: v1, lowercased scheme, lowercased host, the port always explicit, the path, the remaining query with dgk/dgx/dgs removed and the rest sorted and re-encoded, and the expiry. The test vectors in test/attachment-url-signing.test.ts were verified against the Python implementation byte for byte β€” three places where the obvious JavaScript disagrees with Python (!*'() escaping, + decoding as a space, and code-point vs UTF-16 sort order) are why that check exists rather than being assumed.

The ticket travels in the query, not the path, because the verifying sidecar keeps a fetched URL's path in its error messages and strips the query.

Not available in OAuth/OBO mode

Identity there arrives per request on the caller's Authorization header, and a ticket is redeemed later by a fetcher that sends none. Minting refuses with an explanation rather than producing a URL that always fails.

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:

PlatformLocation
Windows%APPDATA%\ms-365-mcp-server\
macOS~/Library/Application Support/ms-365-mcp-server/
Linux$XDG_CONFIG_HOME/ms-365-mcp-server/ (or ~/.config/ms-365-mcp-server/)

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:

bash
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.

Skipping the credential store on purpose:

bash
export MS365_MCP_USE_KEYTAR=0   # also accepts false, no or off

The key then goes to .cache-key on every platform, exactly as it does where no credential store exists, and nothing in the server calls keytar. Useful when the credential store prompts on each start - macOS re-asks whenever the calling binary changes, which under npx is every version bump - or when the native module misbehaves on your platform rather than simply failing to load. Any other value leaves the credential store in use, and an unrecognised one is warned about rather than passed over silently.

Switching it off strands a cache that was encrypted under a key already in the credential store, since nothing can reach that key any more. The server says so and replaces that cache on the next sign-in, which signs out every account it held, not just the one you sign back in as. Unset the variable first if that cache is worth keeping.

Only a cache that nothing on the machine can open is replaced. One that fails to decrypt while a usable key is sitting right there - a truncated file, a downgrade to an older build, a cache from somewhere else - is damage rather than a stranded cache, and is left alone exactly as it is by default.

Two things it deliberately does not do. It never deletes what this server already put in the credential store, on logout or otherwise, because reaching the store is the thing you just asked it to stop doing - clear the ms-365-mcp-server entries by hand if you want them gone. And a .cache-key that exists but cannot be read (wrong owner on a bind-mounted config directory, say) is treated as recoverable rather than missing: the server refuses both to overwrite a cache and to mint a replacement key, and says so, rather than deleting a key that would work again once the permissions are fixed. Fix the permissions, or delete .cache-key yourself to start over - which does mean signing in again.

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_PATH and MS365_MCP_SELECTED_ACCOUNT_PATH to 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:

bash
export MS365_MCP_AUTH_CACHE_COMMAND="/path/to/ms365-auth-cache-store"
export MS365_MCP_AUTH_CACHE_COMMAND_TIMEOUT_MS=10000

When 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:

text
$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-account

Protocol v1:

  • load <key> reads no stdin. Exit 0 with {"found":true,"value":"<stored envelope string>"} when present. A miss is exit 0 with {"found":false} or empty stdout.
  • save <key> receives {"value":"<stamped envelope string>"} on stdin and must exit 0 only after the value is durably committed. There are no fire-and-forget or coalesced saves in v1.
  • delete <key> reads no stdin and exits 0 whether the key existed or not.
  • <key> is token-cache or selected-account.
  • Any non-zero exit is a storage error. Do not use exit code 2 for 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

  1. Create a Key Vault (if you don't have one):

    bash
    az keyvault create --name your-keyvault-name --resource-group your-rg --location eastus
  2. Add secrets to Key Vault:

    bash
    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"
  3. Grant access to Key Vault:

    For Azure Container Apps with managed identity:

    bash
    # 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 list

    For local development with Azure CLI:

    bash
    # Your Azure CLI identity already has access if you have appropriate RBAC roles
    az login
  4. Configure the server:

    bash
    MS365_MCP_KEYVAULT_URL=https://your-keyvault-name.vault.azure.net npx @softeria/ms-365-mcp-server

Secret Name Mapping

Key Vault Secret NameEnvironment VariableRequired
ms365-mcp-client-idMS365_MCP_CLIENT_IDYes
ms365-mcp-tenant-idMS365_MCP_TENANT_IDNo (defaults to 'common')
ms365-mcp-client-secretMS365_MCP_CLIENT_SECRETNo

Authentication

The Key Vault integration uses DefaultAzureCredential from the Azure Identity SDK, which automatically tries multiple authentication methods in order:

  1. Environment variables (AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID)
  2. Managed Identity (recommended for Azure Container Apps)
  3. Azure CLI credentials (for local development)
  4. Visual Studio Code credentials
  5. 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 mes

Installation

TypingMind
Prerequisites:

Node.js 18+

{
  "mcpServers": {
    "ms365": {
      "command": "npx",
      "args": [
        "-y",
        "@softeria/ms-365-mcp-server"
      ]
    }
  }
}

Available Tools

  • login

    Authenticate with Microsoft account

  • logout

    Log out from Microsoft account

  • verify-login

    Check current Microsoft authentication status

  • list-accounts

    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.

  • select-account

    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.

  • remove-account

    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.

  • get-drive-item

    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. Does not return content β€” use download-onedrive-file-content for that.

  • move-rename-onedrive-item

    Update the navigation property items in drives

    πŸ’‘ 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.

  • delete-onedrive-file

    Delete navigation property items for drives

  • list-folder-files

    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.

  • create-onedrive-folder

    Create new navigation property to children for drives

    πŸ’‘ 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'.

  • download-onedrive-file-content

    The content stream, if the item represents a file.

    πŸ’‘ TIP: Returns a temporary download URL, NOT the file content directly.

  • upload-file-content

    The content stream, if the item represents a file.

    πŸ’‘ TIP: Max 4MB. For new files use path format: /items/root:/path/to/file.txt:/content. Overwrites existing files without warning.

  • create-upload-session

    Invoke action createUploadSession

    πŸ’‘ TIP: For large file uploads (no size limit). Returns a pre-authenticated uploadUrl for direct PUT of file bytes. For new files use path: /items/{parentId}:/{fileName}:/createUploadSession. Body (optional): { item: { '@microsoft.graph.conflictBehavior': 'rename' } }.

  • get-drive-delta

    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 del

  • share-drive-item

    Send a sharing invitation for a driveItem. A sharing invitation provides permissions to the recipients and, optionally, sends them an email to notify them that the item was shared.

    πŸ’‘ TIP: Shares a file or folder with specific users. Body: { recipients: [{ email: 'user@example.com' }], roles: ['read'], sendInvitation: true, message: 'Please review this file.' }. Roles: 'read', 'write', 'owner'. Set requireSignIn to true to require authentication.

  • list-drive-item-permissions

    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.

  • delete-drive-item-permission

    Delete navigation property permissions for drives

    πŸ’‘ 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.

  • list-drive-item-versions

    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.

  • list-excel-tables

    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.

  • get-excel-table

    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.

  • list-excel-table-rows

    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.

  • add-excel-table-rows

    Create new navigation property to rows for drives

    πŸ’‘ 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.

  • list-excel-worksheets

    Represents a collection of worksheets associated with the workbook. Read-only.

  • create-excel-chart

    Creates a new chart.

  • format-excel-range

    Update the navigation property format in drives

  • sort-excel-range

    Update the navigation property sort in drives

  • get-excel-range

    Invoke function range

  • get-drive-root-item

    The root folder of the drive. Read-only.

  • search-onedrive-files

    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.

  • get-current-user

    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.

  • list-calendars

    Get all the user's calendars (/calendars navigation property), get the calendars from the default calendar group or from a specific calendar group.

  • create-calendar

    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.

  • update-calendar

    Update the navigation property calendars in me

    πŸ’‘ TIP: Updates a calendar's properties. Body: { name: 'New Name', color: 'lightBlue' }. Cannot update the default calendar's name.

  • delete-calendar

    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.

  • get-specific-calendar-view

    The calendar view for the calendar. Navigation property. Read-only.

    πŸ’‘ 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.

  • list-specific-calendar-events

    The events in the calendar. Navigation property. Read-only.

    πŸ’‘ TIP: WARNING: Does NOT expand recurring events β€” only returns seriesMaster. Use get-specific-calendar-view instead.

  • create-specific-calendar-event

    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.

  • get-specific-calendar-event

    The events in the calendar. Navigation property. Read-only.

  • update-specific-calendar-event

    Update the navigation property events in me

    πŸ’‘ 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.

  • delete-specific-calendar-event

    Delete navigation property events for me

    πŸ’‘ TIP: Deleting a seriesMaster deletes ALL occurrences. To cancel a single occurrence, use the specific instance ID.

  • list-calendar-event-instances

    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.

  • get-calendar-view

    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.

  • list-outlook-contacts

    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.

  • create-outlook-contact

    Add a contact to the root Contacts folder or to the contacts endpoint of another contact folder.

  • get-outlook-contact

    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:

  • update-outlook-contact

    Update the properties of a contact object.

    πŸ’‘ TIP: emailAddresses array is replaced entirely β€” include all addresses, not just new ones.

  • delete-outlook-contact

    Delete a contact.

  • list-drives

    Retrieve the list of Drive resources available for a target User, Group, or Site.

  • list-calendar-events

    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.

  • create-calendar-event

    Create one or more multi-value extended properties in a new or existing instance of a resource. The following user resources are supported: The following group resources are supported: See Extended properties overview for more information about when to use open extensions or extended properties, and how to specify extended properties.

    πŸ’‘ 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.

  • get-calendar-event

    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.

  • update-calendar-event

    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.

  • delete-calendar-event

    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.

  • accept-calendar-event

    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.

  • decline-calendar-event

    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.

  • tentatively-accept-calendar-event

    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 } } }.

  • get-mailbox-settings

    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

  • update-mailbox-settings

    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.

  • list-mail-folders

    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 does not return hidden folders. Use a query parameter includeHiddenFolders to include them in the response. This operation does not 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.

  • create-mail-folder

    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.

  • update-mail-folder

    Update the properties of mailfolder object.

    πŸ’‘ TIP: Renames a mail folder by updating its displayName. Use list-mail-folders to find the folder ID.

  • delete-mail-folder

    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.

  • list-mail-child-folders

    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 does not return hidden folders. Use a query parameter includeHiddenFolders to include them in the response.

  • create-mail-child-folder

    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.

  • list-mail-rules

    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.

  • create-mail-rule

    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.

  • update-mail-rule

    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.

  • delete-mail-rule

    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.

  • list-mail-folder-messages

    Get all the messages in the specified user's mailbox, or those messages in a specified folder in the mailbox.

    πŸ’‘ TIP: 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 bo

  • list-mail-messages

    Get an open extension (openTypeExtension object) identified by name or fully qualified name. The table in the Permissions section lists the resources that support open extensions. The following table lists the three scenarios where you can get an open extension from a supported resource instance.

    πŸ’‘ TIP: 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

  • create-draft-email

    Create an open extension (openTypeExtension object) and add custom properties in a new or existing instance of a resource. You can create an open extension in a resource instance and store custom data to it all in the same operation, except for specific resources. The table in the Permissions section lists the resources that support open extensions.

  • get-mail-message

    Get the properties and relationships of the eventMessage object. Apply the $expand parameter on the event navigation property to get the associated event in an attendee's calendar. Currently, this operation returns event message bodies in only HTML format.

  • update-mail-message

    Update the properties of an eventMessage object.

  • delete-mail-message

    Delete eventMessage.

    πŸ’‘ TIP: Soft delete β€” moves to Deleted Items. To permanently delete, delete again from Deleted Items.

  • list-mail-attachments

    Retrieve a list of attachment objects.

  • add-mail-attachment

    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: Max 3MB. Body requires @odata.type: {"@odata.type": "#microsoft.graph.fileAttachment", "name": "file.pdf", "contentBytes": ""}.

  • get-mail-attachment

    Read the properties, relationships, or raw contents of an attachment that is attached to a user event, message, or group post. An attachment can be one of the following types: All these types of attachments are derived from the attachment resource.

  • delete-mail-attachment

    Delete navigation property attachments for me

  • create-mail-attachment-upload-session

    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 large attachments (3-150MB). Body: { AttachmentItem: { attachmentType: 'file', name: 'report.pdf'

  • create-forward-draft

    Invoke action createForward

    πŸ’‘ TIP: Create a forward draft (does not send). Useful when user wants to review before sending.

  • create-reply-draft

    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.
  • create-reply-all-draft

    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.
  • forward-mail-message

    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.

  • move-mail-message

    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).

  • reply-mail-message

    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.

  • reply-all-mail-message

    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.

  • send-draft-message

    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.

  • list-onenote-notebooks

    Retrieve a list of notebook objects.

  • create-onenote-notebook

    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.

  • list-onenote-notebook-sections

    Retrieve a list of onenoteSection objects from the specified notebook.

  • create-onenote-section

    Create a new onenoteSection in the specified notebook.

    πŸ’‘ TIP: Creates a new section in a notebook. Body: { displayName: 'Section Name' }.

  • create-onenote-page

    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.

  • delete-onenote-page

    Delete a OneNote page.

    πŸ’‘ TIP: Deletes a OneNote page permanently. This cannot be undone.

  • get-onenote-page-content

    The page's HTML content.

  • list-all-onenote-sections

    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.

  • list-onenote-section-pages

    Retrieve a list of page objects from the specified section.

  • create-onenote-section-page

    Create a new page in the specified section.

    πŸ’‘ TIP: Body must be a full HTML document (with ......). Partial HTML fails silently.

  • get-my-profile-photo

    Get the specified profilePhoto or its metadata (profilePhoto properties). The supported sizes of HD photos on Microsoft 365 are as follows: 48x48, 64x64, 96x96, 120x120, 240x240, 360x360, 432x432, 504x504, and 648x648. Photos can be any dimension if they're stored in Microsoft Entra ID. You can get the metadata of the largest available photo or specify a size to get the metadata for that photo size. If the size you request is unavailable, you can still get a smaller size that the user has uploaded and made available. For example, if the user uploads a photo that is 504x504 pixels, all but the 648x648 size of the photo is available for download.

    πŸ’‘ TIP: Returns the current user's profile photo as binary image data. The photo is typically in JPEG format.

  • list-planner-tasks

    Retrieve a list of plannertask objects assigned to a User.

    πŸ’‘ TIP: Priority values: 0=Urgent, 1=Important, 3=Medium, 5=Low, 9=unset.

  • send-mail

    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.

  • list-todo-task-lists

    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.

  • list-todo-tasks

    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'.

  • create-todo-task

    Create a new task object in a specified todoTaskList.

  • get-todo-task

    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.

  • update-todo-task

    Update the properties of a todoTask object.

  • delete-todo-task

    Delete a todoTask object.

  • list-todo-linked-resources

    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.

  • create-todo-linked-resource

    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.

  • delete-todo-linked-resource

    Delete a linkedResource object.

    πŸ’‘ TIP: Removes a linked resource from a To Do task.

  • get-planner-plan

    Retrieve the properties and relationships of a plannerplan object.

  • list-plan-tasks

    Retrieve a list of plannerTask objects associated with a plannerPlan object.

    πŸ’‘ TIP: Priority values: 0=Urgent, 1=Important, 3=Medium, 5=Low, 9=unset.

  • create-planner-task

    Create a new plannerTask.

  • get-planner-task

    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.

  • update-planner-task

    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.

  • get-planner-task-details

    Retrieve the properties and relationships of a plannerTaskDetails object.

    πŸ’‘ TIP: Response includes @odata.etag β€” required for update-planner-task-details. Use includeHeaders=true.

  • update-planner-task-details

    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}}}.

  • parse-teams-url

    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.

Use Microsoft 365 MCP with multiple AI models

TypingMind connects MCP tools at the workspace level, so once Microsoft 365 is connected, you can use it with different AI models in TypingMind instead of setting it up separately for each model. This MCP runs locally through the TypingMind MCP connector on your device.

Setup guide to use the local connector

Use this when the MCP server needs access to local files, apps, or private resources on your computer.

1

Open the MCP settings

In TypingMind, go to Settings, Advanced Settings, then Model Context Protocol and choose Setup Connector.

  1. Open TypingMind in your browser.
  2. Click the Settings icon.
  3. Go to Advanced Settings.
  4. Open the Model Context Protocol section.
  5. Click Setup Connector and choose This Device.
TypingMind MCP connector setup screen with This Device selected
2

Run the connector command

Choose This Device, copy the command from TypingMind, and run it in Terminal. Keep the process running while you use MCP.

  1. Copy the setup command shown by TypingMind.
  2. Open Terminal on macOS or Windows Terminal on Windows.
  3. Paste and run the command.
  4. Approve the package install if Terminal asks you to proceed.
  5. Keep the Terminal window running while using MCP tools.
3

Add Microsoft 365 as a server

When the connector status is Ready, click Edit Servers and paste the MCP server configuration.

  1. Wait until the connector status shows Ready.
  2. Click Edit Servers.
  3. Paste the Microsoft 365 MCP server configuration.
  4. Save the server list.
  5. Refresh if you want to confirm the connector is still ready.
TypingMind MCP settings showing active server and Edit Servers button
{
  "mcpServers": {
    "microsoft-365": {
      "command": "npx",
      "args": [
        "-y",
        "@softeria/ms-365-mcp-server"
      ]
    }
  }
}
4

Use it across models

Save the server list, open Plugins, enable the Microsoft 365 MCP tools, then select any supported AI model in TypingMind and use the tools in chat or assign them to an AI agent.

  1. Open the Plugins page in TypingMind.
  2. Enable the Microsoft 365 MCP tools.
  3. Start a chat and choose the AI model you want to use.
  4. Use the MCP tools in chat or assign them to an AI agent.
  5. Switch to another AI model whenever needed without reconnecting MCP.
TypingMind chat using enabled MCP tools with a selected AI model
Can you use Microsoft 365 to help me with this task?
Microsoft 365
Sure. I read it.
Here is what I found using Microsoft 365.

Frequently asked questions

What is the Microsoft 365 MCP server used for?

Microsoft 365 is an MCP server that lets compatible AI clients connect to external tools and context. In TypingMind, you can add this MCP server once and make its tools available in your AI workspace.

Can I use Microsoft 365 MCP with multiple AI models in TypingMind?

Yes. TypingMind connects MCP tools at the workspace level, so you can use Microsoft 365 with different AI models such as Claude, ChatGPT, Gemini, or other models you have configured in TypingMind without setting up the MCP server separately for each model.

Why use Microsoft 365 MCP with TypingMind?

TypingMind is one of the best frontends for LLM chat because it brings multiple AI models, prompts, plugins, AI agents, API keys, and MCP tools into one workspace. With Microsoft 365 connected, you can use its MCP tools across your preferred models while keeping your chat workflow organized in TypingMind.

How do I connect Microsoft 365 MCP to TypingMind?

Microsoft 365 runs through the TypingMind local MCP connector. This is best when the MCP server needs access to local files, desktop apps, command-line tools, or private resources on your computer.

What tools does Microsoft 365 MCP provide in TypingMind?

Microsoft 365 exposes 118 MCP tools that can be enabled from the TypingMind Plugins page and used in chat or assigned to AI agents.

Do I need to share my API keys with TypingMind to use Microsoft 365 MCP?

No. TypingMind is local-first and lets you keep your model providers, API keys, prompts, and MCP configuration under your control. If Microsoft 365 requires authentication, add the required headers, OAuth settings, or local configuration for that MCP server when you create the connection.

Related MCP Servers

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter πŸ‘‡