Microsoft Entra SCIM

io.github.darrenjrobinson由 GitHub 账号 darrenjrobinson 发布v0.4.0更新于 Oct 25, 2026

Microsoft Entra SCIM 2.0 Provisioning API: user and group lifecycle, with a local mock.

发布方自己的服务:Microsoft Learn MCP

概览

AI 生成的概览

用于 Microsoft Entra SCIM 2.0 预配 API 的本地 MCP 服务器,让助手发现 SCIM 能力并管理用户与组的生命周期。

可以做什么
发现 SCIM 能力、资源类型与架构,包括属性类型和可变性,便于在构造补丁路径前核对。列出并读取用户,支持受限筛选与游标分页,并按属性集读取用户的自定义安全属性。创建、修补和删除用户,包括员工离职日期等生命周期属性。列出、读取、创建、修补和删除组,并在自动处理 API 分块与单次移除规则的前提下添加或移除组成员。所有工具都可在试运行模式下返回将要发送的请求,或针对内置本地模拟服务运行而无需租户。
适用场景
当助手需要通过 SCIM 预配 API 创建、更新或删除 Entra 用户和组、管理组成员关系,或设置生命周期属性与自定义安全属性时适用;也适合先用模拟服务或试运行演练这些调用。
运行要求
以 stdio 本地进程方式通过 npx 运行,由 MCP 客户端启动。连接真实租户需要 ENTRA_TENANT_ID 和 ENTRA_CLIENT_ID,以及 ENTRA_CLIENT_SECRET 或 ENTRA_CLIENT_CERT_PATH 中的恰好一个(可选 ENTRA_CLIENT_CERT_PASSWORD)。还需 Entra ID P1、Azure 订阅、启用 SCIM 预配 API,以及已授予管理员同意的 Graph 权限的应用注册。试运行和本地模拟无需租户凭据。
安装前请注意
服务器持有客户端密钥或 PEM 私钥,并可创建、覆盖和删除用户与组;删除会移除组成员关系,安全组无法恢复。每次 SCIM 调用都会计费,真实环境冒烟脚本约产生 21 次计费调用。自定义安全属性与生命周期工具需要较宽的 Graph 权限。长期使用建议用证书代替密钥,并避免把凭据写入提交的配置文件。

安装

在 SourceWeft 中

  1. 打开 控制台中的 Microsoft Entra SCIM,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

entra-scim-mcp

[npm version] [npm downloads] [license]

Model Context Protocol server for the Microsoft Entra SCIM 2.0 Provisioning API (GA April 2026). Exposes user and group lifecycle operations against https://graph.microsoft.com/rp/scim as MCP tools for agents like Claude.

What you can do with it

  • Discover the tenant's SCIM capabilities (get_service_provider_config, list_resource_types, list_schemas)
  • Provision, read, update, and deprovision users — including Custom Security Attributes and lifecycle attrs
  • Create, update, and delete groups, and manage membership with the API's strict PATCH rules respected automatically

Prerequisites

Before this server can talk to your tenant, complete the one-time setup in the Microsoft docs:

  1. Entra ID P1 (or any SKU containing P1) and an Azure subscription to link for billing.
  2. Enable the SCIM Provisioning API in ID Governance → Dashboard and link a billing resource group.
  3. Register an application with the Microsoft Graph application permissions you need:
    • User.ReadWrite.All, Group.ReadWrite.All (core lifecycle)
    • CustomSecAttributeAssignment.ReadWrite.All, CustomSecAttributeDefinition.Read.All (CSA tools)
    • User-LifeCycleInfo.ReadWrite.All (lifecycle tools)
    • User-Mail.ReadWrite.All, User-Phone.ReadWrite.All, User.EnableDisableAccount.All (least-privilege alternatives)
    • Narrower options added Sep 2026, if you only use part of the toolset: User.ReadBasic.All (read-only; filters on id and userName only), User.Create (create only), User.ReadUpdate.All (update only, no create or delete), Group.Create (create only), GroupMember.ReadWrite.All (add_group_members / remove_group_member only; update_group property changes still need Group.ReadWrite.All) Grant admin consent.
  4. Create either a client secret or upload a PEM client certificate.

Every SCIM API call is billed — this server does not batch beyond what the API requires.

Try it without an Entra tenant

