MCP for Microsoft 365: reading Entra ID and audit logs through a protocol that does not exist yet

Microsoft ships APIs for almost every Microsoft 365 and Entra ID surface. You can read user licenses, audit sign-ins, list conditional access policies, and query service health through Microsoft Graph. What Microsoft does not ship is an MCP server that exposes those APIs as tools that Claude, Cursor, or any other MCP client can discover and call.

We built the bridge. Cloud Horizons's MCP server translates Microsoft Graph API calls into MCP tools, so Claude can reason about your Microsoft 365 tenant in real time. The architecture is different from AWS and GCP because Microsoft authentication is different. Here is how it works.

Why Microsoft is harder

AWS and GCP use long-lived access keys. Microsoft uses OAuth 2.0 with short-lived access tokens. A service account key for AWS lasts until you rotate it. A Microsoft Graph token expires in 1 hour. The MCP server must handle token refresh transparently or the tools stop working mid-conversation.

The second problem is permission models. AWS IAM and GCP IAM are straightforward: you create a role, attach a policy, done. Microsoft Graph permissions require admin consent in Entra ID. If the admin has not consented to AuditLog.Read.All, the sign-in log tool returns a 403 even if the user has the right role assignments.

The third problem is data volume. A 10,000-user tenant generates millions of sign-in events per month. The Graph API paginates at 1,000 events per request. Claude cannot reason about millions of rows. The MCP server filters and aggregates before returning anything.

Architecture: the Microsoft bridge

The MCP server stores three secrets in Cloudflare:

On each tool call, the Function requests an access token from https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token using client credentials grant. The token is cached in a Durable Object for 45 minutes. The Graph API call uses the token, and the result is returned to the MCP client.

The permission model

We recommend an app registration with exactly these Microsoft Graph application permissions:

No Directory.ReadWrite.All. No User.ReadWrite.All. The app cannot create users, change policies, or delete groups. Everything is read-only.

After creating the app registration, an Entra ID admin must grant admin consent in the Azure Portal. Without consent, every Graph call returns 403 regardless of the client credentials.

The tool surface

ToolWhat it returns
ms_entra_list_usersUsers, roles, license assignments, last sign-in times
ms_entra_get_signinsSign-in events filtered by user, app, or risk level
ms_entra_list_policiesConditional access policies, rules, and assignments
ms_service_healthActive incidents, advisories, and planned maintenance
ms_license_summaryAssigned vs available licenses by SKU

What Claude can ask

Claude translates each question into one or more Graph API calls, aggregates the results, and presents a structured answer. The human can then ask follow-up questions without writing KQL or PowerShell.

Demo mode: no tenant required

Without credentials, the MCP server returns realistic demo data: 500 users, 3 conditional access policies, 10,000 sign-in events, and 200 E5 licenses. The structure matches the real Graph API responses. When you connect your tenant, the same queries return live data.

Performance: handling scale

The sign-in log tool defaults to the last 24 hours with a maximum of 1,000 events. For larger queries, the tool aggregates by application, location, and status code before returning. The user gets a summary, not a raw event dump.

Token refresh is automatic. The cached token is refreshed 10 minutes before expiry. The MCP client never sees a 401.

What is next

Exchange Online mailbox audit. SharePoint site inventory. Teams usage analytics. Intune device compliance. Secure Score recommendations. Defender alerts. Every Microsoft 365 surface that exposes a read API through Graph belongs in the MCP server eventually.

The server is open source at spot-techno/cloud-horizons. The MCP config lives at /.well-known/mcp.json.