ms-365-admin-mcp-server

A Model Context Protocol (MCP) server for Microsoft 365 administration via Graph API application permissions (client credentials).
Built on the architecture and endpoint-driven design pioneered by Softeria/ms-365-mcp-server, and complementary to it: Softeria's server uses delegated permissions for end-user productivity scenarios, while this one uses application permissions for admin operations — security monitoring, identity audits, incident response, and service health. See Acknowledgments below.
Features
- 674 tools covering security, audit, identity, app credentials, guest users, Exchange, Intune (devices, apps, MAM, reports, Settings catalog & Endpoint security (BitLocker, Antivirus, Firewall, ASR), encryption report, Windows Update profiles, BitLocker and LAPS rotation, macOS Platform Scripts, macOS custom attribute scripts, assignment filters, Remediations, Windows PowerShell scripts, custom compliance scripts), governance (PIM, access reviews, entitlement, lifecycle), compliance, threat intelligence, advanced hunting, Defender for Identity (sensors, candidates, migration, identity accounts, audit policy), Microsoft 365 Copilot admin (usage reports, interaction history audit, AI users, meeting insights, agent registrations, policy settings), custom security attributes, LAPS, policies, reports, incident response, eDiscovery v3 (cases, custodians, noncustodial data sources, review sets, queries, exports, operations), Purview DSPM (protection scopes), event-based retention triggers, Teams online meeting attendance reports (app-only with Application Access Policy), deleted chats restore (admin recovery flow), Teams chat investigation reads (Chat.Read.All for triage; eDiscovery v3 for court-admissible production), Cloud PC, call records, Universal Print, information protection, SharePoint admin, and records management
- Application permissions (client credentials) — no user interaction required
- Read-only by default — write operations require explicit
--allow-writes
- Risk classification on write tools (low/medium/high/critical)
- Presets to filter tools by domain (security, audit, identity, etc.)
- Two transports: stdio (default) and HTTP (StreamableHTTP)
- Multi-cloud: Microsoft global and China (21Vianet)
- Key Vault support for secrets management
Documentation
Prerequisites
- Node.js >= 18
- An Azure AD app registration with application permissions (not delegated)
- A specific tenant ID (not "common")
Installation
npm (recommended)
npm install -g @okapi-ca/ms-365-admin-mcp-server
ms-365-admin-mcp-server --help
Docker
docker pull ghcr.io/okapi-ca/ms-365-admin-mcp-server:latest
docker run --rm -i \
-e MS365_ADMIN_MCP_CLIENT_ID=... \
-e MS365_ADMIN_MCP_CLIENT_SECRET=... \
-e MS365_ADMIN_MCP_TENANT_ID=... \
ghcr.io/okapi-ca/ms-365-admin-mcp-server:latest
From source
git clone https://github.com/okapi-ca/ms-365-admin-mcp-server.git
cd ms-365-admin-mcp-server
npm install
npm run generate
npm run build
Configuration
Environment variables
| Variable |
Required |
Description |
MS365_ADMIN_MCP_CLIENT_ID |
Yes |
App registration client ID |
MS365_ADMIN_MCP_CLIENT_SECRET |
Yes |
App registration client secret |
MS365_ADMIN_MCP_TENANT_ID |
Yes |
Azure AD tenant ID (must be specific) |
MS365_ADMIN_MCP_CLOUD_TYPE |
No |
global (default) or china |
MS365_ADMIN_MCP_KEYVAULT_URL |
No |
Azure Key Vault URL (overrides env vars) |
MS365_ADMIN_MCP_MAX_TOP |
No |
Cap $top query param to limit result size |
READ_ONLY |
No |
true/1 to force read-only (default behavior) |
ENABLED_TOOLS |
No |
Regex to filter available tools |
MCP client configuration (Claude Desktop, etc.)
{
"mcpServers": {
"ms365-admin": {
"command": "node",
"args": ["/path/to/ms-365-admin-mcp-server/dist/index.js"],
"env": {
"MS365_ADMIN_MCP_CLIENT_ID": "your-client-id",
"MS365_ADMIN_MCP_CLIENT_SECRET": "your-client-secret",
"MS365_ADMIN_MCP_TENANT_ID": "your-tenant-id"
}
}
}
}
VS Code (1.102+)
VS Code consumes the same MCP protocol but uses a different config layout —
servers instead of mcpServers, an explicit type field, and inputs for
secret prompts. A ready-to-copy sample lives at
.vscode/mcp.json.example; copy it to
.vscode/mcp.json and VS Code will prompt for the tenant / client / secret on
first start, then store them in its secret store (the real mcp.json is
gitignored so resolved secrets never reach the repo).
Minimal stdio setup:
{
"inputs": [
{ "type": "promptString", "id": "ms365-tenant-id", "description": "Tenant ID" },
{ "type": "promptString", "id": "ms365-client-id", "description": "Client ID" },
{
"type": "promptString",
"id": "ms365-client-secret",
"description": "Client secret",
"password": true,
},
],
"servers": {
"ms365-admin": {
"type": "stdio",
"command": "ms-365-admin-mcp-server",
"args": ["--preset", "security,audit,identity,health"],
"env": {
"MS365_ADMIN_MCP_TENANT_ID": "${input:ms365-tenant-id}",
"MS365_ADMIN_MCP_CLIENT_ID": "${input:ms365-client-id}",
"MS365_ADMIN_MCP_CLIENT_SECRET": "${input:ms365-client-secret}",
},
},
},
}
For remote HTTP deployments, use "type": "http" with a url field (VS Code
1.103+ handles OAuth 2.0 Dynamic Client Registration natively) or fall back to
the mcp-remote bridge when the native browser flow is unavailable — see the
example file for both shapes.
Tools surface in Agent mode (GitHub Copilot Chat). VS Code asks for
per-tool approval; the --preset flag above keeps the catalog manageable.
Use Cmd/Ctrl+Shift+P → MCP: List Servers → Show Output to see logs.
Catalog size limit on Claude.ai surfaces
Claude.ai, Claude Desktop and cloud sessions serve each connector's tools under a
fixed budget of 1 MiB of input schema (compact JSON; tool names and
descriptions are not counted). Tools are admitted in alphabetical order and a
tool that no longer fits is dropped silently. Claude Code CLI is not affected.
The limit is not documented publicly.
In a cloud session linked to a Mac, the claude-device bridge relays every
local MCP server under one connector (lcl-<server>-<tool>), so all local
servers share that budget, and this server's tools sort after most of them.
Request bodies are therefore advertised shallow (src/advertised-schema.ts):
the entity's own fields with their descriptions, nested objects as free-form
objects, and no Read-only. fields. Graph validates the body; the server
forwards it as sent. With writes enabled the catalog advertises about 595 KB of
input schema, and test/advertised-schema.test.ts fails the build above
768 KiB.
If tools still go missing in a cloud session, shrink what this server
advertises (read-only mode, --preset, --enabled-tools), disable unused
local servers, or register this server as its own connector so it gets a
budget of its own.
Remote HTTP server: device_code authentication (RFC 8628)
If the server runs in HTTP / OAuth mode on a remote host (e.g. Azure Container Apps) and the client connects via mcp-remote, the standard flow requires a browser to reach localhost:14543/oauth/callback. When that isn't possible — macOS Platform SSO hijacks the WebKit flow, Claude Code runs in a headless Docker container, the user is on a remote SSH dev env — use the ms-365-admin-mcp-auth bootstrap to pre-seed mcp-remote's token cache instead.
npx -p @okapi-ca/ms-365-admin-mcp-server@latest ms-365-admin-mcp-auth \
--server https://your-mcp-host.azurecontainerapps.io/mcp
The helper prints a URL and a user code; you sign in on any device you trust (phone, another laptop) and the tokens are written to ~/.mcp-auth/mcp-remote-<version>/. Claude Desktop / Claude Code then launches mcp-remote normally and finds the cached tokens without ever opening a browser.
See docs/TROUBLESHOOTING.md for Docker / remote-dev patterns and exit code reference.
Usage
CLI options
--read-only Read-only mode (default)
--allow-writes Enable write operations
--enabled-tools <regex> Filter tools by regex pattern
--preset <names> Use preset categories (comma-separated)
--partition <name> Tool partition served on /mcp/<group> in HTTP mode (default: domains)
--list-presets List available presets and exit
--list-tools List available tools and exit
--list-permissions List required Graph API permissions and exit
--verify-login Test credentials against Graph API and exit
--cloud <type> Cloud environment: global (default) or china
--transport <type> Transport: stdio (default) or http
--port <number> HTTP port (default: 8080)
--host <address> HTTP bind address (default: 127.0.0.1)
--allowed-clients <ids> Comma-separated Entra app IDs (required for HTTP)
-v Verbose logging
Presets
# Security alerts and incidents only
node dist/index.js --preset security
# Identity management tools
node dist/index.js --preset identity
# Multiple presets
node dist/index.js --preset security,audit,identity
| Preset |
Description |
security |
Security alerts, incidents, attack simulations, and threat intelligence |
audit |
Directory audits, sign-ins, provisioning logs, deleted items |
health |
Service health and Message Center |
reports |
Usage reports (Teams, Email, SharePoint, OneDrive, Mailbox, M365 Apps) |
identity |
Users, groups, roles, devices, PIM, guest users, external identities |
exchange |
Exchange administration (message traces, mailboxes) |
intune |
Managed devices, compliance, configurations, Autopilot, apps, RBAC |
governance |
Access reviews, entitlement management, lifecycle workflows, terms of use |
compliance |
Licenses, Secure Score, Identity Protection, risk detections, policies |
response |
Incident response write operations (disable, revoke, confirm, dismiss) |
ediscovery |
eDiscovery cases (Microsoft Purview) |
cloudpc |
Cloud PC / Windows 365 (provisioning, images, connections, settings, audit) |
callrecords |
Teams call records |
print |
Universal Print (printers, shares, connectors, services, operations, tasks) |
infoprotection |
Information Protection (BitLocker recovery keys, threat assessment) |
sharepointadmin |
SharePoint tenant administration settings |
retention |
Records Management (retention labels, file plan metadata) |
all |
All available tools |
Verify credentials
node dist/index.js --verify-login
Security (11)
| Tool |
Method |
Risk |
list-security-alerts |
GET |
|
get-security-alert |
GET |
|
update-security-alert |
PATCH |
medium |
list-security-incidents |
GET |
|
get-security-incident |
GET |
|
update-security-incident |
PATCH |
medium |
list-attack-simulations |
GET |
|
get-attack-simulation |
GET |
|
create-attack-simulation |
POST |
high |
update-attack-simulation |
PATCH |
medium |
delete-attack-simulation |
DELETE |
medium |
Audit logs & deleted items (5)
| Tool |
Method |
list-directory-audits |
GET |
list-sign-ins |
GET |
list-provisioning-logs |
GET |
list-deleted-users |
GET |
list-deleted-groups |
GET |
Service health (3)
| Tool |
Method |
list-service-health |
GET |
list-service-issues |
GET |
list-service-messages |
GET |
Usage reports (8)
| Tool |
Method |
get-teams-activity-report |
GET |
get-email-activity-report |
GET |
get-active-users-report |
GET |
get-sharepoint-usage-report |
GET |
get-onedrive-usage-report |
GET |
get-active-user-counts-report |
GET |
get-mailbox-usage-report |
GET |
get-m365-apps-usage-report |
GET |
Users (9)
| Tool |
Method |
Risk |
list-users |
GET |
|
get-user |
GET |
|
list-user-memberships |
GET |
|
list-user-auth-methods |
GET |
|
list-user-devices |
GET |
|
create-user |
POST |
high |
delete-user |
DELETE |
critical |
assign-user-license |
POST |
medium |
reprocess-user-license |
POST |
low |
There is no general-purpose update-user. The only PATCH on /users/{id} this server exposes is disable-user-account (see Incident response), which is deliberately narrow: it sets accountEnabled and nothing else.
Devices (2)
| Tool |
Method |
list-devices |
GET |
get-device |
GET |
Groups (11)
| Tool |
Method |
Risk |
list-groups |
GET |
|
get-group |
GET |
|
list-group-members |
GET |
|
list-group-owners |
GET |
|
create-group |
POST |
medium |
update-group |
PATCH |
medium |
delete-group |
DELETE |
critical |
add-group-member |
POST |
medium |
remove-group-member |
DELETE |
medium |
add-group-owner |
POST |
high |
remove-group-owner |
DELETE |
high |
remove-group-member, add-group-owner and remove-group-owner run pre-flight guardrails: they refuse on an on-premises-synced group, a member removal on a dynamic group, and the removal of a group's only owner. See docs/ARCHITECTURE.md.
Directory roles & PIM (9)
| Tool |
Method |
Risk |
list-directory-roles |
GET |
|
list-role-members |
GET |
|
list-role-assignments |
GET |
|
list-role-definitions |
GET |
|
list-pim-eligible-assignments |
GET |
|
list-pim-active-assignments |
GET |
|
add-directory-role-member |
POST |
critical |
delete-role-assignment |
DELETE |
critical |
remove-directory-role-member |
DELETE |
critical |
delete-role-assignment is the one to reach for: list-role-assignments with $filter=principalId eq '{user-id}' already returns the assignment id it takes. remove-directory-role-member covers roles activated the legacy way. Both refuse unconditionally on the tenant's last active Global Administrator — an anti-lockout guardrail no parameter overrides.
Administrative units (8)
| Tool |
Method |
Risk |
list-administrative-units |
GET |
|
get-administrative-unit |
GET |
|
list-administrative-unit-members |
GET |
|
create-administrative-unit |
POST |
medium |
update-administrative-unit |
PATCH |
medium |
delete-administrative-unit |
DELETE |
high |
add-administrative-unit-member |
POST |
medium |
remove-administrative-unit-member |
DELETE |
high |
Conditional access (3)
| Tool |
Method |
list-conditional-access-policies |
GET |
get-conditional-access-policy |
GET |
list-named-locations |
GET |
Applications & app roles (11)
| Tool |
Method |
Risk |
list-applications |
GET |
|
list-service-principals |
GET |
|
list-oauth2-grants |
GET |
|
list-user-app-role-assignments |
GET |
|
list-sp-app-role-assignments |
GET |
|
update-application |
PATCH |
high |
delete-application |
DELETE |
critical |
update-service-principal |
PATCH |
high |
delete-oauth2-grant |
DELETE |
high |
delete-sp-app-role-assignment |
DELETE |
high |
delete-user-app-role-assignment |
DELETE |
high |
delete-oauth2-grant is the remediation for an illicit consent grant: list-oauth2-grants identifies it, this revokes it. A grant with consentType: AllPrincipals revokes the app's delegated access for every user at once — check the blast radius first, and pair with revoke-user-sessions, since revoking consent does not end existing sessions.
App credentials & owners (7)
| Tool |
Method |
get-application |
GET |
list-application-owners |
GET |
list-app-federated-credentials |
GET |
get-app-federated-credential |
GET |
get-service-principal |
GET |
list-service-principal-owners |
GET |
list-sp-delegated-permissions |
GET |
App management policies (2)
| Tool |
Method |
list-app-management-policies |
GET |
get-app-management-policy |
GET |
Organization & domains (4)
| Tool |
Method |
Risk |
get-organization |
GET |
|
list-domains |
GET |
|
create-domain |
POST |
high |
verify-domain |
POST |
medium |
Licenses (2)
| Tool |
Method |
list-subscribed-skus |
GET |
get-subscribed-sku |
GET |
Secure Score (4)
| Tool |
Method |
list-secure-scores |
GET |
get-secure-score |
GET |
list-secure-score-controls |
GET |
get-secure-score-control |
GET |
Identity Protection & risk detections (7)
| Tool |
Method |
list-risky-users |
GET |
get-risky-user |
GET |
list-risky-user-history |
GET |
list-risky-service-principals |
GET |
get-risky-service-principal |
GET |
list-risk-detections |
GET |
get-risk-detection |
GET |
Security & access policies (13)
| Tool |
Method |
Risk |
get-auth-methods-policy |
GET |
|
list-auth-method-configs |
GET |
|
get-auth-method-config |
GET |
|
get-security-defaults |
GET |
|
get-admin-consent-policy |
GET |
|
list-auth-strength-policies |
GET |
|
get-auth-strength-policy |
GET |
|
create-auth-strength-policy |
POST |
high |
update-auth-strength-policy |
PATCH |
high |
delete-auth-strength-policy |
DELETE |
high |
get-cross-tenant-access-policy |
GET |
|
list-cross-tenant-partners |
GET |
|
change-user-password |
POST |
high |
Guest user invitations (2)
| Tool |
Method |
Risk |
list-invitations |
GET |
|
create-invitation |
POST |
medium |
External identity providers (2)
| Tool |
Method |
list-identity-providers |
GET |
get-identity-provider |
GET |
Self-service sign-up (4)
| Tool |
Method |
list-b2x-user-flows |
GET |
get-b2x-user-flow |
GET |
list-api-connectors |
GET |
get-api-connector |
GET |
Custom authentication extensions (2)
| Tool |
Method |
list-custom-auth-extensions |
GET |
get-custom-auth-extension |
GET |
Exchange message traces (2)
| Tool |
Method |
list-message-traces |
GET |
get-message-trace |
GET |
Exchange mailboxes (7)
| Tool |
Method |
Risk |
list-exchange-mailboxes |
GET |
|
get-exchange-mailbox |
GET |
|
list-exchange-mailbox-folders |
GET |
|
get-exchange-mailbox-folder |
GET |
|
export-exchange-mailbox-items |
POST |
medium |
update-exchange-mailbox |
PATCH |
medium |
delete-exchange-mailbox |
DELETE |
critical |
Threat intelligence - hosts (4)
| Tool |
Method |
list-threat-intel-hosts |
GET |
get-threat-intel-host |
GET |
get-threat-intel-host-whois |
GET |
list-threat-intel-host-pairs |
GET |
Threat intelligence - articles & profiles (6)
| Tool |
Method |
list-threat-intel-articles |
GET |
get-threat-intel-article |
GET |
list-threat-intel-article-indicators |
GET |
list-threat-intel-profiles |
GET |
get-threat-intel-profile |
GET |
list-threat-intel-profile-indicators |
GET |
Threat intelligence - vulnerabilities & WHOIS (4)
| Tool |
Method |
list-threat-intel-vulnerabilities |
GET |
get-threat-intel-vulnerability |
GET |
list-threat-intel-whois-records |
GET |
get-threat-intel-whois-record |
GET |
Threat intelligence - infrastructure (2)
| Tool |
Method |
list-threat-intel-host-components |
GET |
list-threat-intel-ssl-certs |
GET |
Managed devices (8)
| Tool |
Method |
Risk |
list-managed-devices |
GET |
|
get-managed-devices-by-names |
GET |
|
get-managed-device |
GET |
|
get-windows-protection-state |
GET |
|
list-device-compliance-states |
GET |
|
list-device-configuration-states |
GET |
|
get-managed-device-overview |
GET |
|
delete-managed-device |
DELETE |
critical |
Compliance policies (5)
| Tool |
Method |
list-compliance-policies |
GET |
get-compliance-policy |
GET |
list-compliance-policy-device-statuses |
GET |
get-compliance-policy-status-overview |
GET |
get-compliance-state-summary |
GET |
Device configurations (4)
| Tool |
Method |
list-device-configurations |
GET |
get-device-configuration |
GET |
get-device-configuration-status-overview |
GET |
list-device-configuration-device-statuses |
GET |
Settings catalog & Endpoint security (6)
Settings catalog policies, including every modern Endpoint security policy (Disk encryption / BitLocker, Antivirus, Firewall, Attack surface reduction, EDR, Account protection). Targets Graph beta — configurationPolicies has no v1.0 equivalent. The two report actions are POSTs that only read; they need --allow-writes like the other Intune reports.
| Tool |
Method |
Risk |
list-configuration-policies |
GET |
|
get-configuration-policy |
GET |
|
list-configuration-policy-settings |
GET |
|
list-configuration-policy-templates |
GET |
|
get-configuration-policy-device-status |
POST |
low |
get-configuration-policy-device-summary |
POST |
low |
Endpoint security intents (legacy templates) (4)
Template-based Endpoint security policies created before the move to the Settings catalog. Targets Graph beta. An Endpoint security audit must cover these and the Settings catalog both.
| Tool |
Method |
list-intents |
GET |
get-intent |
GET |
list-intent-device-states |
GET |
list-intent-templates |
GET |
Device encryption (1)
Intune encryption report: readiness, state, TPM version and the BitLocker / FileVault policies that reached each device. Targets Graph beta.
| Tool |
Method |
list-managed-device-encryption-states |
GET |
Windows Update profiles (3)
Feature and quality update profiles, which override the deferrals of a device's Windows Update for Business ring (rings themselves are device configurations). Targets Graph beta.
| Tool |
Method |
list-windows-feature-update-profiles |
GET |
list-windows-quality-update-profiles |
GET |
list-windows-quality-update-policies |
GET |
Intune shell scripts deployed to managed macOS devices. Targets Graph beta (/beta/deviceManagement/deviceShellScripts) — Microsoft has never promoted this endpoint to v1.0. Requires DeviceManagementScripts.ReadWrite.All.
| Tool |
Method |
Risk |
list-device-shell-scripts |
GET |
|
get-device-shell-script |
GET |
|
create-device-shell-script |
POST |
medium |
update-device-shell-script |
PATCH |
medium |
delete-device-shell-script |
DELETE |
high |
assign-device-shell-script |
POST |
medium |
Notes:
scriptContent is strict base64 (not URL-safe) of a script with LF line endings — CRLF will break execution on Macs.
runAsAccount=user is required for scripts that interact with the user session (e.g. osascript touching System Events).
assign-device-shell-script REPLACES all existing assignments — it is not additive. To add a group without removing others, first GET the current assignments, append the new target, then POST the full merged list.
- By default
get-device-shell-script does not return scriptContent; pass $select=id,displayName,scriptContent,... to fetch the base64 body.
…