The package ships a local mock of the Entra SCIM API (entra-scim-mock-server) so you can drive every tool with zero Azure setup and zero API billing:

bash
# shell 1 — start the mock (seeds a small demo tenant)npx -y --package entra-scim-mcp entra-scim-mock-server

Then point the MCP server at it:

json
{  "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"      }    }  }}

Mock flags: --port, --token, --seed <file.json>, --no-seed, --capture <file.jsonl> (log every request/response), --validator-compat (RFC-standard behavior for the Microsoft SCIM Validator — see docs/scim-validator.md).

Install / run

The server is a stdio MCP server, designed to be launched by your MCP client (Claude Desktop, Claude Code, etc.).

bash
npx -y entra-scim-mcp

Required environment:

VarRequiredDescription
ENTRA_TENANT_IDyesDirectory (tenant) GUID
ENTRA_CLIENT_IDyesApp registration (client) GUID
ENTRA_CLIENT_SECRETone ofClient secret value (dev)
ENTRA_CLIENT_CERT_PATHone ofPath to a PEM containing the certificate and private key
ENTRA_CLIENT_CERT_PASSWORDoptionalPassword if the PEM is encrypted

Set exactly one of ENTRA_CLIENT_SECRET or ENTRA_CLIENT_CERT_PATH.

Development / testing environment variables

VarDescription
ENTRA_SCIM_BASE_URLOverride the SCIM base URL (default https://graph.microsoft.com/rp/scim). Point it at the local mock.
ENTRA_SCIM_STATIC_TOKENUse a fixed bearer token instead of Azure AD. Guardrails: requires ENTRA_SCIM_BASE_URL, refuses any *.microsoft.com / *.microsoft.us host, warns on non-loopback hosts, and cannot be combined with a real credential. Tenant/client IDs are not required in this mode.
ENTRA_SCIM_DRY_RUNSet to 1: tools run all client-side validation, then return the exact request that would have been sent instead of sending it. No token is acquired — works with zero credential config.

Dry-run results come back as a successful payload:

json
{  "dryRun": true,  "request": {    "method": "DELETE",    "url": "https://graph.microsoft.com/rp/scim/users/u-1",    "headers": {}  }}

(DELETE carries no Accept header — the API rejects a specific JSON media type there. Every other method sends Accept: application/json.)

Multi-request tools (e.g. add_group_members beyond 20 ids) surface only their first chunked request in dry-run.

Claude Desktop config

~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

json
{  "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": "..."      }    }  }}

For production, swap the secret for a certificate:

json
{  "env": {    "ENTRA_TENANT_ID": "...",    "ENTRA_CLIENT_ID": "...",    "ENTRA_CLIENT_CERT_PATH": "/secure/path/entra-scim-mcp.pem"  }}

Tools

The Kind column is the tool's MCP annotations, which is what a client reads when it decides whether a call needs your approval: read is readOnlyHint, add is a write that only creates (destructiveHint: false), overwrite is a write that discards or replaces state with no undo through this API (destructiveHint: true).

