@hurtener/ms-365-mcp-server
v1.3.1
Published
A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Office services through the Graph API
Downloads
1,135
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 |
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
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": "[email protected]",
"jobTitle": "Software Engineer"
}
]
}(experimental) TOON Format
Token-Oriented Object Notation for efficient LLM token usage:
value[1]{id,displayName,mail,jobTitle}:
"1",Alice Johnson,[email protected],Software EngineerBenefits:
- 30-60% fewer tokens vs JSON
- Best for uniform array data (lists of emails, calendar events, files, etc.)
- Ideal for cost-sensitive applications at scale
Usage: (experimental) Enable TOON format globally:
Via CLI flag:
npx @hurtener/ms-365-mcp-server --toonVia Claude Desktop configuration:
{
"mcpServers": {
"ms365": {
"command": "npx",
"args": ["-y", "@hurtener/ms-365-mcp-server", "--toon"]
}
}
}Via environment variable:
MS365_MCP_OUTPUT_FORMAT=toon npx @hurtener/ms-365-mcp-serverSupported Services & Tools
Personal Account Tools (Available by default)
Email (Outlook)
list-mail-messages, list-mail-folders, list-mail-folder-messages, get-mail-message, send-mail,
delete-mail-message, create-draft-email, move-mail-message
Calendar
list-calendars, list-calendar-events, get-calendar-event, get-calendar-view, create-calendar-event,
update-calendar-event, delete-calendar-event
OneDrive Files
list-drives, get-drive-root-item, list-folder-files, download-onedrive-file-content, upload-file-content,
upload-new-file, delete-onedrive-file
Excel Operations
list-excel-worksheets, get-excel-range, create-excel-chart, format-excel-range, sort-excel-range
OneNote
list-onenote-notebooks, list-onenote-notebook-sections, list-onenote-section-pages, get-onenote-page-content,
create-onenote-page
To Do Tasks
list-todo-task-lists, list-todo-tasks, get-todo-task, create-todo-task, update-todo-task, delete-todo-task
Planner
list-planner-tasks, get-planner-plan, list-plan-tasks, get-planner-task, create-planner-task
Contacts
list-outlook-contacts, get-outlook-contact, create-outlook-contact, update-outlook-contact,
delete-outlook-contact
User Profile
get-current-user
Search
search-query
Organization Account Tools (Requires --org-mode flag)
Teams & Chats list-chats, get-chat, list-chat-messages, get-chat-message, send-chat-message, list-chat-message-replies, reply-to-chat-message, list-joined-teams, get-team, list-team-channels, get-team-channel, list-channel-messages, get-channel-message, send-channel-message, list-team-members, list-chat-members, get-chat-details, find-chats-by-participant, list-recent-chats, get-chat-context, search-messages, list-channel-members
Online Meetings & Transcripts list-online-meetings, list-meeting-transcripts, get-meeting-transcript-content
SharePoint Sites
search-sharepoint-sites, get-sharepoint-site, get-sharepoint-site-by-path, list-sharepoint-site-drives,
get-sharepoint-site-drive-by-id, list-sharepoint-site-items, get-sharepoint-site-item, list-sharepoint-site-lists,
get-sharepoint-site-list, list-sharepoint-site-list-items, get-sharepoint-site-list-item,
get-sharepoint-sites-delta
Shared Mailboxes
list-shared-mailbox-messages, list-shared-mailbox-folder-messages, get-shared-mailbox-message,
send-shared-mailbox-mail
User Management
list-users, search-users, resolve-person
Canvas-Oriented Custom Tools
This fork includes a custom normalization and resolution layer aimed at Canvas and other chat-first agents. These tools do not change the raw Graph tool payloads; they add normalized, ranked, path-first responses on top.
People
search-usersresolve-personget-userget-managerget-direct-reportssearch-people-across-surfaces
Teams
list-chat-membersget-chat-detailsfind-chats-by-participantlist-recent-chatsget-chat-contextsearch-messageslist-channel-members
Files
resolve-drive-pathlist-foldersearch-filesget-file-metadataget-file-contentget-file-textget-file-contextcreate-text-fileupdate-text-fileedit-text-filedelete-file
SharePoint
list-sitessearch-siteslist-site-driveslist-document-librariessearch-site-filessearch-sharepoint-contentlist-sharepoint-pageslist-sharepoint-listslist-sharepoint-list-itemssearch-sharepoint-pages
Calendar
search-calendar-eventsresolve-attendeesfind-availabilityaccept-calendar-eventdecline-calendar-eventtentatively-accept-calendar-eventget-calendar-event-detailssuggest-meeting-timescreate-calendar-event-with-attendeescreate-calendar-event-from-availabilityreschedule-calendar-eventlist-calendars-details
Tasks
search-taskslist-task-listslist-tasksget-task-contextcomplete-taskreopen-task
search-mailresolve-mail-recipientslist-attachmentsget-attachment-contentthread-mailfind-related-mailsearch-mail-by-personget-mail-contextreply-mailreply-all-mailforward-maildraft-mail-replydraft-mail-reply-alldraft-mail-forward
Cross-Surface Search
search-m365-content
Custom Tool Scopes
These custom tools request scopes independently from endpoints.json so they work with filtered tool sets:
| Tool family | Scopes |
| -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| People resolution (search-users, resolve-person, get-user, get-manager, get-direct-reports) | User.Read.All |
| Teams chat resolution (list-chat-members, get-chat-details, list-recent-chats) | Chat.Read |
| Teams participant/message search (find-chats-by-participant, get-chat-context, search-messages) | Chat.Read, User.Read.All, ChatMessage.Read, ChannelMessage.Read.All as needed |
| Teams channel members (list-channel-members) | ChannelMember.Read.All |
| Files (resolve-drive-path, list-folder, search-files, get-file-*) | Files.Read, Files.Read.All, Sites.Read.All |
| File writes (create-text-file, update-text-file, edit-text-file, delete-file) | Files.ReadWrite, Sites.ReadWrite.All |
| SharePoint (list-sites, search-sites, list-site-drives, search-site-files, search-sharepoint-*) | Sites.Read.All |
| Calendar (search-calendar-events, get-calendar-event-details, list-calendars-details) | Calendars.Read |
| Meeting resolution (resolve-attendees, find-availability, suggest-meeting-times) | Calendars.Read, User.Read.All |
| Calendar responses (accept-calendar-event, decline-calendar-event, tentatively-accept-calendar-event) | Calendars.ReadWrite |
| Calendar writes (create-calendar-event-with-attendees, create-calendar-event-from-availability, reschedule-calendar-event) | Calendars.ReadWrite, User.Read.All |
| Tasks (search-tasks, list-task-lists, list-tasks, get-task-context) | Tasks.Read |
| Task updates (complete-task, reopen-task) | Tasks.ReadWrite |
| Mail (search-mail, list-attachments, get-attachment-content, thread-mail, find-related-mail, get-mail-context) | Mail.Read |
| Mail recipient/person resolution (resolve-mail-recipients, search-mail-by-person) | Mail.Read, User.Read.All |
| Mail actions (reply-mail, reply-all-mail, forward-mail, draft-mail-*) | Mail.ReadWrite, User.Read.All as needed |
| Cross-surface search (search-m365-content) | Mail.Read, Chat.Read, ChannelMessage.Read.All, Files.Read.All, Sites.Read.All, User.Read.All |
Custom Tool Contracts
All Canvas-oriented custom tools follow these response conventions:
- camelCase field names
- object responses, never raw top-level arrays
itemsfor collections- deterministic ordering and ranking
- opaque
cursor/nextCursorvalues - structured identity objects instead of raw Graph fragments
- MCP-friendly structured errors with
isError: true - path-first file operations wherever Microsoft exposes path semantics
- additive
labelfields on normalized entities and search hits previewOnlyon agent-oriented custom mutation wrappers
Error Contract
Custom tools return structured JSON errors in the response body:
{
"error": "ambiguous_match",
"message": "Multiple users matched the email address.",
"candidates": [
{
"userId": "user-123",
"label": "Juan Casiraghi <[email protected]>",
"rank": 1
}
]
}Standard error codes currently used:
not_founduser_not_foundchat_not_foundchat_members_unavailableinsufficient_scopeambiguous_matchunsupported_operationunsupported_chat_typeunsupported_scopeinvalid_cursor
For insufficient_scope responses, custom tools now include:
requiredScopesmissingScopeswhen the current token claims make the gap inferable
Non-Teams Contract Highlights
- Files and SharePoint tools return path-first
driveId+itemIdsummaries so Canvas does not need Graph IDs up front. - SharePoint file access is path-first and site-aware with this precedence:
- explicit
driveId - explicit
siteIdwithoutdriveId - fallback to the signed-in user's default drive
- explicit
list-document-librariesis the preferred app-facing entrypoint for SharePoint document libraries, whilelist-site-drivesremains as a lower-level alias.get-file-textandget-file-contextprovide normalized extraction for text-like files, PDFs, and common Office documents, while unsupported binary formats returnextractionStatus: "unsupported".edit-text-fileapplies structured replace/append/prepend/overwrite operations and supportspreviewOnly.- Calendar tools return enriched organizer, attendee, location, online meeting, and body preview fields through
get-calendar-event-details. - Availability tools expose
allAttendeesAvailable, attendee counts, and a heuristicrankingScoreso agents can prefer slots where every required attendee is free. create-calendar-event-from-availabilitycreates the event in the best fully-available slot by default and only falls back to partial availability whenallowPartialAvailability=true.reschedule-calendar-eventreuses the same availability heuristic to move existing events instead of forcing the agent to manually patch times, and it avoids selecting the event's current slot as the "new" schedule.- Task tools add filtering, counts, and task context on top of the raw To Do endpoints.
- Mail tools now include thread/context wrappers plus direct send and draft wrappers for reply/reply-all/forward.
search-m365-contentreturns one normalized result surface across mail, chat messages, files, and SharePoint pages.- All new collection tools return
{ items, nextCursor? }.
SharePoint App Surface
The current SharePoint surface is read-oriented and retrieval-first. It is intended for app and agent workflows such as:
- finding a site
- listing document libraries
- browsing folders by path
- searching files within a site
- reading SharePoint-hosted file text and context
- searching SharePoint content and pages
- listing lists and list items for structured data scenarios
Recommended app-facing SharePoint/file entrypoints:
list-sitessearch-siteslist-document-librarieslist-foldersearch-site-filesget-file-textget-file-contextsearch-sharepoint-contentlist-sharepoint-pages
Operational list/data follow-ups:
list-sharepoint-listslist-sharepoint-list-items
This milestone is intentionally read-only for SharePoint pages and lists. File editing remains available through the existing path-first file tools.
SharePoint Examples
Find a site:
{
"query": "Marketing",
"limit": 10
}List a site's document libraries:
{
"siteId": "site-1",
"limit": 20
}Browse a library folder by path:
{
"siteId": "site-1",
"path": "/Shared Documents/Plans",
"limit": 50
}Search files inside one site and optional path prefix:
{
"siteId": "site-1",
"query": "launch plan",
"path": "/Shared Documents/Plans",
"limit": 25
}Read SharePoint-backed file text:
{
"siteId": "site-1",
"path": "/Shared Documents/Plans/launch.md",
"maxChars": 20000
}Search SharePoint content and pages:
{
"siteId": "site-1",
"query": "launch",
"limit": 25
}search-users
Input:
{
"query": "Juan Casiraghi",
"limit": 10
}Output:
{
"items": [
{
"userId": "user-123",
"displayName": "Juan Casiraghi",
"givenName": "Juan",
"surname": "Casiraghi",
"email": "[email protected]",
"userPrincipalName": "[email protected]",
"jobTitle": null,
"department": null,
"matchScore": 0.98,
"matchReasons": ["displayName_exact"]
}
]
}list-chat-members
Input:
{
"chatId": "19:[email protected]"
}Output:
{
"chatId": "19:[email protected]",
"members": [
{
"memberId": "membership-1",
"userId": "user-123",
"displayName": "Juan Casiraghi",
"email": "[email protected]",
"userPrincipalName": "[email protected]",
"roles": ["owner"],
"membershipType": "owner"
}
]
}get-chat-details
Input:
{
"chatId": "19:[email protected]",
"includeMembers": true
}Output:
{
"id": "19:[email protected]",
"chatType": "oneOnOne",
"topic": null,
"lastUpdatedDateTime": "2026-03-12T12:00:00Z",
"webUrl": "https://teams.microsoft.com/...",
"memberCount": 2,
"members": [
{
"userId": "user-123",
"displayName": "Juan Casiraghi",
"email": "[email protected]"
}
]
}find-chats-by-participant
Input:
{
"email": "[email protected]",
"includeGroupChats": true,
"limit": 10
}Output:
{
"items": [
{
"chat": {
"id": "19:[email protected]",
"chatType": "oneOnOne",
"topic": null,
"lastUpdatedDateTime": "2026-03-12T12:00:00Z",
"webUrl": "https://teams.microsoft.com/...",
"memberCount": 2
},
"members": [
{
"userId": "user-123",
"displayName": "Juan Casiraghi",
"email": "[email protected]"
}
],
"matchType": "exact_one_on_one",
"rank": 1,
"matchReasons": ["participant_exact", "one_on_one", "recent"]
}
]
}list-recent-chats
Input:
{
"limit": 20,
"includeMembers": true
}Output:
{
"items": [
{
"id": "19:[email protected]",
"chatType": "group",
"topic": "AI Leads",
"lastUpdatedDateTime": "2026-03-12T12:00:00Z",
"webUrl": null,
"memberCount": 4,
"members": [
{
"userId": "user-123",
"displayName": "Juan Casiraghi",
"email": "[email protected]"
}
]
}
],
"nextCursor": "opaque-cursor"
}If member expansion fails for a specific chat, that item is still returned with:
{
"members": [],
"membersUnavailable": true,
"membersError": "chat_members_unavailable"
}get-chat-context
Input:
{
"chatId": "19:[email protected]",
"messageLimit": 10
}Output:
{
"chat": {
"id": "19:[email protected]",
"chatType": "oneOnOne",
"topic": null,
"lastUpdatedDateTime": "2026-03-12T12:00:00Z",
"memberCount": 2
},
"members": [
{
"userId": "user-123",
"displayName": "Juan Casiraghi",
"email": "[email protected]"
}
],
"recentMessages": [
{
"id": "msg-1",
"from": {
"userId": "user-123",
"displayName": "Juan Casiraghi"
},
"createdDateTime": "2026-03-12T11:59:00Z",
"bodyPreview": "deploy prod tonight"
}
]
}search-messages
Input:
{
"query": "deploy prod",
"participantUserId": "user-123",
"scope": "chats",
"limit": 20
}Output:
{
"items": [
{
"chatId": "19:[email protected]",
"messageId": "msg-1",
"chatType": "group",
"topic": "AI Leads",
"from": {
"userId": "user-123",
"displayName": "Juan Casiraghi"
},
"createdDateTime": "2026-03-12T11:59:00Z",
"bodyPreview": "deploy prod tonight"
}
]
}Only scope: "chats" is currently supported.
resolve-person
Input:
{
"query": "Juan Casiraghi"
}Output is either a single normalized user object or an ambiguous_match / user_not_found error.
list-user-presence
Input:
{
"userIds": ["user-123", "user-456"]
}Output:
{
"items": [
{
"userId": "user-123",
"availability": "Available",
"activity": "Available",
"statusMessage": null
}
]
}list-channel-members
Input:
{
"teamId": "team-123",
"channelId": "channel-456"
}Output:
{
"teamId": "team-123",
"channelId": "channel-456",
"members": [
{
"userId": "user-123",
"displayName": "Juan Casiraghi",
"email": "[email protected]",
"roles": ["member", "owner"],
"membershipType": "owner",
"membershipOrigins": ["direct", "indirect"]
}
]
}For shared channels, duplicate memberships are deduplicated by identity and their origin hints are retained in
membershipOrigins.
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", "@hurtener/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.SharedorMail.Send.Sharedscopes - 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 [email protected]
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", "@hurtener/ms-365-mcp-server"]
}
}
}Work/School Account (Global)
{
"mcpServers": {
"ms365": {
"command": "npx",
"args": ["-y", "@hurtener/ms-365-mcp-server", "--org-mode"]
}
}
}Work/School Account (China 21Vianet)
{
"mcpServers": {
"ms365-china": {
"command": "npx",
"args": ["-y", "@hurtener/ms-365-mcp-server", "--org-mode", "--cloud", "china"]
}
}
}Claude Code CLI
Personal Account (MSA)
claude mcp add ms365 -- npx -y @hurtener/ms-365-mcp-serverWork/School Account (Global)
# macOS/Linux
claude mcp add ms365 -- npx -y @hurtener/ms-365-mcp-server --org-mode
# Windows (use cmd /c wrapper)
claude mcp add ms365 -s user -- cmd /c "npx -y @hurtener/ms-365-mcp-server --org-mode"Work/School Account (China 21Vianet)
# macOS/Linux
claude mcp add ms365-china -- npx -y @hurtener/ms-365-mcp-server --org-mode --cloud china
# Windows (use cmd /c wrapper)
claude mcp add ms365-china -s user -- cmd /c "npx -y @hurtener/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 and dynamic registration enabled:
npx @hurtener/ms-365-mcp-server --http --enable-dynamic-registrationIn 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: The
--enable-dynamic-registrationis required for Open WebUI to work. If using a custom Azure Entra app, add your redirect URI under "Mobile and desktop applications" platform (not "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 @hurtener/ms-365-mcp-server --http --enable-dynamic-registrationThen add connection with URL http://localhost:3000/mcp and ID ms-365.
Stateless HTTP Sidecars
Pooled HTTP sidecars should use the HTTP transport in stateless bearer-header mode.
- Run the server with
--http - Send MCP traffic to
/mcp - Include
Authorization: Bearer <Graph access token>on every request - Optionally include
x-microsoft-refresh-token: <refresh_token> - Do not rely on
MS365_MCP_OAUTH_TOKENfor sidecar execution - Sidecar pools may safely reuse the same process across users because auth is request-scoped
Example:
npx @hurtener/ms-365-mcp-server --http 3000 --org-modeContract details live in docs/stateless-http-sidecar.md.
Local Development
For local development or testing:
# From the project directory
claude mcp add ms -- npx tsx src/index.ts --org-modeOr configure Claude Desktop manually:
{
"mcpServers": {
"ms365": {
"command": "node",
"args": ["/absolute/path/to/ms-365-mcp-server/dist/index.js", "--org-mode"]
}
}
}Note: Run
npm run buildafter code changes to update thedist/folder.
Authentication
⚠️ You must authenticate before using tools.
The server supports three authentication methods:
1. Device Code Flow (Default)
For interactive authentication via device code:
- MCP client login:
- Call the
logintool (auto-checks existing token) - If needed, get URL+code, visit in browser
- Use
verify-logintool to confirm
- Call the
- CLI login:
Follow the URL and code prompt in the terminal.npx @hurtener/ms-365-mcp-server --login
Tokens are cached securely in your OS credential store (fallback to file).
2. HTTP Bearer Auth and OAuth Discovery
When running with --http, the server is designed for stateless per-request authentication.
Stateless sidecar path (recommended):
- Send requests directly to
/mcp - Include
Authorization: Bearer <token>on every request - Optionally include
x-microsoft-refresh-token - No session stickiness is required
- No
MS365_MCP_OAUTH_TOKENis required for sidecar operation
Generic MCP HTTP client path:
- The server still advertises OAuth capabilities for clients like Open WebUI
- OAuth discovery, token, authorization, and dynamic registration remain available
2a. OAuth Authorization Code Flow (HTTP mode only)
When running with --http, the server requires OAuth authentication:
npx @hurtener/ms-365-mcp-server --http 3000This 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 without promoting request tokens into shared process auth state
- 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.
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 for stdio or process-scoped usage:
MS365_MCP_OAUTH_TOKEN=your_oauth_token npx @hurtener/ms-365-mcp-serverThis method:
- Bypasses the interactive authentication flows
- Uses your pre-existing OAuth token for Microsoft Graph API requests
- Does not handle token refresh (token lifecycle management is your responsibility)
- Is not the preferred transport model for stateless HTTP sidecars
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 @hurtener/ms-365-mcp-server --login
# Follow the device code prompt, sign in as [email protected]
# Login second account
npx @hurtener/ms-365-mcp-server --login
# Follow the device code prompt, sign in as [email protected]List configured accounts:
npx @hurtener/ms-365-mcp-server --list-accountsUse in tool calls: Pass "account": "[email protected]" in any tool request:
{ "tool": "list-mail-messages", "arguments": { "account": "[email protected]" } }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.[email protected]) or MSALhomeAccountId.
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, use preset tool categories instead of loading all 90+ tools:
npx @hurtener/ms-365-mcp-server --preset mail
npx @hurtener/ms-365-mcp-server --list-presets # See all available presetsAvailable presets: mail, calendar, files, personal, work, excel, contacts, tasks, onenote, search, users, all
Experimental: --discovery starts with only 2 tools (search-tools, execute-tool) for minimal token usage.
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
--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)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-dynamic-registration Enable OAuth Dynamic Client Registration endpoint (required for Open WebUI)
--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 (experimental) Start with search-tools + execute-tool onlyEnvironment variables:
READ_ONLY=true|1: Alternative to --read-only flagENABLED_TOOLS: Filter tools using a regex pattern (alternative to --enabled-tools flag)MS365_MCP_ORG_MODE=true|1: Enable organization/work mode (alternative to --org-mode flag)MS365_MCP_FORCE_WORK_SCOPES=true|1: Backwards compatibility for MS365_MCP_ORG_MODEMS365_MCP_OUTPUT_FORMAT=toon: Enable TOON output format (alternative to --toon flag)MS365_MCP_BODY_FORMAT=html: Return email bodies as HTML instead of plain text (default: text)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 outputMS365_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)MS365_MCP_OAUTH_TOKEN: Pre-existing OAuth token for Microsoft Graph API in stdio/process-scoped BYOT modeMS365_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)
Token Storage
Authentication tokens are stored using the OS credential store (via keytar) when available. If keytar is not installed or fails (common on headless Linux), the server falls back to file-based storage.
Default fallback paths are relative to the installed package directory. This means tokens can be lost when the package is reinstalled or updated via npm.
To persist tokens across updates, set custom paths outside the package directory:
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.
Security note: File-based token storage writes sensitive credentials to disk. Ensure the chosen directory has appropriate access controls. The OS credential store (keytar) is preferred when available.
Azure Key Vault Integration
For production deployments, you can store secrets in Azure Key Vault instead of environment variables. This is particularly useful for Azure Container Apps with managed identity.
Setup
Create a Key Vault (if you don't have one):
az keyvault create --name your-keyvault-name --resource-group your-rg --location eastusAdd secrets to Key Vault:
az keyvault secret set --vault-name your-keyvault-name --name ms365-mcp-client-id --value "your-client-id" az keyvault secret set --vault-name your-keyvault-name --name ms365-mcp-tenant-id --value "your-tenant-id" # Optional: if using confidential client flow az keyvault secret set --vault-name your-keyvault-name --name ms365-mcp-client-secret --value "your-secret"Grant access to Key Vault:
For Azure Container Apps with managed identity:
# Get the managed identity principal ID PRINCIPAL_ID=$(az containerapp show --name your-app --resource-group your-rg --query identity.principalId -o tsv) # Grant access to Key Vault secrets az keyvault set-policy --name your-keyvault-name --object-id $PRINCIPAL_ID --secret-permissions get listFor local development with Azure CLI:
# Your Azure CLI identity already has access if you have appropriate RBAC roles az loginConfigure the server:
MS365_MCP_KEYVAULT_URL=https://your-keyvault-name.vault.azure.net npx @hurtener/ms-365-mcp-server
Secret Name Mapping
| Key Vault Secret Name | Environment Variable | Required | | ----------------------- | ----------------------- | ------------------------- | | ms365-mcp-client-id | MS365_MCP_CLIENT_ID | Yes | | ms365-mcp-tenant-id | MS365_MCP_TENANT_ID | No (defaults to 'common') | | ms365-mcp-client-secret | MS365_MCP_CLIENT_SECRET | No |
Authentication
The Key Vault integration uses DefaultAzureCredential from the Azure Identity SDK, which automatically tries multiple authentication methods in order:
- Environment variables (AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID)
- Managed Identity (recommended for Azure Container Apps)
- Azure CLI credentials (for local development)
- Visual Studio Code credentials
- Azure PowerShell credentials
Optional Dependencies
The Azure Key Vault packages (@azure/identity and @azure/keyvault-secrets) are optional dependencies. They are only loaded when MS365_MCP_KEYVAULT_URL is configured. If you don't use Key Vault, these packages are not required.
Contributing
We welcome contributions! Before submitting a pull request, please ensure your changes meet our quality standards.
Run the verification script to check all code quality requirements:
npm run verifyFor Developers
After cloning the repository, you may need to generate the client code from the Microsoft Graph OpenAPI specification:
npm run generateSupport
If you're having problems or need help:
- Create an issue
- Start a discussion
- For fork-specific behavior, prefer issues or discussions in this repository.
Maintainers
This fork builds on the original work by Softeria.
- Original upstream: Softeria / Eirik Brevik
- Fork maintainer: Hurtener
- Implementation and maintenance support: Santiago Benvenuto and OpenAI Codex
License
MIT © 2026 Hurtener
