mcp-server-microsoft365
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-server-microsoft365create a new user for Jane Doe with Microsoft 365 Business Basic license"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-server-microsoft365
TypeScript MCP server for Microsoft 365 / Entra ID via Microsoft Graph. It runs app-only (client-credentials) and is intentionally boring good: typed, documented, read-first, least-privilege, credential-sane, and idempotent where Graph allows it. Its focus is identity provisioning — create a user, assign a license, manage group membership — plus the supporting reads you need to do that safely.
Disclaimer: This is an independent, unofficial project by Borgels. Borgels is not affiliated with, endorsed by, or supported by Microsoft. "Microsoft 365", "Microsoft Graph", and "Entra ID" are referenced only to describe what this server talks to. You need your own Entra ID app registration, and use of Microsoft Graph is subject to Microsoft's own terms.
Scope
Read tools (enabled by default):
get_user,list_userslist_subscribed_skus(license lookup and seat availability)list_groups,get_group,list_group_membersget_user_license_details
Write tools (disabled by default):
create_userupdate_user(PATCH attributes on an existing user, incl.accountEnabledto enable/disable — e.g. activate a pre-created hire or disable a leaver)set_managerassign_license,remove_licenseadd_group_member,remove_group_memberset_usage_locationcreate_temporary_access_pass(passwordless first sign-in / MFA setup; regenerated until alphanumeric),delete_temporary_access_passactivate_pim_role(just-in-time PIM self-activation of an eligible directory role, e.g. Authentication Administrator for TAP — delegated mode only)
Related MCP server: Microsoft 365 Core MCP Server
Authentication modes
The server authenticates to Graph in one of three ways, chosen by which options are set (never as tool arguments):
App-only (client credentials) —
MS_TENANT_ID+MS_CLIENT_ID+MS_CLIENT_SECRET. Acts as the app; requires the application permissions below.Delegated (on-behalf-of a user) — a
refreshTokenoption /MS_REFRESH_TOKEN(plus the app'sMS_CLIENT_ID/MS_CLIENT_SECRET/MS_TENANT_ID). The server mints short-lived delegated access tokens via therefresh_tokengrant, so calls act as the consenting user and are bounded by that user's own roles. Entra rotates the refresh token on use; the latest is exposed on the client (latestRefreshToken) so the host can persist it.activate_pim_roleonly works in this mode.Static token — a pre-fetched
MS_ACCESS_TOKEN/accessToken(short-circuits the grants).
Least-privilege application permissions
Grant the app registration only what it needs, then admin-consent:
Task | Application permission |
Read users and groups |
|
Create users |
|
Update users / set manager / enable-disable |
|
Read license/SKU state |
|
Assign / remove licenses |
|
Manage group membership |
|
Create / delete Temporary Access Pass |
|
JIT PIM role activation ( | delegated |
User.Create is narrower than User.ReadWrite.All; use it when you only need to
provision new users. update_user and set_manager require User.ReadWrite.All.
Reading SKUs (/subscribedSkus) is covered by LicenseAssignment.Read.All.
Temporary Access Pass additionally requires the app to hold the Authentication
Administrator (or Privileged Authentication Administrator) directory role —
UserAuthenticationMethod.ReadWrite.All alone is not sufficient to manage another
user's authentication methods. Assign the role to the app's service principal in
Entra ID → Roles and administrators, and ensure the tenant's TAP policy is
enabled (it also caps lifetimeInMinutes).
App registration and admin consent
In the Entra admin center, register an application (single tenant).
Under Certificates & secrets, create a client secret and copy its value.
Under API permissions, add the Microsoft Graph Application permissions above, then click Grant admin consent.
Copy the Directory (tenant) ID and Application (client) ID.
Setup
Install dependencies and build the CLI:
npm install
npm run buildProvide credentials through the environment. The server never accepts them as tool arguments.
export MS_TENANT_ID="contoso.onmicrosoft.com" # or the tenant GUID
export MS_CLIENT_ID="your-application-client-id"
export MS_CLIENT_SECRET="your-client-secret"Optional settings:
export MS_GRAPH_BASE_URL="https://graph.microsoft.com/v1.0"
export MS_TIMEOUT_MS=30000
# export MS_AUTHORITY_HOST="https://login.microsoftonline.com"If another process manages tokens, provide a bearer token directly:
export MS_ACCESS_TOKEN="your-access-token"Claude or Cursor config
Use the stdio server for local MCP clients:
{
"mcpServers": {
"microsoft365": {
"command": "node",
"args": ["/absolute/path/to/mcp-server-microsoft365/dist/transports/stdio.js"],
"env": {
"MS_TENANT_ID": "contoso.onmicrosoft.com",
"MS_CLIENT_ID": "your-application-client-id",
"MS_CLIENT_SECRET": "your-client-secret"
}
}
}
}During development:
{
"mcpServers": {
"microsoft365": {
"command": "npm",
"args": ["run", "dev", "--prefix", "/absolute/path/to/mcp-server-microsoft365"],
"env": {
"MS_TENANT_ID": "contoso.onmicrosoft.com",
"MS_CLIENT_ID": "your-application-client-id",
"MS_CLIENT_SECRET": "your-client-secret"
}
}
}
}Streamable HTTP transport
npm run dev:httpThe HTTP transport binds to 127.0.0.1 by default and serves POST /mcp. Set
MCP_HTTP_TOKEN to require bearer auth, and MCP_ALLOWED_ORIGINS for
browser-based local clients. MCP_MAX_BODY_BYTES defaults to 10485760
(10 MiB).
Provisioning workflow
list_subscribed_skusto see which licenses exist and how many seats are free.create_userwith adisplayNameanduserPrincipalName. A strong temporary password is generated when you do not supply one, and returned in the result withforceChangePasswordNextSignInset. Store it securely and share it out-of-band.create_userfails clearly if the UPN already exists.assign_licensewith a friendly name (E3), askuPartNumber(ENTERPRISEPACK), or a SKU GUID. PassusageLocationon the same call for a brand-new user.add_group_memberto place the user in the right groups. It is idempotent: an already-a-member response is treated as success.
The usageLocation gotcha
Microsoft Graph rejects a license assignment for a user with no usageLocation
(the classic "License assignment failed because of an invalid usage location"
error). This server sets usageLocation first:
If you pass
usageLocationtoassign_license, it isPATCHed onto the user before the license is assigned.If you do not, the server reads the user's current
usageLocationand returns a clear, actionable error when it is missing — rather than letting Graph fail cryptically.
usageLocation is an ISO 3166-1 alpha-2 country code, e.g. DK.
License resolution
assign_license / remove_license accept a friendly name, a skuPartNumber,
or a SKU GUID. The server fetches /subscribedSkus (no $filter; it matches
client-side) and:
matches a GUID directly against
skuId;otherwise matches
skuPartNumbercase-insensitively;otherwise maps a friendly name (e.g.
E3→ENTERPRISEPACK/SPE_E3,E5→ENTERPRISEPREMIUM/SPE_E5) to the first candidate the tenant owns;checks availability (
prepaidUnits.enabled - consumedUnits) and refuses to assign when no seats are free.
Rate limiting
On HTTP 429, the raised GraphHttpError carries the Retry-After value and the
Graph request-id. The Borgels connector layer uses these to retry.
Borgels gateway
The ./gateway subpath exports the Borgels gateway contract:
import { createMicrosoft365Gateway, microsoft365GatewayTools } from 'mcp-server-microsoft365/gateway';
const gateway = createMicrosoft365Gateway({ tenantId, clientId, clientSecret });
const result = await gateway.callTool('list_subscribed_skus');microsoft365GatewayTools lists the same tool surface as the MCP server, with
riskLevel and enabledByDefault so the gateway can gate writes. Read tools are
enabledByDefault: true; every write tool is enabledByDefault: false.
Live smoke test
The normal test suite uses mocked fetch. With real credentials in the
environment you can run a read-only live smoke test that counts subscribed SKUs:
npm run smoke:liveSecurity and audit
Credentials are read from environment variables only.
MS_GRAPH_BASE_URLmust behttps://; loopbackhttp://is allowed for local mocks.Formatted errors redact Authorization headers, client secrets, access tokens, and token-like material.
Write tools are disabled by default and surfaced with write/destructive annotations.
The Streamable HTTP transport binds to
127.0.0.1by default.
Security reports: security@borgels.com.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/borgels/mcp-server-microsoft365'
If you have feedback or need assistance with the MCP directory API, please join our Discord server