Skip to main content
Glama
usedrobot

Microsoft 365 remote MCP server

by usedrobot

Microsoft 365 remote MCP server

A remote MCP server that gives Claude access to Microsoft 365 — mail, calendar, files, and Teams chat — with per-user identity. Each person signs in through Microsoft as themselves, and the server exchanges that sign-in for a Graph token via the On-Behalf-Of flow, so every call runs with exactly that person's permissions. It does not hold one app identity with standing access to the whole tenant: if a user cannot see a message, neither can the server acting for them.

Built for and running in production at a small nonprofit.

Design stance

  • Acts as the user, not as the app. Sign-in is a PKCE proxy to Microsoft Entra, and every Graph call goes through On-Behalf-Of exchange of the caller's own token. The server never holds standing access to anyone's mail — no application permissions, no service-account mailbox, nothing to steal that would read a mailbox on its own.

  • Drafts only. Mail.Send is not requested, so it cannot be granted. The model composes; a human opens the draft and presses send. A compromised prompt or a confused model can at worst leave an unwanted draft in your Drafts folder. This is a deliberate boundary, not a missing feature.

  • Tenant-pinned. common and organizations are rejected at startup, and the tenant must parse as a GUID. A wildcard tenant would let Entra accept tokens issued by any organization — the single most damaging misconfiguration available on a public endpoint, so it is made unreachable rather than documented as a caution.

Related MCP server: Microsoft 365 MCP Server

Architecture

User → Claude → /authorize → Microsoft Entra (login + consent)
Claude → /token  (two-leg PKCE proxy → Entra)          ← server holds the confidential client
Claude → /mcp  (Bearer = user token, aud = this app)
   └─ verify.ts: validate JWT (sig/iss/aud/exp + tenant pin + scope)
   └─ obo.ts: exchange for a Graph token (acts as the user)
   └─ tools.ts: call Microsoft Graph

Entra supports neither Dynamic Client Registration nor Client ID Metadata Documents, which is why this server is itself the OAuth authorization server Claude talks to: it exposes /register and brokers every flow down to one pre-registered Entra confidential client.

Setup

This is not a two-minute install, and pretending otherwise would waste your time. Before it will start you need:

  • an Entra app registration in your own tenant, exposing an access_as_user scope, with a client secret;

  • tenant-admin consent on eleven delegated Graph permissions (Mail.Send is not among them, by design);

  • a public HTTPS origin Claude can reach — Claude connects to your server over the internet, so localhost is not enough outside of npm run inspector.

docs/entra-setup.md walks the registration through from scratch, including which permissions to grant and why.

Config

Copy .env.example to .env and fill it in.

Var

Required

Notes

M365_CLIENT_ID

Client id of your delegated Entra app

M365_CLIENT_SECRET

Confidential-client secret (required by OBO)

M365_TENANT_ID

Tenant GUID. common/organizations are rejected at startup.

M365_PUBLIC_URL

Public HTTPS origin Claude reaches, no trailing slash

M365_ORG_NAME

optional

Your organization's name, used in the server instructions

M365_EXTRA_INSTRUCTIONS

optional

House-style notes appended to the server instructions (tone, signatures, place names)

M365_EVENTS_LIBRARY_PATH

optional

SharePoint library the default file-search scope narrows to; unset searches the whole tenant

M365_READ_ONLY

optional

true disables every write tool

M365_DEFAULT_TZ

optional

IANA zone, default America/New_York

M365_REDACT_PII

optional

true scrubs tokens, secrets, and email addresses from logs

M365_ALLOWED_REDIRECT_URIS

optional

Comma-separated exact /authorize redirect_uri allowlist. Defaults to https://claude.ai/api/mcp/auth_callback; set this only if a different client connects.

M365_TRUST_PROXY

optional

Default off. Set to 1 (or an Express hop count) only when running behind a reverse proxy you control, so X-Forwarded-For is trusted. With the port published directly, leaving this off is what keeps the rate limits enforceable.

M365_CORS_ORIGIN

optional

CORS origin allowed to call this server, default https://claude.ai

M365_GRAPH_TIMEOUT_MS

optional

Microsoft Graph request timeout in milliseconds, default 30000

PORT

optional

default 3000

Run

npm install
npm run build
M365_CLIENT_ID=… M365_CLIENT_SECRET=… M365_TENANT_ID=<guid> M365_PUBLIC_URL=https://… npm start

Local tool inspection over stdio, without a public host:

npm run inspector

The included Dockerfile is a plain multi-stage Node build that ships to any container host. No platform deploy configuration is included on purpose — where you run it is your decision, and a checked-in deploy manifest would only encode someone else's.

Tools

Read: search_email, get_email, search_calendar, get_event, find_meeting_times, search_files, get_file, search_teams_chat — with natural-language date parsing and offset pagination. Delegated/shared access via mailboxOwner / calendarOwner is supported by the four mailbox and calendar tools — search_email, get_email, search_calendar, get_event. find_meeting_times, search_files, get_file, and search_teams_chat always act as the signed-in user.

Write (all gated behind M365_READ_ONLY): draft_email, draft_reply, create_event, update_event, cancel_event. Drafts are auto-formatted — agent hard-wraps are healed so prose does not render as a frozen narrow column on mobile — and writes honour the same delegated mailboxOwner / calendarOwner access as the reads.

There is no send tool. See Drafts only, above.

Provenance and security

The auth and transport plumbing is adapted from softeria/ms-365-mcp-server (MIT). Every copied file was reviewed line-by-line before inclusion; the attribution is in NOTICE.md and the file-by-file review in docs/copied-code-audit.md.

Hardening applied over upstream:

  • Tenant-pinned config — the tenant is required and validated as a GUID; upstream defaults to common.

  • Full local JWT validation — signature, issuer, audience, expiry, tenant, and scope are checked against Entra's JWKS with jose before any Graph call. Upstream did an expiry-only pre-check and let the downstream Graph call be the real gate.

  • OBO only — no trust-proxy or token pass-through mode.

  • Curated tool surface — a deliberately chosen set of tools rather than a generated wrapper over the whole Graph API, with no send capability anywhere in it.

Licensed MIT — see LICENSE.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Let ChatGPT, Claude & Cursor use your Mac: email, calendar, iMessage, Teams, files. Local, free.

  • Connect Claude to Fathom meeting recordings, transcripts, and summaries

  • WHOOP recovery, strain, sleep and workouts in Claude via official WHOOP OAuth. Free, open source.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/usedrobot/m365-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server