@co-native-ab/pimdo-ts
v0.1.8
Published
MCP server providing AI agents with scoped access to Microsoft Entra PIM (Privileged Identity Management)
Maintainers
Readme
pimdo-ts
Local MCP server (Model Context Protocol) written in TypeScript that gives an AI assistant scoped access to Microsoft Entra Privileged Identity Management (PIM) - for groups, Entra roles and Azure resource roles - without granting it standing access.
pimdo deliberately keeps the blast radius small: every privilege-changing tool routes through a local browser confirmation form, so a human stays in the loop for every activation, deactivation or approval before pimdo calls Microsoft Graph or ARM.
What pimdo does
pimdo is an MCP server. AI agents that speak MCP can connect to it and use the exposed tools to:
- inspect what role and group assignments the signed-in user is eligible to activate, and which are currently active,
- request a just-in-time elevation,
- as a designated approver, approve another user's pending request,
- with explicit human approval, confirm that an elevation has been used.
The agent acts as the signed-in human user - it never holds standing privileges. Every PIM action goes through the same approval and audit pipeline as a manual aka.ms/myaccess flow.
Installation
pimdo-ts is distributed in three formats from GitHub Releases:
MCPB Bundle (Recommended for Claude Desktop)
The MCPB bundle is self-contained - it includes the server and a bundled Node.js runtime. No separate Node.js installation is required.
Download the latest pimdo-ts-vX.Y.Z.mcpb file from GitHub Releases.
Claude Desktop: Double-click the .mcpb file, or open Claude Desktop → Settings → Extensions → Install Extension and select the file.
After installation, pimdo appears in your extensions list. Configure optional settings (debug logging, custom client ID, tenant ID) via the extension settings UI.
npm (Recommended for other MCP clients)
Requires Node.js 22 or later.
npx @co-native-ab/pimdo-ts@latestThe
@latesttag (or an explicit@vX.Y.Z) is required. On some platformsnpxcannot resolve the binary for a scoped package without an explicit version specifier and fails withnpm error could not determine executable to run.
Configure in your MCP client:
{
"command": "npx",
"args": ["@co-native-ab/pimdo-ts@latest"]
}Pin a specific version (e.g. @co-native-ab/[email protected]) if you want reproducible installs.
Standalone JS bundle
Download pimdo-ts-vX.Y.Z.js from GitHub Releases and run directly with Node.js 22+:
node pimdo-ts-vX.Y.Z.jsVerifying release artifacts
Every artifact in a GitHub Release ships with a sigstore bundle (<artifact>.sigstore) generated by keyless cosign signing in the release workflow. Verify with:
cosign verify-blob \
--bundle pimdo-ts-vX.Y.Z.mcpb.sigstore \
--certificate-identity-regexp '^https://github\.com/co-native-ab/pimdo-ts/\.github/workflows/release\.yml@refs/tags/v.*$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
pimdo-ts-vX.Y.Z.mcpbThe npm tarball additionally carries npm provenance (visible on the npm page), and every artifact has a SLSA build provenance attestation verifiable with gh attestation verify <artifact> --owner co-native-ab.
Tools
The tables below are generated from each tool's descriptor (def.name + def.description) by npm run readme. The matching npm run readme:check runs in CI to detect drift.
Authentication
| Tool | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| login | Sign in to Microsoft Graph. Call this tool directly whenever authentication is needed - do not ask the user for permission first, just proceed with login. Opens a browser for interactive sign-in. Once signed in, all other tools work automatically. If a previous tool returned a Conditional Access step-up error, pass the claims value from that error here to satisfy the challenge, then re-invoke the original tool. |
| logout | Sign out of Microsoft Graph and clear all cached tokens. After logging out, the login tool must be used to re-authenticate. |
| auth_status | Check current authentication status, logged-in user, granted scopes and server version. A good first tool to call when diagnosing PIM access issues or confirming that the right consent has been granted. |
PIM for Entra Groups
| Tool | Description |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| pim_group_eligible_list | List Entra groups the signed-in user is eligible to activate via PIM. Returns the group display name, id, eligibility id and any time bounds. |
| pim_group_active_list | List Entra groups the signed-in user currently has activated via PIM, with their active-until time. |
| pim_group_request_list | List PIM group activation/deactivation requests the signed-in user has submitted that are still pending approval. Stale entries (selfActivate where the user has lost eligibility) are hidden by default; pass includeStale: true to include them - they can then be retracted via pim_group_request_cancel. |
| pim_group_request | Open a browser form for the signed-in user to confirm activation of one or more PIM-eligible Entra groups. The user edits justification and duration per row, then submits. Each confirmed row creates a selfActivate assignment-schedule request via Microsoft Graph. |
| pim_group_request_cancel | Open a browser form for the signed-in user to confirm cancellation of one or more PIM group activation/deactivation requests that are still waiting for approval. Each confirmed row POSTs the cancel sub-resource via Microsoft Graph. |
| pim_group_deactivate | Open a browser form for the signed-in user to confirm deactivation of one or more currently-active PIM group assignments. Each confirmed row submits a selfDeactivate assignment-schedule request via Graph. |
| pim_group_approval_list | List pending PIM group activation requests where the signed-in user is an approver and has not yet recorded a decision. Stale entries (no live stage assigned to the caller) are hidden by default; pass includeStale: true to include them. |
| pim_group_approval_review | Open a browser form for the signed-in user (acting as approver) to Approve, Deny or Skip pending PIM group activation approvals. Each Approve/Deny PATCHes the live approval stage via Microsoft Graph. |
The four read tools return plain text the AI can summarise. The three write tools open a loopback browser form ("requester", "approver", "confirmer") so the human always confirms a privilege change before pimdo posts it to Graph.
PIM for Entra roles
| Tool | Description |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| pim_role_entra_eligible_list | List Entra (directory) roles the signed-in user is eligible to activate via PIM. Returns the role display name, role definition id, eligibility id, directory scope and any time bounds. |
| pim_role_entra_active_list | List Entra (directory) roles the signed-in user currently has activated via PIM, with their active-until time. |
| pim_role_entra_request_list | List PIM Entra-role activation/deactivation requests the signed-in user has submitted that are still pending approval. Stale entries (selfActivate where the user has lost eligibility) are hidden by default; pass includeStale: true to include them - they can then be retracted via pim_role_entra_request_cancel. |
| pim_role_entra_request | Open a browser form for the signed-in user to confirm activation of one or more PIM-eligible Entra (directory) roles. The user edits justification and duration per row, then submits. Each confirmed row creates a selfActivate role-assignment-schedule request via Microsoft Graph. |
| pim_role_entra_request_cancel | Open a browser form for the signed-in user to confirm cancellation of one or more PIM Entra-role activation/deactivation requests that are still waiting for approval. Each confirmed row POSTs the cancel sub-resource via Microsoft Graph. |
| pim_role_entra_deactivate | Open a browser form for the signed-in user to confirm deactivation of one or more currently-active PIM Entra-role assignments. Each confirmed row submits a selfDeactivate role-assignment-schedule request via Graph. |
| pim_role_entra_approval_list | List pending PIM Entra-role activation requests where the signed-in user is an approver and has not yet recorded a decision. Stale entries (no live step assigned to the caller) are hidden by default; pass includeStale: true to include them. |
| pim_role_entra_approval_review | Open a browser form for the signed-in user (acting as approver) to Approve, Deny or Skip pending PIM Entra-role activation approvals. Each Approve/Deny PATCHes the live approval stage via the Microsoft Graph beta endpoint. |
The Entra-role approval read/PATCH operations target the Microsoft Graph beta endpoint, since that is currently the only channel exposing the assignment-approvals surface; the rest of the surface uses v1.0.
PIM for Azure roles
| Tool | Description |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| pim_role_azure_eligible_list | List Azure resource roles (subscriptions, resource groups, resources) the signed-in user is eligible to activate via PIM. Returns the role display name, role definition id, eligibility id, ARM scope and any time bounds. |
| pim_role_azure_active_list | List Azure resource roles the signed-in user currently has activated via PIM, with their active-until time. |
| pim_role_azure_request_list | List PIM Azure-role activation/deactivation requests the signed-in user has submitted. Does not filter by status - surface all visible schedule requests. Stale entries (SelfActivate where the user has lost eligibility) are hidden by default; pass includeStale: true to include them - they can then be retracted via pim_role_azure_request_cancel. |
| pim_role_azure_request | Open a browser form for the signed-in user to confirm activation of one or more PIM-eligible Azure resource roles. The user edits justification and duration per row, then submits. Each confirmed row creates a SelfActivate role-assignment-schedule request via Azure Resource Manager. |
| pim_role_azure_request_cancel | Open a browser form for the signed-in user to confirm cancellation of one or more PIM Azure-role activation/deactivation requests that are still waiting for approval. Each confirmed row POSTs the cancel sub-resource via Azure Resource Manager. |
| pim_role_azure_deactivate | Open a browser form for the signed-in user to confirm deactivation of one or more currently-active PIM Azure-role assignments. Each confirmed row submits a SelfDeactivate role-assignment-schedule request via Azure Resource Manager. |
| pim_role_azure_approval_list | List PIM Azure-role activation requests where the signed-in user is an approver. Does not filter by status - surface the full approver-side queue. Stale entries (no live stage assigned to the caller) are hidden by default; pass includeStale: true to include them. |
| pim_role_azure_approval_review | Open a browser form for the signed-in user (acting as approver) to Approve, Deny or Skip pending PIM Azure-role activation approvals. Each Approve/Deny submits the decision via Azure Resource Manager /batch. |
The Azure-role surface talks to the Azure Resource Manager (ARM) API instead of Microsoft Graph. It uses API version 2020-10-01 for the Microsoft.Authorization/role* resources, 2021-01-01-preview for the roleAssignmentApprovals/.../stages PUT and posts approvals via the 2020-06-01 /batch endpoint.
JSON Schemas for every tool's input are generated under schemas/tools/ from the Zod definitions in src/tools/ (see npm run schemas:generate and the schemas:check CI gate).
Authentication
pimdo uses MSAL (@azure/msal-node) to authenticate with Microsoft. When the agent calls the login tool:
- The tool starts a local loopback HTTP server with a branded landing page and opens it in your browser.
- You click "Sign in with Microsoft", authenticate against Microsoft's OAuth endpoint and the redirect lands back on the loopback server.
- Login completes immediately - no manual code entry needed.
pimdo requires a working browser on the workstation it runs on. If pimdo cannot launch a browser, the login tool fails with an error - there is no manual URL fallback, no device code and no headless mode. pimdo-ts is a workstation tool by design; SSH sessions, containers and other environments without a browser are not supported.
The same login produces tokens for both Microsoft Graph and Azure Resource Manager - pimdo calls Authenticator.tokenForResource(resource, signal) per request and lets MSAL refresh them silently. To sign out and clear cached tokens, call the logout tool.
Use the auth_status tool to check whether you are logged in and see the current user, granted scopes and server version.
Conditional Access step-up
Some PIM activations are gated by Conditional Access "authentication context" rules - most commonly an MFA requirement (acrs=c1), but also compliant-device, hybrid-join, sign-in risk and similar policies. When a tool call hits such a rule, pimdo surfaces a StepUpRequiredError whose message contains the literal claims challenge from Microsoft Graph or Azure Resource Manager.
Recovery is one call: re-invoke the login tool with the claims value from the error message. pimdo opens the browser again, AAD prompts you for the missing factor (MFA, device compliance, …), and once you complete it the AI assistant can re-invoke the original PIM tool. loginHint defaults to the currently signed-in user, so you land directly on the right account without going through the picker.
Required scopes
pimdo authenticates against two resources from the same login:
- Microsoft Graph (
https://graph.microsoft.com) - used for PIM groups and Entra role assignments. - Azure Resource Manager (
https://management.azure.com) - used for Azure resource role assignments.
| Scope | Resource | When required |
| ----------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| User.Read | Graph | always (sign-in identity / refresh tokens) |
| offline_access | Graph | always (sign-in identity / refresh tokens) |
| PrivilegedEligibilitySchedule.Read.AzureADGroup | Graph | List eligible group assignments |
| PrivilegedAssignmentSchedule.ReadWrite.AzureADGroup | Graph | List active group assignments; List my group activation requests / list group approval queue; Submit group activation/deactivation schedule request; Approve or deny a group activation request |
| RoleManagementPolicy.Read.AzureADGroup | Graph | Read group activation policy (max duration, approval rules) |
| RoleEligibilitySchedule.Read.Directory | Graph | List eligible Entra role assignments |
| RoleAssignmentSchedule.ReadWrite.Directory | Graph | List active Entra role assignments; List my Entra role activation requests / list Entra approval queue; Submit Entra role activation/deactivation schedule request; Approve or deny an Entra role activation request (BETA) |
| RoleManagementPolicy.Read.Directory | Graph | Read Entra role activation policy (max duration, approval rules) |
| PrivilegedAccess.ReadWrite.AzureAD | Graph | Approve or deny an Entra role activation request (BETA) |
| https://management.azure.com/user_impersonation | ARM | Read/write Azure role eligibility & assignment schedules (ARM) |
The MCP server starts with no PIM tools enabled. As the tenant grants the scopes listed above (typically through admin consent), the corresponding tools light up automatically on the next login.
Manual Entra app registration
pimdo-ts ships with a default multi-tenant Entra application client ID - 30cdf00b-19c8-4fe6-94bd-2674ee51a3ff, published by Co-native AB - so most users do not need to register their own app. An administrator may need to grant tenant-wide consent for the scopes listed above before non-admin users can sign in.
If your organization requires its own app registration (for example to lock down which tenants can use it), register a multi-tenant Entra application yourself:
- In the Microsoft Entra admin center, register a new application:
- Supported account types: Accounts in any organizational directory (multitenant).
- Platform: Mobile and desktop applications / public client (no client secret).
- Redirect URI:
http://localhost- pimdo's loopback server picks an ephemeral port at runtime, and Microsoft accepts anyhttp://localhost:<port>redirect under the registeredhttp://localhostentry. - Enable "Allow public client flows" under Authentication (required for the loopback PKCE flow).
- Under API permissions, add the delegated permissions listed above for Microsoft Graph and Azure Service Management (
user_impersonation). - Click Grant admin consent for [your tenant] so users can sign in without each individually consenting.
- Copy the application (client) ID. Provide it to pimdo via:
- the Client ID field in the MCPB extension settings (Claude Desktop), or
- the
PIMDO_CLIENT_IDenvironment variable. - Optionally set
PIMDO_TENANT_IDto your tenant GUID (ororganizations); defaults tocommon.
Configuration
| Environment variable | Default | Purpose |
| -------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| PIMDO_CLIENT_ID | 30cdf00b-19c8-4fe6-94bd-2674ee51a3ff | Entra application client ID (defaults to the shared Co-native multi-tenant pimdo-ts app) |
| PIMDO_TENANT_ID | common | Entra tenant ID (common / organizations / GUID) |
| PIMDO_DEBUG | unset | Set to true for verbose stderr logging |
| PIMDO_CONFIG_DIR | OS-default config dir | Override the on-disk config / token-cache location (Linux: ~/.config/pimdo-ts, macOS: ~/Library/Application Support/pimdo-ts, Windows: %APPDATA%/pimdo-ts) |
| PIMDO_GRAPH_URL | https://graph.microsoft.com/v1.0 | Override Microsoft Graph base URL (sovereign clouds, testing). Hostname must be on the built-in Microsoft allow-list unless PIMDO_ALLOW_INSECURE_API_HOSTS=true. |
| PIMDO_GRAPH_BETA_URL | https://graph.microsoft.com/beta | Override Microsoft Graph beta base URL (same allow-list as above) |
| PIMDO_ARM_URL | https://management.azure.com | Override Azure Resource Manager base URL (same allow-list as above) |
| PIMDO_ALLOW_INSECURE_API_HOSTS | unset | Set to true to allow PIMDO_*_URL to point at non-Microsoft https:// hosts. Intended for local mock servers; never set in production. |
| PIMDO_ACCESS_TOKEN | unset | Static access token for testing (bypasses MSAL). Requires PIMDO_ALLOW_STATIC_TOKEN=true to opt in; not for production use. |
| PIMDO_ALLOW_STATIC_TOKEN | unset | Set to true to enable the PIMDO_ACCESS_TOKEN static-token bypass. |
Security model
- No standing access. pimdo only ever holds tokens scoped to the granted permissions and acts as the signed-in user.
- Human-in-the-loop for every privilege change.
request,deactivateandapproval_reviewalways open a loopback browser form so you confirm or override the AI's intent before pimdo issues the API call. - Loopback hardening. The browser flow ships CSRF + Content-Security-Policy + strict header parsing (see
src/browser/security.ts). - Tool gating. Every PIM tool declares the scopes it needs; the registry enables a tool only when all required scopes are present in the granted set, then re-syncs after every login.
Development
npm install
npm run check # format + icons + schemas + lint + typecheck + tests
npm run build # produces dist/index.js
npm run mcpb # produces pimdo.mcpb (uses dist/)
node dist/index.js # starts the MCP server on stdio
npm run docs:check # network-only: verify @see learn.microsoft.com URLs resolvenpm run schemas:generate regenerates schemas/tools/*.json from the Zod input schemas in src/tools/. The matching schemas:check runs in CI to detect drift.
Fuzzing
Property-based fuzz tests live in fuzz/ and run via jazzer.js:
npm run fuzz:duration # ISO-8601 PIM duration parser/formatter round-trip
npm run fuzz:escape-html # HTML-entity escaping invariantsBoth run forever by default; pass -- -- -max_total_time=30 (or a -runs=N limit) for a bounded run. CI runs each target for 30s on PRs and 10min on the nightly schedule (see .github/workflows/fuzz.yml).
FAQ
Do I need to register my own Entra app? No - pimdo-ts ships with a shared multi-tenant Entra application (client ID 30cdf00b-19c8-4fe6-94bd-2674ee51a3ff, published by Co-native AB). Most users can sign in directly. Some tenants require an administrator to grant tenant-wide consent first; an admin can pre-consent the scopes listed above for the shared app, or you can register your own and override PIMDO_CLIENT_ID (see "Manual Entra app registration").
Does pimdo store any of my data? It caches MSAL tokens (encrypted by MSAL) in your OS config dir under pimdo-ts/. No PIM resources, no listings, no approvals. Logout clears the token cache.
Why both Graph and ARM tokens? Entra Groups and Entra Roles live in Microsoft Graph; Azure resource roles live in Azure Resource Manager. The same MSAL account gets a token for each resource silently, so login is still one click.
Can I use a personal Microsoft account? No - Entra PIM is a work/school feature. Use a tenant where PIM is enabled.
Why do some tools target Microsoft Graph beta? The Entra-role assignment-approvals surface is currently only available on beta. Everything else uses v1.0.
License
MIT - see LICENSE.