ToolKindPurpose
get_service_provider_configreadOne-shot capability discovery. Static per API version — fetch once and reuse.
list_resource_typesreadEnumerate SCIM resource types (User, Group).
list_schemasreadEnumerate SCIM schemas and Entra extensions, with each attribute's type, mutability and default-return. Check a patch path here before building it.
list_usersreadList users; supports the API's restricted filter (eq/ew, and-only, not() around ew) and cursor pagination up to 999 per page. Also how you resolve a userName to the object id every other user tool wants, and how you list a group's members (groups.value eq "<groupId>").
get_userreadRead a single user by id with optional attribute projection. Neither CSAs nor group membership are ever included.
provision_useraddCreate a user with the required attribute set enforced (userName, password, displayName, name.givenName, name.familyName). mailNickname is optional; Entra derives it from userName when omitted.
update_useroverwritePATCH a user; blocks remove of mailNickname and enforces [type eq "work"] on address paths.
deprovision_useroverwriteDELETE a user. Soft-deleted for 30 days, restorable only via Graph; also strips every group membership.
update_user_lifecycleoverwriteSet lifecycle attrs (e.g. employeeLeaveDateTime) — which Lifecycle Workflows can fire off. Requires User-LifeCycleInfo.ReadWrite.All.
get_user_custom_security_attributesreadRead a user's CSAs, projected by attribute set. attributeSets is required — the API rejects the bare extension URN, and CSAs never come back from a plain get_user.
update_user_custom_security_attributesoverwritePATCH CSAs on a user. remove, or replace with an empty array, deletes an assignment.
list_groupsreadList groups with the API's restricted filter set. A members.value filter is the only way to read membership.
get_groupreadRead a single group (members are NOT returned — list them with list_users filtered on groups.value).
create_groupaddPOST a group. Sets mailEnabled, securityEnabled, mailNickname, description via the Entra extension. displayName is not unique.
update_groupoverwritePATCH group attributes only (membership ops are rejected here; type flags are fixed at creation).
add_group_membersaddAdd ≥1 users to a group — auto-chunks at 20 ids per PATCH (API cap), one Operation per PATCH. Idempotent. Each PATCH is atomic per RFC 7644, but a multi-chunk sequence is not: if a later chunk fails, this server (not the API) raises AddGroupMembersPartialFailure naming addedMemberIds / failedMemberIds / notAttemptedMemberIds, so a partial write is never silent.
remove_group_memberoverwriteRemove a single user from a group (the API allows only one removal per PATCH, with no other ops). A 404 here usually means not a member, not no such group.
delete_groupoverwriteDELETE a group. Unified groups are recoverable for 30 days via Graph; security groups are not.

What the agent is told, and where

A model never reads this file, so everything it needs has to travel in the protocol. Three places carry it, and the split is deliberate:

  • Server instructions, sent once in the handshake, hold what is true of every tool: ids rather than names, membership being readable in one direction only, CSAs being invisible to ordinary reads, the narrow filter grammar, and that every call is a billed Graph request. Repeating that in eighteen descriptions would cost more context than it bought.
  • Tool descriptions hold what is specific to one tool, including the failure modes worth pre-empting — the 404 that names the group when the user simply is not a member, the mailNickname that cannot be removed once set, the second provision_user that conflicts rather than merges.
  • Input descriptions hold per-argument syntax. The three PATCH tools each carry their own path example, because path syntax is the one thing here that cannot be guessed: the API accepts a narrow subset of RFC 7644, and the subset differs between ordinary attributes and CSAs.

With ENTRA_SCIM_DRY_RUN=1 the instructions gain a paragraph saying so, since a dry-run result is shaped like a success and would otherwise be reported as a change that landed.

test/tool-metadata.test.ts enforces all of this: every tool carries annotations, read-only and write sets are pinned by name, every input has a description, and the PATCH tools must show a URN. A new tool that defaults to looking read-only fails the suite.

What this server enforces for you

The Entra SCIM API has constraints that are easy to miss. The tool layer rejects bad input before a request is sent:

  • Filter allow-list: only the documented attributes and operators per resource; or is rejected; not() is allowed only around a user ew clause on userName or mailNickname; externalId cannot be combined with another clause; boolean attributes (active, securityEnabled, mailEnabled) must be true/false and are sent unquoted. This is required, not cosmetic: live, securityEnabled eq "true" is a 400.
  • Page size: list_users caps count at 999. Anything above 100 only takes effect with an attributes projection that leaves out the enterprise manager; otherwise the API quietly serves 100.
  • Query strings: no whitespace around = (the API returns 400 for any).
  • User PATCH: remove of mailNickname is blocked; addresses path filter must be exactly [type eq "work"].
  • Group PATCH: membership ops go through dedicated tools so the 20-member add cap and the single-remove rule are guaranteed.
  • Idempotent member add: the API treats re-adds as success; add_group_members dedupes input.

Errors come back to the agent as a structured payload with status, scimType, and detail.

API behaviours worth knowing

Things the live API does that the docs either state ambiguously or not at all. Each was found by running against a real tenant or the Microsoft SCIM Validator, and each is handled for you — they are listed because they change how you read a response.

