entra-scim-mcp
entra-scim-mcp
面向 Microsoft Entra SCIM 2.0 Provisioning API(GA,2026年4月)的 Model Context Protocol 服务器。将针对 https://graph.microsoft.com/rp/scim 的用户和组生命周期操作作为 MCP 工具提供给 Claude 等智能体。
可用功能
发现租户的 SCIM 能力(
get_service_provider_config、list_resource_types、list_schemas)设置、读取、更新和取消设置用户——包括 Custom Security Attributes 和生命周期属性
创建、更新和删除组,并自动遵守该 API 严格的 PATCH 规则来管理成员
Related MCP server: mcp-server-microsoft365
前提条件
在此服务器能与你的租户通信之前,需要先完成 Microsoft 文档 中的一次性设置:
需要 Entra ID P1(或任何包含 P1 的 SKU)以及用于关联计费的 Azure 订阅。
在 ID Governance → Dashboard 中启用 SCIM Provisioning API,并关联一个计费资源组。
注册一个应用,并授予你所需的 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(最小权限替代项) 然后授予管理员同意。
创建 一个 client secret 或 上传 PEM 客户端证书。
每次 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(用于 Microsoft SCIM Validator 的 RFC 标准行为——参见 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 其中之一。
开发/测试环境变量
变量 | 说明 |
| 覆盖 SCIM 基础 URL(默认 |
| 使用固定 Bearer 令牌,而非 Azure AD。保护措施: 要求设置 |
| 设为 |
试运行结果会以成功载荷的形式返回:
{
"dryRun": true,
"request": {
"method": "DELETE",
"url": "https://graph.microsoft.com/rp/scim/users/u-1",
"headers": {}
}
}(DELETE 不携带 Accept 头——API 会拒绝该处的特定 JSON 媒体类型。所有其他方法均发送 Accept: application/json。 )
多请求工具(例如 add_group_members 超过 20 个时)在试运行中只显示它的第一个管道请求。
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": "..."
}
}
}
}对于生产环境,将 client secret 替换为证书:
{
"env": {
"ENTRA_TENANT_ID": "...",
"ENTRA_CLIENT_ID": "...",
"ENTRA_CLIENT_CERT_PATH": "/secure/path/entra-scim-mcp.pem"
}
}工具
工具 | 用途 |
| 一次性能力发现。 |
| 枚举 SCIM 资源类型(User、Group)。 |
| 枚举 SCIM 架构和 Entra 扩展。 |
| 列出用户;支持 API 受限的过滤器(eq/ew,且运算符)和游标分页。 |
| 按 ID 读取单个用户,并可选属性投影。 |
| 创建用户,强制要求的属性集(userName、password、displayName、name.givenName、name.familyName、mailNickname)。 |
| 执行 PATCH 更新用户;阻止移除 |
| 删除用户。 |
| 设置生命周期属性(如 |
| 按属性集投影读取用户的 CSAs。 |
| 对用户进行 CSA 的 PATCH 更新。 |
| 使用 API 受限的过滤器集列出组。 |
| 读取单个组(不返回成员,请使用带 |
| 使用 POST 创建组。通过 Entra 扩展设置 |
| 仅更新组属性(此处不接受成员操作)。 |
| 向组中添加 1 个或多个用户——每 PATCH 自动分块为 20 个 ID(API 上限),每个 PATCH 只有一个操作。中途失败时会报告 |
| 从组中移除单个用户(API 每 PATCH 只允许一次移除,且不能包含其他操作)。 |
| 删除组。 |
此服务器为你强制处理的约束
Entra SCIM API 有一些很容易误解的约束。工具层在发送请求前会拒绝无效输入:
过滤器白名单:每种资源只能使用文档规定的属性和操作;拒绝
or;externalId不能与另一个子句一起使用。查询参数:在
=两侧不能有空格(API 会对任何这种请求返回 400)。用户 PATCH:禁止移除
mailName;地址路径过滤器必须正好为[type eq "work"]。组 PATCH:成员操作必须通过专用工具,以确保 20 个成员的添加上限和单移除规则被坚持。
幂等成员添加:API 将重复添加视为成功;
add_group_members会对输入进行去重。
错误会作为包含 status、scimType、detail 的结构化载荷返回给智能体。
值得了解的 API 行为
真实 API 有一类行为,文档表述不清晰或完全没有说明。每一条都是通过与真实租户或 Microsoft SCIM Validator 的连接发现的,并且已经为你处理好了——列出它们,是因为它们会改变你读取响应时的理解。
DELETE 不得携带 Accept 头。 API 返回 400 Accept header application/json is invalid。四种变体都已在真实环境中实际探测过:无 header → 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。它们只在被显式指定名称时出现,而且投影是**按属性集粒度(set-granular)**的:urn:...:CustomSecurityAttributes:<Set>。裸的扩展 URN 会被直接拒绝(400 ... not supported in the "attributes" or "excludedAttributes" query parameter),所以 attributeSets 是必填输入而不是可选输入。
CSA 值有类型划分,并且类型会被强制校验。 Boolean、Integer、String 和多值 String 在以匹配的 JSON 类型发送时都能完好往返,而且一次 PATCH 可以同时携带多个属性。有两种删除行为,微软都没有文档化:
对某个 CSA 路径执行
op: "remove",会只清掉该赋值,其余赋值保持原样。对多值属性执行
replace并赋[],会移除该赋值——之后读取时会完全省略该属性,而不是返回空数组。
password 在创建时必填,但永远不可读。 它是 writeOnly / returned: never,没有任何响应会回显它。创建时的完整必填集合是 userName、password、displayName、name.givenName、name.familyName 和 mailNickname——比 RFC 7643 严格得多,后者只要求 userName。
Group 的 displayName 并不唯一。 Entra 接受重复的组名并返回 201。面向 RFC 的工具通常在这里假设是 409,所以别指望创建失败来发现已存在的组——先过滤。
移除组成员关乎的是成员资格,而不是用户本身。 对 members[value eq "<id>"] 执行 remove 若没有匹配项,不论该 id 属于谁都会得到 404——一个从未入组的真实用户,会与一个从未成为用户的 GUID 被同样地拒绝。这个结论是在真实租户上探测的,且全程有一个陪衬组成员,所以并非清空组的假象:
Case | Result |
真实成员 | 204 |
从未入组的活用户 | 404 |
格式正确的、从未对应任何用户的 GUID | 404 |
用户已被先行删除的成员 | 404 |
有两个后果值得知道。删除用户会剥离其成员关系,所以「先删除用户,再移除成员」的顺序,其结果与处理任何其他非成员并无二致——这可以通过在删除前后用 members.value 过滤器查询 list_groups 来证实。另外,错误信息点名的组,不是成员(Resource '<groupId>' does not exist or one of its queried reference-property objects are not present);由于组明明存在,这读起来很怪。用一个故意伪造的组 id 探测,返回的也是同一句话并把该 id 填入其中,所以这段文本只是直接回显 PATCH 的目标。Mock 完整还原了这一切,而非修正它。用 npx tsx scripts/probe-member-removal.ts --confirm 重新运行(约 17 次计费调用)。
读取组永远不返还成员列表。 get_group 无论在什么页大小下都不返回 members 数组。要查某个用户所在的组,反过来过滤即可:用带 members.value eq "<userId>" 条件的 list_groups。
错误是结构化的,而且值得原样呈现。 失败响应携带 status、scimType 和 detail,其中 detail 文本异常具体(会指名操作序号和违反的约束)。工具会原样透传,而不把它拍平为一条 message。
每次调用都计费。 除 API 自身要求外没有任何批量合并,所以一个话多的 agent 是要花真钱的。add_group_members 按 API 的 20 成员上限分包,这是唯一发生批量的地方。
针对真实租户进行测试
测试套件绝不碰真实租户。要对真实 Entra 验证这些工具,请把凭据放到一个被 gitignore 的 .env 中并运行 smoke 脚本。
cd node
cp .env.example .env # then fill in tenant id, client id, and the secret VALUE.env 只能被 scripts/ 里的脚本读取。发布后的服务器总是读 process.env,所以它永远无法从 MCP 客户端启动它的任意目录里捡到散落的 .env。环境里已设置的变量总是优先于文件。
变量 | 用途 |
| 用来创建一次性 |
| 属性集名;设置它将覆盖两个 Custom Security Attribute 工具所需的 |
| 该属性集内属性名 |
| 要赋的值。CSA 有类型,API 拒绝类型不匹配: |
冒烟脚本
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删除它们。该清理绝不会碰不带有scim-smoke-前缀的账号。
两个 Custom Security Attribute 工具会报告 skip,直到租户里已存在属性集(Entra portal -> Protection -> Custom security attributes)并且 ENTRA_SCIM_SMOKE_CSA_SET / ENTRA_SCIM_SMOKE_CSA_ATTR 指定了它的名字。其他一切都自动运行。
一旦属性集存在,即可只验证这两个工具,约 9 次调用而不是 21 次——它会读回每个值(否则一个在 API 上被接受但不存储任何数据的 PATCH 会像通过),覆盖每个声明的数据类型,并演练删除:
npx tsx scripts/live-smoke.ts --csa-only --confirm在一次运行里声明一次该属性集的形态,则会按类型依次生成测试值:
ENTRA_SCIM_SMOKE_CSA_ATTRS=isManaged:bool,accountType:string,trustLevel:int,locations:string[]四个类型都实测通过:值往返后保持不变;op: "remove" 只删除单个赋值而保留其余;用 [] 替换多值属性则直接移除它——后续读取会把该属性整个省略,而不是返回一个空数组。
在花真钱之前先零成本“彩排”——这验证的是脚本本身,而不是 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 --rehearseMock rehearsal 是值得跑的那一个:它演练真实的 HTTP 和真实 id,以及完整 create/patch/delete 顺序,因此能在你花费之前抓到排序和清理的 bug。但它不能替代真实运行——第一次真实运行就发现了两个 mock 之前从未发现的 bug(见 测试环节实际抓到了什么)。
测试环节实际抓到了什么
三个相互独立的环节,每个都能发现另两个抓不到的东西——这正是三者并存的原因:
环节 | 成本 | 抓到的内容 |
Mock + 单元测试套件 | 免费 | 序列、校验和清理方面的 bug。快速,但它采用的是自身的一套假设,因此无法发现错误的假设。 |
真实租户( | ~21 次计费调用 | DELETE |
免费 | 七处 mock 保真度缺口——mock 比真实 SCIM 客户端更宽松的地方,每个缺口都掩盖了一种真实行为。 |
值得学习的模式是:mock 的宽松隐藏了真实 API 的行为。 每次真实运行发现的 bug 都先通过了整套 mock 测试,因为 mock 与客户端对文档的解读同源。第三方客户端(validator)和真实租户,是能打破这种循环的仅有外部客观参照。
在对话中驱动真实租户
仓库根目录的 .mcp.json 通过 scripts/dev-server.mjs 向 Claude Code 注册服务;该脚本加载 node/.env 并启动构建后的服务器——因此不会把任何机密写入提交到仓库的配置里。
它运行的是构建后的服务器,所以你的 MCP 客户端必须让 node/dist 先存在才可启动该工具。全新 clone 只需:
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 steps audit 也运行,npm audit。
服务器不需要真实租户进行测试。单元测试覆盖 filter、patch、query 和 client 各层;集成测试在进程内启动 mock server,并通过真实 HTTP 对每个 MCP 工具做端到端驱动(见 node/test/integration/)。捕获到的 SCIM Validator 会话可用 npm run fixtures:convert 转成回放 fixture。至于上述测试无法证明的事情——真实 API 是否接受这些 payload——详见 针对真实租户进行测试。
发布
版本号存在于四个地方——node/package.json、node/package-lock.json(出现两次)以及 server.json(出现两次,一次用于 registry 记录,一次用于其指向的 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 会留在你的机器上,而推送会报告成功,但实际上没有发送任何标签——发布流程根本不会运行。
npm version 会更新 package.json 和 lockfile,然后 version 生命周期脚本会把版本号传播到 server.json 并暂存结果。它不会提交或打标签,尽管 npm version 通常两者都会做:npm 会在它正在版本化的包旁边查找 .git,而这个包位于 node/ 中,仓库的 .git 则在上一级目录,所以 npm 判定自己不在 git 仓库中,于是便静默地跳过这些步骤。这正是上面需要显式的 commit 和 tag 的原因。如果这一步搞错,git push --follow-tags 就会悄悄顺利推送,但实际上什么也没有推送——因为它要跟随的标签从未被创建。
npm run check:version 会验证四处版本号是否一致,CI 在每次推送时都会运行它,发布工作流还会针对标签本身再运行一次——所以任何与 package.json 不一致的标签都会在任何内容发布之前失败。服务端在 MCP 连接握手中上报的版本是在运行时从 package.json 读取的,因此它会自动保持一致。
推送一个 v* 标签会运行 .github/workflows/release.yml:
验证 — lint、格式化、类型检查、带覆盖率的测试、版本/标签检查,以及针对实际 registry 的
mcp-publisher validate。在 Windows 上验证 — 在
windows-latest上再次运行相同的测试,因为 mock 绑定的是真实 socket,捕获 sink(capture sink)也会写入真实路径。若没有这一层验证,发布门槛就会比普通提交的门槛更弱,而普通提交的 CI 同样会在 Windows 上运行。发布 — 执行
npm publish,然后等待新版本在 npm 上可见,再将server.json发布到 MCP Registry。
只有当以上步骤全部通过后,才会发布任何内容。
两处发布都通过 GitHub OIDC 进行身份认证,所以这个仓库里没有任何机密——没有发布 token 会泄露、轮换,也不会在最糟糕的时刻发现它已过期。
Registry | 此工作流的授权方式 |
npm | 在该包上的受信任发布者,配置为本仓库和工作流文件 |
MCP Registry | 执行 |
这两点正是发布任务请求 id-token: write 的原因。
MCP Registry 会通过获取 package.json,并比较其中的 mcpName 与 server.json 中的 name,来证明你对 npm 包的所有权——两者都是 io.github.darrenjrobinson/entra-scim-mcp,并 check:version 会校验它们仍保持匹配。
修改工作流的文件名,或在发布任务中添加 environment:,都会让 npm 的受信任发布者配置失效,直到 npmjs.com 上的配置也同步更新为止——因为 OIDC 声明会被精确比对。
许可证
MIT — 参见 LICENSE。
This server cannot be deployed
Maintenance
Related MCP Connectors
- mcp-serverOAuthcom.make
Give your AI agents the tools to build, manage, and run automation workflows.
Context and control for any AI. Agent identity, scoped access, and proof across tools.
- FullmaktOAuthai.fullmakt
Credential broker for AI agents: scoped, revocable API access with policy enforcement and audit.
Connect AI agents to 1000+ apps with managed authentication and tool-calling.
Related MCP Servers
- FlicenseAqualityDmaintenanceEnables AI assistants to inspect employee access, list failed onboarding events, and retry provisioning operations.3-
- AlicenseAqualityBmaintenanceEnables 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 gradedqualityFmaintenanceEnables AI agents to query Microsoft Entra data using natural language, converting requests into Microsoft Graph API calls for read-only enterprise IT scenarios.58CC BY-4.0
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to read conversations, send messages, and manage members in Microsoft Teams via the Teams Chat Service REST API.291 npm8MIT