Entra Planner MCP
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
Entra Planner MCP
A small, stateless remote MCP server that lets Claude (claude.ai, Desktop, mobile, Cowork, and Claude Code through organisation connectors) read and update Microsoft Planner tasks in group plans, acting as the signed-in user.
Microsoft Entra ID is the only authorization server. The server runs no OAuth endpoints of its own; it publishes RFC 9728 protected-resource metadata that points clients at your tenant.
Stateless, no secrets. Each call validates the Entra access token and does an On-Behalf-Of (OBO) exchange for a Graph token. The OBO client credential is a managed identity federated credential, so there is no client secret anywhere.
Narrow by design. Eight Planner tools, no delete tool, no generic Graph passthrough. Write tools are off unless you enable them.
MCP client (Claude) --401 + resource_metadata--> this server (/.well-known/oauth-protected-resource)
| |
+-- user signs in to Entra (PKCE) --> token for the API app (aud = API app ID, scp = Planner.Use)
|
this server: validate token -> On-Behalf-Of (client assertion = managed identity federated credential)
|
Microsoft Graph Planner API as the userTools
Tool | What it does | Kind |
| Plans in the groups you belong to ( | read |
| Buckets and tasks, filtered by bucket, assignee, due before/after, open/completed | read |
| Task plus description, checklist and links, with ETags | read |
| Tasks assigned to you ( | read |
| Create a task (bucket, due date, priority, notes, checklist, assignees) | write |
| Update title, due date, bucket, progress, priority, notes, checklist (add/check/uncheck) | write |
| Set progress to 100% | write |
| Add or remove assignees (user object IDs or | write |
Writes use Planner ETags (If-Match). Pass the etag from planner_get_task for true optimistic concurrency;
otherwise the server reads the current ETag first. A conflict returns "changed by someone else: re-read it and
retry". Premium (Project) plans are not in Graph and are out of scope.
Related MCP server: Planner MCP Server
Configuration (environment only)
Variable | Required | Meaning |
| yes | Tenant GUID. Tokens from any other tenant are rejected |
| yes | API app registration (client) ID; the expected |
| yes | Canonical public URL of the MCP endpoint, e.g. |
| Delegated scope name, default | |
| Extra accepted audiences (comma list); default the API client ID only | |
| If set, only tokens issued to these client apps ( | |
| Comma list of tools to expose; default the four read-only tools | |
|
| |
| Client ID of a user-assigned identity; omit for system-assigned | |
| Only with | |
|
| |
| Per-user tool calls per minute, default 60 | |
| Default | |
| Accepted browser | |
|
|
Endpoints: POST /mcp (streamable HTTP, stateless, JSON responses), GET /.well-known/oauth-protected-resource
and /.well-known/oauth-protected-resource/mcp, GET /health/live, GET /health/ready (ready = configuration
valid and the tenant's signing keys fetched). Requests whose Host isn't the PUBLIC_BASE_URL host get 421.
Deploying to your tenant
You need an Entra administrator (to create app registrations and grant admin consent) and an Azure
subscription. Replace every <...> placeholder.
1. Hosting first, so you know the URL
The MCP URL must be registered on the API app, so create the hosting first. Azure Container Apps (consumption, scale to zero) fits within the monthly free grant for a handful of users:
RG=<resource-group>; LOC=<region>; ENVNAME=<env-name>; APP=<app-name>
az group create -n $RG -l $LOC
az containerapp env create -n $ENVNAME -g $RG -l $LOC --logs-destination none
FQDN="$APP.$(az containerapp env show -n $ENVNAME -g $RG --query properties.defaultDomain -o tsv)"
echo "MCP URL: https://$FQDN/mcp"2. API app registration (the server)
Register an app (single tenant), e.g. "Planner MCP API". No client secret.
Manifest: set
api.requestedAccessTokenVersionto2before adding the URL below. (Needed when the URL is on a domain your tenant hasn't verified, such as*.azurecontainerapps.io.)Expose an API: Application ID URIs
api://<api-app-id>and the exact MCP URL (https://<fqdn>/mcp). Claude sends the MCP URL as the OAuthresource; without it Entra fails withAADSTS9010010.Add a delegated scope
Planner.Use, "Admins only" consent.API permissions, Microsoft Graph delegated:
Tasks.ReadWrite,User.Read,offline_access(andUser.ReadBasic.Allonly if you setENABLE_USER_LOOKUP=true). Grant admin consent.Enterprise application: Assignment required = Yes; assign the users who may use the connector.
3. Client app registration (what Claude signs in with)
Register a second app, e.g. "Planner MCP for Claude".
Platform Mobile and desktop applications (public client, PKCE, no secret) with redirect URIs
https://claude.ai/api/mcp/auth_callback,http://localhost/callback,http://127.0.0.1/callback(Entra ignores the port for loopback addresses).API permissions: your API's
Planner.Use, plus Microsoft Graphopenidandoffline_access; grant admin consent (with user consent disabled, users can't consent themselves).Enterprise application: Assignment required = Yes; assign the same users.
Optional hardening: set
ENTRA_ALLOWED_CLIENT_IDS=<client-app-id>on the server.
If your Claude organisation can't use a public client, add a client secret to this client app (not the API app) and enter it in Claude's connector settings only.
4. Deploy the container
Always deploy by digest, after verifying the signature:
IMAGE=ghcr.io/<owner>/entra-planner-mcp
DIGEST=$(docker buildx imagetools inspect $IMAGE:latest --format '{{json .Manifest}}' | jq -r .digest)
cosign verify $IMAGE@$DIGEST \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity https://github.com/<owner>/entra-planner-mcp/.github/workflows/release.yml@refs/heads/main
az containerapp create -n $APP -g $RG --environment $ENVNAME \
--image $IMAGE@$DIGEST --target-port 8000 --ingress external \
--cpu 0.25 --memory 0.5Gi --min-replicas 0 --max-replicas 2 \
--system-assigned \
--env-vars ENTRA_TENANT_ID=<tenant-id> ENTRA_API_CLIENT_ID=<api-app-id> \
ENTRA_ALLOWED_CLIENT_IDS=<client-app-id> PUBLIC_BASE_URL=https://$FQDN/mcp \
ALLOWED_TOOLS=planner_list_plans,planner_get_plan,planner_get_task,planner_my_tasks
az containerapp ingress update -n $APP -g $RG --allow-insecure falseAdd HTTP liveness/readiness probes on /health/live and /health/ready (port 8000). The managed identity
needs no Azure role assignments.
Optionally restrict ingress to your MCP client's egress range (for Claude, see Anthropic's published IP
addresses):
az containerapp ingress access-restriction set -n $APP -g $RG --rule-name mcp-client --ip-address <cidr> --action Allow.
5. Federated credential: let the managed identity act as the API app
PRINCIPAL=$(az containerapp show -n $APP -g $RG --query identity.principalId -o tsv)
az ad app federated-credential create --id <api-app-id> --parameters "{
\"name\": \"container-app-mi\",
\"issuer\": \"https://login.microsoftonline.com/<tenant-id>/v2.0\",
\"subject\": \"$PRINCIPAL\",
\"audiences\": [\"api://AzureADTokenExchange\"]
}"6. Check it
curl -si -X POST https://$FQDN/mcp | grep -i www-authenticate # 401, Bearer resource_metadata=...
curl -s https://$FQDN/.well-known/oauth-protected-resource/mcp # resource = your MCP URL(If you restricted ingress by IP, run these from an allowed address.)
7. Add the connector in Claude
Organization settings > Connectors > Add > Custom: the MCP URL, then the advanced/OAuth settings: client ID = the client app's ID, client secret blank (public client). Set per-tool permissions (for example reads "Always allow", writes "Ask").
Development
uv sync --locked
uv run ruff check src tests && uv run ruff format --check src tests
uv run mypy
uv run pytestTests use a fake JWKS and a fake Graph; nothing touches the network. For a local run against a real tenant, use a
separate dev app registration with a secret and ENV=development OBO_CREDENTIAL=local_dev_secret (see
.env.example); production refuses that mode.
Supply chain
Base image: Chainguard Python (distroless runtime: no shell, no package manager), non-root (UID 65532), read-only root filesystem friendly. Dependencies locked with hashes in
uv.lock.CI on every PR and push: ruff, mypy, pytest, gitleaks (full history), an identifier deny-list (supplied as a repository secret, never committed), Trivy filesystem scan.
Release on
main, weekly, and after Dependabot merges: build, Trivy image scan (fails on any HIGH/CRITICAL with a fix), smoke test, CycloneDX SBOM, push to GHCR, cosign keyless signature + SBOM attestation, SLSA build provenance. Actions pinned by commit SHA with least-privilege permissions.
See SECURITY.md to report a vulnerability. Licence: MIT.
This server cannot be deployed
Maintenance
Related MCP Connectors
Manage Redbooth tasks, task lists, comments, tags and files from Claude and other AI assistants.
Lets Claude, ChatGPT, and Muse read and update your beckett tasks, notes, calendar, and lists.
Connect Claude to your Platform7n workspaces: chat, links, work and decks. One-click OAuth.
Task management for people and AI agents, with scoped OAuth access to issues, projects, and docs.
Related MCP Servers
- AlicenseAqualityAmaintenanceEnables AI coding agents to read Microsoft Planner and To Do tasks across multiple M365 tenants with proper authentication, and supports optional write operations limited to tasks the agent created.1435 PyPIMIT
- AlicenseAqualityDmaintenanceEnables Claude to manage Microsoft Planner plans, buckets, and tasks via the Microsoft Graph API with OAuth 2.0 authentication.141ISC
- FlicenseAqualityBmaintenanceEnables Claude to manage Outlook calendar events and Microsoft To Do tasks for a specific mailbox via Microsoft Graph API.7-
- AlicenseAqualityAmaintenanceEnables agents to write to Microsoft Planner: create tasks and buckets, list plans and tasks, and complete or reopen tasks using delegated Entra device-code sign-in.821 PyPIMIT