entra-scim-mcp
entra-scim-mcp
Сервер Model Context Protocol для SCIM 2.0 Provisioning API Microsoft Entra (GA апрель 2026). Предоставляет операции жизненного цикла пользователей и групп через https://graph.microsoft.com/rp/scim как MCP-инструменты для агентов, подобных Claude.
Что вы можете с ним делать
Обнаруживать возможности SCIM тенанта (
get_service_provider_config,list_resource_types,list_schemas)Создавать, читать, обновлять и удалять пользователей — включая настраиваемые атрибуты безопасности (Custom Security Attributes) и атрибуты жизненного цикла
Создавать, обновлять и удалять группы, а также управлять членством с автоматическим соблюдением строгих правил PATCH API
Related MCP server: mcp-m365-mgmt
Предварительные требования
Прежде чем этот сервер сможет взаимодействовать с вашим тенантом, выполните одноразовую настройку из документации Microsoft:
Entra ID P1 (или любой SKU, содержащий P1) и подписка Azure для привязки биллинга.
Включите SCIM Provisioning API в ID Governance → Dashboard и привяжите группу ресурсов для биллинга.
Зарегистрируйте приложение с необходимыми разрешениями Microsoft Graph application:
User.ReadWrite.All,Group.ReadWrite.All(основной жизненный цикл)CustomSecAttributeAssignment.ReadWrite.All,CustomSecAttributeDefinition.Read.All(инструменты CSA)User-LifeCycleInfo.ReadWrite.All(инструменты жизненного цикла)User-Mail.ReadWrite.All,User-Phone.ReadWrite.All,User.EnableDisableAccount.All(альтернативы с минимальными правами) Выполните согласие администратора.
Создайте либо секрет клиента, либо загрузите PEM-сертификат клиента.
Каждый вызов SCIM SCIM API тарифицируется — этот сервер не пакетирует запросы сверх того, что требует API.
Попробуйте без тенанта Entra
Пакет содержит локальный мок Entra SCIM API (entra-scim-mock-server), поэтому вы можете проверить все инструменты без настройки Azure и без расходов на API:
# shell 1 — start the mock (seeds a small demo tenant)
npx -y --package entra-scim-mcp entra-scim-mock-serverЗатем укажите на него MCP-сервер:
{
"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"
}
}
}
}Флаги мока: --port, --token, --seed <file.json>, --no-seed, --capture <file.jsonl> (журналирует каждый запрос и ответ), --validator-compat (поведение, совместимое с RFC, для Microsoft SCIM Validator — см. docs/scim-validator.md).
Установка и запуск
Сервер — это stdio MCP-сервер, предназначенный для запуска вашим MCP-клиентом (Claude Desktop, Claude Code и т. п.).
npx -y entra-scim-mcpНеобходимые переменные окружения:
Переменная | Обязательность | Описание |
| да | GUID каталога (тенанта) |
| да | GUID регистрации приложения (клиента) |
| один из | Значение секрета клиента (для разработки) |
| один из | Путь к PEM-файлу, содержащему сертификат и закрытый ключ |
| необязательно | Пароль, если PEM-файл зашифрован |
Задайте ровно одно из значений: ENTRA_CLIENT_SECRET или ENTRA_CLIENT_CERT_PATH.
Переменные окружения для разработки и тестирования
Переменная | Описание |
| Переопределяет базовый URL SCIM (по умолчанию |
| Использует фиксированный bearer-токен вместо Azure AD. Ограничения: требует |
| Установите |
Результаты dry-run возвращаются как успешный ответ:
{
"dryRun": true,
"request": {
"method": "DELETE",
"url": "https://graph.microsoft.com/rp/scim/users/u-1",
"headers": {}
}
}(DELETE не содержит заголовок Accept — API отклоняет конкретный JSON media type именно в этом случае. Все остальные методы отправляют Accept: application/json.)
Инструменты с несколькими запросами (например, add_group_members с более чем 20 идентификаторами) в режиме dry-run возвращает только свой первый фрагментированный запрос.
Конфигурация Claude Desktop
~/Library/Application Support/Claude/claude_desktop_config.json (macOS) или %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": "..."
}
}
}
}Для продакшена замените секрет на сертификат:
{
"env": {
"ENTRA_TENANT_ID": "...",
"ENTRA_CLIENT_ID": "...",
"ENTRA_CLIENT_CERT_PATH": "/secure/path/entra-scim-mcp.pem"
}
}Инструменты
Инструмент | Назначение |
| Разовая проверка возможностей. |
| Перечисление типов ресурсов SCIM (User, Group). |
| Перечисление схем SCIM SCIM и расширений Entra. |
| Список пользователей; поддерживает ограниченный фильтр API (eq/эв, только and) и пагинацию по курсору. |
| Чтение одного пользователя по идентификатору с необязательной проекцией атрибутов. |
| Создание пользователя с обязательным набором атрибутов (userName, password, displayName, name.givenName, name.familyName, mailNickname). |
| PATCH-обновление пользователя; блокирует |
| Удаление пользователя (DELETE). |
| Устанавливает атрибуты жизненного цикла (например, |
| Чтение CSA пользователя с проекцией по набору атрибутов. |
| PATCH-обновление CSA пользователя. |
| Список групп с ограниченным фильтром API. |
| Чтение одной группы (участники НЕ возвращаются — используйте |
| POST-создание группы. Устанавливает |
| PATCH только атрибутов группы (операции членства здесь отклоняются). |
| Добавляет ≥1 пользователя в группу — автоматическая разбивка по 20 идентификаторам на каждый PATCH (лимит API), одна операция на PATCH. При сбое в середине последовательности сообщает |
| Удаляет одного пользователя из группы (API допускает только одно удаление на PATCH, без других операций). |
| DELET-группы. |
Что этот сервер обеспечивает для вас
У SCIM API Entra есть ограничения, которые легко упустить. Слой инструментов отклоняет некорректные входные данные до отправки запроса:
Список разрешенных фильтров: только задокументированные атрибуты и операторы для каждого ресурса;
orотклоняется;externalIdне может быть скомбинирован с другим условием.Параметры запроса: без пробелов вокруг
=. (API возвращает 400 в любом случае.)PATCH пользователя:
и removeуmailNicknameблокируется; фильтр пути адресов должен быть точно[type eq "work"].PATCH группы: операции с членством выполняются через выделенные инструменты, поэтому добавление ограничено 20 участниками, а правило одного удаления гарантировано.
Идемпотентное добавление участников: API считает повторное добавление успехом;
add_group_membersустраняет дубликаты из входных данных.
Ошибки возвращаются агенту в виде структурированного payload с полями status, scimType и detail.
Поведение API, которое стоит знать
То, что реальный API делает так, как документация указывает либо неоднозначно, либо вообще не описывает. Каждый вариант был найден при работе с реальным тенантом или Microsoft SCIM Validator, и каждый пункт уже обработан для вас — они указаны, потому что меняют то, как читать ответ.
DELETE не должен содержать заголовок Accept. API отвечает 400 Accept header application/json is invalid. Все четыре варианта были проверены вживую: без заголовка → 204, */* → 204, application/json → 400, application/scim+json → 400. Только для DELETE правило инвертируется — каждый другой метод требует JSON-Accept, и его отсутствие дает документированную ошибку 400. Именно это незаметно ломало deprovision_user и delete_group до первого живого прогона.
Custom Security Attributes никогда не возвращаются при обычном чтении. В /Schemas атрибут уровня набора атрибутов имеет значение returned: "request", поэтому get_user не включит CSA, что бы вы ни запросили. Они появляются только при явном указании имени, а проекция — на уровне набора: urn:...:CustomSecurityAttributes:<Set>. Сам URN расширения отклоняется напрямую (400 ... not supported in the "attributes" or "excludedAttributes" query parameter), поэтому attributeSets — обязательный вход, а не необязательный.
Значения CSA типизированы, и тип контролируется. Boolean, Integer, String и многозначный String полностью проходят при отправке соответствующим JSON-типом; один PATCH может нести сразу несколько атрибутов. Два способа удаления, ни один из которых не задокументирован Microsoft:
op: "remove"по пути CSA очищает это одно назначение и оставляет остальные нетронутыми.replaceсо значением[]на многозначном атрибуте удаляет назначение — при последующем чтении атрибут отсутствует, а не возвращается как пустой массив.
password обязателен при создании, но никогда не читается. Он помечен как writeOnly / returned: never, и ни один ответ его не возвращает. Полный обязательный набор для создания: userName, password, displayName, name.givenName, name.familyName и mailNickname — значительно строже, чем RFC 7643, который требует только userName.
displayName группы не уникален. Entra принимает дублирующееся имя группы и возвращает 201. RFC-ориентированные инструменты часто ожидают в этом случае 409, поэтому не полагайтесь на ошибку при создании как на способ обнаружить существующую группу — сначала фильтруйте.
Удаление участника группы — это про членство, а не про пользователя. remove по пути members[value eq "<id>"], не совпавший ни с чем, возвращает 404, чему бы этот id ни принадлежал: реальный пользователь, который просто никогда не состоял в этой группе, отклоняется точно так же, как GUID, который никогда не был пользователем. Проверено на реальном тенанте с дополнительным участником в группе на протяжении всего теста, так что это не результат её опустошения:
Сценарий | Результат |
Реальный участник | 204 |
Живой когда-либо user, но не участник | 404 |
Корректный GUID, который не user | 404 |
Участник, чей пользователь удален раньше | 404 |
Стоит знать два следствия. Удаление пользователя снимает его членства, поэтому порядок «сначала удалить, потом убрать членство» попадает в ту же категорию «не участник» — подтверждается запросами list_groups с фильтром members.value до и после удаления. А сообщение об ошибке называет группу, а не участника (Resource '<groupId>' does not exist or one of its queried reference-property objects are not present), что выглядит странно: группа-то существует. Пробы с заведомо несуществующим id группы вернули ту же фразу, но с этим id — текст указывает просто цель PATCH. Мок воспроизводит всё это один в один, а не «чинит». Повторный прогон: npx tsx scripts/probe-member-removal.ts --confirm (~17 платных вызовов).
Чтение группы никогда не включает участников. get_group не возвращает массив members ни при каком размере страницы. Чтобы найти группы пользователя, фильтруйте наоборот: list_groups с members.value eq "<userId>".
Ошибки структурированы, и их стоит показывать дословно. Сбои несут status, scimType и detail, причем текст detail необычно конкретен (он укажет индекс проблемной операции и нарушенное ограничение). Инструменты передают его без изменений.
не сводя к обычному тексту сообщения.
Каждый вызов тарифицируется. Никакого батчинга, сверх того, что требуется самим API, нет, поэтому «болтливый» агент стоит реальных денег. add_group_members разбивает запрос по ограничению API в 20 участников — это единственное место, где батчинг есть.
Тестирование на реальном тенанте
Тестовый комплекс никогда не касается реального тенанта. Чтобы проверить инструменты против живого Entra, поместите учётные данные в gitignored .env и запустите smoke-скрипт.
cd node
cp .env.example .env # then fill in tenant id, client id, and the secret VALUE.env читается только скриптами в scripts/. Опубликованный сервер всегда читает process.env, поэтому он никогда не подберёт случайный .env из каталога, в котором его запускает MCP-клиент. Переменная, уже установленная в окружении, всегда имеет приоритет над файлом.
Переменная | Назначение |
| Проверенный домен, в котором создаются одноразовые тестовые сущности |
| Имя набора атрибутов; задайте его, чтобы покрыть два инструмента Custom Security Attributes |
| Имя атрибута внутри этого набора |
| Присваиваемое значение. CSA типизированы, и API отклоняет несоответствие: |
Smoke-скрипт
ENTRA_SCIM_LIVE=1 npm run smoke:live # bash
$env:ENTRA_SCIM_LIVE=1; npm run smoke:live # PowerShellЧтобы передавать флаги, вызывайте скрипт напрямую — npm run x -- --flag на Windows передает флаги ненадежно:
npx tsx scripts/live-smoke.ts --confirmОдин упорядоченный проход по всем 18 инструментам примерно за 21 платный вызов. Он создает двух пользователей и группу, пробы на каждом из них все чтения, PATCH и удаления, затем удаляем их обратно. Цитаты:
Он не запускается по случайности. Без
ENTRA_SCIM_LIVE=1или--confirmон печатает тенанет, endpoint и стоимость, затем выходит. Если заданыENTRA_SCIM_DRY_RUNилиENTRA_SCIM_STATIC_TOKEN, он отказывается запускаться категориччно, пока не передан--rehearse, потому что такой прогон ничего не доказывает относительно живого API.Он не останавливается на первой ошибке. Неудачный шаг помечает зависимые как
skip, независимые все равно выполняются, так что по результатам одного автогона понятно, какие инструменты живый API принимает. Код возврата ненулевой, если случилась ошибка.Тестовые сущности очевидны:
scim-smoke-<runId>-1@<domain>и группаSCIM Smoke <runId>.Очистка гарантирована. Все созданное удаляется в блоке
finally, и любые остатки печатаются с их id. Для восстановления после упавшего прогона запуститеnpx tsx scripts/live-smoke.ts --sweep, чтобы вывести потерянных пользователейscim-smoke-*, затем добавьте--confirm, чтобы их удалить. Sweep никогда не тронет аккаунты без префиксаscim-smoke-.
Два инструмента Custom Security Attributes помечаются skip, пока в тенанте не появится набор атрибутов (портал Entra -> Protection -> Custom security security attributes) и ENTRA_SCIM_SMOKE_CSA_SET / ENTRA_SCIM_SMOKE_CSA_ATTR его не зададут. Всё остальное работает полностью автоматически.
Когда набор существует, можно проверить только эти два инструмента примерно за 9 вызовов, а не 21 — это читает каждое значение обратно (то есть PATCH, который API принимает, но ничего не хранит, не выглядел условно успешным), также это покрывает все заявленные типы данных и проверяет удаление):
npx tsx scripts/live-smoke.ts --csa-only --confirmОпишите форму набора один раз — прого выводчит тестовые значения для каждого типа:
ENTRA_SCIM_SMOKE_CSA_ATTRS=isManaged:bool,accountType:string,trustLevel:int,locations:string[]Подтверждено живьем по всем четырем типам: значения уходят без изменений, op: "remove" сбрасывает одно присваивание и оставляет остальные, replaceна многозначный атрибут с[]` удаляет его полностью — при чтении атрибут может отсутствовать, вместо возврата пустого массива.
Репетиция бесплатна, до того как тратить: она проверяет сам скрипт, а не 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 --rehearseМоковая репетициz — та, которую стоит сделать: она задействует настоящий HTTP, реальные id и full последовательность create/patch/delete, поэтому отлавливает ошибки очередности и очистки до того, как вы потратите деньги. Она не заменяет живой прогон — он первый живой прогон нашел два бага, которых не поймало ни один мок (см. Что именно поймали тестовые ветки).
Что на самом деле поймали тестовые ветки
Три независимые ветки, каждая из которых находила то, чего не могли другие — поэтому их три:
Ветка | Стоимость | Что выявлено |
Мок + unit-набор | бесплатно | Ошибки последовательности, валидации и очистки. Быстро, но разделяет свои собственные допущения, поэтому не может поймать ошибочное допущение. |
Реальный тенант ( | ~21 платных вызовов | Вылет из команды |
бесплатно | Семь расхождений в точности мока – мест, где мок был лояльнее, чем ожидает реальный SCIM-клиент, за каждым из которых было скрыто реальное поведение. |
Принцип, который стоит вынести: лояльность мока скрывает реальное поведение API. Каждый дефект, найденный живым прогоном, предварительно пережил полный набор миок-тестов, потому что мок писался по тому же прочтению документации, что и клиент. Разорвать этот круг могли только сторонный клиент (валидатор) и реальный тенант.
Работа с живым тенантом в диалоге
В начало репозитория, .mcp.json, региструет сервер с Claude Code через scripts/dev-server.mjs, который загружает node/.env и запускает собранный сервер — так никакие секреты не попадают в мированный конфиг.
Он запускает собранный сервер, поэтому node/dist должен существовать до того, как ваш MCP-клиент сможет его стартовать. На свежем клонировании этим занимается:
cd node && npm install # the "prepare" script builds as part of installПосле любого изменения исходников пересоберите и перезапустите клиент, чтобы он подхватил команду:
cd node && npm run buildРазработка
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 и test — запускаются CI на каждом push и pull request, также npm audit --audit-level=high.
Сервер не имеет тестовой зависимости от реального тенента. Модульные тесты покрывают слои фильтров, патча, запросов и клиента; интеграционные тесты поднимают внутрипроцессный мок-сервер и прогоняют кажждый MCP-инструмент end-to-end по настоящему HTTP (node/test/integration/). Сфузы взаимодействия с SCIM Validator конвертируются в воспрои просматриваемые фикстуры через npm run fixtures:convert. Единственное, что такого рода проверки не доказывают — что живое API принимает эти нагрузки, — лежит в Тестирование на реальном тенанте.
Релиз
Версия живет в четырых местах — node/package.json, node/package-lock.json (дважды) и server.json (дважды: один раз для записи в реестр, второй для npm-пакета, на который она указывает). Одна команда обновит их все:
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-tags-a важно: --follow-tags отправляет только аннотированные теги, поэтому лёгкий git tag v0.2.0 остаётся на вашей машине, и push сообщает об успехе, не отправив ни одного тега — релиз просто никогда не выполняется.
npm version увеличивает версию в package.json и в lock-файле, затем скрипт жизненного цикла version переносит её в server.json и добавляет результат в индекс. Он не создаёт коммит и не ставит тег, хотя обычно npm version делает и то, и другое: npm ищет .git рядом с версионируемым пакетом, этот пакет находится в node/, а .git репозитория — уровнем выше. Поэтому npm решает, что он не в git-репозитории, и молча пропускает эти шаги. Отсюда явные коммит и тег выше. Если сделать это неправильно, git push --follow-tags молча не отправит ничего, потому что тег, за которым он должен был бы следовать, так и не был создан.
npm run check:version проверяет, что все четыре значения совпадают, CI запускает его при каждом пуше, а релизный workflow запускает его снова для самого тега — поэтому тег, расходящийся с package.json, упадёт до публикации чего-либо. Версия, которую сервер отдаёт в MCP-рукопожатии, читается из package.json во время выполнения, так что она обновляется автоматически.
Отправка тега v* запускает .github/workflows/release.yml:
verify — линт, форматирование, проверка типов, тесты с покрытием, проверка версии/тега и
mcp-publisher validateпротив реального реестра.verify on Windows — те же тесты снова на
windows-latest, потому что mock привязывается к реальным сокетам и записывает реальные пути в capture. Без этого релизные проверки были бы слабее, чем проверки для обычного коммита, который CI тоже гоняет на Windows.publish —
npm publish, затем ожидание, пока новая версия станет видимой в npm, и публикацияserver.jsonв MCP Registry.
Ничего не публикуется, пока не пройдут все эти шаги.
Обе публикации аутентифицируются через GitHub OIDC, поэтому в репозитории вообще нет секретов — нет ни токена публикации, который мог бы утечь, устареть или оказаться истёкшим в самый неподходящий момент.
Реестр | Как он авторизует этот workflow |
npm | Настоящее trusted publisher для пакета, назначенное на этот репозиторий и файл workflow |
MCP Registry |
|
Поэтому задачам публикации нужен запрос id-token: write.
MCP Registry доказывает владение npm-пакетом: он получает package.json и сравнивает его mcpName с name в server.json — оба значения это io.github.darrenjrobinson/entra-scim-mcp, а check:version проверяет, что они по-прежнему совпадают.
Изменение имени файла workflow или добавление environment: к задаче публикации ломает конфигурацию доверенного издателя npm до тех пор, пока настройка на npmjs.com не будет обновлена для соответствия — OIDC-claims сравниваются точно.
Лицензия
MIT — см. LICENSE.
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