mcp-server-microsoft365
Manage Microsoft 365 / Entra ID identity resources via Microsoft Graph, read-first with writes disabled by default.
Read users, groups, group members, subscribed SKUs, and assigned user licenses.
Create users with generated temporary passwords.
Update user attributes and enable/disable accounts.
Assign and remove licenses, set usage locations, and set managers.
Add and remove group members.
Create and delete Temporary Access Passes (extra opt-in).
Activate eligible PIM directory roles in delegated mode.
Write tools require
MS_ENABLE_WRITES=true; TAP tools requireMS_ENABLE_TAP=true.
Click on "Deploy 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-microsoft365 create 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 |
|
Privileged-target check (before password reset, TAP, group membership) |
|
Read Intune devices ( |
|
Read Intune configuration / compliance policies |
|
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"
}
}
}
}Intune (read)
list_user_devices, list_managed_devices, list_device_configurations and
list_compliance_policies read Intune through Graph (/users/{id}/managedDevices,
/deviceManagement/...). They need the application permissions
DeviceManagementManagedDevices.Read.All and DeviceManagementConfiguration.Read.All.
Pass includeAssignments: true to expand group assignments on policies.
Write gating
All write and destructive tools (create_user, assign_license, add_group_member,
create_temporary_access_pass, ...) are only registered when MS_ENABLE_WRITES=true.
The default is read-only, so a container started without the flag exposes the seven
read tools and nothing else; callers see write tools as unknown rather than forbidden.
Gating is by tool annotation (readOnlyHint), so a new write tool is off by default.
Account-takeover operations
Password reset and Temporary Access Pass hand a working credential to whoever
called the tool, so they have their own opt-in on top of MS_ENABLE_WRITES=true:
Variable | Default | Effect |
|
| Registers |
|
| Registers |
| empty | Comma-separated object ids / UPNs / mail addresses that are always refused. |
| empty | Comma-separated group object ids / display names / mailNicknames that are always refused. |
Before reset_password, create_temporary_access_pass, add_group_member and
remove_group_member the server checks the target and refuses when:
the user holds a directory role (
transitiveMemberOfcontains adirectoryRole) or has any entry in/roleManagement/directory/roleAssignments(including administrative-unit scoped roles);the user is a member of a role-assignable group, or of a group in
MS_PROTECTED_GROUPS;the user matches
MS_PROTECTED_USERS;the group is role-assignable (
isAssignableToRole=true) or is inMS_PROTECTED_GROUPS.
If any of these lookups fails (missing permission, throttling, unexpected
response) the operation is refused: the check fails closed. Privileged accounts
are meant to be handled by a human in the Entra portal. PIM-eligible (not
active) role holders are not detected by these lookups; list such accounts in
MS_PROTECTED_USERS.
Identifier validation
Identifiers that end up in a Graph URL path (user, group, method ids) must not be
empty, . or .., and must not contain /, \, ?, # or percent-encoded
., / or \. Guest accounts whose UPN contains #EXT# must therefore be
addressed by object id.
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 (compared in constant time), 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.
Available Tools
18 toolsactivate_pim_roleActivate PIM Role (self)A
Just-in-time PIM elevation: the delegated caller self-activates one of their own ELIGIBLE directory roles (e.g. Authentication Administrator, needed for Temporary Access Pass) for a bounded window. Requires a delegated token; fails if the caller is not eligible.
Inputs: roleDefinitionId (required), duration, justification, directoryScopeId.
add_group_memberAdd Group MemberA
Add a user to a group. Idempotent when the user is already a member.
Inputs: userId (required), groupId (required).
assign_licenseAssign License To UserA
Assign a license to a user. Sets usageLocation first when provided; fails clearly if the user has no usageLocation.
Inputs: user (required), license (required), disabledPlans, usageLocation.
create_temporary_access_passCreate Temporary Access PassA
Create a Temporary Access Pass (TAP) for passwordless first sign-in / MFA setup. Multi-use by default; regenerated until the passcode is alphanumeric so it is easy to relay. lifetimeInMinutes is bounded by the tenant TAP policy. Returns the passcode; deliver it out-of-band.
Inputs: user (required), maxAttempts, isUsableOnce, startDateTime, +2 more.
create_userCreate Microsoft 365 UserB
Create a user with a generated temporary password. Returns the temporary password; store it securely and share out-of-band.
Inputs: displayName (required), userPrincipalName (required), password, mailNickname, +4 more.
delete_temporary_access_passDelete Temporary Access PassADestructiveIdempotent
Delete a user's Temporary Access Pass. With methodId deletes that pass; otherwise deletes every TAP on the user.
Inputs: user (required), methodId.
get_groupGet Microsoft 365 GroupARead-onlyIdempotent
Fetch one group by object id.
Inputs: groupId (required), select.
get_userGet Microsoft 365 UserARead-onlyIdempotent
Fetch one user by object id or userPrincipalName.
Inputs: user (required), select.
get_user_license_detailsGet User License DetailsARead-onlyIdempotent
List the licenses currently assigned to a user.
Inputs: user (required).
list_group_membersList Group MembersCRead-onlyIdempotent
List the members of a group.
Inputs: groupId (required), top.
list_groupsList Microsoft 365 GroupsCRead-onlyIdempotent
List groups with optional $top, $filter, $select, and $orderby.
Inputs: top, filter, select, orderby.
list_subscribed_skusList Subscribed SKUsARead-onlyIdempotent
List tenant subscribed SKUs for license lookup and seat availability.
Inputs: no named parameters.
list_usersList Microsoft 365 UsersARead-onlyIdempotent
List users with optional $top, $filter, $select, and $orderby.
Inputs: top, filter, select, orderby.
remove_group_memberRemove Group MemberADestructiveIdempotent
Remove a user from a group. Idempotent when the user is not a member.
Inputs: userId (required), groupId (required).
remove_licenseRemove License From UserCDestructiveIdempotent
Remove a license from a user.
Inputs: user (required), license (required).
set_managerSet User ManagerB
Set a user's manager. Provide the manager's object id or userPrincipalName.
Inputs: user (required), manager (required).
set_usage_locationSet User Usage LocationA
Set a user usageLocation (required before license assignment).
Inputs: user (required), usageLocation (required).
update_userUpdate Microsoft 365 UserA
Update attributes on an existing user (PATCH). Use accountEnabled to enable/disable an existing account (e.g. activate a pre-created hire or disable a leaver). Only provided fields are changed.
Inputs: user (required), city, state, country, +15 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 18 tools
Each tool targets a specific resource and action, such as get_user vs list_users, assign_license vs remove_license, and create_temporary_access_pass vs delete_temporary_access_pass. There is no meaningful overlap that would cause an agent to select the wrong tool.
All tool names follow a consistent verb_noun snake_case pattern (e.g., get_user, list_groups, assign_license, create_temporary_access_pass). No mixing of conventions or vague verbs.
With 18 tools, the server is slightly above the typical 3-15 range but justifiably so, as it covers user/group management, licensing, temporary access passes, and PIM roles. The count feels appropriate for the broad Microsoft 365 domain without being bloated.
The tool surface covers core user lifecycle (create/get/update/list), license management, group membership, and authentication flows. The main gap is lack of user deletion, but this is workable via update_user disable, and group creation/deletion appears out of scope, making these minor gaps.
Related MCP Connectors
Manage Microsoft 365 email, calendar, contacts and inbox rules via the Graph API with OAuth 2.0.
Microsoft 365, CRM, Partner Center and tenant operations for organisations and MSPs.
Permissioned access to Outlook, OneDrive and Teams via the user's own Microsoft account
*Updated June 17th 2025** Manage your Microsoft 365 services effortlessly. Create and manage distr…
Related MCP Servers
- -licenseNot gradedqualityNot gradedmaintenanceProvides secure access to Microsoft Entra ID (Azure AD) resources including users, devices, and applications through Microsoft Graph API. Enables querying organizational data with comprehensive audit logging to Azure Blob Storage.-
- AlicenseBqualityNot gradedmaintenanceProvides comprehensive management of Microsoft 365 services including Exchange, SharePoint, Teams, Azure AD, Intune device management, security & compliance frameworks, and universal access to 1000+ Microsoft Graph API endpoints with advanced features like batch operations, delta queries, and real-time webhooks.5015-
- AlicenseBqualityBmaintenanceEnables management of Microsoft 365 users, licenses, and groups through Microsoft Graph API. Supports user provisioning, license assignment, group management, and automated M365 administration workflows.192MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants and automation tools to manage Microsoft 365, Entra ID, and Intune resources through 32 tools for user/device/file management and infrastructure monitoring.8MIT