DELETE must not carry an Accept header. The API answers 400 Accept header application/json is invalid. All four variants were probed live: no header → 204, */* → 204, application/json → 400, application/scim+json → 400. Only DELETE inverts the rule — every other method requires a JSON Accept, and omitting it is a documented 400. This one silently broke deprovision_user and delete_group until the first live run.

Custom Security Attributes never come back from a plain read. In /Schemas the attribute-set-level attribute is returned: "request", so get_user will not include CSAs no matter what you ask for. They only appear when named explicitly, and projection is set-granular: urn:...:CustomSecurityAttributes:<Set>. The bare extension URN is rejected outright (400 ... not supported in the "attributes" or "excludedAttributes" query parameter), which is why attributeSets is a required input rather than an optional one.

CSA values are typed, and the type is enforced. Boolean, Integer, String and multi-valued String all round-trip intact when sent as the matching JSON type, and one PATCH may carry several attributes at once. Two removal behaviours, neither documented by Microsoft:

  • op: "remove" on a CSA path clears that one assignment and leaves the others intact.
  • replace with [] on a multi-valued attribute removes the assignment — a later read omits the attribute entirely rather than returning an empty array.

password is required on create but never readable. It is writeOnly / returned: never, and no response ever echoes it. The full required create set is userName, password, displayName, name.givenName and name.familyName, which is considerably stricter than RFC 7643, where only userName is required.

mailNickname is optional on create, and permanent once set. Since August 2026 Entra derives it from userName when it is omitted, null or empty: everything before the first @, or the whole value when there is none ([email protected] gives abc123). The alias is not unique, whatever /Schemas says: it marks the attribute uniqueness: server, but a live probe on 2026-09-30 created a second user with an explicitly duplicated alias and got a 201. So [email protected] and [email protected] can both end up as a. The mock matches this. Pass mailNickname explicitly if the alias matters. After creation it cannot be removed by PATCH. The resource types now report both user extensions as required: false, but on 2026-09-30 the live /Schemas still reported the attribute itself as required: true. The create behaviour is the reliable signal: the same probe confirmed derivation live, for both an omitted and an empty mailNickname. Re-run it with npx tsx scripts/probe-mailnickname.ts --confirm (about 14 billed calls).

Group displayName is not unique. Entra accepts a duplicate group name and returns 201. RFC-oriented tooling often assumes 409 here, so do not rely on create failing to detect an existing group — filter first.

Removing a group member is about membership, not the user. A remove on members[value eq "<id>"] that matches nothing is a 404, whoever the id belongs to — a live user who was simply never in the group is refused exactly like a GUID that was never a user. Probed against a real tenant with a ballast member present throughout, so none of it is an artefact of emptying the group:

CaseResult
A real member204
A live user who was never a member404
A well-formed GUID that was never a user404
A member whose user was deleted first404

Two consequences worth knowing. Deleting a user strips their memberships, so the delete-then-remove-membership ordering lands in the same place as any other non-member — confirmed by querying list_groups with a members.value filter either side of the delete. And the error message names the group, not the member (Resource '<groupId>' does not exist or one of its queried reference-property objects are not present), which reads oddly since the group plainly exists; a probe with a deliberately bogus group id returned the same sentence naming that id, so the text simply echoes the PATCH target. The mock reproduces all of this rather than improving on it. Re-run it with npx tsx scripts/probe-member-removal.ts --confirm (~17 billed calls).

Group reads never include members. get_group returns no members array at any page size. To list a group's direct members, use list_users with groups.value eq "<groupId>", which is the route Microsoft now recommends. To find a user's groups, filter the other way: list_groups with members.value eq "<userId>". Ownership works the same way. owners and ownedGroups are never returned, but list_groups accepts owners.value eq "<userId>" and list_users accepts ownedGroups.value eq "<groupId>".

Errors are structured, and worth surfacing verbatim. Failures carry status, scimType and detail, and the detail text is unusually specific (it will name the offending operation index and constraint). The tools pass it through unchanged rather than flattening it to a message.

Every call is billed. There is no batching beyond what the API itself requires, so a chatty agent costs real money. add_group_members chunks at the API's 20-member cap, which is the one place batching happens.

Testing against a real tenant

The test suite never touches a real tenant. To verify the tools against live Entra, put credentials in a gitignored .env and run the smoke script.

bash
cd nodecp .env.example .env      # then fill in tenant id, client id, and the secret VALUE

.env is read only by the scripts in scripts/. The published server always reads process.env, so it can never pick up a stray .env from whatever directory an MCP client launches it in. A variable already set in the environment always wins over the file.

VarPurpose
ENTRA_SCIM_SMOKE_DOMAINVerified domain the throwaway scim-smoke-* identities are created in
ENTRA_SCIM_SMOKE_CSA_SETAttribute set name; set it to cover the two Custom Security Attribute tools
ENTRA_SCIM_SMOKE_CSA_ATTRAttribute name within that set
ENTRA_SCIM_SMOKE_CSA_VALUEValue to assign. CSAs are typed and the API rejects a mismatch: true/false are sent as a JSON boolean, a bare integer as a number, anything else as a string. Defaults to a string, so set this for Boolean or Integer attributes

The smoke script

bash
ENTRA_SCIM_LIVE=1 npm run smoke:live              # bash$env:ENTRA_SCIM_LIVE=1; npm run smoke:live        # PowerShell

To pass flags, call the script directly - npm run x -- --flag does not forward reliably on Windows:

bash
npx tsx scripts/live-smoke.ts --confirm

One ordered pass over all 18 tools in roughly 21 billed calls. It creates two users and a group, exercises every read, PATCH and delete against them, then deletes them again. Highlights:

  • It refuses to run by accident. Without ENTRA_SCIM_LIVE=1 or --confirm it prints the tenant, endpoint and cost, then exits. With ENTRA_SCIM_DRY_RUN or ENTRA_SCIM_STATIC_TOKEN set it refuses outright unless you pass --rehearse, because such a run proves nothing about the live API.
  • It does not stop at the first failure. A failed step marks its dependents skip and everything independent still runs, so one run tells you which tools the live API accepts. Exit code is non-zero if anything failed.
  • Test identities are obvious. scim-smoke-<runId>-1@<domain> and a SCIM Smoke <runId> group.
  • Cleanup is guaranteed. Everything created is deleted in a finally block, and any leftovers are printed with their ids. Recover from a crashed run with npx tsx scripts/live-smoke.ts --sweep to list stranded scim-smoke-* users, then add --confirm to delete them. The sweep will never touch an account that lacks the scim-smoke- prefix.

The two Custom Security Attribute tools report skip until an 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. Everything else runs unattended.

Once a set exists, validate just those two tools for about 9 calls instead of 21 — this reads every value back (a PATCH the API accepts but stores nothing would otherwise look like a pass), covers each declared data type, and exercises removal:

bash
npx tsx scripts/live-smoke.ts --csa-only --confirm

Declare the set's shape once and the run derives a test value per type:

ENTRA_SCIM_SMOKE_CSA_ATTRS=isManaged:bool,accountType:string,trustLevel:int,locations:string[]

Confirmed live against all four types: values round-trip intact, op: "remove" clears one assignment and leaves the rest, and replacing a multi-valued attribute with [] removes it — a later read omits the attribute entirely rather than returning an empty array.

Rehearse at zero cost before spending anything - this validates the script, not the API:

bash
# no network at allENTRA_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 itexport ENTRA_SCIM_BASE_URL=http://127.0.0.1:8990export ENTRA_SCIM_STATIC_TOKEN=dev-tokennpx tsx scripts/live-smoke.ts --rehearse

The mock rehearsal is the one worth doing: it exercises real HTTP, real ids and the full create/patch/delete ordering, so it catches sequencing and cleanup bugs before you spend anything. It is not a substitute for the live run — the first live pass found two bugs no mock run had caught (see What the test legs actually caught).

What the test legs actually caught

Three independent legs, each of which found things the others could not — the reason all three exist:

LegCostFound
Mock + unit suitefreeSequencing, validation and cleanup bugs. Fast, but shares its own assumptions, so it cannot catch a wrong assumption.
Live tenant (smoke:live)~21 billed callsThe DELETE Accept bug (two tools that had never worked), the invalid bare CSA URN, and CSA type/removal semantics.
SCIM ValidatorfreeSeven mock-fidelity gaps — places the mock was more lenient than a real SCIM client expects, each of which had been hiding a real behaviour.

The pattern worth taking away: mock leniency hides real API behaviour. Every defect the live run found had passed a full mock suite first, because the mock had been written from the same reading of the docs as the client. A third-party client (the validator) and a real tenant were the only things that could break that circularity.

Driving the live tenant conversationally

The repo-root .mcp.json registers the server with Claude Code using scripts/dev-server.mjs, which loads node/.env and starts the built server — so no secret goes into a committed config file.

It runs the built server, so node/dist has to exist before your MCP client can launch it. A fresh clone gets there with:

bash
cd node && npm install     # the "prepare" script builds as part of install

After any source change, rebuild and restart your client so it relaunches the command:

bash
cd node && npm run build

Development

bash
cd nodenpm install             # installs, then builds via "prepare"npm testnpm run lint            # ESLint, type-awarenpm run format:check    # Prettiernpm run typecheck       # strict tsc over src, test and scriptsnpm run test:coverage   # vitest with the coverage gatenpm run build           # rebuild after a source changenpm 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 and pull request, alongside npm audit --audit-level=high.

The server has no test dependency on a real tenant. Unit tests cover the filter, patch, query, and client layers; integration tests boot the in-process mock server and drive every MCP tool end-to-end over real HTTP (node/test/integration/). Captured SCIM Validator sessions convert into replay fixtures with npm run fixtures:convert. For the one thing none of that can prove — that the live API accepts these payloads — see Testing against a real tenant.

Releasing

The version lives in four places — node/package.json, node/package-lock.json (twice), and server.json (twice, once for the registry record and once for the npm package it points at). One command writes all of them:

bash
cd nodenpm version minor          # or patch / major — writes all four, stages threecd ..VERSION=$(node -p "require('./node/package.json').version")git commit -m "v$VERSION"git tag -a "v$VERSION" -m "v$VERSION"git push --follow-tags

VERSION is read back from package.json rather than typed, so the commit and tag can never name a version other than the one npm just wrote.

The -a matters: --follow-tags pushes annotated tags only, so a lightweight git tag "v$VERSION" stays on your machine and the push reports success having sent no tag at all — the release simply never runs.

npm version bumps package.json and the lockfile, then the version lifecycle script propagates it to server.json and stages the result. It does not commit or tag, even though npm version normally does both: npm looks for .git beside the package it is versioning, this package lives in node/, and the repository's .git is a level up — so npm decides it is not in a git repository and skips those steps without saying so. Hence the explicit commit and tag above. Get this wrong and git push --follow-tags silently pushes nothing, because the tag it would follow was never created.

npm run check:version verifies all four agree, CI runs it on every push, and the release workflow runs it again against the tag itself — so a tag that disagrees with package.json fails before anything is published. The version the server reports in its MCP handshake is read from package.json at runtime, so it follows automatically.

Pushing a v* tag runs .github/workflows/release.yml:

  1. verify — lint, format, typecheck, tests with coverage, the version/tag check, and mcp-publisher validate against the live registry.
  2. verify on Windows — the same tests again on windows-latest, because the mock binds real sockets and the capture sink writes real paths. Without it the release gate would be weaker than the gate on an ordinary commit, which CI runs on Windows too.
  3. publish — npm publish, then waits for the new version to become visible on npm, then publishes server.json to the MCP Registry.

Nothing is published until every one of those passes.

Both publishes authenticate by GitHub OIDC, so the repository holds no secrets at all — there is no publish token to leak, rotate, or find expired at the worst moment.

RegistryHow it authorises this workflow
npmA trusted publisher on the package, set to this repo and workflow file release.yml with an empty environment. npm matches the OIDC claims against it, and attaches a provenance attestation from the same token. Needs npm >= 11.5.1, which is why the job upgrades npm before publishing.
MCP Registrymcp-publisher login github-oidc. Running in darrenjrobinson/entra-scim-mcp is what authorises the io.github.darrenjrobinson/* namespace.

Both are the reason the publish jobs request id-token: write.

The MCP Registry proves you own the npm package by fetching package.json and comparing its mcpName to the name in server.json — both are io.github.darrenjrobinson/entra-scim-mcp, and check:version asserts they still match.

Changing the workflow's filename, or adding an environment: to the publish job, breaks the npm trusted publisher until the configuration on npmjs.com is updated to match — the OIDC claims are compared exactly.

License

MIT — see LICENSE.

来源:README.md,提交 5cf54ae

工具

0
SourceWeft 尚未在沙箱中启动过该软件包,因此其工具未知。

版本历史

3
  1. v0.4.0最新Oct 14, 2026
  2. v0.3.0Sep 30, 2026
  3. v0.2.1Sep 16, 2026

评分

已满足适用检查项的 50%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分

    请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。

  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具15 分

    请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。

  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分

其他 Microsoft 集成