@teamnetwork-nz/m365-mcp-server
v1.1.1
Published
MCP server for Microsoft 365 — Email, Calendar, To Do, and SharePoint document libraries — using client credentials (app-only auth). Supports multiple accounts, shared mailboxes, per-mailbox read-only access, and grant-based SharePoint access control.
Downloads
711
Maintainers
Readme
@teamnetwork-nz/m365-mcp-server
A Model Context Protocol (MCP) server for Microsoft 365 — Email, Calendar, To Do, and SharePoint document libraries — using app-only (client credentials) authentication — no user login flows required.
Supports multiple accounts, shared mailboxes, per-mailbox read-only access control, folder navigation, date/content filtering, and grant-based SharePoint access control.
Prerequisites
- Node.js 18+
- A Microsoft 365 tenant with an Azure AD app registration configured for app-only access (see Admin Setup below)
Installation
npm install -g @teamnetwork-nz/m365-mcp-serverOr run directly without installing:
npx @teamnetwork-nz/m365-mcp-serverConfiguration
Create a JSON config file (e.g. m365-config.json) based on the example below. Keep this file secure — it contains your client secret.
{
"connection": {
"tenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"clientSecret": "your-client-secret"
},
"accounts": [
{
"id": "main",
"displayName": "Main Organisation",
"mailboxes": [
{ "email": "[email protected]", "readonly": false },
{ "email": "[email protected]", "readonly": true, "displayName": "Shared (read-only)" }
]
},
{
"id": "partner",
"displayName": "Partner Tenant",
"tenantId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
"clientId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
"clientSecret": "partner-secret",
"mailboxes": [
{ "email": "[email protected]", "readonly": false }
]
}
]
}Config notes
- The top-level
connectionblock provides default credentials for all accounts. - Each account can override
tenantId,clientId, and/orclientSecretindividually — useful for multi-tenant setups. readonly: truemailboxes block all write operations (send, reply, move, create/update events) at the server level.
Set the path to your config via the M365_CONFIG_PATH environment variable:
export M365_CONFIG_PATH=/path/to/m365-config.jsonDatetime & Timezone Handling
All datetime inputs and outputs are automatically converted to and from your local timezone — you never need to think about UTC.
Input
Supply datetimes in your local time using ISO 8601 format. A timezone offset is optional:
2024-01-15T09:00:00 # naive — interpreted as your configured local timezone
2024-01-15T09:00:00+13:00 # explicit offset — converted to the configured timezoneOutput
All dates returned by the server (email receivedDateTime, event start/end, etc.) are expressed in the configured timezone with their UTC offset, e.g. 2024-01-15T09:00:00+13:00.
Configuration
Set DATETIME_FORMAT in your environment:
| Value | Behaviour |
|---|---|
| local (default) | Use the server process's system local timezone |
| Any IANA timezone name | Use that timezone, e.g. Pacific/Auckland, America/New_York |
| UTC | No conversion — all datetimes are passed through in UTC as-is |
export DATETIME_FORMAT=Pacific/AucklandOr in your Claude Desktop MCP config:
{
"mcpServers": {
"m365": {
"command": "npx",
"args": ["@teamnetwork-nz/m365-mcp-server"],
"env": {
"M365_CONFIG_PATH": "/path/to/m365-config.json",
"DATETIME_FORMAT": "Pacific/Auckland"
}
}
}
}Claude Desktop / Claude Code Setup
Add the following to your Claude Desktop config (claude_desktop_config.json):
{
"mcpServers": {
"m365": {
"command": "npx",
"args": ["@teamnetwork-nz/m365-mcp-server"],
"env": {
"M365_CONFIG_PATH": "/path/to/m365-config.json"
}
}
}
}If installed globally, you can use the m365-mcp command instead:
{
"mcpServers": {
"m365": {
"command": "m365-mcp",
"env": {
"M365_CONFIG_PATH": "/path/to/m365-config.json"
}
}
}
}Default Mailbox Behaviour
All email and calendar tools accept optional accountId and mailbox parameters. When either is omitted:
accountIddefaults to the first account in the config file.mailboxdefaults to the first mailbox listed under the resolved account.
Config file ordering is the source of truth — put your primary account and mailbox first. Use list_mailboxes to see which account and mailbox are marked isDefault: true.
Operations always target exactly one mailbox per request, either the default or a specific one provided by name.
Available Tools
| Tool | Description |
|---|---|
| list_mailboxes | List configured accounts and mailboxes with read/write status |
| list_folders | List mail folders (top-level or child folders) |
| list_messages | List messages with optional filters: date range, subject, body keyword, sender |
| get_message | Get full message details. Set includeBody, includeAttachments, or includeInternetMessageHeaders as needed |
| send_message | Send an email (non-readonly mailboxes only) |
| reply_message | Reply or reply-all to a message (non-readonly mailboxes only) |
| move_message | Move a message to another folder (non-readonly mailboxes only) |
| list_message_attachments | List all attachments on a message (file, item, and reference types) |
| download_attachment | Download a file attachment as base64. Images returned as MCP image content; reference attachments return their preview URL |
Calendar
| Tool | Description |
|---|---|
| list_calendars | List calendars for a mailbox |
| list_events | List events in a date range (recurring events expanded into instances) |
| get_event | Get full event details |
| create_event | Create a new event (non-readonly mailboxes only) |
| update_event | Update an existing event (non-readonly mailboxes only) |
To Do
| Tool | Description |
|---|---|
| list_todo_lists | List all To Do lists for a mailbox. The default list has wellknownListName: "defaultList" |
| list_todo_tasks | List tasks in a To Do list with optional status and importance filters |
| get_todo_task | Get full details of a specific task |
| create_todo_task | Create a new task (non-readonly mailboxes only) |
| update_todo_task | Update a task — title, body, status, importance, due date, reminder (non-readonly mailboxes only) |
| delete_todo_task | Permanently delete a task (non-readonly mailboxes only) |
| list_linked_resources | List all linked resources (external back-references) attached to a task |
| get_linked_resource | Get a specific linked resource |
| create_linked_resource | Attach a linked resource to a task — set externalId for reverse lookup, webUrl for a one-click link back to the source item |
| update_linked_resource | Update a linked resource (non-readonly mailboxes only) |
| delete_linked_resource | Remove a linked resource from a task (non-readonly mailboxes only) |
SharePoint
| Tool | Description |
|---|---|
| list_sharepoint_libraries | List configured SharePoint libraries with their grant definitions |
| list_drive_items | List files and folders at a path — always permitted, no grant required |
| get_drive_item | Get metadata for a file or folder — always permitted, no grant required |
| check_sharepoint_access | Check whether an operation is permitted by the grant config (no network call, never errors) — use before content operations |
| download_file | Download a file as base64. Images returned as MCP image content; others as a JSON payload with fileName, mimeType, encoding, and data. Requires read grant |
| upload_file | Upload a file (base64 content, max 4 MB). Creates or overwrites. Requires create or update grant |
| create_folder | Create a new folder. Requires create grant on the parent path |
| delete_drive_item | Permanently delete a file or folder. Requires delete grant |
| move_drive_item | Move a file or folder, optionally renaming. Requires update at source and create at destination |
| search_drive | Full-text search within a library (only for libraries with searchAllowed: true). Only results passing all three grant axes (path + file + operation) are returned — everything else is silently dropped |
SharePoint grant model
Access to file content and write operations is controlled by grants defined per library. Each grant combines three axes — all three must match for access to be allowed:
| Axis | Description |
|---|---|
| allowedPaths | Which folder paths are in scope. [] = nothing; ["*"] = any path; ["/Finance"] = exact folder only; ["/Finance/*"] = folder and all descendants |
| allowedFiles | Which filenames are allowed. [] = nothing; ["*"] = any file; ["*.pdf", "*.xlsx"] = glob patterns. If omitted, inherits globalAllowedFiles |
| allowedOperations | Which operations are permitted: read, create, update, delete, or *. If omitted, inherits globalAllowedOperations |
globalAllowedFiles and globalAllowedOperations are library-level defaults inherited by grants that omit those fields. An explicit grant value (even []) always overrides the library default. The defaults are a convenience — they are not a ceiling; grants may expand beyond them.
list_drive_items and get_drive_item bypass grant evaluation entirely — always permitted.
"sharepoint": [
{
"id": "company-docs",
"displayName": "Company Documents",
"siteId": "contoso.sharepoint.com,aaa-guid,bbb-guid",
"driveId": "b!abc123...",
"searchAllowed": true,
"globalAllowedOperations": ["read"],
"globalAllowedFiles": ["*.pdf"],
"grants": [
{
"name": "Finance spreadsheets and PDFs",
"description": "Read and create Excel and PDF files in Finance and subfolders",
"allowedPaths": ["/Finance", "/Finance/*"],
"allowedFiles": ["*.xlsx", "*.pdf"],
"allowedOperations": ["read", "create"]
},
{
"name": "Reports PDFs (inherited defaults)",
"description": "Read-only PDF access in /Reports — inherits globalAllowedFiles and globalAllowedOperations",
"allowedPaths": ["/Reports", "/Reports/*"]
}
]
}
]Finding your siteId and driveId
You can find these values in the Azure Portal → SharePoint admin centre, or by making a Graph API call:
GET https://graph.microsoft.com/v1.0/sites/{hostname}:/sites/{sitePath}
GET https://graph.microsoft.com/v1.0/sites/{siteId}/drivesThe siteId has the format hostname,site-guid,web-guid. The driveId is the id field on the default document library drive.
Admin Setup
Before the server can access mailboxes, a tenant admin must complete the following steps.
1. Register an Azure AD Application
In the Azure Portal → App registrations → New registration:
- Set a name (e.g.
M365 MCP Server) - Note the Application (client) ID and Directory (tenant) ID
- Under Certificates & secrets, create a client secret and note the value
Grant the following Application permissions (not Delegated) and click Grant admin consent:
| Permission | Required for |
|---|---|
| Mail.Read | Reading email |
| Mail.ReadWrite | Moving messages |
| Mail.Send | Sending and replying |
| Calendars.Read | Reading calendar events |
| Calendars.ReadWrite | Creating and updating events |
| Tasks.ReadWrite.All | Reading and writing To Do lists and tasks |
| Sites.Read.All | Reading SharePoint sites, drives, and file metadata |
| Sites.ReadWrite.All | Uploading files, creating folders, deleting, and moving items in SharePoint |
2. Restrict Mailbox Access (Recommended)
By default, the above permissions grant access to all mailboxes in the tenant. Use an ApplicationAccessPolicy in Exchange Online to restrict access to only the mailboxes in your config.
Run the following in Exchange Online PowerShell:
# Create a mail-enabled security group and add the allowed mailboxes
New-DistributionGroup -Name "MCP-Allowed-Mailboxes" -Type Security
Add-DistributionGroupMember -Identity "MCP-Allowed-Mailboxes" -Member "[email protected]"
Add-DistributionGroupMember -Identity "MCP-Allowed-Mailboxes" -Member "[email protected]"
# Restrict the app to only those mailboxes
New-ApplicationAccessPolicy `
-AppId "<your-client-id>" `
-PolicyScopeGroupId "MCP-Allowed-Mailboxes" `
-AccessRight RestrictAccess `
-Description "Restrict MCP server to configured mailboxes"
# Verify (should return Granted for allowed mailboxes)
Test-ApplicationAccessPolicy -AppId "<your-client-id>" -Identity "[email protected]"Note: Policy changes can take up to 1 hour to propagate. Shared mailboxes must be added to the group directly.
Security
- Credentials are never logged. The server scrubs secrets, tokens, and email body content from all log output.
- All Graph API calls use the
/users/{email}/...path (application permissions), never/me/. - Write operations are blocked at the server level for any mailbox marked
readonly: truein the config — this check happens before any network call is made. - The config file should be kept outside your project directory and never committed to source control. Use restrictive file permissions (
chmod 600on Linux/macOS).
License
MIT
