ms-365-mcp-server: exposing Microsoft 365 to an agent, one Graph endpoint at a time
A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Microsoft Office services through the Graph API
At a glance
- What is it?
- This MCP server maps more than 300 tools one-to-one onto Microsoft Graph endpoints from a declarative endpoints.json, and its real design work is in permissions, since it can hide tools whose scopes are not on an allowlist before it ever shows them to the model.
- Who is it for?
- ms-365-mcp-server fits a team putting an agent in front of Microsoft 365 with a Graph app registration it controls, and it fits an enterprise that has to pre-approve permissions before deploying a new version of the server. It is a poor fit for an agent that should see everything, because the tool surface is deliberately narrowable, and it is a poor fit if you cannot register an Azure AD application.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the server is, and how the tools are defined
ms-365-mcp-server is a Model Context Protocol server for interacting with Microsoft 365 and Microsoft Office services through the Graph API. The project is a TypeScript package published to npm as @softeria/ms-365-mcp-server, MIT licensed, with the last push on 2026-10-15 and releases v0.168.0, v0.167.0 and v0.166.1 all on the same day, which is a fast release cadence.
The tools are defined declaratively. The server provides 300+ tools covering most of the Microsoft Graph API surface, each tool maps one-to-one to a Graph API endpoint, and every one of them is defined in src/endpoints.json. That is the key architectural decision: the tool surface is data, not code, so adding an endpoint does not mean writing an implementation.
The personal account tools are available by default and cover Email, Outlook, Calendar, OneDrive Files, Excel, OneNote, To Do Tasks, Planner, Contacts, User Profile and Search. The organization account tools require the --org-mode flag and add Teams and Chats, Online Meetings, Transcripts and Recordings, Attendance Reports, SharePoint Sites and Lists, Shared Mailboxes and Calendars, User Management, Presence and Virtual Events.
That split exists because the permission surface is completely different. A personal account needs delegated scopes on one mailbox; organization mode reaches into Teams, SharePoint and user management, which in a real tenant means an admin consent conversation. Making the second set opt-in is the correct default.
There is a newer feature inside the teams and work presets: custom Teams emojis, available through list-custom-emojis and create-custom-emoji. Those use the Graph beta API and request the delegated permissions TeamworkCustomEmoji.Read and TeamworkCustomEmoji.Create respectively, with read-only mode exposing only the list tool. The documentation is candid about the consequence, since existing deployments may need consent for the new scopes and reauthentication, and adding tool support does not upgrade an already-issued token.
Permissions are computed before the model ever sees a tool
The most interesting part of this server is the permission model, and it is the part that decides whether it can be deployed in an enterprise.
Permissions are requested dynamically based on which tools are enabled, and the --list-permissions command shows 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-permissionsThe documentation calls this useful in enterprise environments where Graph API permissions must be pre-approved and admin-consented before deploying a new version, which is exactly the workflow problem. You cannot ask an admin to consent to a set of scopes you have not been able to enumerate, and with 300+ tools the unenumerated set is large.
The JSON output is detailed enough to be the input to that conversation. It includes toolPermissions, the permissions implied by the tool surface before --allowed-scopes filtering, and effectivePermissions, the permissions implied by the tools that remain enabled after the filter. There is a permissions field kept as a legacy alias for effectivePermissions for compatibility with existing scripts, the allowedScopes list when provided, disabledTools for tools hidden because their required Graph scopes are not covered by the allowlist, missingAllowedScopesForTools with the unique missing scopes across those disabled tools, and extraAllowedScopesNotUsedByTools for allowed scopes the current tool surface does not use.
That last field is the one that catches a misconfiguration, since a scope in the allowlist that no tool needs is either a leftover or a sign the tool list is wrong.
Allowed scopes, presets, and the tool surface filter
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 the MS365_MCP_ALLOWED_SCOPES environment variable. When it is 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 then request only the effective permissions for the tools that remain enabled.
The example shows all three levers at once:
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'So the enable list is a regular expression, and here it names five specific tools out of the several hundred. The allowlist is the second boundary, and the two are independent: a tool can be enabled and then hidden because its scope is not on the list.
The precedence is stated plainly: the CLI value takes precedence over MS365_MCP_ALLOWED_SCOPES. So an environment variable can set a baseline and a flag can narrow or widen it for one run, but the flag wins when both are present.
Alongside this there are tool presets, which are named bundles of tools for a domain, and dynamic tool discovery, which the feature list describes as a way to shrink the tool surface and token usage. Both exist for the same reason, since 300+ tool definitions in the model's context is a lot of tokens, and the ones you are not going to use are pure cost. Read-only mode is the other narrowing lever, and for the emoji tools it exposes only the list operation.
Two output formats, and why TOON exists
The server supports two output formats, configured globally, and the second one is the more interesting design decision.
The default is JSON with pretty-printing:
{
"value": [
{
"id": "1",
"displayName": "Alice Johnson",
"mail": "[email protected]",
"jobTitle": "Software Engineer"
}
]
}The experimental alternative is TOON, Token-Oriented Object Notation, for efficient LLM token usage:
value[1]{id,displayName,mail,jobTitle}:
"1",Alice Johnson,[email protected],Software EngineerThe stated benefits are 30-60% fewer tokens against JSON, that it is best for uniform array data such as lists of emails, calendar events and files, and that it is ideal for cost-sensitive applications at scale. The format is essentially a header line naming the columns followed by rows, and the savings come from not repeating the key on every row.
The important qualifier is the one the documentation puts in the label itself, which is experimental. The @toon-format/toon package is a runtime dependency in the manifest, so the code path is always present, but the format is flagged as not yet settled.
It can be turned on three ways: with a CLI flag, with an environment variable, or in the client configuration. The flag is --toon, and the environment variable is MS365_MCP_OUTPUT_FORMAT=toon. The Claude Desktop configuration example is the one most people will copy, since the MCP client config is where the server is actually declared:
{
"mcpServers": {
"ms365": {
"command": "npx",
"args": ["-y", "@softeria/ms-365-mcp-server", "--toon"]
}
}
}Deployment, clouds, and the parts that are still rough
The cloud support is a table with two rows, and the second one matters for anyone outside the global cloud. The default Global cloud uses login.microsoftonline.com and graph.microsoft.com, and the China cloud, operated by 21Vianet, uses login.chinacloudapi.cn and microsoftgraph.chinacloudapi.cn. The cloud is selected with MS365_MCP_CLOUD_TYPE.
There is also a proxy hook. To route Graph traffic through a proxy of your own, one that terminates the client's plain HTTP and originates TLS to Microsoft, you set MS365_MCP_GRAPH_BASE_URL to its base URL, and the environment variable list under CLI Options says what still bypasses it.
The container build is a two-stage Alpine build on node:24-alpine. The builder installs with npm ci, runs npm run generate and then npm run build, and the release stage copies only the dist directory and the package files, sets NODE_ENV=production, and installs with npm ci --ignore-scripts --omit=dev. The entrypoint is node dist/index.js. The --ignore-scripts flag in the release stage is worth noticing, since it is the same idea the npm allow-scripts tooling exists to enforce.
The authentication story is MSAL, the Microsoft Authentication Library, with @azure/msal-node as a dependency, and the .env.example walks through creating an Azure AD app registration, including the redirect URIs for the MCP Inspector on localhost:6274 and an optional server callback on localhost:3000. It also notes that .env is read from the directory the server is started in, that the MCP client picks, and that only four variables are read from it, everything else having to be set in the shell or the client config.
The rough edges are in the details. Listing can return contentBytes: null by default, so to get base64 PNG or GIF images you have to pass select: "displayName,contentBytes", and you should use a small top to keep image responses manageable. Optional dependencies include @azure/identity, @azure/keyvault-secrets and keytar, so the Key Vault and credential paths are opt-in.
The repository also contains a remove-recursive-refs.js script, two files named test-calendar-fix.js and test-real-calendar.js at the root, a server.json and a glama.json for directory listings, and an examples/azure-container-apps directory.
Editorial conclusion
ms-365-mcp-server fits a team putting an agent in front of Microsoft 365 with a Graph app registration it controls, and it fits an enterprise that has to pre-approve permissions before deploying a new version of the server. It is a poor fit for an agent that should see everything, because the tool surface is deliberately narrowable, and it is a poor fit if you cannot register an Azure AD application. Before adopting, run the --list-permissions command for your intended configuration and get those scopes admin-consented, since adding a tool later does not upgrade a token that was already issued.
Frequently asked questions
What is ms-365-mcp-server?
It is a Model Context Protocol server for interacting with Microsoft 365 and Microsoft Office services through the Graph API, published to npm as @softeria/ms-365-mcp-server. It provides 300+ tools covering most of the Graph API surface, each mapping one-to-one to a Graph endpoint and defined declaratively in src/endpoints.json.
How do I see which Graph permissions a configuration needs?
Run the server with --list-permissions for the configuration you intend to use, for example with --org-mode or --preset mail. The JSON output includes toolPermissions, effectivePermissions, the allowedScopes list, disabledTools hidden because their scopes are not covered, missingAllowedScopesForTools, and extraAllowedScopesNotUsedByTools.
What does --allowed-scopes do?
It adds a scope boundary for enterprise and headless deployments, and can also be set with MS365_MCP_ALLOWED_SCOPES. The server computes the normal tool surface first, then hides Graph tools whose required scopes are not covered by the allowlist, and the OAuth metadata and login flows then request only the effective permissions for the tools that remain enabled. The CLI value takes precedence over the environment variable.
What is the TOON output format and when should I use it?
TOON, Token-Oriented Object Notation, is an experimental alternative to JSON that cuts token usage by 30-60% against JSON and works best for uniform array data such as lists of emails, calendar events and files. You enable it with the --toon flag, with MS365_MCP_OUTPUT_FORMAT=toon, or in the MCP client configuration.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/softeria-ms-365-mcp-server)Was this useful?