mcpField
M
Microsoft Entra SCIM

Microsoft Entra SCIM

ローカル
github.comセキュリティ21 日前に更新

Microsoft Entra SCIM について

Microsoft Entra SCIM は、Microsoft Entra SCIM 2.0 API を通じてユーザーやグループのライフサイクルを管理する MCP サーバーです。Entra テナントなしで試せるローカルモックも含みます。

主な機能

  1. 01SCIM の機能・リソース型・スキーマの確認
  2. 02ユーザーの作成・取得・更新・無効化
  3. 03ライフサイクル属性とカスタムセキュリティ属性の管理
  4. 04グループとメンバーの作成・更新・削除
  5. 05ローカル API モックとドライラン

活用シーン

  1. 01Entra ユーザーの入退社処理の自動化
  2. 02グループとメンバー構成の管理
  3. 03ローカルモックを使ったツール呼び出しの検証

MCP 対応

MCP サーバーとして、Microsoft Entra SCIM のユーザーおよびグループのライフサイクル操作をクライアントに公開します。Entra テナントに接続せず試せるローカル SCIM モックも付属します。

設定

ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。

mcp.json · 共通設定
{
"mcpServers": {
"darrenjrobinson-entra-scim": {
"command": "npx",
"args": [
"-y",
"entra-scim-mcp"
],
"env": {
"ENTRA_TENANT_ID": "<ENTRA_TENANT_ID>",
"ENTRA_CLIENT_ID": "<ENTRA_CLIENT_ID>",
"ENTRA_CLIENT_SECRET": "<ENTRA_CLIENT_SECRET>",
"ENTRA_CLIENT_CERT_PATH": "<ENTRA_CLIENT_CERT_PATH>",
"ENTRA_CLIENT_CERT_PASSWORD": "<ENTRA_CLIENT_CERT_PASSWORD>",
"ENTRA_SCIM_BASE_URL": "<ENTRA_SCIM_BASE_URL>",
"ENTRA_SCIM_STATIC_TOKEN": "<ENTRA_SCIM_STATIC_TOKEN>",
"ENTRA_SCIM_DRY_RUN": "<ENTRA_SCIM_DRY_RUN>"
}
}
}
}
起動コマンドnpx -y entra-scim-mcp
設定が必要な環境変数
ENTRA_TENANT_IDDirectory (tenant) GUID. Required to authenticate against a live tenant; not needed for ENTRA_SCIM_DRY_RUN or ENTRA_SCIM_STATIC_TOKEN, which supply their own placeholder.
ENTRA_CLIENT_IDApp registration (client) GUID. Required to authenticate against a live tenant; not needed for ENTRA_SCIM_DRY_RUN or ENTRA_SCIM_STATIC_TOKEN.
ENTRA_CLIENT_SECRETシークレットClient secret value. For a live tenant set exactly one of ENTRA_CLIENT_SECRET or ENTRA_CLIENT_CERT_PATH; a certificate is preferred for anything long-lived.
ENTRA_CLIENT_CERT_PATHPath to a PEM holding the certificate and its private key. Set exactly one of ENTRA_CLIENT_SECRET or ENTRA_CLIENT_CERT_PATH.
ENTRA_CLIENT_CERT_PASSWORDシークレットPassword for the PEM, if it is encrypted.
ENTRA_SCIM_BASE_URLOverride the SCIM base URL (default https://graph.microsoft.com/rp/scim). Point it at the bundled mock to try the tools without a tenant.
ENTRA_SCIM_STATIC_TOKENシークレットUse a fixed bearer token instead of Azure AD. Refuses any microsoft.com/microsoft.us host and requires ENTRA_SCIM_BASE_URL; intended for the local mock only.
ENTRA_SCIM_DRY_RUNSet to 1 to run every client-side validation and return the request that would have been sent, without sending it or acquiring a token.

ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。

パッケージ
npmentra-scim-mcp0.4.0

ツール

ツール一覧はまだ取得されていません

ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。

詳細

entra-scim-mcp

npm version npm downloads license

Microsoft Entra SCIM 2.0 Provisioning API(2026年4月にGA)向けのModel Context Protocol サーバーです。https://graph.microsoft.com/rp/scim に対するユーザーとグループのライフサイクル操作を、Claude などのエージェント向けにMCP ツールとして公開します。

できること

  • テナントのSCIM機能を検出します(get_service_provider_config、list_resource_types、list_schemas)
  • Custom Security Attributesとライフサイクル属性を含む、ユーザーのプロビジョニング、読み取り、更新、プロビジョニング解除を行います
  • グループの作成、更新、削除、およびメンバーシップの管理を行います。APIの厳格なPATCHルールは自動的に遵守されます

前提条件

このサーバーがテナントと通信する前に、Microsoftのドキュメントに従って一度だけセットアップを完了してください。

  1. Entra ID P1(またはP1を含むSKU)と、課金に紐付けるAzureサブスクリプション。
  2. ID Governance → Dashboard でSCIM Provisioning APIを有効にし、課金リソースグループを紐付けます。
  3. 必要なMicrosoft Graph アプリケーション権限を付与して、アプリケーションを登録します。
    • 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(最小権限の代替)
    • 2026年9月に追加された、ツールセットの一部だけを使用する場合の、より範囲を絞った権限:User.ReadBasic.All(読み取り専用。idとuserNameでのみフィルター可能)、User.Create(作成のみ)、User.ReadUpdate.All(更新のみ。作成と削除は不可)、Group.Create(作成のみ)、GroupMember.ReadWrite.All(add_group_members/remove_group_memberのみ。update_groupのプロパティ変更には引き続きGroup.ReadWrite.Allが必要) 管理者の同意を付与します。
  4. クライアントシークレットを作成するか、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

必須の環境変数:

変数必須説明
ENTRA_TENANT_IDはいディレクトリ(テナント)のGUID
ENTRA_CLIENT_IDはいアプリ登録(クライアント)のGUID
ENTRA_CLIENT_SECRETいずれか1つクライアントシークレット値(開発用)
ENTRA_CLIENT_CERT_PATHいずれか1つ証明書と秘密鍵を含むPEMファイルのパス
ENTRA_CLIENT_CERT_PASSWORD任意PEMが暗号化されている場合のパスワード

ENTRA_CLIENT_SECRETまたはENTRA_CLIENT_CERT_PATHのいずれか一方だけを設定してください。

開発/テスト環境変数
変数説明
ENTRA_SCIM_BASE_URLSCIM のベース URL を上書きします(デフォルトは https://graph.microsoft.com/rp/scim)。ローカルのモックを指定してください。
ENTRA_SCIM_STATIC_TOKENAzure AD の代わりに固定のベアラートークンを使用します。安全策: ENTRA_SCIM_BASE_URL が必要です。*.microsoft.com/*.microsoft.us ホストは拒否され、ループバック以外のホストには警告が表示されます。また、実際の認証情報と併用できません。このモードでは、テナント ID とクライアント ID は不要です。
ENTRA_SCIM_DRY_RUN1 に設定すると、ツールはクライアント側のすべての検証を実行した後、リクエストを送信する代わりに、送信されるはずだった正確なリクエストを返します。トークンは取得されないため、認証情報の設定がなくても動作します。

ドライランの結果は、成功ペイロードとして返されます。

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

(DELETE では Accept ヘッダーは送信されません。この API は、そこで特定の JSON メディアタイプを拒否します。それ以外のすべてのメソッドでは Accept: application/json が送信されます。)

複数のリクエストを行うツール(例:ID が 20 件を超える add_group_members)では、ドライラン時に最初のチャンク化されたリクエストのみが表示されます。

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

ツール

Kind 列はツールの MCP アノテーションです。クライアントは、呼び出しに承認が必要かどうかを判断する際にこの情報を読み取ります。read は readOnlyHint、add は新規作成のみを行う書き込み(destructiveHint: false)、overwrite はこの API から元に戻すことなく状態を破棄または置換する書き込み(destructiveHint: true)です。

ツール種類目的
get_service_provider_configread一度だけ実行する機能の確認です。API バージョンごとに固定されるため、一度取得して再利用します。
list_resource_typesreadSCIM リソースタイプ(User、Group)を列挙します。
list_schemasreadSCIM スキーマと Entra 拡張機能を列挙し、各属性の型、変更可能性、デフォルトの返却設定を表示します。パッチを作成する前に、ここでパスを確認してください。
list_usersreadユーザーを一覧表示します。API の制限付きフィルター(eq/ew、and のみ、ew を囲む not())と、1 ページあたり最大 999 件のカーソルページネーションに対応しています。また、他のすべてのユーザーツールが必要とするオブジェクト ID に userName を解決する方法であり、グループのメンバー(groups.value eq "<groupId>")を一覧表示する方法でもあります。
get_userreadオプションの属性投影を指定して、ID で単一のユーザーを読み取ります。CSA もグループメンバーシップも含まれることはありません。
provision_useradd必須属性セット(userName、password、displayName、name.givenName、name.familyName)を適用してユーザーを作成します。mailNickname は省略可能です。省略した場合、Entra は userName から値を生成します。
update_useroverwriteユーザーに PATCH を実行します。mailNickname の remove をブロックし、アドレスパスに [type eq "work"] を適用します。
deprovision_useroverwriteユーザーを DELETE します。30 日間ソフト削除され、復元できるのは Graph 経由のみです。また、すべてのグループメンバーシップも解除されます。
update_user_lifecycleoverwriteライフサイクル属性(employeeLeaveDateTime など)を設定します。これをきっかけに Lifecycle Workflows が実行される場合があります。User-LifeCycleInfo.ReadWrite.All が必要です。
get_user_custom_security_attributesread属性セットごとに投影したユーザーの CSA を読み取ります。attributeSets は必須です。API は拡張機能の URN のみを指定したリクエストを拒否し、通常の get_user では CSA は返されません。
update_user_custom_security_attributesoverwriteユーザーの CSA に PATCH を実行します。remove、または空の配列を指定した replace によって割り当てが削除されます。
list_groupsreadAPI の制限付きフィルターセットを使用してグループを一覧表示します。メンバーシップを読み取る唯一の方法は、members.value フィルターです。
get_groupread単一のグループを読み取ります(メンバーは返されません。groups.value でフィルターした list_users を使って一覧表示してください)。
create_groupaddグループに POST を実行します。Entra 拡張機能を通じて mailEnabled、securityEnabled、mailNickname、description を設定します。displayName は一意ではありません。
update_groupoverwriteグループ属性にのみ PATCH を実行します(ここではメンバーシップ操作は拒否されます。型を示すフラグは作成時に固定されます)。
add_group_membersaddグループに 1 人以上のユーザーを追加します。API の上限に従い、PATCH 1 回あたり 20 個の ID に自動分割し、各 PATCH で 1 つの Operation を実行します。冪等です。RFC 7644 により各 PATCH はアトミックですが、複数チャンクからなる一連の処理はアトミックではありません。後続のチャンクが失敗した場合、このサーバー(API ではありません)が addedMemberIds/failedMemberIds/notAttemptedMemberIds を示す AddGroupMembersPartialFailure を発生させるため、部分的な書き込みが通知されないことはありません。
remove_group_memberoverwriteグループから単一のユーザーを削除します(API では PATCH 1 回につき削除は 1 件のみ許可され、ほかの操作は指定できません)。ここで 404 が返される場合、通常はグループが存在しないのではなく、ユーザーがメンバーではないことを意味します。
delete_groupoverwriteグループを DELETE します。統合グループは Graph 経由で 30 日間復元できますが、セキュリティグループは復元できません。
エージェントに伝える内容とその伝達先

モデルがこのファイルを読むことはないため、必要な情報はすべてプロトコルを通じて渡す必要があります。その情報を担う場所は3つあり、意図的に分けられています。

  • サーバーの指示はハンドシェイク時に一度だけ送信され、すべてのツールに共通する内容を伝えます。名前ではなくIDを使うこと、メンバーシップは一方向からしか読み取れないこと、CSAは通常の読み取りでは見えないこと、限定的なフィルター文法、そしてすべての呼び出しが課金対象のGraphリクエストであることです。これらを18個の説明で繰り返すと、得られる情報量よりもコンテキストを多く消費します。
  • ツールの説明は、各ツール固有の内容を伝えます。事前に注意を促すべき失敗例も含まれます。ユーザーが単にメンバーでない場合にグループ名を示す404、設定後は削除できないmailNickname、マージではなく競合になる2回目のprovision_userなどです。
  • 入力の説明は、各引数固有の構文を伝えます。3つのPATCHツールには、それぞれ独自のpathの例が記載されています。ここでは、パスの構文だけは推測できないためです。APIが受け付けるのはRFC 7644の限定的なサブセットであり、そのサブセットは通常の属性とCSAで異なります。

ENTRA_SCIM_DRY_RUN=1を指定すると、ドライランの結果は成功時と同じ形式で、そうでなければ変更が適用されたと報告されてしまうため、その旨を記した段落が指示に追加されます。

test/tool-metadata.test.tsでこれらすべてを検証します。すべてのツールにアノテーションがあること、読み取り専用ツールと書き込みツールのセットが名前で固定されていること、すべての入力に説明があること、PATCHツールにURNが示されていることを確認します。読み取り専用として扱われる新しいツールを追加すると、テストスイートは失敗します。

このサーバーが代わりに適用する制約

Entra SCIM APIには、見落としやすい制約があります。ツール層は、リクエストを送信する前に不正な入力を拒否します。

  • フィルターの許可リスト:リソースごとに、文書化された属性と演算子のみが許可されます。orは拒否されます。not()は、userNameまたはmailNicknameに対するユーザーのew句を囲む場合にのみ使用できます。externalIdを別の句と組み合わせることはできません。ブール属性(active、securityEnabled、mailEnabled)にはtrue/falseを指定し、引用符なしで送信する必要があります。これは見た目の問題ではなく必須です。実環境では、securityEnabled eq "true"は400エラーになります。
  • ページサイズ:list_usersのcountは最大999です。100を超える値が有効になるのは、エンタープライズ属性のmanagerを除外するattributesプロジェクションを指定した場合のみです。それ以外では、APIはひそかに100件を返します。
  • クエリ文字列:=の前後に空白を入れることはできません(APIはそのいずれについても400エラーを返します)。
  • ユーザーのPATCH:mailNicknameのremoveはブロックされます。addressesのパスフィルターは、正確に[type eq "work"]でなければなりません。
  • グループのPATCH:メンバーシップ操作は専用ツールを使用するため、メンバー追加の上限が20件であることと、削除が1件ずつであることが保証されます。
  • 冪等なメンバー追加:APIはメンバーの再追加を成功として扱います。add_group_membersは入力の重複を除去します。

エラーは、status、scimType、detailを含む構造化ペイロードとしてエージェントに返されます。

知っておくべきAPIの動作

実際のAPIには、ドキュメントで曖昧に記載されていたり、まったく触れられていなかったりする動作があります。それぞれ実際のテナントまたはMicrosoft SCIM Validatorに対して実行して確認済みで、すべてこちらで処理されます。レスポンスの読み方に影響するため、ここに記載しています。

DELETEにはAcceptヘッダーを付けてはいけません。 APIは400 Accept header application/json is invalidを返します。4通りすべてを実環境で確認しました。ヘッダーなし → 204、*/* → 204、application/json → 400、application/scim+json → 400です。このルールが逆転するのはDELETEだけです。それ以外のすべてのメソッドではJSONのAcceptが必須で、省略するとドキュメントに記載された400エラーになります。この問題により、最初に実環境で実行するまでdeprovision_userとdelete_groupがひそかに動作しなくなっていました。 カスタムセキュリティ属性は、通常の読み取りでは決して返されません。 /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 型で送信すれば、そのまま往復し、1 回の PATCH で複数の属性を同時に指定できます。Microsoft のドキュメントにはない削除動作が 2 つあります。

  • CSA パスに対する op: "remove" は、その割り当てだけをクリアし、ほかの割り当てはそのまま残します。
  • 複数値属性に対する replace と [] の組み合わせは、その割り当てを削除します。後続の読み取りでは空の配列が返されるのではなく、その属性自体が省略されます。

作成時には password が必須ですが、読み取りはできません。 writeOnly / returned: never に設定されており、どの応答にもその値は含まれません。作成時に必要な属性一式は userName、password、displayName、name.givenName、name.familyName であり、userName だけが必須である RFC 7643 と比べて、かなり厳しい要件です。

作成時の mailNickname は省略可能ですが、設定後は変更できません。 2026 年 8 月以降、userName から、値が省略された場合、null の場合、または空の場合に導出されます。最初の @ より前の部分が使われ、@ がなければ値全体が使われます([email protected] なら abc123)。/Schemas では属性に uniqueness: server と記されていますが、ライブプローブでは 2026-09-30 にエイリアスを明示的に重複させた 2 人目のユーザーが作成され、201 が返されたため、エイリアスは一意ではありません。したがって、[email protected] と [email protected] は、どちらも最終的に a になる可能性があります。モックもこの挙動に合わせています。エイリアスが重要な場合は、mailNickname を明示的に指定してください。作成後は PATCH で削除できません。リソースタイプでは両方のユーザー拡張機能が required: false と報告されるようになりましたが、2026-09-30 時点でライブの /Schemas は、属性自体を引き続き required: true と報告していました。信頼できる指標は作成時の挙動です。同じプローブで、mailNickname を省略した場合と空にした場合の両方について、ライブ環境で導出されることが確認されました。npx tsx scripts/probe-mailnickname.ts --confirm で再実行できます(課金対象の呼び出しが約 14 回発生します)。

グループの displayName は一意ではありません。 Entra は重複したグループ名を受け付け、201 を返します。RFC に準拠するツールはここで 409 を想定しがちなので、グループの作成が失敗することを既存グループの検出に頼らず、先にフィルターで確認してください。

グループメンバーの削除は、ユーザーではなくメンバーシップに対する操作です。 members[value eq "<id>"] に対する remove が何も見つけられない場合、その ID が誰のものであっても404になります。グループに一度も所属していない実在のユーザーも、ユーザーではない GUID とまったく同じように拒否されます。実際のテナントで、ダミーのメンバーを常に残した状態でプローブを実施したため、グループを空にしたことによる見かけ上の現象ではありません。

ケース結果
実際のメンバー204
一度もメンバーではなかった有効なユーザー404
ユーザーではなかった、形式が正しい GUID404
ユーザーが先に削除されたメンバー404

知っておく価値のある点が 2 つあります。ユーザーを削除するとそのユーザーのメンバーシップも解除されるため、削除してからメンバーシップを削除する順序でも、ほかの非メンバーと同じ結果になります。これは、削除の前後に members.value フィルターを使って list_groups をクエリして確認済みです。また、エラーメッセージではメンバーではなくグループの名前が示されます(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 配列を返しません。グループの直接のメンバーを一覧表示するには、Microsoft が現在推奨している方法である list_users を groups.value eq "<groupId>" とともに使用してください。ユーザーが所属するグループを探すには、逆方向にフィルターを指定し、list_groups を members.value eq "<userId>" とともに使用します。所有権も同様です。owners と ownedGroups は返されませんが、list_groups では owners.value eq "<userId>"、list_users では ownedGroups.value eq "<groupId>" を使用できます。

エラーは構造化されており、そのまま伝える価値があります。 失敗時には status、scimType、detail が含まれ、detail のテキストは非常に具体的です(問題のある操作のインデックスと制約が示されます)。ツールはメッセージにまとめず、そのまま渡します。

呼び出しはすべて課金対象です。 API 自体が必要とする以上のバッチ処理はないため、頻繁に呼び出すエージェントには実際の費用がかかります。add_group_members は API の上限である 20 メンバー単位に分割します。バッチ処理が行われるのはここだけです。

実際のテナントに対するテスト

テストスイートが実際のテナントにアクセスすることはありません。稼働中の Entra に対してツールを検証するには、Git で無視される .env に認証情報を設定し、スモークテスト用スクリプトを実行してください。

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

.env は scripts/ 内のスクリプトでのみ読み込まれます。公開されているサーバーは常に process.env を読み取るため、MCP クライアントが起動されたディレクトリにある .env を誤って読み込むことはありません。環境にすでに設定されている変数は、常にファイル内の値より優先されます。

変数用途
ENTRA_SCIM_SMOKE_DOMAIN使い捨ての scim-smoke-* ID の作成先となる、検証済みドメイン
ENTRA_SCIM_SMOKE_CSA_SET属性セット名。この値を設定すると、2 つのカスタムセキュリティ属性ツールをテストできます
ENTRA_SCIM_SMOKE_CSA_ATTRそのセット内の属性名
ENTRA_SCIM_SMOKE_CSA_VALUE割り当てる値。CSA には型があり、API は型が一致しない値を拒否します。true/false は JSON のブール値として、整数のみの値は数値として、それ以外は文字列として送信されます。デフォルトは文字列なので、Boolean 型または Integer 型の属性ではこの値を設定してください
スモークスクリプト
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 個すべてのツールを順番に実行する 1 回のテストで、課金対象の呼び出しは約 21 回です。ユーザー 2 人とグループ 1 つを作成し、それらに対するすべての読み取り、PATCH、削除をテストした後、最後に再度削除します。主なポイントは次のとおりです。

  • 誤って実行されることはありません。 ENTRA_SCIM_LIVE=1 または --confirm を指定しない場合、テナント、エンドポイント、コストを表示して終了します。ENTRA_SCIM_DRY_RUN または ENTRA_SCIM_STATIC_TOKEN が設定されている場合も、--rehearse を指定しない限り実行を拒否します。このような実行では、ライブ API について何も検証できないためです。
  • 最初の失敗で停止しません。 ステップが失敗すると、その依存ステップには skip が付けられ、独立したステップはすべて引き続き実行されるため、1 回の実行でライブ API がどのツールを受け付けるかがわかります。何かが失敗すると、終了コードはゼロ以外になります。
  • テスト用 ID は見分けやすくなっています。 scim-smoke-<runId>-1@<domain> と SCIM Smoke <runId> グループを使用します。
  • クリーンアップは必ず行われます。 作成したものはすべて finally ブロックで削除され、残ったものがあれば ID とともに表示されます。クラッシュした実行から復旧するには、npx tsx scripts/live-smoke.ts --sweep を実行して、残存する scim-smoke-* ユーザーを一覧表示し、削除する場合は --confirm を追加します。スイープで scim-smoke- プレフィックスのないアカウントが操作されることはありません。

2 つの Custom Security Attribute ツールは、テナントに属性セットが存在し(Entra ポータル -> Protection -> Custom security attributes)、その名前が ENTRA_SCIM_SMOKE_CSA_SET / ENTRA_SCIM_SMOKE_CSA_ATTR に指定されるまで skip を報告します。それ以外はすべて自動で実行されます。

属性セットが存在するようになったら、約 9 回の呼び出しでその 2 つのツールだけを検証できます。これは、各値をすべて読み戻し(API が受け付けた PATCH が実際には何も保存しない場合、通常は成功に見えてしまいます)、宣言されている各データ型を網羅し、削除もテストします。

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

属性セットの構造を一度宣言すれば、実行時に型ごとのテスト値が生成されます。

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

4 つすべての型についてライブで確認済みです。値は往復後も変更されず、op: "remove" は割り当てを 1 つクリアし、残りは維持します。また、複数値を持つ属性を [] で置き換えると、その属性は削除されます。後続の読み取りでは、空の配列が返されるのではなく、属性自体が省略されます。

費用をかける前に、ゼロコストでリハーサルしてください。これはスクリプトを検証するものであり、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

モックによるリハーサルは実施する価値があります。実際の HTTP、実際の ID、作成/パッチ/削除の完全な順序をテストできるため、費用をかける前に順序やクリーンアップのバグを検出できます。ただし、ライブ実行の代わりにはなりません。最初のライブ実行では、モック実行では検出できなかった 2 つのバグが見つかりました(テストの各段階で実際に見つかった問題を参照してください)。

テストの各段階で実際に見つかった問題

互いには見つけられない問題をそれぞれ発見した、独立した 3 つの段階です。3 つすべてがある理由はここにあります。

段階コスト発見した問題
モック + ユニットテスト一式無料順序、検証、クリーンアップのバグ。高速ですが、独自の前提に依存しているため、前提の誤りは検出できません。
ライブテナント(smoke:live)約 21 回の課金対象呼び出しDELETE の Accept バグ(これまで一度も動作していなかった 2 つのツール)、無効な単独の CSA URN、CSA の型と削除のセマンティクス。
SCIM Validator無料モックの忠実度に関する 7 つの不足点。モックが実際の SCIM クライアントの想定より寛容だった箇所であり、それぞれが実際の挙動を覆い隠していました。

ここから得られる重要なパターンは、モックの寛容さが実際の API の挙動を覆い隠すということです。ライブ実行で見つかった不具合はすべて、完全なモックテスト一式を通過していました。モックは、クライアントと同じドキュメントの解釈に基づいて作られていたためです。この循環を断ち切ることができたのは、サードパーティ製クライアント(Validator)と実際のテナントだけでした。

ライブテナントを対話形式で操作する

リポジトリルートの .mcp.json は、scripts/dev-server.mjs を使用してサーバーを Claude Code に登録します。このスクリプトは node/.env を読み込み、ビルド済みのサーバーを起動するため、コミットする設定ファイルにシークレットを含める必要はありません。

ビルド済みのサーバーを実行するため、MCP クライアントから起動する前に node/dist が存在している必要があります。新しくクローンした場合は、次の手順で準備できます。

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 がすべてのプッシュとプルリクエストで実行する 4 つのゲートです。これに加えて、npm audit --audit-level=high も実行されます。

サーバーのテストに実際のテナントは必要ありません。ユニットテストではフィルター、パッチ、クエリ、クライアントの各レイヤーを網羅します。統合テストではプロセス内モックサーバーを起動し、実際の HTTP を介してすべての MCP ツールをエンドツーエンドで実行します(node/test/integration/)。キャプチャした SCIM Validator のセッションは、npm run fixtures:convert でリプレイ用フィクスチャに変換できます。これらでは証明できない唯一の点、つまりライブ API がこれらのペイロードを受け入れるかどうかについては、実際のテナントに対するテストをご覧ください。

リリース

バージョンは4か所に記載されています。node/package.json、node/package-lock.json (2か所)、server.json(2か所。レジストリレコード用と、参照先の npm パッケージ用)です。次のコマンドですべて更新できます。

cd node
npm version minor          # or patch / major — writes all four, stages three
cd ..
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 は入力するのではなく package.json から読み取るため、コミットとタグに npm が書き込んだバージョン以外の値が指定されることはありません。

-a が重要です。--follow-tags がプッシュするのは注釈付きタグだけなので、軽量タグ git tag "v$VERSION" は手元に残り、プッシュはタグをまったく送信していないのに成功したと報告します。つまり、リリースは実行されません。

npm version は package.json とロックファイルのバージョンを更新し、その後 version ライフサイクルスクリプトが server.json に反映して、変更をステージします。通常の npm version とは異なり、コミットもタグ付けもしません。バージョンを設定するパッケージの隣に .git があるかを npm が確認するためです。このパッケージは node/ にあり、リポジトリの .git は1つ上の階層にあるため、npm は git リポジトリではないと判断し、何も通知せずにこれらの手順を省略します。そのため、上記のように明示的にコミットとタグ付けを行います。これを誤ると、追従させるタグが作成されていないため、git push --follow-tags は何もプッシュせずに終了します。

npm run check:version は4か所のバージョンがすべて一致していることを検証し、CI ではプッシュのたびに実行します。さらにリリースワークフローではタグ自体に対してもう一度実行するため、package.json と一致しないタグは、公開前にエラーになります。サーバーが MCP ハンドシェイクで報告するバージョンは、実行時に package.json から読み取られるため、自動的に追従します。

v* タグをプッシュすると、.github/workflows/release.yml が実行されます。

  1. 検証 — lint、フォーマット、型チェック、カバレッジ付きテスト、バージョンとタグのチェック、および稼働中のレジストリに対する mcp-publisher validate を実行します。
  2. Windows での検証 — windows-latest で同じテストを再度実行します。モックが実際のソケットをバインドし、キャプチャシンクが実際のパスに書き込むためです。これがなければ、リリースゲートは通常のコミットに対するゲートより緩くなります。CI では通常のコミットも Windows で実行されるためです。
  3. 公開 — npm publish を実行し、新しいバージョンが npm に表示されるまで待ってから、server.json を MCP Registry に公開します。

これらすべてが成功するまで、何も公開されません。

どちらの公開も GitHub OIDC で認証されるため、リポジトリにはシークレットが一切ありません。漏えいしたり、ローテーションが必要になったり、最悪のタイミングで期限切れに気づいたりする公開トークンはありません。

レジストリこのワークフローの認証方法
npmパッケージに設定された信頼されたパブリッシャーです。このリポジトリとワークフローファイル release.yml を指定し、environment は空にします。npm は OIDC クレームと照合し、同じトークンから来歴アテステーションを付与します。npm >= 11.5.1 が必要なため、公開前にジョブで npm をアップグレードします。
MCP Registrymcp-publisher login github-oidc を使用します。darrenjrobinson/entra-scim-mcp で実行することで、io.github.darrenjrobinson/* 名前空間への公開が認証されます。

公開ジョブで id-token: write を要求するのは、これら2つの認証のためです。

MCP Registry は package.json を取得し、その mcpName と server.json の name を比較することで、npm パッケージの所有権を確認します。両方とも io.github.darrenjrobinson/entra-scim-mcp であり、check:version によって一致が維持されていることを検証します。

ワークフローのファイル名を変更したり、公開ジョブに environment: を追加したりすると、npmjs.com の設定を一致するように更新するまで、npm の信頼されたパブリッシャーが機能しなくなります。OIDC クレームは完全一致で照合されます。

ライセンス

MIT — 詳細は LICENSE を参照してください。

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。