m365-mcp
# m365-mcp
[](https://www.npmjs.com/package/@sam2kb/m365-mcp)
[](https://clawhub.ai/sam2kb/skills/m365-mcp)
**Production-grade Microsoft 365 MCP server** — Email, Calendar, Contacts, OneDrive, Teams, Tasks, and Users via delegated OAuth.
Combines delegated OAuth and multiple accounts with MCP tools, pagination, rate-limit handling, timezone-aware calendars, and TypeScript.
## Why this exists
This comparison reflects the code shipped in
[`mcp-microsoft365` v1.0.0](https://clawhub.ai/makhatib/skills/mcp-microsoft365)
and
[`office365-connector` v2.0.0](https://clawhub.ai/tirandagan/skills/office365-connector)
as reviewed on November 1, 2026. Both were the latest published versions at the time.
| Feature | `mcp-microsoft365` | `office365-connector` | **`m365-mcp`** |
|---|---|---|---|
| Auth flow | Client credentials (app-only) | Public-client device code | **Public-client device code** |
| Access model | Application permissions | Delegated (signed-in user) | **Delegated (signed-in user)** |
| Client secret | Required | Stored by setup; unused in OAuth | **None** ✅ |
| MCP server | ✅ | ❌ (CLI scripts) | ✅ |
| Multi-account | ❌ | ✅ | ✅ |
| Pagination | ❌ | ❌ | ✅ |
| Graph 429 retries | ❌ | ❌ (not implemented in shipped code) | ✅ |
| Read-only mode | ❌ | ❌ | ✅ |
| Email | Read/search/send | Read/search/send/reply | Read/search/send/reply/manage |
| Custom Reply-To | ❌ | Helper function only | ✅ |
| Calendar | Read/create/free-busy | Read/cancel | Read/create/update/cancel/decline/free-busy |
| Contacts | ❌ | ❌ (documented, not shipped) | ✅ |
| OneDrive | ✅ | ❌ | ✅ |
| Teams | ✅ | ❌ | ✅ |
| Tasks | ✅ | ❌ | ✅ |
| Users | ✅ | ❌ | ✅ |
| Implementation | TypeScript | JavaScript CLI | TypeScript |
| Interface coverage | 19 MCP tools | CLI scripts | **47 MCP tools** |
`office365-connector` documents contacts, attachments, and calendar creation,
but those operations are absent from its published scripts. Its custom Reply-To
option is available through the exported send helper, not its CLI.
`m365-mcp` does not store a client secret or password. Tenant and client IDs are
non-secret identifiers; sensitive OAuth access and refresh tokens are stored
locally as permission-restricted plaintext files. See [Security](#security) for
the storage and revocation details.
## Scope
The app uses delegated permissions. Microsoft Graph limits access to the granted scopes and what the signed-in user can access, including shared resources where permitted.
- `Mail.Read` / `Mail.ReadWrite` / `Mail.Send`
- `Calendars.Read` / `Calendars.ReadWrite`
- `Contacts.Read` / `Contacts.ReadWrite`
- `People.Read` (relevance-ranked people search)
- `Files.Read.All` / `Files.ReadWrite` (read accessible files and create folders in your drive)
- `Tasks.Read` / `Tasks.ReadWrite` (To Do)
- `Chat.Read` / `Chat.ReadWrite` (Teams)
- `User.Read` + `offline_access`
These are delegated scopes, not tenant-wide application permissions. Set
`M365_MCP_READ_ONLY=true` before authentication and when running the server to
request only the read variants and disable mutating MCP tools.
Organization-wide mail application permissions such as `Mail.Read.All` are not used.
## Setup
### 1. Create Azure Entra ID App Registration
1. Go to [Azure Portal → App registrations](https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade)
2. **New registration**
- Name: `m365-mcp`
- Supported account types: **Single tenant** (or multi-tenant if you manage your own tenant)
- Redirect URI: leave this blank; device-code flow does not use one
3. Click **Register**
4. Go to **Authentication** → **Advanced settings**
5. Set **"Allow public client flows"** to **Yes**, then click **Save**
> This is required for device-code OAuth. Do not create or configure a client
> secret; `m365-mcp` is a public client.
### 2. Add API Permissions
Under **API Permissions** → **Add a permission** → **Microsoft Graph** → **Delegated permissions**:
```
offline_access
User.Read
Mail.Read
Mail.ReadWrite
Mail.Send
Calendars.Read
Calendars.ReadWrite
Contacts.Read
Contacts.ReadWrite
People.Read
Files.Read.All
Files.ReadWrite
Tasks.Read
Tasks.ReadWrite
Chat.Read
Chat.ReadWrite
```
Click **Grant admin consent** (or each user will consent individually during login).
> Organization-wide user listing and lookup require delegated `User.Read.All` with admin consent. To use these tools, add that permission to the registration and set `M365_MCP_DIRECTORY_READ=true` during authentication and server startup. This broader scope is off by default.
### 3. Install
Install the published command-line tools:
```bash
npm install --global @sam2kb/m365-mcp
```
Or build from source:
```bash
# Clone
git clone https://github.com/sam2kb/m365-mcp.git
cd m365-mcp
# Install dependencies
npm install
# Build
npm run build
```
### 4. Add Your Account & Authenticate
```bash
m365-mcp-auth add work <tenant-id> <client-id> you@company.com "Work account"
m365-mcp-auth login --account=work
```
> **`AADSTS7000218` or a missing `client_secret` / `client_assertion` error:**
> Microsoft is treating the registration as a confidential client. Confirm that
> the configured Application (client) ID belongs to the registration you edited,
> then return to **Authentication** and verify **Allow public client flows** is
> saved as **Yes**. Do not work around this error by adding a client secret.
When running from a source checkout, use `node dist/auth-cli.js` instead of
`m365-mcp-auth`.
Follow the on-screen URL + code to sign in. Access and refresh tokens are stored
as plaintext JSON in `~/.m365-mcp/auth/` by default, protected with directory
mode `0700` and file mode `0600` where supported. Set `M365_MCP_AUTH_DIR` to
use another protected location.
### 5. Configure an MCP Client
The server uses standard MCP over stdio and works with any MCP client (Claude Desktop, Cursor, Continue, etc.).
#### OpenClaw
For the global npm installation, add this to your mcporter config at `~/.openclaw/mcporter.json`:
```json
{
"mcpServers": {
"m365": {
"command": "m365-mcp",
"args": [],
"env": {
"M365_ACCOUNT": "work",
"M365_TIMEZONE": "America/Chicago"
}
}
}
}
```
Or via mcporter CLI:
```bash
mcporter config add m365 --stdio "m365-mcp" \
--env M365_ACCOUNT=work \
--env M365_TIMEZONE=America/Chicago
```
For a source checkout, use `"command": "node"` with
`"args": ["/absolute/path/to/m365-mcp/dist/index.js"]`.
Then restart OpenClaw for the server to load.
#### Other MCP Clients
Use the same JSON config in your client's MCP server configuration — `m365-mcp` is a standard stdio MCP server with no client-specific requirements.
## Multi-Account
```bash
# Add more accounts
node dist/auth-cli.js add personal <tenant2> <client2> you@outlook.com "Personal"
node dist/auth-cli.js add client <tenant3> <client3> you@client.com "Consulting"
# Authenticate each
node dist/auth-cli.js login --account=personal
node dist/auth-cli.js login --account=client
# Set default
node dist/auth-cli.js default work
# List
node dist/auth-cli.js list
```
Each account needs its own App Registration in its respective tenant. Tokens are isolated per account.
Changing an account's tenant or client ID clears that account's cached tokens.
Sign in again after changing permissions or the read-only/directory settings.
Login checks cached permissions before reusing a token.
## Available Tools (47)
### 📧 Mail (9 tools)
| Tool | Description |
|---|---|
| `m365_mail_list` | List emails (folder, filter, search, paginated) |
| `m365_mail_read` | Read an email by ID, with the body limited to 10,000 characters |
| `m365_mail_send` | Send email (to/cc/bcc, optional Reply-To, HTML or plain) |
| `m365_mail_reply` | Reply / reply-all to an email |
| `m365_mail_search` | Search emails across folders |
| `m365_mail_move` | Move email to another folder |
| `m365_mail_delete` | Delete email |
| `m365_mail_mark_read` | Mark as read/unread |
| `m365_mail_folders` | List all mail folders with counts |
Set `replyTo` on `m365_mail_send` to a single email address when replies should
go to a different inbox. This sets the message's Reply-To address; it does not
change the signed-in sender or grant access to the reply mailbox.
### 📅 Calendar (10 tools)
| Tool | Description |
|---|---|
| `m365_calendar_list` | Events in a date range (paginated) |
| `m365_calendar_get` | Read an event and its notes, with bounded output and truncation status |
| `m365_calendar_today` | Today's events, nicely formatted |
| `m365_calendar_week` | Week view grouped by day |
| `m365_calendar_create` | Create event (optional Teams meeting) |
| `m365_calendar_update` | Update event |
| `m365_calendar_delete` | Legacy alias for organizer cancellation |
| `m365_calendar_cancel` | Cancel an event you organize and notify attendees |
| `m365_calendar_decline` | Decline an invitation as an attendee |
| `m365_calendar_availability` | Free/busy lookup |
#### Event notes and specific calendars
`m365_calendar_list`, `m365_calendar_get`, `m365_calendar_create`, and
`m365_calendar_update` accept an optional `calendarId` for a calendar under the
signed-in user's `/me/calendars` collection. Omitting it preserves the existing
`/me/events` or `/me/calendarView` behavior. IDs are opaque values, not URLs.
Use `m365_calendar_get` with `eventId` to read notes. It returns
`body: { contentType, content }`, `bodyLength`, and `bodyTruncated`. The default
limit is 10,000 UTF-16 code units; `maxBodyChars` accepts integers from 0 to 50,000.
Zero returns only body metadata. The tool preserves the format returned by
Graph, which defaults to HTML; truncated HTML may be incomplete. List results remain
compact and do not include full notes. Do not mistake truncated notes for the
complete event body or write the truncated result back as a replacement.
Create and update accept `bodyContentType: "text" | "html"` when supplying
`body`; the default remains `"html"`. On update, omitting `body` leaves notes
unchanged, while `body: ""` explicitly clears them. A body update replaces the
existing notes; for online meetings, preserve the meeting details in that body.
These operations use the existing delegated `Calendars.Read` /
`Calendars.ReadWrite` permissions, with no additional OAuth scopes. The new get
tool is available in read-only mode; create and update remain blocked there.
Calendar listings include `isOrganizer` and `responseStatus` so clients can
choose the correct action. Cancellation is organizer-only and notifies attendees;
decline is attendee-only. There is no ambiguous silent-remove tool.
### 👤 Contacts (12 tools)
| Tool | Description |
|---|---|
| `m365_contacts_list` | List by folder and filter by one or more exact categories (any/all) |
| `m365_contacts_search` | Search by name, email, or company |
| `m365_contacts_read` | Full contact details |
| `m365_contacts_create` | Create with multiple emails, phones, categories, addresses, and work or personal details |
| `m365_contacts_update` | Update or clear contact fields and category assignments |
| `m365_contacts_delete` | Move a contact to Deleted Items |
| `m365_contacts_categories_list` | List unique categories in the default or selected folder with usage counts |
| `m365_contacts_folders_list` | List top-level or child contact folders |
| `m365_contacts_folder_create` | Create a child contact folder |
| `m365_contacts_folder_update` | Rename or move a contact folder |
| `m365_contacts_folder_delete` | Delete a non-default contact folder |
| `m365_contacts_people_list` | Fuzzy-search relevance-ranked recipients from mailbox signals, the organization directory, or both; filter identity type and optionally include profile details |
Outlook categories are tags stored on each contact; contact folders are containers. Category filtering is exact and case-insensitive, supports any/all matching, and can be combined with folder selection. Folder tools manage top-level folders and one direct child level, keeping the interface predictable.
The People API is intended for people-picker scenarios. `m365_contacts_people_list`
defaults to mailbox-relevant people and can target the organization directory or
both sources. Results include the primary email relevance score; optional profile
details add company, department, job title, office, and phones. Filters distinguish
organization users, personal contacts, and groups. The API does not expose private
Outlook Contact Lists or their membership.
### 📁 OneDrive (5 tools)
| Tool | Description |
|---|---|
| `m365_files_list` | List files/folders (paginated) |
| `m365_files_search` | Search files |
| `m365_files_read` | Read text file content |
| `m365_files_info` | File/folder metadata |
| `m365_files_create_folder` | Create folder |
Text reads default to 50,000 bytes and accept `maxBytes` from 0 to 1,000,000.
The downloader stops reading at that limit, including when the server returns a larger file.
### 💬 Teams (3 tools)
| Tool | Description |
|---|---|
| `m365_teams_chats` | List your chats |
| `m365_teams_messages` | Get chat messages |
| `m365_teams_send` | Send chat message |
### ✅ Tasks (5 tools)
| Tool | Description |
|---|---|
| `m365_tasks_lists` | List To Do lists |
| `m365_tasks_list` | List tasks in a list |
| `m365_tasks_create` | Create task |
| `m365_tasks_update` | Update or complete a task |
| `m365_tasks_delete` | Delete task |
### 👥 Users (3 tools)
| Tool | Description |
|---|---|
| `m365_users_list` | List org users |
| `m365_users_profile` | Get user profile |
| `m365_users_manager` | Get user's manager |
## Development
Use Node.js 20 or later for the development test runner. CI tests on Linux and Windows with Node.js 24.
```bash
npm install
npm run dev # run once from TypeScript source
npm run build # compile TypeScript
```
## Security
- **Delegated OAuth** — the app acts as the authenticated user; it does not use
application credentials or tenant-wide mail permissions.
- **Device code flow** — you never type your password into anything but Microsoft's login page.
- **Sensitive reads** — mail, files, calendar entries, contacts, Teams messages,
tasks, and user profiles can enter the MCP client's model context.
- **Real side effects** — send, reply, move, create, update, cancel, decline, and delete tools
change Microsoft 365 data. Configure your MCP client to require explicit user
approval before it invokes them.
- **MCP safety annotations** — every tool declares read-only, destructive,
idempotent, and open-world hints for clients that enforce tool policies.
- **Enforced read-only mode** — set `M365_MCP_READ_ONLY=true` both when
authenticating and running the server. The auth flow requests read-only Graph
scopes, mutating tools are omitted from discovery, and direct calls are blocked.
- **Local token storage** — access and refresh tokens are plaintext JSON protected
by `0700` directories and `0600` files where supported. Protect
`~/.m365-mcp/auth/` or your configured `M365_MCP_AUTH_DIR`.
- **Auto-refresh** — tokens refreshed before expiry, expired refresh tokens trigger re-auth.
- **Network destinations** — OAuth uses `login.microsoftonline.com`; API calls use `graph.microsoft.com`. File reads also follow HTTPS download URLs supplied by Graph, such as OneDrive and SharePoint URLs. The Graph bearer token is attached only to Graph-origin requests. The server has no telemetry or analytics.
Reads retry temporary failures and honor `Retry-After` for 429 and 503 responses.
Writes retry explicit throttling responses, but connection failures or server errors
can leave their outcome uncertain. Check the target before trying those writes again.
To delete a local token, remove its account:
```bash
m365-mcp-auth remove work
```
If a token or device may be compromised, also revoke the application's consent
from the Microsoft account or Entra ID portal. Re-authenticate after changing
`M365_MCP_READ_ONLY` so the stored token reflects the intended scope set.
## License
MIT
TDQS
Scored across 47 tools
Most tools target clearly distinct resources and actions, and descriptions explicitly cross-reference alternatives such as mail list vs. search vs. read. The main ambiguity is the deliberate alias m365_calendar_delete for m365_calendar_cancel, plus some closely related calendar/folder operations, but the descriptions make the intended boundaries clear.
The set largely follows a predictable m365_<domain>_<action> convention, e.g. m365_mail_read, m365_contacts_create, m365_files_search. There are minor singular/plural inconsistencies such as m365_contacts_folders_list vs. m365_contacts_folder_create and m365_tasks_lists vs. m365_tasks_list, but the pattern remains readable.
47 tools is very heavy for a single MCP server and exceeds the range where each tool is easy to keep in view. The breadth is partly justified by covering mail, calendar, contacts, files, Teams, tasks and users, but the surface could be consolidated or split into domain-specific servers.
Mail, calendar, contacts and tasks have broad lifecycle coverage, but files and Teams have notable gaps: files support browse/read/create-folder but not upload, download, delete or move, and Teams supports chats/messages/send but not channels or chat creation. Agents can work around some gaps, but key workflows in those domains will dead-end.