Server · #230 trending MCP repo · ↗ +20 stars this week
Ms 365 MCP Server
Softeria/ms-365-mcp-server
A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Microsoft Office services through the Graph API
- Stars
- 1.1k
- This week
- +20
- Forks
- 414
- Open issues
- 15
- Latest release
v0.179.1
Get started
Add it to your agent
claude mcp add ms365 -- npx -y @softeria/ms-365-mcp-serverRun in your terminal. Add --scope project to share it with your team through .mcp.json.
codex mcp add ms-365 -- npx -y @softeria/ms-365-mcp-server --toonSaved to ~/.codex/config.toml.
{
"mcpServers": {
"ms-365": {
"command": "npx",
"args": [
"-y",
"@softeria/ms-365-mcp-server",
"--toon"
]
}
}
}Add to ~/.cursor/mcp.json (or .cursor/mcp.json in a project).
{
"servers": {
"ms-365": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@softeria/ms-365-mcp-server",
"--toon"
]
}
}
}Add to .vscode/mcp.json, then start it from the MCP view in Copilot Chat.
gemini mcp add ms-365 npx -y @softeria/ms-365-mcp-server --toonSaved to .gemini/settings.json.
Documentation
README
ms-365-mcp-server
Microsoft 365 MCP Server
A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Microsoft Office services through the Graph API.
Supported Clouds
This server supports multiple Microsoft cloud environments:
| Cloud | Description | Auth Endpoint | Graph API Endpoint |
|---|---|---|---|
| Global (default) | International Microsoft 365 | login.microsoftonline.com | graph.microsoft.com |
| China (21Vianet) | Microsoft 365 operated by 21Vianet | login.chinacloudapi.cn | microsoftgraph.chinacloudapi.cn |
| GCC High | Microsoft 365 US Government GCC High | login.microsoftonline.us | graph.microsoft.us |
GCC High has no pre-registered client: register an app in your government tenant and set MS365_MCP_CLIENT_ID (and MS365_MCP_TENANT_ID), then use --cloud gcc-high.
To route Graph traffic through a proxy of your own (one that terminates the client's plain HTTP and originates TLS to Microsoft), set MS365_MCP_GRAPH_BASE_URL to its base URL; see the environment variable list under CLI Options, including what still bypasses it.
Prerequisites
- Node.js >= 20 (recommended)
- Node.js 14+ may work with dependency warnings
Features
- Authentication via Microsoft Authentication Library (MSAL)
- Comprehensive Microsoft 365 service integration
- Read-only mode support for safe operations
- Tool filtering for granular access control
- Tool presets and dynamic discovery to shrink the tool surface and token usage
Output Format: JSON vs TOON
The server supports two output formats that can be configured globally:
JSON Format (Default)
Standard JSON output with pretty-printing:
{
"value": [
{
"id": "1",
"displayName": "Alice Johnson",
"mail": "alice@example.com",
"jobTitle": "Software Engineer"
}
]
}
(experimental) TOON Format
Token-Oriented Object Notation for efficient LLM token usage:
value[1]{id,displayName,mail,jobTitle}:
"1",Alice Johnson,alice@example.com,Software 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:
npx @softeria/ms-365-mcp-server --toon
Via Claude Desktop configuration:
{
"mcpServers": {
"ms365": {
"command": "npx",
"args": ["-y", "@softeria/ms-365-mcp-server", "--toon"]
}
}
}
Via environment variable:
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
Custom Teams emojis are available in organization mode through list-custom-emojis
and create-custom-emoji (teams and work presets). These use the Microsoft Graph
beta API and request the delegated permissions TeamworkCustomEmoji.Read and
TeamworkCustomEmoji.Create, respectively. Read-only mode exposes only the list tool.
Existing deployments may need consent for these new scopes and reauthentication;
adding tool support does not upgrade an already-issued token.
Listing can return contentBytes: null by default. To request base64 PNG/GIF images,
pass select: "displayName,contentBytes" or select: ["displayName", "contentBytes"].
Use a small top to keep image responses manageable.
To create an emoji, pass body: { displayName, contentBytes }
with the exact approved name and base64 PNG/GIF file bytes. See Microsoft's
list
and create
contracts. These tools do not post messages or reactions.
Required Graph API Permissions
Permissions are requested dynamically based on which tools are enabled. Use --list-permissions to see the exact permissions for your configuration:
# Personal mode (default)
npx @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-scopesfilteringeffectivePermissions: permissions implied by the tools that remain enabled after--allowed-scopespermissions: legacy alias foreffectivePermissions, kept for compatibility with existing scriptsallowedScopes: the configured scope allowlist, when provideddisabledTools: tools hidden because their required Graph scopes are not covered byallowedScopesmissingAllowedScopesForTools: unique missing scopes across disabled toolsextraAllowedScopesNotUsedByTools: allowed scopes that are not used by the current tool surface
Allowed Scopes
By default, MSAL requests the scopes implied by the enabled tools, and the tool surface is controlled by --enabled-tools, --enabled-tools-file, --preset, --org-mode, and --read-only.
Enterprise and headless deployments can add a scope boundary with --allowed-scopes or MS365_MCP_ALLOWED_SCOPES. When configured, the server first computes the normal tool surface, then hides Graph tools whose required scopes are not covered by the allowlist. OAuth metadata and login flows request only the effective permissions for the tools that remain enabled.
npx @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'
For longer filters, put one regex per line in a file and pass --enabled-tools-file / ENABLED_TOOLS_FILE. Blank lines and whole-line # comments are dropped; the rest are joined with |, so the file is equivalent to --enabled-tools=line1|line2|…:
# tools.txt
^list-mail-messages$
^get-mail-message$
drive|download-bytes
npx @softeria/ms-365-mcp-server --org-mode --enabled-tools-file ./tools.txt
CLI value takes precedence over MS365_MCP_ALLOWED_SCOPES; if neither is set, the default tool-derived scope behavior is unchanged. Supplying an empty value fails at startup so deployments do not accidentally fall back to a wider tool surface.
Scope coverage is hierarchy-aware: for example, Mail.ReadWrite covers tools that require Mail.Read, and Files.ReadWrite.All covers tools that require Files.Read.
SharePoint supports two enterprise permission models:
- Broad tenant scopes such as
Sites.Read.All,Sites.ReadWrite.All, andSites.Manage.All. - Microsoft Graph
Sites.Selected, where SharePoint site access is granted to the app on specific site collections and Graph evaluates the signed-in user's own permissions at request time.
The default org-mode behavior continues to request the broad SharePoint scopes used by existing deployments. Enterprises that want selected-site SharePoint access can set an allowlist containing Sites.Selected instead of broad Sites.*.All scopes. Direct site/list/item tools that target an explicit SharePoint site, 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.
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 <clientId>/access_as_user for protected-resource metadata and requests it on /authorize; --allowed-scopes does not override OBO, it only narrows the tool surface.
Restricting user profile fields
Deployments can set a comma-separated field filter for selected user-directory paths with
--user-fields or MS365_MCP_USER_FIELDS. It narrows the $select sent to Microsoft Graph
on /users reads and projects responses on the covered paths listed below. This is a
path-specific data filter, not a complete Graph authorization boundary: other Graph
routes and resource shapes can expose overlapping profile data.
npx @softeria/ms-365-mcp-server \
--org-mode \
--user-fields 'id,displayName,mail,userPrincipalName'
The filter is keyed on known Graph paths rather than tool names. It applies to direct tools,
discovery mode's execute-tool, and matching graph-batch subrequests. Batch subrequest URLs
are rewritten on covered /users reads, and responses are projected on all covered profile
paths. The byte-passthrough tools (download-bytes, download-bytes-to-file,
get-download-url) return responses verbatim and refuse targets matching the covered user
profile paths while the filter is active.
Two levels of enforcement apply, depending on what Graph returns:
| Path | Enforcement |
|---|---|
/users, /users/{id} |
$select is narrowed on the request and the response is projected, so excluded fields never leave the tenant |
/me/manager, /me/directReports, /users/{id}/manager, /users/{id}/directReports, /groups/{id}/members, /groups/{id}/owners |
The response is projected. These are typed as directoryObject, where Graph requires an OData cast before it will $select a user-only property such as jobTitle, so narrowing the request would risk breaking calls that work today |
While the filter is active, $expand is removed from /me, /groups, and /groups/{id}
requests because those resources can expand to related user profiles. This also applies to
matching batch subrequests. The base /me and group responses are otherwise not projected.
This does not cover every route that can return a user-shaped object. In particular,
generic Graph access can still reach alternate routes or representations such as
/directoryObjects/{id}, /groups/{id}/transitiveMembers, or /users('{id}'). Those are
not currently normalized to the covered paths. If this setting is a security requirement,
also restrict generic Graph access (for example graph-batch) and validate the effective
tool surface for your deployment; do not rely on --user-fields alone as a tenant-wide
data-access control.
Resources below a user, such as /users/{id}/messages or /users/{id}/photo/$value, are
mail, calendar and binary resources rather than profile properties, and are unaffected. The
signed-in user's own /me profile is also unaffected, as are the Teams and chat member
lists, which return conversationMember rather than user profiles.
Known gap: list-relevant-people
list-relevant-people (/me/people) is not covered. It returns person resources,
a different type whose properties only partly overlap with user — it carries jobTitle,
department and officeLocation, but addresses arrive as scoredEmailAddresses rather
than mail. Applying a user-field allowlist to it would reject valid field names and
narrow the result to something the tool could not use.
Deployments that need the people surface closed as well should drop the tool from the
surface, for example with --enabled-tools or by choosing a preset that excludes it:
npx @softeria/ms-365-mcp-server \
--org-mode \
--user-fields 'id,displayName,mail' \
--enabled-tools '^(?!list-relevant-people$).*'
CLI values take precedence over the environment variable. A value that names no fields fails
at startup. When neither is configured, existing behavior is unchanged. Configure this in
the LibreChat MCP server environment or command arguments; project-local .env files are
intentionally restricted to application credentials and are not used for this setting.
The allowlist is exhaustive for user properties: unlike an ordinary $select, id is only
returned when it appears in the list, so include it if downstream tools need it to address a
user. Collection annotations such as @odata.nextLink are retained so paging keeps working.
If Graph returns none of the allowlisted properties, the response is projected to empty
rather than returned untrimmed.
Requesting extra scopes
--allowed-scopes only ever narrows the token request. To request a Graph scope that no bundled tool needs — for example to drive an endpoint via graph-batch — use --extra-scopes (or MS365_MCP_EXTRA_SCOPES). These scopes are appended verbatim to the token request, on top of the tool-derived scopes.
npx @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:
{
"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:
- Organization mode: Shared mailbox tools require
--org-modeflag (work/school accounts only) - Delegated permissions:
Mail.Read.Sharedto read,Mail.ReadWrite.Sharedto create, update or move messages,Mail.Send.Sharedto send, reply or forward, andCalendars.Read.Sharedfor the shared calendar tools - Exchange permissions: The signed-in user must have been granted access to the shared mailbox
- Usage: Use the shared mailbox's email address as the
user-idparameter in the shared mailbox tools
Finding shared mailboxes: Use the list-users tool to discover available users and shared mailboxes in your
organization.
Example: list-shared-mailbox-messages with user-id set to shared-mailbox@company.com
Quick Start Example
Test login in Claude Desktop:
Examples
Integration
Claude Desktop
To add this MCP server to Claude Desktop, edit the config file under Settings > Developer.
Personal Account (MSA)
{
"mcpServers": {
"ms365": {
"command": "npx",
"args": ["-y", "@softeria/ms-365-mcp-server"]
}
}
}
Work/School Account (Global)
{
"mcpServers": {
"ms365": {
"command": "npx",
"args": ["-y", "@softeria/ms-365-mcp-server", "--org-mode"]
}
}
}
Work/School Account (China 21Vianet)
{
"mcpServers": {
"ms365-china": {
"command": "npx",
"args": ["-y", "@softeria/ms-365-mcp-server", "--org-mode", "--cloud", "china"]
}
}
}
Claude Code CLI
Personal Account (MSA)
claude mcp add ms365 -- npx -y @softeria/ms-365-mcp-server
Work/School Account (Global)
# 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)
# 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.
Start the server with HTTP mode:
npx @softeria/ms-365-mcp-server --httpIn Open WebUI, go to Admin Settings → Tools (
/admin/settings/tools) → Add Connection:- Type: MCP Streamable HTTP
- URL: Your MCP server URL with
/mcppath - Auth: OAuth 2.1
Click Register Client.
Note: Dynamic client registration is enabled by default in HTTP mode. Use
--no-dynamic-registration(or setMS365_MCP_DISABLE_DCR=true) to disable it. If using a custom Azure Entra app, the platform type for your redirect URI depends on whether the app has a client secret: with a secret use "Web", without one use "Mobile and desktop applications" (never "Single-page application").
Quick test setup using the default Azure app (ID ms-365 and localhost:8080 are pre-configured):
docker run -d -p 8080:8080 \
-e WEBUI_AUTH=false \
-e OPENAI_API_KEY \
ghcr.io/open-webui/open-webui:main
npx @softeria/ms-365-mcp-server --http
Then add connection with URL http://localhost:3000/mcp and ID ms-365.
Running in Docker behind a reverse proxy? Set
--public-url https://your-domain.comso the OAuth authorize URL handed to the user's browser is reachable from outside the container network. See docs/deployment.md for the full guide.
Local Development
For local development or testing:
# From the project directory
claude mcp add ms -- npx tsx src/index.ts --org-mode
Or configure Claude Desktop manually:
{
"mcpServers": {
"ms365": {
"command": "node",
"args": ["/absolute/path/to/ms-365-mcp-server/dist/index.js", "--org-mode"]
}
}
}
Note: Run
npm run buildafter code changes to update thedist/folder.
Authentication
⚠️ You must authenticate before using tools.
The server supports three authentication methods:
1. Device Code Flow (Default)
For interactive authentication via device code:
- MCP client login:
- Call the
logintool (auto-checks existing token) - If needed, get URL+code, visit in browser
- Use
verify-logintool to confirm
- Call the
- CLI login:
Follow the URL and code prompt in the terminal.npx @softeria/ms-365-mcp-server --login
Tokens are cached securely in your OS credential store (fallback to file).
2. OAuth Authorization Code Flow (HTTP mode only)
When running with --http, the server requires OAuth authentication:
npx @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-toolsto enable them)
MCP clients will automatically handle the OAuth flow when they see the advertised capabilities.
Setting up Azure AD for OAuth Testing
To use OAuth mode with custom Azure credentials (recommended for production), you'll need to set up an Azure AD app registration:
- Create Azure AD App Registration:
- Go to Azure Portal
- Navigate to Azure Active Directory → App registrations → New registration
- Set name: "MS365 MCP Server"
- Configure Redirect URIs:
- Configure the OAuth callback URI: Go to your app registration and on the left side, go to Authentication.
- Under Platform configurations:
- Click Add a platform (if you don’t already see one for "Mobile and desktop applications" / "Public client").
- Choose Mobile and desktop applications or Public client/native (mobile & desktop) (label depends on portal version).
- Testing with MCP Inspector (
npm run inspector):
- Go to your app registration and on the left side, go to Authentication.
- Under Platform configurations:
- Click Add a platform (if you don’t already see one for "Web").
- Choose Web.
- Configure the following redirect URIs
http://localhost:6274/oauth/callbackhttp://localhost:6274/oauth/callback/debughttp://localhost:3000/callback(optional, for server callback)
- Get Credentials:
- Copy the Application (client) ID from Overview page
- Go to Certificates & secrets → New client secret → Copy the secret value (optional for public apps)
- Configure Environment Variables:
Create a
.envfile in your project root:MS365_MCP_CLIENT_ID=your-azure-ad-app-client-id-here MS365_MCP_CLIENT_SECRET=your-secret-here # Optional for public apps MS365_MCP_TENANT_ID=common
With these configured, the server will use your custom Azure app instead of the built-in one.
Note:
.envis read from the directory the server is started in, and the MCP client decides what that is. OnlyMS365_MCP_CLIENT_ID,MS365_MCP_CLIENT_SECRET,MS365_MCP_TENANT_IDandMS365_MCP_CLOUD_TYPEare read from it. Every other variable listed above must be set in your shell or MCP client config; anything else found in a.envis ignored with a warning on stderr.
3. Bring Your Own Token (BYOT)
If you are running ms-365-mcp-server as part of a larger system that manages Microsoft OAuth tokens externally, you can provide an access token directly to this MCP server:
MS365_MCP_OAUTH_TOKEN=your_oauth_token npx @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)
4. App Mode (application permissions, no signed-in user)
Warning: application permissions apply to the whole tenant.
Mail.Readgranted to the app reads every mailbox,Files.ReadWrite.Allwrites every OneDrive and site, and whoever can call this server acts with them. Grant the fewest permissions you can. To limit which mailboxes and calendars it reaches, grant the mail and calendar permissions with RBAC for Applications in Exchange Online instead of in Microsoft Entra: the two add up, so an EntraMail.Readgrant left in place still reaches every mailbox. SharePoint sites can be narrowed withSites.Selected.
For unattended agents that act as the app registration itself (client credentials):
MS365_MCP_CLIENT_ID=<your app id> \
MS365_MCP_TENANT_ID=<your tenant id or domain> \
MS365_MCP_CLIENT_SECRET=<secret> \
npx @softeria/ms-365-mcp-server --app-mode --read-only
Use your own app registration with application permissions and admin consent, and set exactly one credential:
MS365_MCP_CLIENT_SECRETMS365_MCP_CLIENT_CERTIFICATE_PATH: a PEM file with the certificate and its unencrypted private keyMS365_MCP_FEDERATED_TOKEN_FILE: a token from an identity provider the app trusts through workload identity federation, read again on every token request. On AKS set it to$AZURE_FEDERATED_TOKEN_FILE, which Kubernetes keeps fresh. A GitHub Actions OIDC token is valid for 5 minutes, so writing it to a file once only covers the first Graph token (about an hour) unless something rewrites the file. If Entra answersAADSTS700213, set the federated credential's subject to the one quoted in the error (GitHub's subject can include the owner and repo ids).
MS365_MCP_TENANT_IDmust be a real tenant, notcommon.On an Azure host (App Service, Container Apps, VM, ...) set
MS365_MCP_MANAGED_IDENTITY=system, or the client id of a user-assigned identity, instead. No app registration, tenant id or secret is needed, and Key Vault (MS365_MCP_KEYVAULT_URL) is not read. Graph application permissions for a managed identity can't be granted in the portal; assign them with PowerShell or the Graph API. On Azure Container Instances, give the container one kind of identity: with both a system- and a user-assigned identity, the system-assigned token request fails there.Tokens are renewed automatically. Nothing is cached on disk.
Only tools that work with application permissions are offered: the
/users/{user-id}mail and calendar tools (pass a UPN or object id asuser-id), users and groups, OneDrive/SharePoint drives and lists, Planner, Teams reads, presence, places and subscriptions./metools, Excel, OneNote and sending Teams chat or channel messages are not available, because Graph does not support them with application permissions.--list-permissions --app-modelists the application permissions the offered tools use.--allowed-scopes,--enabled-tools,--presetand--read-onlynarrow the surface as usual.--verify-login --app-modegets a token and shows the app id, tenant and granted roles (Exchange RBAC grants do not show up there).The
mail,calendarandoutlookpresets are built from/metools and leave little in app mode; the/users/{user-id}mail and calendar tools are inwork.stdio only. It refuses
--http,--obo,--extra-scopes,MS365_MCP_OAUTH_TOKENand the login/account options.Webinar tools also need an application access policy assigned to the organizer.
Note: HTTP mode requires authentication. For unauthenticated testing, use stdio mode with device code flow.
Authentication Tools: In HTTP mode, login/logout tools are disabled by default since OAuth handles authentication. Use
--enable-auth-toolsif you need them available.
Multi-Account Support
Use a single server instance to serve multiple Microsoft accounts. When more than one account is logged in, an account parameter is automatically injected into every tool, allowing you to specify which account to use per tool call.
Login multiple accounts (one-time per account):
# Login first account (device code flow)
npx @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:
npx @softeria/ms-365-mcp-server --list-accounts
Use in tool calls: Pass "account": "work@company.com" in any tool request:
{ "tool": "list-mail-messages", "arguments": { "account": "work@company.com" } }
Behavior:
- With a single account configured, it auto-selects (no
accountparameter needed). - With multiple accounts and no
accountparameter, the server uses the selected default or returns a helpful error listing available accounts. - 100% backward compatible: existing single-account setups work unchanged.
- The
accountparameter accepts email address (e.g.user@outlook.com) or MSALhomeAccountId.
Strict Account Pinning
Headless stdio deployments can pin the local MSAL cache to one expected Microsoft account:
# Username matching is case-insensitive
MS365_MCP_EXPECTED_USERNAME=work@company.com npx @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 overMS365_MCP_EXPECTED_USERNAMEandMS365_MCP_EXPECTED_HOME_ACCOUNT_ID. - Supplying an empty pin value fails at startup instead of being ignored.
- Username pins are compared case-insensitively;
homeAccountIdpins are exact. - If both pins are set, they must resolve to the same cached account.
- Local stdio startup fails fast when the expected account is not in the token cache. Bootstrap by setting the pin, running
--login, then starting the headless server. - Device-code and browser logins reject a missing or mismatched account before persisting the selected account or token cache.
- Pinning collapses the effective MCP mode to single-account: the server does not advertise an
accountparameter and MCP instructions do not suggest account switching. --http,--obo, andMS365_MCP_OAUTH_TOKENuse request-provided tokens for Graph calls, so account pins are warning-only in those modes. If HTTP auth tools are enabled, the pin still applies to those local MSAL helper flows.--logoutclears all cached accounts, including the pinned account. For surgical cleanup, prefer--remove-account <id>.
For MCP multiplexers (Legate, Governor): Multi-account mode replaces the N-process pattern. Instead of spawning one server per account, a single instance handles all accounts via the
accountparameter, reducing tool duplication from N×110 to 110.
Tool Presets
To reduce initial connection overhead and token usage, use preset tool categories instead of loading the full tool set:
npx @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
See PRESETS.md for the full list of tools each preset unlocks (regenerated on npm run generate).
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, and joins every other preset (bar teams-write) when HTTP mode runs with --enable-attachment-urls. So a preset that can find a file can always read its bytes.
The outlook, onedrive and teams presets are app-scoped: they expose exactly one Microsoft app. Use these for "expose exactly one app" deployments:
# Outlook only (mail + calendar + contacts; no shared mailboxes, no files)
npx @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):
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:
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).
Hybrid discovery with direct tools
Keep the full enabled catalogue discoverable while exposing frequent operations directly:
…
Releases