borgels

mcp-server-microsoft365

Community borgels
Updated

Policy-aware MCP server for Microsoft 365 / Microsoft Graph (Entra ID).

mcp-server-microsoft365

TypeScript MCP server for Microsoft 365 / Entra ID via Microsoft Graph. It runsapp-only (client-credentials) and is intentionally boring good: typed,documented, read-first, least-privilege, credential-sane, and idempotent whereGraph allows it. Its focus is identity provisioning — create a user, assign alicense, manage group membership — plus the supporting reads you need to do thatsafely.

Disclaimer: This is an independent, unofficial project by Borgels. Borgelsis not affiliated with, endorsed by, or supported by Microsoft. "Microsoft365", "Microsoft Graph", and "Entra ID" are referenced only to describe whatthis server talks to. You need your own Entra ID app registration, and use ofMicrosoft Graph is subject to Microsoft's own terms.

Scope

Read tools (enabled by default):

  • get_user, list_users
  • list_subscribed_skus (license lookup and seat availability)
  • list_groups, get_group, list_group_members
  • get_user_license_details

Write tools (disabled by default):

  • create_user
  • update_user (PATCH attributes on an existing user, incl. accountEnabled to enable/disable — e.g. activate a pre-created hire or disable a leaver)
  • set_manager
  • assign_license, remove_license
  • add_group_member, remove_group_member
  • set_usage_location
  • create_temporary_access_pass (passwordless first sign-in / MFA setup; regenerated until alphanumeric), delete_temporary_access_pass
  • activate_pim_role (just-in-time PIM self-activation of an eligible directory role, e.g. Authentication Administrator for TAP — delegated mode only)

Authentication modes

The server authenticates to Graph in one of three ways, chosen by which optionsare 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 refreshToken option / MS_REFRESH_TOKEN (plus the app's MS_CLIENT_ID/MS_CLIENT_SECRET/MS_TENANT_ID). The server mints short-lived delegated access tokens via the refresh_token grant, 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_role only 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 User.Read.All
Create users User.Create (or User.ReadWrite.All)
Update users / set manager / enable-disable User.ReadWrite.All
Read license/SKU state LicenseAssignment.Read.All
Assign / remove licenses LicenseAssignment.ReadWrite.All
Manage group membership GroupMember.ReadWrite.All
Create / delete Temporary Access Pass UserAuthenticationMethod.ReadWrite.All
JIT PIM role activation (activate_pim_role, delegated) delegated RoleManagement.ReadWrite.Directory (bounded by the user's eligible roles)

User.Create is narrower than User.ReadWrite.All; use it when you only need toprovision 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 AuthenticationAdministrator (or Privileged Authentication Administrator) directory role —UserAuthenticationMethod.ReadWrite.All alone is not sufficient to manage anotheruser's authentication methods. Assign the role to the app's service principal inEntra ID → Roles and administrators, and ensure the tenant's TAP policy isenabled (it also caps lifetimeInMinutes).

App registration and admin consent

  1. In the Entra admin center, register an application (single tenant).
  2. Under Certificates & secrets, create a client secret and copy its value.
  3. Under API permissions, add the Microsoft Graph Application permissionsabove, then click Grant admin consent.
  4. Copy the Directory (tenant) ID and Application (client) ID.

Setup

Install dependencies and build the CLI:

npm install
npm run build

Provide credentials through the environment. The server never accepts them astool 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:http

The HTTP transport binds to 127.0.0.1 by default and serves POST /mcp. SetMCP_HTTP_TOKEN to require bearer auth, and MCP_ALLOWED_ORIGINS forbrowser-based local clients. MCP_MAX_BODY_BYTES defaults to 10485760(10 MiB).

Provisioning workflow

  1. list_subscribed_skus to see which licenses exist and how many seats arefree.
  2. create_user with a displayName and userPrincipalName. A strongtemporary password is generated when you do not supply one, and returned inthe result with forceChangePasswordNextSignIn set. Store it securely andshare it out-of-band. create_user fails clearly if the UPN already exists.
  3. assign_license with a friendly name (E3), a skuPartNumber(ENTERPRISEPACK), or a SKU GUID. Pass usageLocation on the same call for abrand-new user.
  4. add_group_member to 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 usageLocation to assign_license, it is PATCHed onto the userbefore the license is assigned.
  • If you do not, the server reads the user's current usageLocation and returnsa clear, actionable error when it is missing — rather than letting Graph failcryptically.

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 matchesclient-side) and:

  • matches a GUID directly against skuId;
  • otherwise matches skuPartNumber case-insensitively;
  • otherwise maps a friendly name (e.g. E3ENTERPRISEPACK / SPE_E3,E5ENTERPRISEPREMIUM / SPE_E5) to the first candidate the tenant owns;
  • checks availability (prepaidUnits.enabled - consumedUnits) and refuses toassign when no seats are free.

Rate limiting

On HTTP 429, the raised GraphHttpError carries the Retry-After value and theGraph 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, withriskLevel and enabledByDefault so the gateway can gate writes. Read tools areenabledByDefault: true; every write tool is enabledByDefault: false.

Live smoke test

The normal test suite uses mocked fetch. With real credentials in theenvironment you can run a read-only live smoke test that counts subscribed SKUs:

npm run smoke:live

Security and audit

  • Credentials are read from environment variables only.
  • MS_GRAPH_BASE_URL must be https://; loopback http:// is allowed for localmocks.
  • Formatted errors redact Authorization headers, client secrets, access tokens,and token-like material.
  • Write tools are disabled by default and surfaced with write/destructiveannotations.
  • The Streamable HTTP transport binds to 127.0.0.1 by default.

Security reports: [email protected].

MCP Server · Populars

MCP Server · New

    talivia-group

    Talivia Agent Kit

    Revenue-first website analytics installed and verified by AI agents through MCP

    Community talivia-group
    gura105

    Operational Ontology

    A minimal, readable reference implementation of the Operational Ontology pattern. Palantir Foundry is one implementation; this is the concept, minimized.

    Community gura105
    EllisMorrow

    Caelune

    Caelune (星野) — Local-first retrieval for private Markdown, PDF, and Tika documents, with a Windows desktop app and read-only MCP server.|本地优先的私人知识检索工具。

    Community EllisMorrow
    vmware-skills

    VMware AIops

    VMware vCenter/ESXi AI-powered monitoring and operations. Two skills: vmware-monitor (read-only, safe) and vmware-aiops (full operations) | Claude Code Skill

    Community vmware-skills
    asdecided

    AsDecided

    Native deterministic requirements-as-code engine and read-only MCP server.

    Community asdecided