entra-scim-mcp
entra-scim-mcp
Servidor de Model Context Protocol para la API de aprovisionamiento SCIM 2.0 de Microsoft Entra (GA abril de 2026). Expone operaciones del ciclo de vida de usuarios y grupos contra https://graph.microsoft.com/rp/scim como herramientas MCP para agentes como Claude.
Qué puedes hacer con él
Descubrir las capacidades SCIM del inquilino (
get_service_provider_config,list_resource_types,list_schemas)Aprovisionar, leer, actualizar y desaprovisionar usuarios, incluyendo Atributos de Seguridad Personalizados y atributos de ciclo de vida
Crear, actualizar y eliminar grupos, y gestionar la pertenencia respetando automáticamente las estrictas reglas PATCH de la API
Related MCP server: mcp-m365-mgmt
Requisitos previos
Antes de que este servidor pueda comunicarse con tu tenant, completa la configuración única en la documentación de Microsoft:
Entra ID P1 (o cualquier SKU que incluya P1) y una suscripción de Azure que vincular para facturación.
Habilita la SCIM Provisioning API en ID Governance → Dashboard y vincula un grupo de recursos de facturación.
Registra una aplicación con los permisos de aplicación de Microsoft Graph que necesites:
User.ReadWrite.All,Group.ReadWrite.All(ciclo de vida principal)CustomSecAttributeAssignment.ReadWrite.All,CustomSecAttributeDefinition.Read.All(herramientas de CSA)User-LifeCycleInfo.ReadWrite.All(herramientas de ciclo de vida)User-Mail.ReadWrite.All,User-Phone.ReadWrite.All,User.EnableDisableAccount.All(alternativas de privilegio mínimo) Concede el consentimiento del administrador.
Crea o bien un secreto de cliente o bien sube un certificado de cliente PEM.
Cada llamada a la API SCIM se factura; este servidor no agrupa solicitudes más allá de lo que la API exige.
Pruébalo sin un tenant de Entra
El paquete incluye un mock local de la API SCIM de Entra (entra-scim-mock-server) para que puedas usar todas las herramientas sin configuración de Azure y sin coste de API:
# shell 1 — start the mock (seeds a small demo tenant)
npx -y --package entra-scim-mcp entra-scim-mock-serverDespués, apunta el servidor MCP hacia él:
{
"mcpServers": {
"entra-scim-mock": {
"command": "npx",
"args": ["-y", "entra-scim-mcp"],
"env": {
"ENTRA_SCIM_BASE_URL": "http://127.0.0.1:8990",
"ENTRA_SCIM_STATIC_TOKEN": "dev-token"
}
}
}
}Opciones del mock: --port, --token, --seed <file.json>, --no-seed, --capture <file.jsonl> (registra cada solicitud/respuesta), --validator-compat (comportamiento estándar RFC para el Validador SCIM de Microsoft — consulta la documentación).
Instalación / ejecución
El servidor es un servidor MCP stdio, diseñado para ser iniciado por tu cliente MCP (Claude Desktop, Claude Code, etc.).
npx -y entra-scim-mcpEntorno requerido:
Var | Requerida | Descripción |
| sí | GUID del directorio (tenant) |
| sí | GUID del registro de la aplicación (cliente) |
| una de | Valor del secreto de cliente (desarrollo) |
| una de | Ruta a un PEM que contenga el certificado y la clave privada |
| opcional | Contraseña si el PEM está cifrado |
Configura exactamente una de las variables ENTRA_CLIENT_SECRET o ENTRA_CLIENT_CERT_PATH.
Variables de entorno para desarrollo / pruebas
Variable | Descripción |
| Sobrescribe la TDL SCIM (por defecto |
| Usa un token de portador fijo en lugar de Azure AD. Protecciones: requiere |
| Configúrala a |
Los resultados de la simulación en seco devuelven un payload exitoso:
{
"dryRun": true,
"request": {
"method": "DELETE",
"url": "https://graph.microsoft.com/rp/scim/users/u-1",
"headers": {}
}
}(DELETE no lleva ningún encabezado Accept; la API rechaza un tipo de medio JSON específico en ese caso. Todos los demás métodos envían Accept: application/json.)
Las herramientas de múltiples solicitudes (p. ej., add_group_members con más de 20 identificadores) solo devuelven su primera solicitud segmentada en modo de prueba.
Configuración de Claude Desktop
~/Library/Application Support/Claude/claude_desktop_config.json (macOS) o %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"entra-scim": {
"command": "npx",
"args": ["-y", "entra-scim-mcp"],
"env": {
"ENTRA_TENANT_ID": "00000000-0000-0000-0000-000000000000",
"ENTRA_CLIENT_ID": "11111111-1111-1111-1111-111111111111",
"ENTRA_CLIENT_SECRET": "..."
}
}
}
}Para producción, cambia el secreto por un certificado:
{
"env": {
"ENTRA_TENANT_ID": "...",
"ENTRA_CLIENT_ID": "...",
"ENTRA_CLIENT_CERT_PATH": "/secure/path/entra-scim-mcp.pem"
}
}Herramientas
Herramienta | Propósito |
| Descubrimiento de capacidades automático. |
| Enumerar tipos de recursos SCIM (Persona, Grupo). |
| Enumerar esquemas SCIM y extensiones Entra. |
| Listar usuarios; admite el filtro restringido de la API (solo |
| Leer un único usuario por identificador con proyección de atributos opcional. |
| Crear un usuario con el conjunto de atributos obligatorio impuesto ( |
| Actualizar un usuario con PATCH; impide |
| Eliminar un usuario con DELETE. |
| Establecer atributos del ciclo de vida (p. ej., |
| Leer los ASC de un usuario, proyectados por conjunto de atributos. |
| Actualizar los ASC de un usuario con PATCH. |
| Listar grupos con el conjunto de filtros restringido de la API. |
| Leer un único grupo (no devuelve los miembros; usa |
| Crear un grupo con POST. Configura |
| Actualizar solo atributos de grupo con PATCH; aquí se rechazan operaciones de pertenencia. |
| Añade ≥1 usuarios a un grupo: segmenta automáticamente en bloques de máximo 20 miembros por PATCH (límite de la API), una sola operación por PATCH. Si falla a mediados de la secuencia, reporta |
| Eliminar un usuario de un grupo con una sola eliminación por PATCH (la API solo permite una sola por PATCH, sin otras operaciones). |
| Eliminar un grupo con DELETE. |
Qué impone este servidor por ti
La API SCIM de Entra tiene restricciones que son fáciles de pasar de entrar. La capa de herramientas rechaza entradas incorrectas antes de enviar una solicitud:
Lista blanca de filtros: solo los atributos y operadores documentados por recurso; se rechaza
or;externalIdno se puede combinar con otra cláusula.Cadenas de consulta: sin espacios en los lados de
=(la API devuelve un 400 para cualquiera).PATCH de usuario: se bloquea
removedemailNickname; el filtro de rutas de addresses debe ser exactamente[type eq "work"].PATCH de grupo: las operaciones de pertenencia se deben hacer a través de herramientas dedicadas para garantizar el límite de 20 miembros de alta y la regla de eliminación individual.
Alta de miembros idempotente: la API considera exitosas las reintroducciones;
add_group_memberselimina duplicados de la entrada.
Los errores se devuelven al agente como un payload estructurado con status, scimType y detail.
Comportamientos de la API que conviene conocer
Cosas que hace la API en vivo que los documentos no dicen claramente o no dicen en absoluto. Cada una se encontró funcionando contra un tenant real o el Validador SCIM de Microsoft, y cada una está resuelta por ti; se listan en este apartado de cómunicación de comprender la respuesta.
**DELETE no debe llevar una cabecera Accept. ** La API responde 400 Accept header application/json is invalid. Se probaron las cuatro variantes en vivo: sin cabe cera → 204, */* → 204, application/json → 400, application/scim+json → 400. Only DELETE invertor la regla — cada otro método requiere un Accept JSON, y omitirlo es un 400 documentado. Esto romió silenciosaente deprovision_user y delete_group hasta la primea ejecución en vivo.
CustomSecurity Attributes nunca vuelven a devolver en una lectura simple. En/SChems el atributo a nivel de conjunto de atributos es retured: "request", as so get_ser no inclúira CSA sin que impórt lo que se pid. Solo apaarrect when se nomb r explíci, and la proyección es por conjunto: urn:...:CustomSecurityAttributes:<Set>. La URN de extrema desstandard rejected directly (400 ... not supported in the "attributes" or "excludedAttributes" query parameter), and que por eso attributeSets ent us required input and not one opcionales.
CSA values are tipoized, and the type is enforced. Boolean, Integer, String and multi-valued String all round-trip both in tact when enviano as the only commonation; is type JSON, and one PATCH may can carry many attrs at once. Two removal behaviors, no one documented by Microsoft:
op: "remove"on a CSA road causes that one cluster or other intact.replacewith[]on an a multi-vaqued attribute removes the quote — a data.
password is required on create but can be never read. It is writeOnly / returneds, and no response eechoes it. The full required set at create is userName, password, displayName, name.givenName, name.familyName and mailNickname, considerably stronger that RFC 7643, which only demands userName.
Group displayName is not unique. Enta is acceptable duplicado and returns 201. The tool that has a RFC orientation often assumes 409 here, so don’t trusts of create to fail to detect an existing group — first filter it.
Remove a group member is a question of membership, not of user. A remove on members[value eq "<id>"] that matches nothing is a 404 because the id of person; a live user simply never in grupo is rejected exactly like GUID who never was a user. It has been checked against a real tenant with a mobile member present throughout, so no is it. is not another artifact of the emptied group:
Case | Result |
A real member | 204 |
A live user who never was a member | 404 |
A well-formed GUID that was never a user | 404 |
A member whose user was fired first | 404 |
Two consequences you should know. When a user is removed, his memberships disappear, so the ordering of "delete then check delete memberships" ends up in the same situation than "any others not member"; this was confirmed by looking at list_groups with a filter on members.value just before and after the delete. And the error names the group, not the member (Resource '<groupId>' does not exist or one of its queried reference-property objects are not present), which is weird because the group actually remains. A probe with a deliberately invalid group id also threw the same sentence naming that id, so the text simply echoes the PATCH targeted. The mock reproduces all of this rather than improve it. Run it again with npx tsx scripts/probe-member-removal.ts --confirm (~17 billable calls).
Reads of a group never include members. get_group does not return in any member anywhere, whatever the size of page. To find groups of a user, filter to the reverse: list_groups con members.value eq "<userId>".
The errors have structure and are worth displaying verbatim. A failure carries status, scimType and detail; and the detail text is unusually specific (names the offending operation index and the constraint). The tools pass this unchanged instead of flattening it into a message.
All calls are billed. There is no batching beyond what the API itself requires, so a wordy agent is really expensive. add_group_members makes batches with the API's cap at 20 members, and this is the only point where batching happens.
Test against a real tenant
The test suite never hits a real tenant. To validate the tools against a live Entra, put the credentials in a .env ignored by git and run the smoke script.
cd node
cp .env.example .env # then fill in tenant id, client id, and the secret VALUEThe .env are read only by the scripts in scripts/. The published server always reads process.env, so it can never pick up a .env inserted by any folder where an MCP client starts it. A variable published in environment always wins over a file.
Variable | Purpose |
| Verified setting the temporary |
| Set attribute name. Set it so to cover the two Custom Security Attributes tools |
| Attribute name inside that set |
| What value twice. CSAs are typed and the API rejects a type mismatch: |
The smoke script
ENTRA_SCIM_LIVE=1 npm run smoke:live # bash
$env:ENTRA_SCIM_LIVE=1; npm run smoke:live # PowerShellTo pass flags, call the script directly – npm run x -- --flag not robust forwarding on Windows:
npx tsx scripts/live-smoke.ts --confirmOne orderly pass over all 18 tools in around 21 billed calls. It creates two users and one group, does all reads, PATCHes and deletes on them, and then deletes them again. Characteristics:
It refuses to be run by accident. If there isn’t
ENTRA_SCIM_LIVE=1or--confirm, it prints the tenant, endpoint and cost, and then stops. IfENTRA_SCIM_DRY_RUNorENTRA_SCIM_STATIC_TOKENis set, it explicitly refuses unless--rehearseis passed, because such run does not prove anything for the live API.It doesn't stop when one fails. A failed step marks its dependencies as
skip, and everything independent still runs, so a single run lets you know which tools the live API actually supports. Exit code is nonzero if anything fails.Test identity are obvious
scim-smoke-<runId>-1@<domain>and a groupSCIM Smoke <runId>.Cleanup is always performed. Whatever is created is gone in a
finallyblock, the leftovers are displayed with their ids. Recover from shed back a process withnpx tsx scripts/live-smoke.ts --sweepthat lists the missingscim-smoke-*users; then add--confirmto remove them. The sweep will never modify an account not inscim- smoke-prefix.
While until a attribute set exists in the tenant (“Entra portal -> Protection -> Custom security attributes”) and ENTRA_SCIM_SMOKE_CSA_SET / ENTRA_SCIM_SMOKE_CSA_ATTR name it, those two Custom Security Attributes produce skip. Everything other runs without assistance.
Once the set is present, validate only these two tools for ~9 calls instead of 21. Validated reads every value back (a PATCH that the API accepts but does not store would otherwise look like a success), covers each type already declared, and does a removal:
npx tsx scripts/live-smoke.ts --csa-only --confirmSet the set form once and derive a test value for each type:
ENTRA_SCIM_SMOKE_CSA_ATTRS=isManaged:bool,accountType:string,trustLevel:int,locations:string[]Confirmed live for all four types: values round-trip intact, op: "remove" clears one entry and leaves the rest, and replace of a multi-valued → [] deletes it. The later read omits the attribute entirely, instead of giving empty array.
For zero cost before spending, rehearsals. Validates the script, not the API.
# no network at all
ENTRA_SCIM_DRY_RUN=1 npx tsx scripts/live-smoke.ts --rehearse
# or against the local mock: start it in one shell...
npm run mock
# ...and in another, aim the script at it
export ENTRA_SCIM_BASE_URL=http://127.0.0.1:8990
export ENTRA_SCIM_STATIC_TOKEN=dev-token
npx tsx scripts/live-smoke.ts --rehearseThe mock rehearsal is worth doing: real HTTP, real id and the full ordering create / PATCH / delete are exercised, so catches ordering and cleanup failures before you pay. It does not replace the real run (first live initialization found two bugs no mock run had caught; see what the testing legs really caught).
What the testing legs really exposed
Three independent legs, each has found things the other could not. That's the reason all three exist:
Leg | Cost | Found |
Mock + unit tests | free | Sequencing, validation and cleanup errors. Fast, but shares its own hypothesis, so it cannot catch a wrong hypothesis. |
Live tenant ( | Side: ~21 billed event | The DELETE error Accept (two never executed tool), the invalid bare CSA URN, and the types/removes. |
SCIM Validator ([node/docs/scim-validator.md) | free | Seven gaps in mock fidelity: places the mock was more likely than a real SCIM client expects; each one had hidden a real behavior. |
What worth preserving take away: the mock leniency hides actual behavior of the API..: How mock leniency hides the real behavior. Every deficiency that a live execution found before had passed a full mock suite, because the mock had been written using the same reading of the documentation as the client. A third-party client (the validator) and a living tenant were the only factors that could break this circularity.
Driving the live tenant conversationally
The .mcp.json at root registers the server in Claude Code using scripts/dev-server.mjs; that script loads node/.env and starts the built server, so no secret is put into a committed configuration file.
It executes the built server, so node/dist must exist before your MCP client can launch it. A fresh clone is reached by:
cd node && npm install # the "prepare" script builds as part of installAfter a source modification, rebuild and restart the client so it relaunches the command:
cd node && npm run buildDevelopment
cd node
npm install # installs, then builds via "prepare"
npm test
npm run lint # ESLint, type-aware
npm run format:check # Prettier
npm run typecheck # strict tsc over src, test and scripts
npm run test:coverage # vitest with the coverage gate
npm run build # rebuild after a source change
npm run mock # run the local mock server (tsx, no build needed)
npm run mock:capture # mock in validator-compat mode, capturing traffic to captures/npm run lint, format:check, typecheck and test are the four gates CI runs on every push/pull request, along with npm audit --audit-level=high.
The server has no test dependency on a real tenant. Unit testing covers the filter, patch, query and the client layer; integration tests start the in-process mock server and point all MCP tools end-to-end over real HTTP (node/test/integration/). Captured SCIM Validator sessions can be turned into replay fixtures using npm run fixtures:convert. For the one thing all of this can't guarantee – that live API accepts those payloads – see Testing against a real tenant.
Releasing
The version physically occupies four places: node/package.json, node/package-lock.json (twice) and server.json (twice: once for registry record, and once for the npm Package it points). A single command writes all of them.
cd node
npm version minor # or patch / major — writes all four, stages three
cd ..
git commit -m "v0.2.0" # the version npm just printed
git tag -a v0.2.0 -m v0.2.0
git push --follow-tagsEl -a importa: --follow-tags solo envía etiquetas anotadas, así que una etiqueta ligera como git tag v0.2.0 se queda en tu máquina y el push informa de éxito sin haber enviado ninguna etiqueta — el lanzamiento simplemente nunca se ejecuta.
npm version incrementa la versión en package.json y en el lockfile; luego el script del ciclo de vida version lo propaga a server.json y prepara el resultado para el commit. No hace commit ni etiqueta, aunque npm version normalmente hace ambas cosas: npm busca .git junto al paquete que está versionando, este paquete vive en node/, y el .git del repositorio está un nivel más arriba — así que npm decides que no está en un repo git y se salta esos pasos sin decirlo. De ahí el commit y la etiqueta explícitos de arriba. Si te equívocas en esto, git push --follow-tags no envía nada en silencio, porque la etiqueta a seguir nunca se creó.
npm run check:version comprueba que los cuatro coinciden, CI lo ejecuuta en every push y the workflow lanzamiento lo vuelve a ejecutar contra la propia etiqueta — así que una etiqueta que no cocuerde con package.json falla antes de publicar any cos a. La versión que el servidor reporta en su handshake de MCP se lee de package.json en tiempo de ejecución, así que sigue automáticaamente that valor.
Al hacer push de una etiqueta v* se ejecuta [`.github/workflows/releas¿. ---
verify: lint, format, typoformat, typecheck, cobertura tests, version/tag check, and
mcp-publisher validatecontra el registro vivo. ...
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseAqualityBmaintenanceEnables AI assistants to inspect employee access, list failed onboarding events, and retry provisioning operations.3
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants and automation tools to manage Microsoft 365, Entra ID, and Intune resources through 32 tools for user/device/file management and infrastructure monitoring.5MIT
- AlicenseAqualityCmaintenanceEnables identity provisioning and management for Microsoft 365/Entra ID via Microsoft Graph, including user creation, license assignment, group membership management, and more, with a focus on least-privilege and idempotency.18Apache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to safely provision new Google Workspace accounts for employee onboarding, with availability checks, account creation, and credential delivery, all behind OAuth and per-user allowlists.MIT
Related MCP Connectors
Create and manage AI agents that collaborate and solve problems through natural language interacti…
Runtime permission, approval, and audit layer for AI agent tool execution.
Give AI agents the LinkedIn tools to find, qualify, engage, and follow up with prospects.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/darrenjrobinson/entra-scim-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server