Whoever takes your old GitHub name gets your MCP namespace
---
title: "Whoever takes your old GitHub name gets your MCP namespace"
published: true
description: "Rename your GitHub account and whoever takes the old name can publish new versions of your MCP servers. 145 live ones sit under names nobody holds."
tags: "ai, security, github, mcp"
canonical_url: "blog.peng.ly"
cover_image: "blog.peng.ly"
ai_assisted: true
---
Rename your GitHub account and your MCP servers stay in the official registry under your old name. Whoever registers that name next can publish new versions of them, and the registry can't tell it isn't you.
On 2 November I checked every io.github name in the registry against GitHub. At least 111 names that published servers have since been renamed or deleted, and don't belong to any account now. Between them:
- 145 live servers are still listed
- 17,000 or so times a week, their npm and PyPI packages get downloaded
- 49 of them only have a remote URL, so there's no package to check at all
They're not toys, either. Here are the busiest:
!Weekly downloads of the busiest servers under unclaimed names
Weekly downloads of the busiest servers under unclaimed names (the data)
_These are npm and PyPI downloads over the last week, as of 2 November. Each one is listed in the registry under a GitHub name that nobody holds now._
aiterm-mcp lets your coding agent drive terminals, agents-fleet reaches every machine you've got over SSH, the Shodan and VirusTotal ones run with your API keys for those services, and azure-board-mcp wants a personal access token. Further down the list, four more handle crypto wallets, payments or transactions.
Why it works
The registry hands out io.github.<login>/* based on your GitHub login, not your account's ID, which is the bit that never changes (github_at.go):
permissions := h.buildPermissions(user.Login, orgs)
When you rename, GitHub's docs say your old name becomes available for anyone else to claim. Whoever claims it gets your namespace, and from there:
- a new version is always active, and the highest one becomes latest
- deprecating your old entry doesn't stop it (a maintainer said as much in October)
- the package check only asks for a package that carries the server's name, and it doesn't have to be the one you published
- a remote server has no package to check, so its next version can point at any URL
What a bad version gets
A local MCP server runs as you, with whatever keys you put in its config. A remote one sees every tool call your agent makes to it, and gets to write the answers.
If you installed the npm or PyPI package directly, a new registry version doesn't touch you. It's the clients and galleries that pick servers from the registry by name that would follow it, and the registry tells aggregators to copy it and keep their own copy.
Names are already changing hands
22 namespaces in the registry now belong to a GitHub account that was created after they were published. io.github.facebook is one, since Meta became meta on 28 October. So far most of them look like owners holding on to their old name, and none has published anything.
The other half has bitten for real. About twenty publishers have filed tickets since April because they're locked out of their own old entries, like PaidSync, whose old listing kept advertising an address they'd switched off until a maintainer stepped in.
If you've renamed
Register your old name again today. An empty org costs nothing. Once you hold the name, nobody else can publish there, and you can deprecate the old entries yourself.
If you install MCP servers, pin the package and version rather than trusting the registry name, and treat a change of package or URL between versions like a new server.
Kiro の初期セットアップ後によく聞かれる設定をまとめる【IT 管理者編】
はじめに
こんにちは!AWS でソリューションアーキテクトをしている町田です。
Kiro の導入支援をしていると、利用者の方からの相談と並んで、IT 担当の方から次のような相談を受けます。
「社員の PC にまとめて設定を配りたい」
「利用者が自分で許可しても、組織として止めたいものは止めたい」
「使える MCP サーバーやモデルを、会社で決めたものに絞りたい」
「社用のサインイン方式だけを使わせたい」
本記事では、これらに答える Kiro の設定を説明します。対象は Kiro IDE と Kiro CLI(V3)で、Windows の PC に配る前提です。記事は 2 本に分けました。
記事
対象
内容
利用者編
Kiro を使う方
日本語化、Python、確認のボタン、steering、skills、MCP、モデル
IT 管理者編(この記事)
社員の PC に設定を配る IT 担当の方
managed-settings.json、使える MCP やモデルの制限、チームへの配り方
1 章の「やってみる」では、利用者編の 1・2・5 章で許可した操作を、管理者の設定で止めて確かめます。先に利用者編の手順を試しておくと、違いが分かりやすくなります。
!
個人の見解であり、所属する組織の公式見解ではありません。
本記事の内容は 2026-11-01 時点 の公式ドキュメントに基づきます。Kiro は更新が速いため、最新の公式ドキュメントもあわせて確認してください。
先に結論
組織として止めたい操作は、managed-settings.json の deny と ask に書きます。 利用者の設定より優先されます。allow は書けないので、確認を減らすのは利用者の設定の役目です(1 章)。
managed-settings.json は「うっかり防止」です。 コマンドの禁止は書き方を変えると通り抜け、ローカル管理者の権限がある利用者はファイルを消せます。確実に止めたいことは、PC の管理やネットワークで止めます(1 章)。
managed-settings.json は配る前にテスト端末で 1 回起動します。 壊れているとすべてのツールが止まります(1 章)。
使える MCP サーバーは MCP レジストリで、使えるモデルは承認リストで絞ります。 どちらも Kiro コンソールの設定で、IAM Identity Center などでサインインした利用者に効きます(2・3 章)。
permissions は、作業フォルダに置いてもチームには配れません。 作業フォルダ単位の許可は、フォルダの外に保存されるためです。そろえたいなら PC に配るか、managed-settings.json を使います(4 章)。
章
内容
前提
設定ファイルの置き場所と、グローバルと作業フォルダの両方に置いたときの動き
1. managed-settings.json
端末に組織の決まりを配る
2. MCP レジストリ
使える MCP サーバーを絞る
3. モデルの承認リスト
使えるモデルを絞る
4. チームに配る
何をどこに置けばチームに行き渡るか
前提:設定ファイルの置き場所と読み込まれ方
Kiro の設定には、グローバル(~/.kiro/、自分の全プロジェクト)、プロジェクト(<作業フォルダ>/.kiro/)、エージェント(カスタムエージェントの定義)の 3 つのスコープがあります(Configuration scopes)。
設定
グローバル
プロジェクト
同じものが複数あるとき
steering
~/.kiro/steering/
.kiro/steering/
全部読み込む(矛盾したらプロジェクト優先)
skills
~/.kiro/skills/
.kiro/skills/
全部読み込む(同じ名前ならプロジェクト優先)
permissions
~/.kiro/settings/permissions.yaml
~/.kiro/workspace-roots/<hash>/permissions.yaml(作業フォルダの外)
deny が勝つ
MCP
~/.kiro/settings/mcp.json
.kiro/settings/mcp.json
エージェント > プロジェクト > グローバル
カスタムエージェント
~/.kiro/agents/
.kiro/agents/
同じ名前ならプロジェクト優先
CLI の設定
~/.kiro/settings/cli.json
.kiro/settings/cli.json(モデルごとの既定値など、一部の設定だけ)
プロジェクト優先
CLI V3 では、/config でいまのセッションに効いている steering・skills・MCP・エージェント・Powers・Hooks の一覧を確認できます。
/config # 一覧
/config steering # steering だけ
ワークスペースの信頼
作業フォルダの .kiro\ に置いた設定は、そのフォルダを信頼するまで読み込まれません(Permissions > Workspace trust)。人からもらったフォルダや、インターネットから取ってきたフォルダに、AI の振る舞いを変える設定が仕込まれているのを防ぐためです。
信頼していないワークスペースでは、Kiro は次のように動きます。
.kiro/ 配下のカスタムエージェント・steering・MCP の設定・skills を読み込まない
allow の規則があっても、シェルコマンドと MCP ツールを毎回確認する
.kiro/steering や .kiro/skills への書き込みを確認する
信頼したかどうかはフォルダの外に保存されるので、フォルダの中身が自分で自分を信頼させることはできません。
CLI V3 では、フォルダで初めて起動したときに Trust this workspace? の画面が出ます。ただし、--no-interactive で動かすときや、V1 / V2 では、この確認は出ずに作業フォルダの設定が読み込まれます。
グローバルとワークスペースの違い
グローバル(~.kiro\)と作業フォルダ(ワークスペース)の両方に同じ種類の設定を置いたとき、どちらが効くかを、公式ドキュメントから読み取ってまとめました。筆者は実機では試していません。 読み飛ばしても、各章の手順はそのままできます。
#
設定の置き方
ドキュメントから読み取れる動き
出典
A
グローバルに steering を置く
どの作業フォルダでも読み込まれる
Steering
B
グローバルと作業フォルダの両方に、矛盾するルールの steering を置く
上書きではなく両方読み込まれ、矛盾したところは作業フォルダの方が優先される
Steering、Configuration
C
同じ名前の skill を、グローバルと作業フォルダの両方に置く
作業フォルダの skill が使われる
Skills
D
C の作業フォルダの外で、同じ skill を呼ぶ
グローバルの skill が使われる。作業フォルダの skill は、そのフォルダでだけ使われる
Skills
E
同じ名前の MCP サーバーを、グローバルでは有効、作業フォルダでは無効にする
そのフォルダでは無効になる。作業フォルダの設定が優先される
MCP configuration
F
グローバルの permissions で、あるコマンドを確認なしにする
どの作業フォルダでも確認なしで実行される(信頼していないフォルダを除く。H を参照)
Permissions
G
確認ダイアログで Always allow → This workspace を選ぶ
そのフォルダでだけ確認が出なくなる。許可は作業フォルダの外(~.kiro\workspace-roots\)に保存される
Permissions
H
作業フォルダを 信頼せずに 開く
作業フォルダの .kiro\ の steering・skills・MCP・カスタムエージェントは読み込まれない。シェルコマンドと MCP ツールは、グローバルで確認なしにしていても毎回確認される
Workspace trust
I
steering を読み込む設定のないカスタムエージェントを使う
steering は自動では読み込まれない
Steering
1. managed-settings.json:端末に組織の設定を配る
よくある質問:「利用者が勝手に allow を書いても、組織として止めたいものは止めたい」「PC に一括で配りたい」
managed-settings.json は、管理者が端末に置く設定ファイルです。IDE と CLI が同じファイルを読みます(Permission policies、Sign-in controls)。
OS
置き場所
配り方
Windows
C:\ProgramData\Kiro\managed-settings.json
Intune、Group Policy、SCCM
macOS
/Library/Application Support/Kiro/managed-settings.json
Jamf、Kandji
Linux
/etc/kiro/managed-settings.json
構成管理ツール
いずれも管理者・root 権限がないと書き換えられない場所です。
まずやること:利用者編で許可してきた操作を、管理者の設定で止めてみます。管理者の設定が利用者の設定より優先されることを確かめてから、配る内容を決めます。
やってみる
利用者編で許可したことを、管理者の設定で止めてみる
利用者編では、利用者の設定で、いくつかの操作を許可しました。IT 管理者が managed-settings.json で止めると、それらがどうなるかを確かめます。次の 4 つの決まりだけを書いた managed-settings.json を使います。
利用者編
利用者の設定で許可したこと
管理者の決まり
試す頼み方
こうなれば OK
2 章
drafts フォルダへの保存を、確認なしにした
drafts フォルダへの保存を禁止
試し ①
保存できない
1 章(Python を入れる)
Kiro が Python の公式ドキュメントを読む
業務で使うドメイン以外の Web の取得を禁止
試し ②
ページを読めない
5 章
AWS のドキュメントを調べる MCP
MCP のツールは毎回確認
試し ③
使うたびに確認が出る
1 章
ライブラリのインストール(確認を見て Allow)
pip install などのインストールのコマンドを禁止
試し ④
止まる。ただし、コマンドの書き方によっては通る(下の試し ④)
中身は次のとおりです。
{
"rules": [
{
"capability": "shell",
"match": [
"python -m pip install*",
"py -m pip install*",
"pip install*"
],
"effect": "deny"
},
{
"capability": "fs_write",
"match": [
"/drafts/"
],
"effect": "deny"
},
{
"capability": "web_fetch",
"exclude": [
"docs.aws.amazon.com",
"*.amazonaws.com",
"kiro.dev"
],
"effect": "deny"
},
{
"capability": "mcp",
"effect": "ask"
}
]
}
手順
先に、利用者編の 1・2・5 章の手順で、表の「利用者の設定で許可したこと」が動くことを確かめておきます
エクスプローラーのアドレス欄に C:\ProgramData と入力して開きます。Kiro フォルダがなければ作ります
メモ帳を開いて上の内容を貼り付け、名前を付けて保存 で、ファイル名を managed-settings.json、ファイルの種類を すべてのファイル、エンコードを UTF-8 にして、いったんデスクトップに保存します。保存したファイルを Kiro フォルダにコピーし、管理者の確認が出たら 続行 を押します。ファイル名は managed-settings.json にしてください。 ほかの名前だと Kiro は読み込みません。書き間違えがあると、Kiro のツールがすべて止まるので、中身は書き換えないでください
置けたかどうかは、PowerShell で次の 2 行を実行して確かめます。1 行目に managed-settings.json が表示され、2 行目でエラーが出なければ OK です
Get-ChildItem "$env:ProgramData\Kiro"
Get-Content "$env:ProgramData\Kiro\managed-settings.json" -Raw | ConvertFrom-Json
Kiro のウィンドウをすべて閉じてから、開き直します
Kiro で、利用者編の 2 章でルールを置いた作業フォルダを開き、次の 4 つの文を送ります。前の答えに引きずられないように、1 つ送るごとに新しいチャットを開きます
試し ①:確認なしにした保存が、できなくなるか(利用者編 2 章)
drafts フォルダに test.txt というファイルを作って、中に「テスト」とだけ書いて。
利用者編の 2 章で「確認なしで保存してよい」にした drafts フォルダなのに、保存が止められれば成功です。Kiro がコマンドなど別の方法で書こうとしたら、確認のボタンで Deny を押します。
試し ②:Web のページが読めなくなるか(利用者編 1 章の Python を入れる手順)
Web 検索は使わずに、次のページを直接読んで、要点を 3 行で教えて。
docs.python.org
ツールの欄に Blocked by administration permission policy(管理者の決まりで止められた)と出て、ページを読めなかった理由が返ってくれば成功です。筆者の環境では、Kiro は Web 検索やコマンドで回り道をせず、「制限を外す必要があれば、管理者に相談してください」と答えました。
試し ③:MCP のツールに確認が出るか(利用者編 5 章)
AWS Knowledge MCP Server を使って、S3 のバケットの名前の付け方のルールを調べて。
MCP のツールを使うたびに確認のボタンが出れば成功です。利用者編の 5 章で、MCP のツールを確認なしにしていた場合も、確認が出ます。
試し ④:ライブラリのインストールを止められるか(利用者編 1 章)
python-pptx を、インストールし直して。すでに入っていても、もう一度入れて。
ツールの欄に Blocked by administration permission policy と出て、インストールが実行されなければ成功です。筆者の環境で 2 回試すと、2 回とも Kiro は python -m pip install ... を実行しようとして、managed-settings.json の禁止の決まりで止められました。Kiro も「組織が決めた制限なので、別の書き方で回り込むことはしていません」と答えています。
ただし、書き方が違うと止まりません。 利用者編の 1 章で python という名前を使えるようにする前に同じことを試したときは、Kiro は python が使えないと分かると、& "<Python の場所>\python.exe" -m pip install ... のように Python の場所を直接書く書き方に切り替えました。このコマンドはどの禁止の決まりにも当たらず、インストールが実行されました。
!
コマンドの禁止で、インストールを止めきるのは難しいと考えてください。 今回止まったのは、Kiro がたまたま禁止した書き方を選んだからです。コマンドの決まりは、コマンドの先頭からの文字列で照らし合わせます。インストールのコマンドは、pip、python -m pip、py -m pip、Python の場所を直接書く形、uv、winget、スクリプトの中に書く形など、書き方がいくらでもあります。禁止する書き方を増やしても、Kiro が動く書き方を探して別の書き方を選ぶと、そのまま通ります。Kiro が禁止をすり抜けようとしたわけではなく、動く書き方を選んだだけでも、こうなります。
インストールを確実に止めたいなら、コマンドの禁止ではなく、PC の管理(ローカル管理者の権限を外す、配るソフトを決める)やネットワーク(pypi.org などへの通信を止める)で止めます(「知っておくとよいこと」の最後)。
Kiro の設定にも、コマンドの書き方に左右されない方法が 1 つあります。Local command sandbox(Kiro IDE 1.3 以降、Kiro CLI 2.31 以降の V3)で networkAccess を DEFAULT_BLOCKED にすると、AI が実行するコマンドから外への通信が止まるので、どの書き方の pip install もダウンロードできなくなります(筆者は実機では試していません)。ただし、sandbox は既定でオフで、設定ファイル(sandbox.json)は利用者が自分で書くものです。利用者は自分でオフにでき、managed-settings.json で sandbox をオンに固定する方法もドキュメントには見当たらないので、組織として止める手段にはなりません。
!
C:\ProgramData\Kiro\managed-settings.json がすでにある PC では、この実験をしないでください。 会社が配っている設定なので、上書きすると会社の決まりが外れます。テスト用の PC で試します。
結果から分かるのは、次の 3 つです。
管理者の決まりは、利用者の設定より優先されます。 Kiro は、いちばん厳しい決まりを採るためです(Permissions)。利用者が「確認なしで進める」にしていても、管理者が「毎回確認」「禁止」にした操作はそのとおりになります
コマンドの禁止は、書き方を変えると通り抜けます。 試し ④ のとおり、禁止した書き方なら止まりますが、別の書き方では実行されることがあります。コマンドの禁止は「うっかり防止」と考え、確実に止めたいことは PC の管理やネットワークで止めます
管理者の設定でできるのは、止めることと、確認させることだけです。 管理者の決まりに「確認なしで進める」(allow)は書けません(Permission policies)。確認を減らすのは、利用者の設定(利用者編 2 章)の役目です
片付ける:試し終わったら、C:\ProgramData\Kiro\managed-settings.json を削除して、Kiro を開き直します。残したままだと、drafts への保存、Python の公式ドキュメントの読み込み、pip install で始まるインストールが止まったままになります。
実際に配る内容は、「知っておくとよいこと」の 3 段階のテンプレートから選びます。
知っておくとよいこと(必要になったら読む)
managed-settings.json に任せること、任せないこと
managed-settings.json は、うっかりや事故を減らすための仕組み と考えます。コマンドの禁止は書き方を変えると通り抜けますし(「やってみる」の結果)、ローカル管理者の権限がある利用者はファイルを消して外せます。確実に止めたいことは、PC の管理やネットワークの側で止めます。
やりたいこと
向いている仕組み
理由
認証情報や機密フォルダを AI に読ませない・書き換えさせない
managed-settings.json(fs_read / fs_write の deny)
コマンドの書き方ではなく、ファイルの場所で照らし合わせる。ただし、AI がコマンドでファイルを読み書きする場合は、shell の規則で判定される
業務と関係ない Web のページを読ませない
managed-settings.json(web_fetch の deny)
「やってみる」の試し ② で止まることを確かめた
MCP のツールを使うたびに確認させる
managed-settings.json(mcp の ask)
すべての MCP に効く
社用のサインイン方式だけにする
managed-settings.json(signin_method)
サインインの画面で選べる方式を絞れる。ファイルを置いていない PC と Kiro Web には効かない(Sign-in controls)
取り返しのつかないコマンドを止める
managed-settings.json(shell の deny)
うっかり防止として使う。書き方を変えると通り抜ける
ソフトやライブラリのインストールを止める
PC の管理、ネットワーク
コマンドの禁止はすり抜ける。Local command sandbox で外への通信を止める方法もあるが、利用者がオフにできる(「やってみる」の試し ④、下の「自分の PC に入れたくないとき」)
managed-settings.json を消させない
利用者からローカル管理者の権限を外す
これがないと、ほかの決まりもまとめて外せてしまう
書けるもの
部分
中身
対応バージョン
rules(permissions)
deny と ask だけ。allow は書けない
—
rules(signin_method)
使わせないサインイン方式
IDE 1.2 以降、CLI 2.25.0 以降
settings
IAM Identity Center の Start URL とリージョン、外部 IdP の情報、ヘルプページの URL
同上
3 段階のテンプレート
最初から厳しくすると利用者が使いにくさを感じて個人アカウントや別のツールに流れやすいので、① から始めて、運用しながら ② → ③ へ絞る 進め方をお勧めします。
段階
読み取り(fs_read)
コマンド(shell)
Web
サインイン
① まず始める
認証情報のファイルを deny
取り返しのつかないコマンドを deny
制限しない
社用の方式だけ
② 標準
① に加えて、社内の機密フォルダを deny
① に加えて、外にデータが出るコマンドを ask
業務のドメイン以外を deny
同上
③ 厳格
② と同じ
すべて ask(① の deny はそのまま)
取得・検索とも deny
同上
① まず始める(このまま全員に配る)
認証情報のファイルの読み書きと、取り返しのつかない操作だけを止めます。業務を妨げずに事故を防ぐ組み合わせです。
{
"rules": [
{
"capability": "fs_read",
"match": ["/.env", "/.env.", "/.pem", "/*.key", "/.aws/", "/.ssh/"],
"effect": "deny"
},
{
"capability": "fs_write",
"match": ["/.env", "/.env.*", "/.aws/", "/.ssh/"],
"effect": "deny"
},
{
"capability": "shell",
"match": [
"rm -rf *",
"Remove-Item -Recurse",
"rmdir /s*",
"rd /s*",
"del /s*",
"git push --force*",
"git push -f*",
"sudo *"
],
"effect": "deny"
},
{ "capability": "signin_method", "match": ["*"], "exclude": ["idc"], "effect": "deny" }
],
"settings": {
"idc_start_url": "my-org.awsapps.com",
"idc_region": "ap-northeast-1",
"signin_help_url": "it.example.com"
}
}
規則
狙い
fs_read / fs_write の deny
.env、秘密鍵、AWS の認証情報、SSH の鍵を AI に読ませない・書き換えさせない
shell の deny
フォルダごとの削除(macOS / Linux の rm -rf、PowerShell の Remove-Item -Recurse、コマンドプロンプトの rmdir /s・rd /s・del /s)、リモートの履歴を壊す強制 push、管理者権限での実行(sudo。Windows 11 の sudo も同じ名前)を止める。* は空白を含む任意の文字列に一致するので、Remove-Item -Recurse は -Recurse がどの位置にあっても当たる。ただし、-r のような短縮形や、rm・ri などの別名で書かれると当たらない
signin_method
社用の IAM Identity Center 以外でのサインインを止める。個人の GitHub などで入ると、MCP の統制が効かず、内容がサービス改善に使われる可能性があるため。外部 IdP に直接つないでいる場合は exclude を external_idp にする
② 標準(① の rules に追加する)
機密フォルダの読み取りを止め、外にデータが出る操作は人が確認してから実行します。次の 3 行は、① の rules の [ ] の中に足す部分です。① の最後の規則(signin_method の行)の後ろにカンマを付けてから貼り付けます。このままでは 1 つのファイルとしては読み込めません。
{ "capability": "fs_read", "match": ["/confidential/", "/人事/"], "effect": "deny" },
{ "capability": "shell", "match": ["git push*", "curl *", "wget *", "aws *"], "effect": "ask" },
{ "capability": "web_fetch", "exclude": ["docs.aws.amazon.com", ".amazonaws.com", "kiro.dev", ".github.com"], "effect": "deny" }
機密フォルダの名前(confidential・人事)は、実際のフォルダ構成(共有ドライブのパス、プロジェクト名など)に置き換えます
web_fetch の exclude に書いたドメインは、管理者の規則の対象外になり、利用者の設定に従います。業務で使う公式ドキュメントのドメインを入れます
mcp を ask にしたい場合もこの段階で足します({ "capability": "mcp", "effect": "ask" })
③ 厳格(② の rules に追加する)
機密案件や閉域環境など、AI が勝手に外へ出ることを一切認めない場合の設定です。② と同じく、rules の [ ] の中に足す部分です。
{ "capability": "shell", "match": ["*"], "effect": "ask" },
{ "capability": "web_fetch", "effect": "deny" },
{ "capability": "web_search", "effect": "deny" }
すべてのシェルコマンドが確認になります。① で deny にしたコマンドは、deny が ask より強いので deny のままです
② の web_fetch の exclude 付きの規則は残っていても構いませんが、③ の deny が勝つため、すべての Web 取得が止まります
!
shell の規則は、コマンドの先頭からの文字列の一致で判定します。;・&&・|・|| でつないだコマンドは分割して判定されます。Kiro IDE 1.4.51 以降と Kiro CLI V3 では、規則と照らし合わせられないほど複雑な bash・PowerShell のコマンドは止められ、AI に書き直しが求められます。cmd.exe のコマンドは確認が出ます(Permissions > Shell command parsing)。それでも、試し ④ のように、別の書き方のコマンドは通ることがあります。外部への持ち出しを確実に止めたいなら、社内の Proxy・FW の許可リストを重ねてください。
配る前のチェックリスト
managed-settings.json は、壊れているとすべてのツールが止まります。 配る前にテスト端末で次を確認してください。
UTF-8(BOM なし)で保存した(Windows PowerShell の Out-File は既定で UTF-16 を書くので使わない)
JSON として正しい(末尾のカンマやコメントがない)
effect に allow が入っていない(1 つでもあるとファイル全体が無効)
フィールド名のつづりが正しい(effct のような未知のフィールドがあると全ツールが拒否される)
shell・web_fetch・mcp のパターンに ** や ? を使っていない
サインイン方式の名前が idc・external_idp・builder_id・google・github・social のどれかになっている(画面の表示名は使えない)
Kiro を再起動して、IDE の通知や kiro-cli login の Warning: 行が出ない
標準ユーザーでファイルを保存しようとして、拒否される
!
壊れたときの倒れ方が、規則の種類で逆です。
permissions の規則:壊れていると 全ツールを拒否(安全側)
サインイン方式の規則:壊れていると 全方式を許可(利用者を締め出さないため)
同じファイルに両方を書いていると、JSON が壊れたとき「サインインは何でも通るが、ツールは全部止まる」状態になります。
!
managed-settings.json もクライアント側で強制されます。 端末のローカル管理者権限を持つ利用者は、ファイルを消して回避できます。利用者にローカル管理者権限を渡していない環境で使う前提の仕組みです。
managed-settings.json に入らないもの
設定
配り方
自動更新の向き先・停止(IDE は UpdateUrl・UpdateMode、CLI は update.baseUrl など)
OS のポリシー(macOS は IDE が dev.kiro.desktop、CLI が dev.kiro.cli ドメイン)。Managed updates
steering・skills・利用者の permissions
~/.kiro/ 配下にファイルとして配る(利用者が消せる)
モデルの承認リスト・MCP・Web ツール・API キー
Kiro コンソール
自分の PC に入れたくないとき:仮想デスクトップで使う
Kiro や Python、ライブラリを自分の PC に入れたくない場合は、Amazon WorkSpaces のような仮想デスクトップで Kiro を使う方法があります。Kiro が入れるソフトやライブラリは仮想デスクトップの中に入るので、自分の PC には何も入りません。 仮想デスクトップは管理者が用意して配るので、何が入っているかも管理者がまとめて把握できます。
WorkSpaces にしても、WorkSpaces の中でのインストールは止まりません。 WorkSpaces Personal は、既定で利用者がローカル管理者です。また、利用者編の 1 章のライブラリのように、自分のユーザーフォルダに入れるインストールは、管理者の権限がなくてもできます。WorkSpaces の中で入れられるものも絞りたい場合は、次の 3 つを組み合わせます。
やること
効き方
ローカル管理者の権限を外す(ディレクトリの Local administrator setting)
管理者の権限が要るインストールと、managed-settings.json の削除を防ぐ。変更は、新しく作る WorkSpace と、作り直した WorkSpace に反映される。この設定を使えるのは、AWS Directory Service(AD Connector、AWS Managed Microsoft AD、Simple AD)で管理しているディレクトリだけ(Manage local administrator permissions)
外への通信を絞る(セキュリティグループ、AWS Network Firewall)
pypi.org などへの通信を止めるので、ユーザーフォルダへのインストールも、コマンドの書き方に関係なく失敗する。セキュリティグループは、既定ではすべての外向きの通信を許可している(How do I restrict internet access on WorkSpaces Personal?)
必要なものを入れたイメージを配る
Kiro、Python、ライブラリを最初から入れておき、利用者には入れさせない(How to secure your Amazon WorkSpaces for external users)
外への通信を絞るときは、Kiro のサインインやモデルの呼び出しに必要な行き先は通します。必要な行き先は、構成を決める前に公式ドキュメントで確認してください
pypi.org を止めた環境では、利用者編の 1 章の「PowerPoint・Excel・Word 用のライブラリを入れる」の手順は使えません。Python とライブラリは、管理者がイメージに入れておきます
2. MCP レジストリ:使える MCP サーバーを絞る
管理者は Kiro コンソールで、MCP 自体を無効にするか、許可するサーバーの一覧(MCP レジストリ)を登録できます(Kiro Enterprise > MCP)。
MCP レジストリ URL に入れるもの
Kiro コンソールの Settings > Shared settings > MCP Registry URL(日本語の画面では セッティング > 共有設定の詳細 > MCP レジストリ URL - オプション)には、許可するサーバーの一覧を書いた JSON ファイルの URL を 1 つ入れます。サーバーの一覧そのものを欄に書くのではありません。
mcp-registry.example.co.jp
JSON ファイルも、それを置く場所も、管理者が自前で用意します。 Kiro は置き場所を用意しません。ドキュメントには、Kiro コンソールに JSON を直接アップロードする方法も書かれていません。置き場所には、次の 2 つの条件があります。
有効な証明書の HTTPS であること(自己署名の証明書は使えません)
利用者の PC から読めること(社内ネットワークの中だけで見える URL でも構いません)
ドキュメントには、置き場所の例として Amazon S3・Apache・nginx が挙がっています。社内に条件を満たす Web サーバーがあれば、そこに置けます。ない場合は、AWS 上に S3 と CloudFront を自前で用意して配信します。S3 のバケットは公開せず、CloudFront の Origin Access Control 経由で読ませます。
JSON ファイルの書き方
AWS Knowledge MCP Server だけを許可する例です。書き方は、MCP の公式の登録簿(MCP Registry)の形式の一部です。
{
"servers": [
{
"server": {
"name": "aws-knowledge-mcp-server",
"title": "AWS Knowledge",
"description": "AWS の公式ドキュメントやブログを検索する",
"version": "1.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "knowledge-mcp.global.api.aws"
}
]
}
}
]
}
項目
書き方
name
ファイルの中で重複しない名前。3〜200 文字の英数字と ._-
description
説明。100 文字まで
version
1.0.0 のような番号。^1.2.3 のような範囲の書き方は使えない
remotes
インターネットや社内のサーバーにつなぐ場合。1 件だけ。type は streamable-http か sse
packages
PC の上でプログラムを動かす場合。1 件だけ。registryType は npm・pypi・oci のどれか。それぞれ npx・uvx・docker で起動するので、利用者の PC にあらかじめ入れておく
サーバーを足すときは、servers に 1 件を加えて、同じ URL のファイルを上書きします。Kiro が読み直すので、コンソールの設定を変える必要はありません。
登録するとどう動くか
一覧にないサーバーは、表示されず、起動もしません。 利用者が自分の mcp.json に書いたサーバーも同じです。IDE の MCP パネルには「N servers hidden」とだけ出ます(MCP registry)
利用者は、MCP パネルに出る一覧からサーバーを選んで追加します。 CLI では /mcp add で選びます
CLI V3 では、mcp.json の書き方も変わります。 レジストリのサーバーは、"aws-knowledge-mcp-server": { "type": "registry" } のように "type": "registry" と書いた項目だけが起動します。利用者編の 5 章のように url や command を自分で書いた項目は、名前が一覧と同じでも読み飛ばされます(MCP registry > Servers not in the registry)
Kiro は、起動したときと 24 時間ごとに一覧を読み直します。 一覧から外したサーバーは、利用者の PC でも止まります
効くのは、IAM Identity Center か API キーでサインインした利用者だけです。 Builder ID やソーシャルログインの利用者には効きません
制限は利用者の PC の側でかけています。 PC の管理者の権限を持つ人は外せます
登録するときに気をつけること
JSON ファイルには、トークンなどの秘密を書きません。 URL を知っている人なら読めるファイルにすることが多いためです。利用者は自分の mcp.json で、HTTP ヘッダーや環境変数を足して上書きできるので、トークンはそちらに書いてもらいます
URL を登録した時点で、一覧にないサーバーはすべて止まります。 利用者がいま使っているサーバーを先に調べて、一覧に入れておきます
3. モデルの承認リスト:使えるモデルを絞る
管理者は Kiro コンソールの Settings > Shared settings > Model availability(日本語の画面では セッティング > 共有設定の詳細 > モデルの可用性)で Control which models are available to users(ユーザーが利用できるモデルを制御)をオンにし、Manage approved list で承認リストを作ります。組織の既定のモデルも、ここで指定できます(Kiro Enterprise > Models)。押さえておくことは次の 4 つです。
承認リストは プロファイルの共有設定の 1 項目 です。コンソールの画面には、利用者やグループを選ぶ項目はありません(筆者の画面で確認)
承認リストを使うと、新しいモデルは管理者が選んで保存するまで使えません。 新しいモデルが出るとコンソールに通知が届くので、使わせたいものを選んで保存します。承認リストを使わない(オフの)間は、新しいモデルは自動で使えるようになります(Fable 5.1 と Kimi K3 を除く)
Auto を許可すると、承認していないモデルに処理が回ることがあります。 承認したモデルだけで処理したいなら、Auto を外して既定のモデルを指定します(Available models)
experimental のモデルと Kimi K3 は、出力に対する補償の対象外 です(Kiro Excluded Models)
4. チームに配る:何をどこに置けばよいか
「何をどこに置けばチームに行き渡るか」をまとめます。
設定
作業フォルダごと共有
IT 管理者が ~/.kiro/ に配る
managed-settings.json
Kiro コンソール
steering
◎ プロジェクトの規約
〇 全社の規約(消せる)
—
—
skills
◎ プロジェクトの手順
〇 全社の手順(消せる)
—
—
permissions の allow
✕ 置けない
〇 確認を減らす既定値
✕ 書けない
—
permissions の deny / ask
✕ 置けない
△ 利用者が消せる
◎ 消せない(ローカル管理者以外)
—
MCP サーバーの定義
◎ .kiro/settings/mcp.json
〇
—
—
使える MCP の制限
—
—
—
◎ レジストリ
カスタムエージェント
◎ .kiro/agents/
〇
—
—
既定のモデル・effort
△ エージェントの model、.kiro/settings/cli.json(CLI のみ)
〇 ~/.kiro/settings/cli.json(CLI のみ)
—
〇 組織の既定のモデル
使えるモデルの制限
—
—
—
◎ 承認リスト
サインイン方式
—
—
◎
〇 デバイスフロー(CLI)
steering は、次の 2 つの方法で配れます。
方法
対象
置き場所
作業フォルダごと共有する(共有ドライブ、Git など)
そのフォルダを使う全員
作業フォルダの .kiro\steering\
IT 管理者が PC に配る
配った先のユーザーの全部の作業
そのユーザーの ~.kiro\steering\(公式に「Team steering」として記載)
よくある質問
Q. 利用者ごとに使えるモデルを変えたい
Kiro の承認リストは、プロファイルの共有設定の 1 項目です。コンソールの画面には利用者やグループを選ぶ項目がなく、利用者ごとに分ける方法は見当たりません(3 章)。用途ごとの既定はカスタムエージェントの model で決められますが、利用者はモデルを切り替えられます。
Q. Auto を選んだとき、どのモデルが使われたかを知りたい
公式の手段はありません(2026-11-01 時点)。
プロンプトログの項目には、モデル名はありません
クレジットは Auto の倍率(1.0 倍)で数えられるので、消費量から推測することもできません
回答ごとにモデルを表示してほしいという要望は、公式の GitHub に Issue として上がっています(kirodotdev/Kiro#8903)
公式に書かれているのは、Auto が振り分ける範囲だけです。Auto は、そのプロファイルで使える Active のモデルに振り分け、experimental(試験提供)のモデルには振り分けません。管理者の承認リストの外のモデルに振り分けることもあります(Available models)。
どのモデルで処理したかを把握したいなら、Auto ではなくモデルを指定して使ってください。 組織として必要なら、管理者が承認リストから Auto を外し、承認済みのモデルを既定にします。こうすれば、管理者のユーザー単位のアクティビティレポートにあるモデルごとのメッセージ数の列が、そのまま「使ったモデル」になります(Viewing per-user activity)。
まとめ
組織として止めたい操作は managed-settings.json の deny と ask に書く。 allow は書けないので、確認を減らすのは利用者の設定に任せる
managed-settings.json は「うっかり防止」と考える。 確実に止めたいことは、ローカル管理者の権限を外す、通信を絞るなど、PC の管理やネットワークで止める
managed-settings.json は配る前にテスト端末で 1 回起動する。 壊れると全ツールが止まる
使える MCP サーバーはレジストリ、使えるモデルは承認リストで絞る。 Auto を許可すると、承認していないモデルに処理が回ることがある
チームでそろえる設定は、置き場所で配り方が変わる。 steering・skills は作業フォルダごと共有するか PC に配り、permissions は PC に配るか managed-settings.json に書く
利用者の方には、利用者編を案内してください。管理者の設定で止めている操作があると「許可したのに動かない」と問い合わせが来ることがあるので、何を止めているかを社内で共有しておくと、問い合わせが減ります。
"Our MCP directory said \"active\". The publisher had deprecated it five days earlier."
I run ProtocolProbe, a directory of facts about every server in the official MCP Registry: each server gets a page, read from public records (the registry, npm, PyPI, GitHub), with a date on every fact.
A publisher emailed us. They had renamed their server, marked the old registry entry as deprecated, and added a message pointing to the new name. Our page for the old entry still said active.
I told them it would update on our next daily run. That was wrong. This post is about why, and what we changed.
The bug
Our daily job pulls the full registry list, compares it with yesterday's, and loads what changed: new servers, new versions, removed servers. For those, it re-reads the package and repository facts the same day.
The publisher's change was none of those. The registry lets a publisher change an entry's status (active → deprecated) and its status message without publishing a new version. Same version, different status.
Our job noticed it: it counted it as a "metadata-only" change. It just never loaded it. The page kept the old status, and the run still finished with ok.
How big was it?
One report can be a coincidence, so we compared every stored record with the registry list of that day, field by field (read-only, 51,834 servers):
| | mismatches |
|---|---|
| servers missing / extra | 0 / 0 |
| version | 0 |
| status (stored active, registry deprecated) | 53 |
| status message | 60 |
So the parts the job was built for were exact, and the gap was precisely the case it skipped: entries deprecated by their publishers that we still showed as active. Our own record check "deprecated in the registry" gave the wrong answer for all of them.
Fix 1: reconcile what the registry owns, every day
The registry is the source of truth for a few fields: status, status message, published and updated dates. Now, after every daily load, the job compares those fields for all servers with the registry list and patches the ones that differ at the same version:
- a backup of every value before the first write, and a journal, so a crash mid-way resumes instead of half-patching;
- every patched document read back; a mismatch rolls the whole step back;
- a cap of 2,000 patches a day (a registry-wide change needs a person to look);
- each change recorded publicly with its date, so anyone can see what changed and when.
First run: 59 servers patched (some had changed since the count above). A second comparison right after: 0 mismatches across 52,138 servers.
Fix 2: a job that says "ok" must check the page
The deeper problem was not the missing field. It was that the job reported success without looking at what visitors see.
So the job now ends with a live check: after the pages are refreshed, it takes a small fixed sample of the day's changed servers (new, new version, status patched) and opens their public pages.
We first tried reading our own public API instead. It was the wrong thing to check, for two reasons we found by reading our code:
1. The API is not what was stale. The page was.
2. The API answer is cached at the CDN and inside each function instance, and the instance copy comes back without a cache header. A check right after a load can read an old copy and raise a false alarm.
Each server page now carries a small machine-readable marker in its head: version, status, a short hash of the status message (never the message itself), and the snapshot date. The job compares that with what it loaded.
- A mismatch is a warning, never a rollback.
- Our own failures (rate limits, timeouts) are "not checked", never a mismatch.
- If half the sample can't be checked, that is a warning too.
Fix 3: a date range, not a date
While checking this, we found a quieter version of the same problem on our leaderboards. They said "facts checked 2026-10-29". That was the newest date among the records, which meant the servers that changed that day.
Most stars and download counts behind the ranking had been read days earlier. The page now shows the range: "facts checked 2026-10-24 to 2026-10-29".
How stale does a directory get?
We measured it. We re-read the free facts (npm/PyPI, OSV, GitHub) of 1,729 servers that had not changed in the registry, a 1/30 slice of the catalogue. It took about 14 minutes and about 1,200 GitHub API requests.
614 of them (35%) had at least one fact that had changed since our last read:
- 396 download counts
- 301 last-push dates
- 99 star counts
- 23 version-pin states
- 7 repository states (not found, found again, archived)
- 1 newly withdrawn package
None of these show up as a registry change. We built a daily rotation that re-reads such a slice every day, so every server is re-read at least monthly. It is switched off until we have watched a few normal runs of the new checks.
What I'd tell anyone building on a registry
- A listing is a snapshot. The official MCP Registry checks an entry when it is published (each new version), and nothing re-checks it on a schedule. We asked for that upstream: registry issue #1715. If you mirror it, you inherit the same gap.
- Same version doesn't mean same record. Compare the fields the source owns, not just the version.
- Check the output, not the exit code. Our job said ok for days while pages were wrong.
- Show the age of your data honestly. A single date can quietly overstate how fresh it is.
- Say when you were wrong. I told the publisher the wrong thing first. Admitting it took one email. Our corrections are logged at protocolprobe.com.
Every change the job records is public, by day, at protocolprobe.com, and in a free API with no key (/docs/api). If you run an MCP server and something on its page looks wrong, /operators says how to tell us.
9 comments
What the official MCP registry looks like when you check it twice
I run ProtocolProbe, a free site that records dated facts about every server in the official MCP registry. We pulled the whole registry on 2026-10-04 and again on 2026-10-23, and we have pulled it daily since. This post is about what changed, what the registry itself checks and when, and how to reproduce the counts yourself. No server's tools were called to get any of it: everything below comes from the registry API, npm, PyPI, GitHub and OSV.
What changed in 19 days
- 2026-10-04: 38,977 latest entries. 2026-10-23: 48,551. (2026-10-24: 49,445.)
- Between the two pulls: 9,803 new servers, 5,646 existing servers listing a newer version, and 229 servers no longer listed. The arithmetic closes: 38,977 + 9,803 − 229 = 48,551.
- The registry gives no reason for a removal, so we do not guess one. Each removed server's page stays up, marked as removed.
The day-by-day record is at protocolprobe.com.
What the registry checks, and when
This is from reading the registry's source (commit c384c4b, read on 2026-10-06), so treat it as a statement about that code, not about anyone's intent:
- At publish time the validators fetch the exact listed npm or PyPI version and check the ownership token.
- I found nothing that re-validates an entry after it is published. repository.url is checked with a regular expression only, and the schema requires only name, description and version, so an entry with no package and no remote is allowed.
- On 2026-10-26 the registry's roadmap (docs/design/roadmap.md) has no re-validation item. It also says its own phase labels are out of date as of 2026-08-10.
I opened issue #1715 asking for periodic re-checks of published entries.
What a second look finds (dataset snapshot 2026-10-24)
| What we counted | Number |
|---|---|
| Entries in the snapshot | 49,445 |
| Remote only / local package only / both / neither | 30,185 / 16,395 / 2,348 / 517 |
| Entries whose linked GitHub repository exists | 29,039 |
| Entries whose linked GitHub repository returns 404 | 4,622 (13.7% of the 33,661 that link one) |
| Entries with no GitHub link we could check | 15,784 |
| Entries marked deprecated in the registry | 620 |
One earlier number from the 2026-10-04 snapshot, which we have not re-run on the newer one: of 14,431 npm and PyPI entries, 157 (1.09%) listed a package or version that cannot be installed because it does not exist on npm or PyPI.
What these numbers are not: a 404 can mean a deleted, private or moved repository, and we cannot tell which. A count of public records on a date is not a verdict on any server, and we do not rank or grade servers.
Reproduce the repository count
The dataset is CC BY 4.0, on Zenodo (the DOI of all versions leads to the newest), Hugging Face and Kaggle. Standard library only:
import csv, io, urllib.request, collections
url = "https://huggingface.co/datasets/sultan57/mcp-registry-facts/resolve/main/servers.csv"
rows = list(csv.DictReader(io.StringIO(urllib.request.urlopen(url).read().decode("utf-8"), newline="")))
c = collections.Counter(r["repository"] or "none" for r in rows)
linked = c["present"] + c["repo-not-found"]
print(len(rows), dict(c), f"{c['repo-not-found'] / linked:.1%} of {linked}")
# 49445 {'none': 15784, 'present': 29039, 'repo-not-found': 4622} 13.7% of 33661
I ran this on 2026-10-26 and got the output in the last comment. The CSV has 22 columns (hosting_type, lifecycle_status, license_class, maintenance and more); the full records are one JSON file per part, exactly as the public API serves them.
If you run an MCP server
There is a small command that runs ProtocolProbe's runtime checks against a server you run yourself, on your own machine: npx protocolprobe-selftest. It can fail a CI job (--fail-on-finding) and write SARIF (--sarif). It is separate from the data above, which never calls anyone's tools.
Corrections are welcome: protocolprobe.com lists the ones we have made.
The MCP registry checks a package at publish. Here's what drifts after that.
When you install an MCP server from the official registry, you get exactly the package version the entry lists. The registry checks that version when the entry is published: it fetches it from npm or PyPI and confirms the package names the server.
I read the registry's source to see what happens after that. The package check runs again only when the publisher edits the entry. Nothing re-checks entries on a schedule. Repository links are checked against a URL pattern and never fetched.
That's a reasonable design for a young registry, and it isn't anyone's mistake. But it means entries can drift. So I checked the whole registry against npm, PyPI and GitHub and counted what no longer matches.
What I counted
Registry snapshot 2026-10-04: 38,977 servers (the latest version of each name). Lookups ran on 2026-10-06.
| What | Count | Out of | Share |
|---|---|---|---|
| Repository link returns 404 (missing or private) | 4,298 | 28,743 entries with a GitHub link | 15.0% |
| Listed version is withdrawn upstream (npm deprecated / PyPI yanked) | 663 | 14,431 npm/PyPI entries | 4.6% |
| Linked repository is archived | 309 | 24,256 reachable repos | 1.3% |
| Listed package or version can't be installed at all | 157 | 14,431 npm/PyPI entries | 1.1% |
| Active, but no package, no remote and no website | 216 | 38,977 | 0.6% |
Together, at least 5,192 of 38,977 entries (13.3%), more than 1 in 8, had at least one of these.
A few notes on reading this:
- The 157 are the clearest case of drift. All of them were published after the registry's version check existed. If validation was on (it is by default), each one passed an exact-version check and the package or version disappeared later.
- Withdrawn doesn't mean malicious. I classified the publishers' own deprecation messages. Most are renames, moved distributions and discontinued packages. A few say the backing service shut down, and 12 publishers cite a security issue. A registry-driven install still gets the withdrawn version, with no warning.
- A 404 can be a renamed or private repo. I don't tell those apart.
The fix is now being tracked
I opened a feature request on the registry, modelcontextprotocol/registry#1715. It asks to re-run the existing checks on a schedule and record the result (for example lastVerifiedAt plus a status) instead of deleting anything.
On 2026-10-23, a member of the MCP organization consolidated the older re-validation request (#93) and five endpoint-health reports into that issue, so periodic re-validation now has one implementation tracker. The direction in the thread: dated results, explicit "auth-required / transient / unknown" states, and no removal on a single failed check.
That last point matters. A missing repo or a yanked version is a fact a client should see. It shouldn't silently turn into a takedown.
What this doesn't say
- Nothing here says whether any server is safe or unsafe. I didn't run or call any server's tools. Everything comes from the registry and public package records.
- The numbers are a dated snapshot. The registry has grown since then, and I haven't recounted these figures.
If you publish an MCP server
Five minutes of housekeeping:
1. Open your registry entry's repository.url. Does it still load?
2. Is the version you list still published, and not deprecated or yanked?
3. If you renamed or moved the package, publish an update that points to the new one.
Full tables, with each figure's source and date: protocolprobe.com
Dataset (CC BY 4.0, citable): doi.org
Disclosure: I built ProtocolProbe, a free directory that reads the registry's public data. Corrections welcome: hello@protocolprobe.com.
3 comments
A Private MCP Registry in 10 Minutes with agentregistry
Originally published at webofmike.com on 2026-10-13. The demo repo and every command in it were run before publishing.
A private MCP registry is a catalogue of approved MCP servers that your clients discover from instead of the open public one. I built private-mcp-registry to stand one up on agentregistry v0.4.0: three servers declared in YAML, served through the official MCP Registry v0.1 API, checked against the official OpenAPI document, and discovered by a client that knows nothing about agentregistry. Run as a script, the whole quickstart took 49 seconds on my laptop.
Why run a private MCP registry?
The public MCP Registry is open. Anyone can publish to it. On 2026-09-29 I searched it for fetch and got 70 servers at their latest version. None of them was mcp-server-fetch, the reference fetch server from the MCP project. A team that tells its developers "install the fetch server" has not said which of the 70 it means.
A private registry answers that once. The platform team decides which servers exist, which package they come from, and which version is approved. Every client reads that list. The approved list also becomes the baseline you need for everything else: you cannot flag a shadow MCP server without a list of the servers that are not shadows.
What changed in agentregistry v0.4.0
v0.4.0, released 2026-08-03, rebuilt agentregistry as a declarative control plane. Every artifact is now a Kubernetes-style resource under ar.dev/v1alpha1, managed with arctl apply, arctl get and arctl delete. MCP servers, agents, skills, prompts, plugins and models share the format.
Two things in the release notes matter for this demo:
1. It is not backward compatible with v0.3.x. The release says there is no in-place upgrade. The v0.3.x CLI has no arctl apply at all. The machine I built this on had v0.3.3 in /usr/local/bin, so the repo downloads v0.4.0 into its own ./bin and verifies the checksum.
2. The official registry API is back, opt-in and read-only. agentregistry started as a fork of the official registry, then moved its native API to v1alpha1 resources, which took it off the upstream server.json contract. v0.4.0 adds a compatibility layer that re-exposes MCPServer resources through the v0.1 read endpoints. It is off by default.
The quickstart
Prerequisites are Docker with Compose v2, uv, Node.js for npx, curl and jq. The full command list, including the arctl download, is in the repo README.
The compose file is agentregistry's own compose file at the v0.4.0 tag, with the compatibility API switched on:
# The point of this demo: serve GET /v0.1/servers in the official
# MCP Registry shape. Off by default because it is anonymous and
# flattens every namespace. See README "Before you expose this".
AGENT_REGISTRY_MCP_REGISTRY_COMPAT_ENABLED: "true"
Start it:
docker compose up -d --wait
curl -s localhost:12121/v0/version
{"version":"v0.4.0","git_commit":"aef6563","build_time":"2026-08-03"}
Then publish the servers. One file holds three MCPServer resources, one for each source type: the MCP "everything" reference server from npm, the reference fetch server from PyPI, and DeepWiki's public remote endpoint. This is the PyPI entry:
apiVersion: ar.dev/v1alpha1
kind: MCPServer
metadata:
namespace: com.example
name: fetch
spec:
title: Fetch
description: "Fetches a URL and returns the page as markdown."
source:
package:
origin:
type: pypi
identifier: mcp-server-fetch
pypi:
version: "2026.8.18"
serverName: io.github.modelcontextprotocol/server-fetch
transport:
type: stdio
arctl apply -f registry/servers.yaml --dry-run
arctl apply -f registry/servers.yaml
✓ MCPServer/everything (latest) created
✓ MCPServer/fetch (latest) created
✓ MCPServer/deepwiki (latest) created
The package version is pinned in the manifest, and the manifest lives in Git. Adding a server to the approved list is now a pull request someone reviews.
Read it back through the official API path:
curl -s localhost:12121/v0.1/servers | jq '.servers[].server | {name, version}'
{
"name": "com.example/deepwiki",
"version": "0.0.0"
}
{
"name": "com.example/everything",
"version": "2026.8.31"
}
{
"name": "com.example/fetch",
"version": "2026.8.18"
}
The server name is <namespace>/<name>. Setting metadata.namespace: com.example gives you the reverse-DNS prefix the official naming convention uses.
About the 49 seconds: that is a script running every quickstart command in the README, from downloading arctl through the last discovery call, on an Apple silicon laptop. The agentregistry server image was not cached and the uv and npm caches were empty. The postgres:16 image was already on the machine, so a first run will also pull that. Reading and typing along adds more. Ten minutes is a comfortable ceiling.
Is agentregistry really compatible with the official MCP Registry API?
For reads, yes, and you can check it instead of taking my word for it. scripts/check_conformance.py fetches the official OpenAPI document at a pinned commit and validates three kinds of response against it:
- GET /v0.1/servers against the ServerList schema
- GET /v0.1/servers/{name}/versions against ServerList
- GET /v0.1/servers/{name}/versions/latest against ServerResponse
It then validates every server object against the server.json schema named in its own $schema field, which is the 2025-12-11 schema on static.modelcontextprotocol.io.
uv run scripts/check_conformance.py
GET /v0.1/servers
PASS envelope vs ServerList
PASS server com.example/deepwiki vs 2025-12-11/server.schema.json
PASS server com.example/everything vs 2025-12-11/server.schema.json
PASS server com.example/fetch vs 2025-12-11/server.schema.json
...
3 servers checked, 0 schema violations
Every response passed: the list and versions envelopes against ServerList, the single-version body against ServerResponse, and each server object against the 2025-12-11 server.json schema.
A checker that never fails proves nothing, so I tested it against a bad entry too. That test turned up a real gap, covered in the gotchas below.
"Compatible" has limits, and they are worth knowing before you point a fleet of clients at it:
- Read-only. The spec's POST /v0.1/publish and the status endpoints are not served. You publish through arctl apply or the native API. The spec marks publish as optional, so this is allowed. It does mean tooling built on the official publish flow will not work here.
- The default listing differs. The public registry's GET /v0.1/servers returned every version of a server when I queried it without a version parameter. agentregistry's returns only the latest tag. Clients that pass version=latest see the same behavior from both.
- version filters on the tag. The spec describes version as a server version such as 1.2.3. agentregistry matches it against metadata.tag. If your tags and package versions are the same string, you will not notice.
- Remote servers report 0.0.0. A remote entry has no package to take a version from, so it falls back to the tag, and latest becomes 0.0.0.
- Tokens are ignored. The official generic registry API lets a subregistry require authentication. agentregistry's compatibility doc says presented tokens are ignored on these routes and every caller sees the same catalogue.
How does a client discover servers from it?
scripts/discover.py uses only the v0.1 read API. It searches, takes the first match it knows how to launch (npx for npm, uvx for PyPI, streamable HTTP for remotes), connects with the MCP Python SDK, and lists the tools the server returns:
uv run scripts/discover.py fetch
http://localhost:12121/v0.1/servers?search=fetch
com.example/fetch 2026.8.18 pypi
connecting to com.example/fetch: uvx mcp-server-fetch==2026.8.18
1 tools: fetch
The npm entry launched as npx -y @modelcontextprotocol/server-everything@2026.8.31 and listed 13 tools. The remote entry connected to DeepWiki and listed 3.
The same client, with one flag changed, reads the public registry:
uv run scripts/discover.py fetch --no-connect --registry https://registry.modelcontextprotocol.io
It lists and does not launch. Running a package picked from an open catalogue, without review, is the thing a private registry exists to prevent. The public registry took about 30 seconds to answer each search when I ran this.
agentregistry also runs its own MCP server, on port 31313 in this compose file, with list_servers, get_server and matching tools for agents, skills, prompts and the other kinds. An agent that has it configured can ask the registry what exists without any HTTP client code:
npx -y @modelcontextprotocol/inspector@2.8.0 --cli http://localhost:31313/ \
--transport http --method tools/call --tool-name list_servers \
--tool-arg namespace=com.example --tool-arg search=fetch
For VS Code, agentregistry's docs say the gallery reads the organization Copilot policy McpGalleryServiceUrl and appends /v0.1/servers to it. I did not test VS Code for this post.
Gotchas
Why is /v0.1/servers empty?
{"servers":[],"metadata":{"count":0}}
That was my first result, with three servers applied. My first manifests set metadata.tag to a version on every entry, which seemed like good hygiene. The compatibility API's default listing only returns rows whose tag is the literal string latest, so all three were stored, visible to arctl get, and absent from /v0.1/servers. GET .../versions/latest returned 404 too.
The repo's servers.yaml omits metadata.tag, so every entry gets latest. registry/pinned-tag.yaml reproduces the problem:
arctl apply -f registry/pinned-tag.yaml
curl -s 'localhost:12121/v0.1/servers?search=pinned' # empty
curl -s 'localhost:12121/v0.1/servers?search=pinned&version=2026.8.18' # found
The registry's MCP list_servers tool returns every tag by default. So the same entry is visible over MCP and invisible over the v0.1 API.
arctl get mcps says "No mcps found"
arctl get mcps lists the default namespace, and these servers live in com.example. Ask by qualified name:
arctl get mcp com.example/fetch
Descriptions over 100 characters
agentregistry accepted a 132-character spec.description and served it through /v0.1/servers unchanged. The official server.json schema caps description at 100 characters, so the conformance check fails:
FAIL server com.example/longdesc vs 2025-12-11/server.schema.json: description: 'Answers questions about public GitHub repositories. This se... acters.' is too long
A client that validates against the official schema can reject that entry. Keep descriptions under 100 characters, and run the conformance check in CI against the manifests you are about to apply.
Before you expose it
The compatibility API is off by default for a reason. In the OSS build it is anonymous and flattens every namespace into one catalogue. In this compose file, the native API and the MCP server on 31313 also answered without any credentials. That is fine on localhost. Anywhere else, put it behind a gateway that authenticates callers, and treat the list of server names as information about your internal tooling.
Two limits of a registry entry are worth stating plainly.
A registry entry pins a package version. It does not pin what the server says about its tools at runtime. The server.json schema has no field for tool definitions at all. A server whose tool descriptions change after approval still passes a name check, which is the problem in MCP tool poisoning. The registry tells a client which package to run. Pinning the tool definitions it returns is a separate control.
A registry is also a write path and a list path, and that pair can carry data in both directions, as the RubyGems incident in Your Artifact Registry Is a Two-Way Channel for Agents showed. Here the write path is arctl apply. Keep it in CI, under one identity, and do not hand agents write access to the registry they discover from.
What's next
The demo stops at discovery. agentregistry v0.4.0 can also deploy what it catalogs, through Deployment and Runtime resources for local Docker and Kubernetes, and compose agents from MCP servers, skills and prompts. The next step is taking one approved server from this catalogue to a running deployment, with the conformance check as a gate in front of arctl apply. The code is at themsquared/private-mcp-registry.
Frequently asked questions
How do I run a private MCP registry?
Run agentregistry v0.4.0 and its Postgres database with Docker Compose, declare each approved server as an ar.dev/v1alpha1 MCPServer resource in YAML, and publish the file with arctl apply. Set AGENT_REGISTRY_MCP_REGISTRY_COMPAT_ENABLED=true and the registry also serves GET /v0.1/servers in the official MCP Registry shape. Run as a script, the full quickstart took 49 seconds on my laptop.
Is agentregistry compatible with the official MCP Registry API?
For reads, yes. With the compatibility API enabled, agentregistry v0.4.0 serves the three v0.1 read endpoints: list servers, list versions, and get one version. Its responses passed validation against the official OpenAPI document's ServerList and ServerResponse schemas and the 2025-12-11 server.json schema. There is no publish endpoint, and a description over 100 characters is served as-is even though the official schema rejects it.
Why does agentregistry's /v0.1/servers return an empty list?
The compatibility API's default listing only returns entries whose tag is the literal string latest. An MCPServer published with metadata.tag set to a version and nothing else is stored, and arctl get finds it, but GET /v0.1/servers skips it and GET .../versions/latest returns 404. Omit metadata.tag so the entry gets latest, or have clients pass ?version=<tag>.
Can an AI agent discover MCP servers from agentregistry?
Two ways. Any client that speaks the official v0.1 read API can list and fetch servers from /v0.1/servers, and the same client code works against the public MCP Registry. agentregistry also runs its own MCP server, on port 31313 in the demo, with tools such as list_servers and get_server, so an agent can query the catalogue over MCP directly.
---
*Canonical version, with machine-readable markdown at https://webofmike.com/private-mcp-registry/index.md: webofmike.com
Mentions
Whoever takes your old GitHub name gets your MCP namespace --- title: "Whoever takes your old GitHub name gets your MCP namespace" published: true description: "Rename your GitHub account and whoever takes the old name can publish new versions of your MCP servers. 145 live ones sit under names nobody holds." tags: "ai, security, github, mcp" canonical_url: "blog.peng.ly" cover_image: "blog.peng.ly" ai_assisted: true --- Rename your GitHub account and your MCP servers stay in the official registry under your old name. Whoever registers that name next can publish new versions of them, and the registry can't tell it isn't you. On 2 November I checked every io.github name in the registry against GitHub. At least 111 names that published servers have since been renamed or deleted, and don't belong to any account now. Between them: - 145 live servers are still listed - 17,000 or so times a week, their npm and PyPI packages get downloaded - 49 of them only have a remote URL, so there's no package to check at all They're not toys, either. Here are the busiest: !Weekly downloads of the busiest servers under unclaimed names Weekly downloads of the busiest servers under unclaimed names (the data) _These are npm and PyPI downloads over the last week, as of 2 November. Each one is listed in the registry under a GitHub name that nobody holds now._ aiterm-mcp lets your coding agent drive terminals, agents-fleet reaches every machine you've got over SSH, the Shodan and VirusTotal ones run with your API keys for those services, and azure-board-mcp wants a personal access token. Further down the list, four more handle crypto wallets, payments or transactions. Why it works The registry hands out io.github.<login>/* based on your GitHub login, not your account's ID, which is the bit that never changes (github_at.go): permissions := h.buildPermissions(user.Login, orgs) When you rename, GitHub's docs say your old name becomes available for anyone else to claim. Whoever claims it gets your namespace, and from there: - a new version is always active, and the highest one becomes latest - deprecating your old entry doesn't stop it (a maintainer said as much in October) - the package check only asks for a package that carries the server's name, and it doesn't have to be the one you published - a remote server has no package to check, so its next version can point at any URL What a bad version gets A local MCP server runs as you, with whatever keys you put in its config. A remote one sees every tool call your agent makes to it, and gets to write the answers. If you installed the npm or PyPI package directly, a new registry version doesn't touch you. It's the clients and galleries that pick servers from the registry by name that would follow it, and the registry tells aggregators to copy it and keep their own copy. Names are already changing hands 22 namespaces in the registry now belong to a GitHub account that was created after they were published. io.github.facebook is one, since Meta became meta on 28 October. So far most of them look like owners holding on to their old name, and none has published anything. The other half has bitten for real. About twenty publishers have filed tickets since April because they're locked out of their own old entries, like PaidSync, whose old listing kept advertising an address they'd switched off until a maintainer stepped in. If you've renamed Register your old name again today. An empty org costs nothing. Once you hold the name, nobody else can publish there, and you can deprecate the old entries yourself. If you install MCP servers, pin the package and version rather than trusting the registry name, and treat a change of package or URL between versions like a new server.
Kiro の初期セットアップ後によく聞かれる設定をまとめる【IT 管理者編】 はじめに こんにちは!AWS でソリューションアーキテクトをしている町田です。 Kiro の導入支援をしていると、利用者の方からの相談と並んで、IT 担当の方から次のような相談を受けます。 「社員の PC にまとめて設定を配りたい」 「利用者が自分で許可しても、組織として止めたいものは止めたい」 「使える MCP サーバーやモデルを、会社で決めたものに絞りたい」 「社用のサインイン方式だけを使わせたい」 本記事では、これらに答える Kiro の設定を説明します。対象は Kiro IDE と Kiro CLI(V3)で、Windows の PC に配る前提です。記事は 2 本に分けました。 記事 対象 内容 利用者編 Kiro を使う方 日本語化、Python、確認のボタン、steering、skills、MCP、モデル IT 管理者編(この記事) 社員の PC に設定を配る IT 担当の方 managed-settings.json、使える MCP やモデルの制限、チームへの配り方 1 章の「やってみる」では、利用者編の 1・2・5 章で許可した操作を、管理者の設定で止めて確かめます。先に利用者編の手順を試しておくと、違いが分かりやすくなります。 ! 個人の見解であり、所属する組織の公式見解ではありません。 本記事の内容は 2026-11-01 時点 の公式ドキュメントに基づきます。Kiro は更新が速いため、最新の公式ドキュメントもあわせて確認してください。 先に結論 組織として止めたい操作は、managed-settings.json の deny と ask に書きます。 利用者の設定より優先されます。allow は書けないので、確認を減らすのは利用者の設定の役目です(1 章)。 managed-settings.json は「うっかり防止」です。 コマンドの禁止は書き方を変えると通り抜け、ローカル管理者の権限がある利用者はファイルを消せます。確実に止めたいことは、PC の管理やネットワークで止めます(1 章)。 managed-settings.json は配る前にテスト端末で 1 回起動します。 壊れているとすべてのツールが止まります(1 章)。 使える MCP サーバーは MCP レジストリで、使えるモデルは承認リストで絞ります。 どちらも Kiro コンソールの設定で、IAM Identity Center などでサインインした利用者に効きます(2・3 章)。 permissions は、作業フォルダに置いてもチームには配れません。 作業フォルダ単位の許可は、フォルダの外に保存されるためです。そろえたいなら PC に配るか、managed-settings.json を使います(4 章)。 章 内容 前提 設定ファイルの置き場所と、グローバルと作業フォルダの両方に置いたときの動き 1. managed-settings.json 端末に組織の決まりを配る 2. MCP レジストリ 使える MCP サーバーを絞る 3. モデルの承認リスト 使えるモデルを絞る 4. チームに配る 何をどこに置けばチームに行き渡るか 前提:設定ファイルの置き場所と読み込まれ方 Kiro の設定には、グローバル(~/.kiro/、自分の全プロジェクト)、プロジェクト(<作業フォルダ>/.kiro/)、エージェント(カスタムエージェントの定義)の 3 つのスコープがあります(Configuration scopes)。 設定 グローバル プロジェクト 同じものが複数あるとき steering ~/.kiro/steering/ .kiro/steering/ 全部読み込む(矛盾したらプロジェクト優先) skills ~/.kiro/skills/ .kiro/skills/ 全部読み込む(同じ名前ならプロジェクト優先) permissions ~/.kiro/settings/permissions.yaml ~/.kiro/workspace-roots/<hash>/permissions.yaml(作業フォルダの外) deny が勝つ MCP ~/.kiro/settings/mcp.json .kiro/settings/mcp.json エージェント > プロジェクト > グローバル カスタムエージェント ~/.kiro/agents/ .kiro/agents/ 同じ名前ならプロジェクト優先 CLI の設定 ~/.kiro/settings/cli.json .kiro/settings/cli.json(モデルごとの既定値など、一部の設定だけ) プロジェクト優先 CLI V3 では、/config でいまのセッションに効いている steering・skills・MCP・エージェント・Powers・Hooks の一覧を確認できます。 /config # 一覧 /config steering # steering だけ ワークスペースの信頼 作業フォルダの .kiro\ に置いた設定は、そのフォルダを信頼するまで読み込まれません(Permissions > Workspace trust)。人からもらったフォルダや、インターネットから取ってきたフォルダに、AI の振る舞いを変える設定が仕込まれているのを防ぐためです。 信頼していないワークスペースでは、Kiro は次のように動きます。 .kiro/ 配下のカスタムエージェント・steering・MCP の設定・skills を読み込まない allow の規則があっても、シェルコマンドと MCP ツールを毎回確認する .kiro/steering や .kiro/skills への書き込みを確認する 信頼したかどうかはフォルダの外に保存されるので、フォルダの中身が自分で自分を信頼させることはできません。 CLI V3 では、フォルダで初めて起動したときに Trust this workspace? の画面が出ます。ただし、--no-interactive で動かすときや、V1 / V2 では、この確認は出ずに作業フォルダの設定が読み込まれます。 グローバルとワークスペースの違い グローバル(~.kiro\)と作業フォルダ(ワークスペース)の両方に同じ種類の設定を置いたとき、どちらが効くかを、公式ドキュメントから読み取ってまとめました。筆者は実機では試していません。 読み飛ばしても、各章の手順はそのままできます。 # 設定の置き方 ドキュメントから読み取れる動き 出典 A グローバルに steering を置く どの作業フォルダでも読み込まれる Steering B グローバルと作業フォルダの両方に、矛盾するルールの steering を置く 上書きではなく両方読み込まれ、矛盾したところは作業フォルダの方が優先される Steering、Configuration C 同じ名前の skill を、グローバルと作業フォルダの両方に置く 作業フォルダの skill が使われる Skills D C の作業フォルダの外で、同じ skill を呼ぶ グローバルの skill が使われる。作業フォルダの skill は、そのフォルダでだけ使われる Skills E 同じ名前の MCP サーバーを、グローバルでは有効、作業フォルダでは無効にする そのフォルダでは無効になる。作業フォルダの設定が優先される MCP configuration F グローバルの permissions で、あるコマンドを確認なしにする どの作業フォルダでも確認なしで実行される(信頼していないフォルダを除く。H を参照) Permissions G 確認ダイアログで Always allow → This workspace を選ぶ そのフォルダでだけ確認が出なくなる。許可は作業フォルダの外(~.kiro\workspace-roots\)に保存される Permissions H 作業フォルダを 信頼せずに 開く 作業フォルダの .kiro\ の steering・skills・MCP・カスタムエージェントは読み込まれない。シェルコマンドと MCP ツールは、グローバルで確認なしにしていても毎回確認される Workspace trust I steering を読み込む設定のないカスタムエージェントを使う steering は自動では読み込まれない Steering 1. managed-settings.json:端末に組織の設定を配る よくある質問:「利用者が勝手に allow を書いても、組織として止めたいものは止めたい」「PC に一括で配りたい」 managed-settings.json は、管理者が端末に置く設定ファイルです。IDE と CLI が同じファイルを読みます(Permission policies、Sign-in controls)。 OS 置き場所 配り方 Windows C:\ProgramData\Kiro\managed-settings.json Intune、Group Policy、SCCM macOS /Library/Application Support/Kiro/managed-settings.json Jamf、Kandji Linux /etc/kiro/managed-settings.json 構成管理ツール いずれも管理者・root 権限がないと書き換えられない場所です。 まずやること:利用者編で許可してきた操作を、管理者の設定で止めてみます。管理者の設定が利用者の設定より優先されることを確かめてから、配る内容を決めます。 やってみる 利用者編で許可したことを、管理者の設定で止めてみる 利用者編では、利用者の設定で、いくつかの操作を許可しました。IT 管理者が managed-settings.json で止めると、それらがどうなるかを確かめます。次の 4 つの決まりだけを書いた managed-settings.json を使います。 利用者編 利用者の設定で許可したこと 管理者の決まり 試す頼み方 こうなれば OK 2 章 drafts フォルダへの保存を、確認なしにした drafts フォルダへの保存を禁止 試し ① 保存できない 1 章(Python を入れる) Kiro が Python の公式ドキュメントを読む 業務で使うドメイン以外の Web の取得を禁止 試し ② ページを読めない 5 章 AWS のドキュメントを調べる MCP MCP のツールは毎回確認 試し ③ 使うたびに確認が出る 1 章 ライブラリのインストール(確認を見て Allow) pip install などのインストールのコマンドを禁止 試し ④ 止まる。ただし、コマンドの書き方によっては通る(下の試し ④) 中身は次のとおりです。 { "rules": [ { "capability": "shell", "match": [ "python -m pip install*", "py -m pip install*", "pip install*" ], "effect": "deny" }, { "capability": "fs_write", "match": [ "/drafts/" ], "effect": "deny" }, { "capability": "web_fetch", "exclude": [ "docs.aws.amazon.com", "*.amazonaws.com", "kiro.dev" ], "effect": "deny" }, { "capability": "mcp", "effect": "ask" } ] } 手順 先に、利用者編の 1・2・5 章の手順で、表の「利用者の設定で許可したこと」が動くことを確かめておきます エクスプローラーのアドレス欄に C:\ProgramData と入力して開きます。Kiro フォルダがなければ作ります メモ帳を開いて上の内容を貼り付け、名前を付けて保存 で、ファイル名を managed-settings.json、ファイルの種類を すべてのファイル、エンコードを UTF-8 にして、いったんデスクトップに保存します。保存したファイルを Kiro フォルダにコピーし、管理者の確認が出たら 続行 を押します。ファイル名は managed-settings.json にしてください。 ほかの名前だと Kiro は読み込みません。書き間違えがあると、Kiro のツールがすべて止まるので、中身は書き換えないでください 置けたかどうかは、PowerShell で次の 2 行を実行して確かめます。1 行目に managed-settings.json が表示され、2 行目でエラーが出なければ OK です Get-ChildItem "$env:ProgramData\Kiro" Get-Content "$env:ProgramData\Kiro\managed-settings.json" -Raw | ConvertFrom-Json Kiro のウィンドウをすべて閉じてから、開き直します Kiro で、利用者編の 2 章でルールを置いた作業フォルダを開き、次の 4 つの文を送ります。前の答えに引きずられないように、1 つ送るごとに新しいチャットを開きます 試し ①:確認なしにした保存が、できなくなるか(利用者編 2 章) drafts フォルダに test.txt というファイルを作って、中に「テスト」とだけ書いて。 利用者編の 2 章で「確認なしで保存してよい」にした drafts フォルダなのに、保存が止められれば成功です。Kiro がコマンドなど別の方法で書こうとしたら、確認のボタンで Deny を押します。 試し ②:Web のページが読めなくなるか(利用者編 1 章の Python を入れる手順) Web 検索は使わずに、次のページを直接読んで、要点を 3 行で教えて。 docs.python.org ツールの欄に Blocked by administration permission policy(管理者の決まりで止められた)と出て、ページを読めなかった理由が返ってくれば成功です。筆者の環境では、Kiro は Web 検索やコマンドで回り道をせず、「制限を外す必要があれば、管理者に相談してください」と答えました。 試し ③:MCP のツールに確認が出るか(利用者編 5 章) AWS Knowledge MCP Server を使って、S3 のバケットの名前の付け方のルールを調べて。 MCP のツールを使うたびに確認のボタンが出れば成功です。利用者編の 5 章で、MCP のツールを確認なしにしていた場合も、確認が出ます。 試し ④:ライブラリのインストールを止められるか(利用者編 1 章) python-pptx を、インストールし直して。すでに入っていても、もう一度入れて。 ツールの欄に Blocked by administration permission policy と出て、インストールが実行されなければ成功です。筆者の環境で 2 回試すと、2 回とも Kiro は python -m pip install ... を実行しようとして、managed-settings.json の禁止の決まりで止められました。Kiro も「組織が決めた制限なので、別の書き方で回り込むことはしていません」と答えています。 ただし、書き方が違うと止まりません。 利用者編の 1 章で python という名前を使えるようにする前に同じことを試したときは、Kiro は python が使えないと分かると、& "<Python の場所>\python.exe" -m pip install ... のように Python の場所を直接書く書き方に切り替えました。このコマンドはどの禁止の決まりにも当たらず、インストールが実行されました。 ! コマンドの禁止で、インストールを止めきるのは難しいと考えてください。 今回止まったのは、Kiro がたまたま禁止した書き方を選んだからです。コマンドの決まりは、コマンドの先頭からの文字列で照らし合わせます。インストールのコマンドは、pip、python -m pip、py -m pip、Python の場所を直接書く形、uv、winget、スクリプトの中に書く形など、書き方がいくらでもあります。禁止する書き方を増やしても、Kiro が動く書き方を探して別の書き方を選ぶと、そのまま通ります。Kiro が禁止をすり抜けようとしたわけではなく、動く書き方を選んだだけでも、こうなります。 インストールを確実に止めたいなら、コマンドの禁止ではなく、PC の管理(ローカル管理者の権限を外す、配るソフトを決める)やネットワーク(pypi.org などへの通信を止める)で止めます(「知っておくとよいこと」の最後)。 Kiro の設定にも、コマンドの書き方に左右されない方法が 1 つあります。Local command sandbox(Kiro IDE 1.3 以降、Kiro CLI 2.31 以降の V3)で networkAccess を DEFAULT_BLOCKED にすると、AI が実行するコマンドから外への通信が止まるので、どの書き方の pip install もダウンロードできなくなります(筆者は実機では試していません)。ただし、sandbox は既定でオフで、設定ファイル(sandbox.json)は利用者が自分で書くものです。利用者は自分でオフにでき、managed-settings.json で sandbox をオンに固定する方法もドキュメントには見当たらないので、組織として止める手段にはなりません。 ! C:\ProgramData\Kiro\managed-settings.json がすでにある PC では、この実験をしないでください。 会社が配っている設定なので、上書きすると会社の決まりが外れます。テスト用の PC で試します。 結果から分かるのは、次の 3 つです。 管理者の決まりは、利用者の設定より優先されます。 Kiro は、いちばん厳しい決まりを採るためです(Permissions)。利用者が「確認なしで進める」にしていても、管理者が「毎回確認」「禁止」にした操作はそのとおりになります コマンドの禁止は、書き方を変えると通り抜けます。 試し ④ のとおり、禁止した書き方なら止まりますが、別の書き方では実行されることがあります。コマンドの禁止は「うっかり防止」と考え、確実に止めたいことは PC の管理やネットワークで止めます 管理者の設定でできるのは、止めることと、確認させることだけです。 管理者の決まりに「確認なしで進める」(allow)は書けません(Permission policies)。確認を減らすのは、利用者の設定(利用者編 2 章)の役目です 片付ける:試し終わったら、C:\ProgramData\Kiro\managed-settings.json を削除して、Kiro を開き直します。残したままだと、drafts への保存、Python の公式ドキュメントの読み込み、pip install で始まるインストールが止まったままになります。 実際に配る内容は、「知っておくとよいこと」の 3 段階のテンプレートから選びます。 知っておくとよいこと(必要になったら読む) managed-settings.json に任せること、任せないこと managed-settings.json は、うっかりや事故を減らすための仕組み と考えます。コマンドの禁止は書き方を変えると通り抜けますし(「やってみる」の結果)、ローカル管理者の権限がある利用者はファイルを消して外せます。確実に止めたいことは、PC の管理やネットワークの側で止めます。 やりたいこと 向いている仕組み 理由 認証情報や機密フォルダを AI に読ませない・書き換えさせない managed-settings.json(fs_read / fs_write の deny) コマンドの書き方ではなく、ファイルの場所で照らし合わせる。ただし、AI がコマンドでファイルを読み書きする場合は、shell の規則で判定される 業務と関係ない Web のページを読ませない managed-settings.json(web_fetch の deny) 「やってみる」の試し ② で止まることを確かめた MCP のツールを使うたびに確認させる managed-settings.json(mcp の ask) すべての MCP に効く 社用のサインイン方式だけにする managed-settings.json(signin_method) サインインの画面で選べる方式を絞れる。ファイルを置いていない PC と Kiro Web には効かない(Sign-in controls) 取り返しのつかないコマンドを止める managed-settings.json(shell の deny) うっかり防止として使う。書き方を変えると通り抜ける ソフトやライブラリのインストールを止める PC の管理、ネットワーク コマンドの禁止はすり抜ける。Local command sandbox で外への通信を止める方法もあるが、利用者がオフにできる(「やってみる」の試し ④、下の「自分の PC に入れたくないとき」) managed-settings.json を消させない 利用者からローカル管理者の権限を外す これがないと、ほかの決まりもまとめて外せてしまう 書けるもの 部分 中身 対応バージョン rules(permissions) deny と ask だけ。allow は書けない — rules(signin_method) 使わせないサインイン方式 IDE 1.2 以降、CLI 2.25.0 以降 settings IAM Identity Center の Start URL とリージョン、外部 IdP の情報、ヘルプページの URL 同上 3 段階のテンプレート 最初から厳しくすると利用者が使いにくさを感じて個人アカウントや別のツールに流れやすいので、① から始めて、運用しながら ② → ③ へ絞る 進め方をお勧めします。 段階 読み取り(fs_read) コマンド(shell) Web サインイン ① まず始める 認証情報のファイルを deny 取り返しのつかないコマンドを deny 制限しない 社用の方式だけ ② 標準 ① に加えて、社内の機密フォルダを deny ① に加えて、外にデータが出るコマンドを ask 業務のドメイン以外を deny 同上 ③ 厳格 ② と同じ すべて ask(① の deny はそのまま) 取得・検索とも deny 同上 ① まず始める(このまま全員に配る) 認証情報のファイルの読み書きと、取り返しのつかない操作だけを止めます。業務を妨げずに事故を防ぐ組み合わせです。 { "rules": [ { "capability": "fs_read", "match": ["/.env", "/.env.", "/.pem", "/*.key", "/.aws/", "/.ssh/"], "effect": "deny" }, { "capability": "fs_write", "match": ["/.env", "/.env.*", "/.aws/", "/.ssh/"], "effect": "deny" }, { "capability": "shell", "match": [ "rm -rf *", "Remove-Item -Recurse", "rmdir /s*", "rd /s*", "del /s*", "git push --force*", "git push -f*", "sudo *" ], "effect": "deny" }, { "capability": "signin_method", "match": ["*"], "exclude": ["idc"], "effect": "deny" } ], "settings": { "idc_start_url": "my-org.awsapps.com", "idc_region": "ap-northeast-1", "signin_help_url": "it.example.com" } } 規則 狙い fs_read / fs_write の deny .env、秘密鍵、AWS の認証情報、SSH の鍵を AI に読ませない・書き換えさせない shell の deny フォルダごとの削除(macOS / Linux の rm -rf、PowerShell の Remove-Item -Recurse、コマンドプロンプトの rmdir /s・rd /s・del /s)、リモートの履歴を壊す強制 push、管理者権限での実行(sudo。Windows 11 の sudo も同じ名前)を止める。* は空白を含む任意の文字列に一致するので、Remove-Item -Recurse は -Recurse がどの位置にあっても当たる。ただし、-r のような短縮形や、rm・ri などの別名で書かれると当たらない signin_method 社用の IAM Identity Center 以外でのサインインを止める。個人の GitHub などで入ると、MCP の統制が効かず、内容がサービス改善に使われる可能性があるため。外部 IdP に直接つないでいる場合は exclude を external_idp にする ② 標準(① の rules に追加する) 機密フォルダの読み取りを止め、外にデータが出る操作は人が確認してから実行します。次の 3 行は、① の rules の [ ] の中に足す部分です。① の最後の規則(signin_method の行)の後ろにカンマを付けてから貼り付けます。このままでは 1 つのファイルとしては読み込めません。 { "capability": "fs_read", "match": ["/confidential/", "/人事/"], "effect": "deny" }, { "capability": "shell", "match": ["git push*", "curl *", "wget *", "aws *"], "effect": "ask" }, { "capability": "web_fetch", "exclude": ["docs.aws.amazon.com", ".amazonaws.com", "kiro.dev", ".github.com"], "effect": "deny" } 機密フォルダの名前(confidential・人事)は、実際のフォルダ構成(共有ドライブのパス、プロジェクト名など)に置き換えます web_fetch の exclude に書いたドメインは、管理者の規則の対象外になり、利用者の設定に従います。業務で使う公式ドキュメントのドメインを入れます mcp を ask にしたい場合もこの段階で足します({ "capability": "mcp", "effect": "ask" }) ③ 厳格(② の rules に追加する) 機密案件や閉域環境など、AI が勝手に外へ出ることを一切認めない場合の設定です。② と同じく、rules の [ ] の中に足す部分です。 { "capability": "shell", "match": ["*"], "effect": "ask" }, { "capability": "web_fetch", "effect": "deny" }, { "capability": "web_search", "effect": "deny" } すべてのシェルコマンドが確認になります。① で deny にしたコマンドは、deny が ask より強いので deny のままです ② の web_fetch の exclude 付きの規則は残っていても構いませんが、③ の deny が勝つため、すべての Web 取得が止まります ! shell の規則は、コマンドの先頭からの文字列の一致で判定します。;・&&・|・|| でつないだコマンドは分割して判定されます。Kiro IDE 1.4.51 以降と Kiro CLI V3 では、規則と照らし合わせられないほど複雑な bash・PowerShell のコマンドは止められ、AI に書き直しが求められます。cmd.exe のコマンドは確認が出ます(Permissions > Shell command parsing)。それでも、試し ④ のように、別の書き方のコマンドは通ることがあります。外部への持ち出しを確実に止めたいなら、社内の Proxy・FW の許可リストを重ねてください。 配る前のチェックリスト managed-settings.json は、壊れているとすべてのツールが止まります。 配る前にテスト端末で次を確認してください。 UTF-8(BOM なし)で保存した(Windows PowerShell の Out-File は既定で UTF-16 を書くので使わない) JSON として正しい(末尾のカンマやコメントがない) effect に allow が入っていない(1 つでもあるとファイル全体が無効) フィールド名のつづりが正しい(effct のような未知のフィールドがあると全ツールが拒否される) shell・web_fetch・mcp のパターンに ** や ? を使っていない サインイン方式の名前が idc・external_idp・builder_id・google・github・social のどれかになっている(画面の表示名は使えない) Kiro を再起動して、IDE の通知や kiro-cli login の Warning: 行が出ない 標準ユーザーでファイルを保存しようとして、拒否される ! 壊れたときの倒れ方が、規則の種類で逆です。 permissions の規則:壊れていると 全ツールを拒否(安全側) サインイン方式の規則:壊れていると 全方式を許可(利用者を締め出さないため) 同じファイルに両方を書いていると、JSON が壊れたとき「サインインは何でも通るが、ツールは全部止まる」状態になります。 ! managed-settings.json もクライアント側で強制されます。 端末のローカル管理者権限を持つ利用者は、ファイルを消して回避できます。利用者にローカル管理者権限を渡していない環境で使う前提の仕組みです。 managed-settings.json に入らないもの 設定 配り方 自動更新の向き先・停止(IDE は UpdateUrl・UpdateMode、CLI は update.baseUrl など) OS のポリシー(macOS は IDE が dev.kiro.desktop、CLI が dev.kiro.cli ドメイン)。Managed updates steering・skills・利用者の permissions ~/.kiro/ 配下にファイルとして配る(利用者が消せる) モデルの承認リスト・MCP・Web ツール・API キー Kiro コンソール 自分の PC に入れたくないとき:仮想デスクトップで使う Kiro や Python、ライブラリを自分の PC に入れたくない場合は、Amazon WorkSpaces のような仮想デスクトップで Kiro を使う方法があります。Kiro が入れるソフトやライブラリは仮想デスクトップの中に入るので、自分の PC には何も入りません。 仮想デスクトップは管理者が用意して配るので、何が入っているかも管理者がまとめて把握できます。 WorkSpaces にしても、WorkSpaces の中でのインストールは止まりません。 WorkSpaces Personal は、既定で利用者がローカル管理者です。また、利用者編の 1 章のライブラリのように、自分のユーザーフォルダに入れるインストールは、管理者の権限がなくてもできます。WorkSpaces の中で入れられるものも絞りたい場合は、次の 3 つを組み合わせます。 やること 効き方 ローカル管理者の権限を外す(ディレクトリの Local administrator setting) 管理者の権限が要るインストールと、managed-settings.json の削除を防ぐ。変更は、新しく作る WorkSpace と、作り直した WorkSpace に反映される。この設定を使えるのは、AWS Directory Service(AD Connector、AWS Managed Microsoft AD、Simple AD)で管理しているディレクトリだけ(Manage local administrator permissions) 外への通信を絞る(セキュリティグループ、AWS Network Firewall) pypi.org などへの通信を止めるので、ユーザーフォルダへのインストールも、コマンドの書き方に関係なく失敗する。セキュリティグループは、既定ではすべての外向きの通信を許可している(How do I restrict internet access on WorkSpaces Personal?) 必要なものを入れたイメージを配る Kiro、Python、ライブラリを最初から入れておき、利用者には入れさせない(How to secure your Amazon WorkSpaces for external users) 外への通信を絞るときは、Kiro のサインインやモデルの呼び出しに必要な行き先は通します。必要な行き先は、構成を決める前に公式ドキュメントで確認してください pypi.org を止めた環境では、利用者編の 1 章の「PowerPoint・Excel・Word 用のライブラリを入れる」の手順は使えません。Python とライブラリは、管理者がイメージに入れておきます 2. MCP レジストリ:使える MCP サーバーを絞る 管理者は Kiro コンソールで、MCP 自体を無効にするか、許可するサーバーの一覧(MCP レジストリ)を登録できます(Kiro Enterprise > MCP)。 MCP レジストリ URL に入れるもの Kiro コンソールの Settings > Shared settings > MCP Registry URL(日本語の画面では セッティング > 共有設定の詳細 > MCP レジストリ URL - オプション)には、許可するサーバーの一覧を書いた JSON ファイルの URL を 1 つ入れます。サーバーの一覧そのものを欄に書くのではありません。 mcp-registry.example.co.jp JSON ファイルも、それを置く場所も、管理者が自前で用意します。 Kiro は置き場所を用意しません。ドキュメントには、Kiro コンソールに JSON を直接アップロードする方法も書かれていません。置き場所には、次の 2 つの条件があります。 有効な証明書の HTTPS であること(自己署名の証明書は使えません) 利用者の PC から読めること(社内ネットワークの中だけで見える URL でも構いません) ドキュメントには、置き場所の例として Amazon S3・Apache・nginx が挙がっています。社内に条件を満たす Web サーバーがあれば、そこに置けます。ない場合は、AWS 上に S3 と CloudFront を自前で用意して配信します。S3 のバケットは公開せず、CloudFront の Origin Access Control 経由で読ませます。 JSON ファイルの書き方 AWS Knowledge MCP Server だけを許可する例です。書き方は、MCP の公式の登録簿(MCP Registry)の形式の一部です。 { "servers": [ { "server": { "name": "aws-knowledge-mcp-server", "title": "AWS Knowledge", "description": "AWS の公式ドキュメントやブログを検索する", "version": "1.0.0", "remotes": [ { "type": "streamable-http", "url": "knowledge-mcp.global.api.aws" } ] } } ] } 項目 書き方 name ファイルの中で重複しない名前。3〜200 文字の英数字と ._- description 説明。100 文字まで version 1.0.0 のような番号。^1.2.3 のような範囲の書き方は使えない remotes インターネットや社内のサーバーにつなぐ場合。1 件だけ。type は streamable-http か sse packages PC の上でプログラムを動かす場合。1 件だけ。registryType は npm・pypi・oci のどれか。それぞれ npx・uvx・docker で起動するので、利用者の PC にあらかじめ入れておく サーバーを足すときは、servers に 1 件を加えて、同じ URL のファイルを上書きします。Kiro が読み直すので、コンソールの設定を変える必要はありません。 登録するとどう動くか 一覧にないサーバーは、表示されず、起動もしません。 利用者が自分の mcp.json に書いたサーバーも同じです。IDE の MCP パネルには「N servers hidden」とだけ出ます(MCP registry) 利用者は、MCP パネルに出る一覧からサーバーを選んで追加します。 CLI では /mcp add で選びます CLI V3 では、mcp.json の書き方も変わります。 レジストリのサーバーは、"aws-knowledge-mcp-server": { "type": "registry" } のように "type": "registry" と書いた項目だけが起動します。利用者編の 5 章のように url や command を自分で書いた項目は、名前が一覧と同じでも読み飛ばされます(MCP registry > Servers not in the registry) Kiro は、起動したときと 24 時間ごとに一覧を読み直します。 一覧から外したサーバーは、利用者の PC でも止まります 効くのは、IAM Identity Center か API キーでサインインした利用者だけです。 Builder ID やソーシャルログインの利用者には効きません 制限は利用者の PC の側でかけています。 PC の管理者の権限を持つ人は外せます 登録するときに気をつけること JSON ファイルには、トークンなどの秘密を書きません。 URL を知っている人なら読めるファイルにすることが多いためです。利用者は自分の mcp.json で、HTTP ヘッダーや環境変数を足して上書きできるので、トークンはそちらに書いてもらいます URL を登録した時点で、一覧にないサーバーはすべて止まります。 利用者がいま使っているサーバーを先に調べて、一覧に入れておきます 3. モデルの承認リスト:使えるモデルを絞る 管理者は Kiro コンソールの Settings > Shared settings > Model availability(日本語の画面では セッティング > 共有設定の詳細 > モデルの可用性)で Control which models are available to users(ユーザーが利用できるモデルを制御)をオンにし、Manage approved list で承認リストを作ります。組織の既定のモデルも、ここで指定できます(Kiro Enterprise > Models)。押さえておくことは次の 4 つです。 承認リストは プロファイルの共有設定の 1 項目 です。コンソールの画面には、利用者やグループを選ぶ項目はありません(筆者の画面で確認) 承認リストを使うと、新しいモデルは管理者が選んで保存するまで使えません。 新しいモデルが出るとコンソールに通知が届くので、使わせたいものを選んで保存します。承認リストを使わない(オフの)間は、新しいモデルは自動で使えるようになります(Fable 5.1 と Kimi K3 を除く) Auto を許可すると、承認していないモデルに処理が回ることがあります。 承認したモデルだけで処理したいなら、Auto を外して既定のモデルを指定します(Available models) experimental のモデルと Kimi K3 は、出力に対する補償の対象外 です(Kiro Excluded Models) 4. チームに配る:何をどこに置けばよいか 「何をどこに置けばチームに行き渡るか」をまとめます。 設定 作業フォルダごと共有 IT 管理者が ~/.kiro/ に配る managed-settings.json Kiro コンソール steering ◎ プロジェクトの規約 〇 全社の規約(消せる) — — skills ◎ プロジェクトの手順 〇 全社の手順(消せる) — — permissions の allow ✕ 置けない 〇 確認を減らす既定値 ✕ 書けない — permissions の deny / ask ✕ 置けない △ 利用者が消せる ◎ 消せない(ローカル管理者以外) — MCP サーバーの定義 ◎ .kiro/settings/mcp.json 〇 — — 使える MCP の制限 — — — ◎ レジストリ カスタムエージェント ◎ .kiro/agents/ 〇 — — 既定のモデル・effort △ エージェントの model、.kiro/settings/cli.json(CLI のみ) 〇 ~/.kiro/settings/cli.json(CLI のみ) — 〇 組織の既定のモデル 使えるモデルの制限 — — — ◎ 承認リスト サインイン方式 — — ◎ 〇 デバイスフロー(CLI) steering は、次の 2 つの方法で配れます。 方法 対象 置き場所 作業フォルダごと共有する(共有ドライブ、Git など) そのフォルダを使う全員 作業フォルダの .kiro\steering\ IT 管理者が PC に配る 配った先のユーザーの全部の作業 そのユーザーの ~.kiro\steering\(公式に「Team steering」として記載) よくある質問 Q. 利用者ごとに使えるモデルを変えたい Kiro の承認リストは、プロファイルの共有設定の 1 項目です。コンソールの画面には利用者やグループを選ぶ項目がなく、利用者ごとに分ける方法は見当たりません(3 章)。用途ごとの既定はカスタムエージェントの model で決められますが、利用者はモデルを切り替えられます。 Q. Auto を選んだとき、どのモデルが使われたかを知りたい 公式の手段はありません(2026-11-01 時点)。 プロンプトログの項目には、モデル名はありません クレジットは Auto の倍率(1.0 倍)で数えられるので、消費量から推測することもできません 回答ごとにモデルを表示してほしいという要望は、公式の GitHub に Issue として上がっています(kirodotdev/Kiro#8903) 公式に書かれているのは、Auto が振り分ける範囲だけです。Auto は、そのプロファイルで使える Active のモデルに振り分け、experimental(試験提供)のモデルには振り分けません。管理者の承認リストの外のモデルに振り分けることもあります(Available models)。 どのモデルで処理したかを把握したいなら、Auto ではなくモデルを指定して使ってください。 組織として必要なら、管理者が承認リストから Auto を外し、承認済みのモデルを既定にします。こうすれば、管理者のユーザー単位のアクティビティレポートにあるモデルごとのメッセージ数の列が、そのまま「使ったモデル」になります(Viewing per-user activity)。 まとめ 組織として止めたい操作は managed-settings.json の deny と ask に書く。 allow は書けないので、確認を減らすのは利用者の設定に任せる managed-settings.json は「うっかり防止」と考える。 確実に止めたいことは、ローカル管理者の権限を外す、通信を絞るなど、PC の管理やネットワークで止める managed-settings.json は配る前にテスト端末で 1 回起動する。 壊れると全ツールが止まる 使える MCP サーバーはレジストリ、使えるモデルは承認リストで絞る。 Auto を許可すると、承認していないモデルに処理が回ることがある チームでそろえる設定は、置き場所で配り方が変わる。 steering・skills は作業フォルダごと共有するか PC に配り、permissions は PC に配るか managed-settings.json に書く 利用者の方には、利用者編を案内してください。管理者の設定で止めている操作があると「許可したのに動かない」と問い合わせが来ることがあるので、何を止めているかを社内で共有しておくと、問い合わせが減ります。
"Our MCP directory said \"active\". The publisher had deprecated it five days earlier." I run ProtocolProbe, a directory of facts about every server in the official MCP Registry: each server gets a page, read from public records (the registry, npm, PyPI, GitHub), with a date on every fact. A publisher emailed us. They had renamed their server, marked the old registry entry as deprecated, and added a message pointing to the new name. Our page for the old entry still said active. I told them it would update on our next daily run. That was wrong. This post is about why, and what we changed. The bug Our daily job pulls the full registry list, compares it with yesterday's, and loads what changed: new servers, new versions, removed servers. For those, it re-reads the package and repository facts the same day. The publisher's change was none of those. The registry lets a publisher change an entry's status (active → deprecated) and its status message without publishing a new version. Same version, different status. Our job noticed it: it counted it as a "metadata-only" change. It just never loaded it. The page kept the old status, and the run still finished with ok. How big was it? One report can be a coincidence, so we compared every stored record with the registry list of that day, field by field (read-only, 51,834 servers): | | mismatches | |---|---| | servers missing / extra | 0 / 0 | | version | 0 | | status (stored active, registry deprecated) | 53 | | status message | 60 | So the parts the job was built for were exact, and the gap was precisely the case it skipped: entries deprecated by their publishers that we still showed as active. Our own record check "deprecated in the registry" gave the wrong answer for all of them. Fix 1: reconcile what the registry owns, every day The registry is the source of truth for a few fields: status, status message, published and updated dates. Now, after every daily load, the job compares those fields for all servers with the registry list and patches the ones that differ at the same version: - a backup of every value before the first write, and a journal, so a crash mid-way resumes instead of half-patching; - every patched document read back; a mismatch rolls the whole step back; - a cap of 2,000 patches a day (a registry-wide change needs a person to look); - each change recorded publicly with its date, so anyone can see what changed and when. First run: 59 servers patched (some had changed since the count above). A second comparison right after: 0 mismatches across 52,138 servers. Fix 2: a job that says "ok" must check the page The deeper problem was not the missing field. It was that the job reported success without looking at what visitors see. So the job now ends with a live check: after the pages are refreshed, it takes a small fixed sample of the day's changed servers (new, new version, status patched) and opens their public pages. We first tried reading our own public API instead. It was the wrong thing to check, for two reasons we found by reading our code: 1. The API is not what was stale. The page was. 2. The API answer is cached at the CDN and inside each function instance, and the instance copy comes back without a cache header. A check right after a load can read an old copy and raise a false alarm. Each server page now carries a small machine-readable marker in its head: version, status, a short hash of the status message (never the message itself), and the snapshot date. The job compares that with what it loaded. - A mismatch is a warning, never a rollback. - Our own failures (rate limits, timeouts) are "not checked", never a mismatch. - If half the sample can't be checked, that is a warning too. Fix 3: a date range, not a date While checking this, we found a quieter version of the same problem on our leaderboards. They said "facts checked 2026-10-29". That was the newest date among the records, which meant the servers that changed that day. Most stars and download counts behind the ranking had been read days earlier. The page now shows the range: "facts checked 2026-10-24 to 2026-10-29". How stale does a directory get? We measured it. We re-read the free facts (npm/PyPI, OSV, GitHub) of 1,729 servers that had not changed in the registry, a 1/30 slice of the catalogue. It took about 14 minutes and about 1,200 GitHub API requests. 614 of them (35%) had at least one fact that had changed since our last read: - 396 download counts - 301 last-push dates - 99 star counts - 23 version-pin states - 7 repository states (not found, found again, archived) - 1 newly withdrawn package None of these show up as a registry change. We built a daily rotation that re-reads such a slice every day, so every server is re-read at least monthly. It is switched off until we have watched a few normal runs of the new checks. What I'd tell anyone building on a registry - A listing is a snapshot. The official MCP Registry checks an entry when it is published (each new version), and nothing re-checks it on a schedule. We asked for that upstream: registry issue #1715. If you mirror it, you inherit the same gap. - Same version doesn't mean same record. Compare the fields the source owns, not just the version. - Check the output, not the exit code. Our job said ok for days while pages were wrong. - Show the age of your data honestly. A single date can quietly overstate how fresh it is. - Say when you were wrong. I told the publisher the wrong thing first. Admitting it took one email. Our corrections are logged at protocolprobe.com. Every change the job records is public, by day, at protocolprobe.com, and in a free API with no key (/docs/api). If you run an MCP server and something on its page looks wrong, /operators says how to tell us.
What the official MCP registry looks like when you check it twice I run ProtocolProbe, a free site that records dated facts about every server in the official MCP registry. We pulled the whole registry on 2026-10-04 and again on 2026-10-23, and we have pulled it daily since. This post is about what changed, what the registry itself checks and when, and how to reproduce the counts yourself. No server's tools were called to get any of it: everything below comes from the registry API, npm, PyPI, GitHub and OSV. What changed in 19 days - 2026-10-04: 38,977 latest entries. 2026-10-23: 48,551. (2026-10-24: 49,445.) - Between the two pulls: 9,803 new servers, 5,646 existing servers listing a newer version, and 229 servers no longer listed. The arithmetic closes: 38,977 + 9,803 − 229 = 48,551. - The registry gives no reason for a removal, so we do not guess one. Each removed server's page stays up, marked as removed. The day-by-day record is at protocolprobe.com. What the registry checks, and when This is from reading the registry's source (commit c384c4b, read on 2026-10-06), so treat it as a statement about that code, not about anyone's intent: - At publish time the validators fetch the exact listed npm or PyPI version and check the ownership token. - I found nothing that re-validates an entry after it is published. repository.url is checked with a regular expression only, and the schema requires only name, description and version, so an entry with no package and no remote is allowed. - On 2026-10-26 the registry's roadmap (docs/design/roadmap.md) has no re-validation item. It also says its own phase labels are out of date as of 2026-08-10. I opened issue #1715 asking for periodic re-checks of published entries. What a second look finds (dataset snapshot 2026-10-24) | What we counted | Number | |---|---| | Entries in the snapshot | 49,445 | | Remote only / local package only / both / neither | 30,185 / 16,395 / 2,348 / 517 | | Entries whose linked GitHub repository exists | 29,039 | | Entries whose linked GitHub repository returns 404 | 4,622 (13.7% of the 33,661 that link one) | | Entries with no GitHub link we could check | 15,784 | | Entries marked deprecated in the registry | 620 | One earlier number from the 2026-10-04 snapshot, which we have not re-run on the newer one: of 14,431 npm and PyPI entries, 157 (1.09%) listed a package or version that cannot be installed because it does not exist on npm or PyPI. What these numbers are not: a 404 can mean a deleted, private or moved repository, and we cannot tell which. A count of public records on a date is not a verdict on any server, and we do not rank or grade servers. Reproduce the repository count The dataset is CC BY 4.0, on Zenodo (the DOI of all versions leads to the newest), Hugging Face and Kaggle. Standard library only: import csv, io, urllib.request, collections url = "https://huggingface.co/datasets/sultan57/mcp-registry-facts/resolve/main/servers.csv" rows = list(csv.DictReader(io.StringIO(urllib.request.urlopen(url).read().decode("utf-8"), newline=""))) c = collections.Counter(r["repository"] or "none" for r in rows) linked = c["present"] + c["repo-not-found"] print(len(rows), dict(c), f"{c['repo-not-found'] / linked:.1%} of {linked}") # 49445 {'none': 15784, 'present': 29039, 'repo-not-found': 4622} 13.7% of 33661 I ran this on 2026-10-26 and got the output in the last comment. The CSV has 22 columns (hosting_type, lifecycle_status, license_class, maintenance and more); the full records are one JSON file per part, exactly as the public API serves them. If you run an MCP server There is a small command that runs ProtocolProbe's runtime checks against a server you run yourself, on your own machine: npx protocolprobe-selftest. It can fail a CI job (--fail-on-finding) and write SARIF (--sarif). It is separate from the data above, which never calls anyone's tools. Corrections are welcome: protocolprobe.com lists the ones we have made.
The MCP registry checks a package at publish. Here's what drifts after that. When you install an MCP server from the official registry, you get exactly the package version the entry lists. The registry checks that version when the entry is published: it fetches it from npm or PyPI and confirms the package names the server. I read the registry's source to see what happens after that. The package check runs again only when the publisher edits the entry. Nothing re-checks entries on a schedule. Repository links are checked against a URL pattern and never fetched. That's a reasonable design for a young registry, and it isn't anyone's mistake. But it means entries can drift. So I checked the whole registry against npm, PyPI and GitHub and counted what no longer matches. What I counted Registry snapshot 2026-10-04: 38,977 servers (the latest version of each name). Lookups ran on 2026-10-06. | What | Count | Out of | Share | |---|---|---|---| | Repository link returns 404 (missing or private) | 4,298 | 28,743 entries with a GitHub link | 15.0% | | Listed version is withdrawn upstream (npm deprecated / PyPI yanked) | 663 | 14,431 npm/PyPI entries | 4.6% | | Linked repository is archived | 309 | 24,256 reachable repos | 1.3% | | Listed package or version can't be installed at all | 157 | 14,431 npm/PyPI entries | 1.1% | | Active, but no package, no remote and no website | 216 | 38,977 | 0.6% | Together, at least 5,192 of 38,977 entries (13.3%), more than 1 in 8, had at least one of these. A few notes on reading this: - The 157 are the clearest case of drift. All of them were published after the registry's version check existed. If validation was on (it is by default), each one passed an exact-version check and the package or version disappeared later. - Withdrawn doesn't mean malicious. I classified the publishers' own deprecation messages. Most are renames, moved distributions and discontinued packages. A few say the backing service shut down, and 12 publishers cite a security issue. A registry-driven install still gets the withdrawn version, with no warning. - A 404 can be a renamed or private repo. I don't tell those apart. The fix is now being tracked I opened a feature request on the registry, modelcontextprotocol/registry#1715. It asks to re-run the existing checks on a schedule and record the result (for example lastVerifiedAt plus a status) instead of deleting anything. On 2026-10-23, a member of the MCP organization consolidated the older re-validation request (#93) and five endpoint-health reports into that issue, so periodic re-validation now has one implementation tracker. The direction in the thread: dated results, explicit "auth-required / transient / unknown" states, and no removal on a single failed check. That last point matters. A missing repo or a yanked version is a fact a client should see. It shouldn't silently turn into a takedown. What this doesn't say - Nothing here says whether any server is safe or unsafe. I didn't run or call any server's tools. Everything comes from the registry and public package records. - The numbers are a dated snapshot. The registry has grown since then, and I haven't recounted these figures. If you publish an MCP server Five minutes of housekeeping: 1. Open your registry entry's repository.url. Does it still load? 2. Is the version you list still published, and not deprecated or yanked? 3. If you renamed or moved the package, publish an update that points to the new one. Full tables, with each figure's source and date: protocolprobe.com Dataset (CC BY 4.0, citable): doi.org Disclosure: I built ProtocolProbe, a free directory that reads the registry's public data. Corrections welcome: hello@protocolprobe.com.
A Private MCP Registry in 10 Minutes with agentregistry Originally published at webofmike.com on 2026-10-13. The demo repo and every command in it were run before publishing. A private MCP registry is a catalogue of approved MCP servers that your clients discover from instead of the open public one. I built private-mcp-registry to stand one up on agentregistry v0.4.0: three servers declared in YAML, served through the official MCP Registry v0.1 API, checked against the official OpenAPI document, and discovered by a client that knows nothing about agentregistry. Run as a script, the whole quickstart took 49 seconds on my laptop. Why run a private MCP registry? The public MCP Registry is open. Anyone can publish to it. On 2026-09-29 I searched it for fetch and got 70 servers at their latest version. None of them was mcp-server-fetch, the reference fetch server from the MCP project. A team that tells its developers "install the fetch server" has not said which of the 70 it means. A private registry answers that once. The platform team decides which servers exist, which package they come from, and which version is approved. Every client reads that list. The approved list also becomes the baseline you need for everything else: you cannot flag a shadow MCP server without a list of the servers that are not shadows. What changed in agentregistry v0.4.0 v0.4.0, released 2026-08-03, rebuilt agentregistry as a declarative control plane. Every artifact is now a Kubernetes-style resource under ar.dev/v1alpha1, managed with arctl apply, arctl get and arctl delete. MCP servers, agents, skills, prompts, plugins and models share the format. Two things in the release notes matter for this demo: 1. It is not backward compatible with v0.3.x. The release says there is no in-place upgrade. The v0.3.x CLI has no arctl apply at all. The machine I built this on had v0.3.3 in /usr/local/bin, so the repo downloads v0.4.0 into its own ./bin and verifies the checksum. 2. The official registry API is back, opt-in and read-only. agentregistry started as a fork of the official registry, then moved its native API to v1alpha1 resources, which took it off the upstream server.json contract. v0.4.0 adds a compatibility layer that re-exposes MCPServer resources through the v0.1 read endpoints. It is off by default. The quickstart Prerequisites are Docker with Compose v2, uv, Node.js for npx, curl and jq. The full command list, including the arctl download, is in the repo README. The compose file is agentregistry's own compose file at the v0.4.0 tag, with the compatibility API switched on: # The point of this demo: serve GET /v0.1/servers in the official # MCP Registry shape. Off by default because it is anonymous and # flattens every namespace. See README "Before you expose this". AGENT_REGISTRY_MCP_REGISTRY_COMPAT_ENABLED: "true" Start it: docker compose up -d --wait curl -s localhost:12121/v0/version {"version":"v0.4.0","git_commit":"aef6563","build_time":"2026-08-03"} Then publish the servers. One file holds three MCPServer resources, one for each source type: the MCP "everything" reference server from npm, the reference fetch server from PyPI, and DeepWiki's public remote endpoint. This is the PyPI entry: apiVersion: ar.dev/v1alpha1 kind: MCPServer metadata: namespace: com.example name: fetch spec: title: Fetch description: "Fetches a URL and returns the page as markdown." source: package: origin: type: pypi identifier: mcp-server-fetch pypi: version: "2026.8.18" serverName: io.github.modelcontextprotocol/server-fetch transport: type: stdio arctl apply -f registry/servers.yaml --dry-run arctl apply -f registry/servers.yaml ✓ MCPServer/everything (latest) created ✓ MCPServer/fetch (latest) created ✓ MCPServer/deepwiki (latest) created The package version is pinned in the manifest, and the manifest lives in Git. Adding a server to the approved list is now a pull request someone reviews. Read it back through the official API path: curl -s localhost:12121/v0.1/servers | jq '.servers[].server | {name, version}' { "name": "com.example/deepwiki", "version": "0.0.0" } { "name": "com.example/everything", "version": "2026.8.31" } { "name": "com.example/fetch", "version": "2026.8.18" } The server name is <namespace>/<name>. Setting metadata.namespace: com.example gives you the reverse-DNS prefix the official naming convention uses. About the 49 seconds: that is a script running every quickstart command in the README, from downloading arctl through the last discovery call, on an Apple silicon laptop. The agentregistry server image was not cached and the uv and npm caches were empty. The postgres:16 image was already on the machine, so a first run will also pull that. Reading and typing along adds more. Ten minutes is a comfortable ceiling. Is agentregistry really compatible with the official MCP Registry API? For reads, yes, and you can check it instead of taking my word for it. scripts/check_conformance.py fetches the official OpenAPI document at a pinned commit and validates three kinds of response against it: - GET /v0.1/servers against the ServerList schema - GET /v0.1/servers/{name}/versions against ServerList - GET /v0.1/servers/{name}/versions/latest against ServerResponse It then validates every server object against the server.json schema named in its own $schema field, which is the 2025-12-11 schema on static.modelcontextprotocol.io. uv run scripts/check_conformance.py GET /v0.1/servers PASS envelope vs ServerList PASS server com.example/deepwiki vs 2025-12-11/server.schema.json PASS server com.example/everything vs 2025-12-11/server.schema.json PASS server com.example/fetch vs 2025-12-11/server.schema.json ... 3 servers checked, 0 schema violations Every response passed: the list and versions envelopes against ServerList, the single-version body against ServerResponse, and each server object against the 2025-12-11 server.json schema. A checker that never fails proves nothing, so I tested it against a bad entry too. That test turned up a real gap, covered in the gotchas below. "Compatible" has limits, and they are worth knowing before you point a fleet of clients at it: - Read-only. The spec's POST /v0.1/publish and the status endpoints are not served. You publish through arctl apply or the native API. The spec marks publish as optional, so this is allowed. It does mean tooling built on the official publish flow will not work here. - The default listing differs. The public registry's GET /v0.1/servers returned every version of a server when I queried it without a version parameter. agentregistry's returns only the latest tag. Clients that pass version=latest see the same behavior from both. - version filters on the tag. The spec describes version as a server version such as 1.2.3. agentregistry matches it against metadata.tag. If your tags and package versions are the same string, you will not notice. - Remote servers report 0.0.0. A remote entry has no package to take a version from, so it falls back to the tag, and latest becomes 0.0.0. - Tokens are ignored. The official generic registry API lets a subregistry require authentication. agentregistry's compatibility doc says presented tokens are ignored on these routes and every caller sees the same catalogue. How does a client discover servers from it? scripts/discover.py uses only the v0.1 read API. It searches, takes the first match it knows how to launch (npx for npm, uvx for PyPI, streamable HTTP for remotes), connects with the MCP Python SDK, and lists the tools the server returns: uv run scripts/discover.py fetch http://localhost:12121/v0.1/servers?search=fetch com.example/fetch 2026.8.18 pypi connecting to com.example/fetch: uvx mcp-server-fetch==2026.8.18 1 tools: fetch The npm entry launched as npx -y @modelcontextprotocol/server-everything@2026.8.31 and listed 13 tools. The remote entry connected to DeepWiki and listed 3. The same client, with one flag changed, reads the public registry: uv run scripts/discover.py fetch --no-connect --registry https://registry.modelcontextprotocol.io It lists and does not launch. Running a package picked from an open catalogue, without review, is the thing a private registry exists to prevent. The public registry took about 30 seconds to answer each search when I ran this. agentregistry also runs its own MCP server, on port 31313 in this compose file, with list_servers, get_server and matching tools for agents, skills, prompts and the other kinds. An agent that has it configured can ask the registry what exists without any HTTP client code: npx -y @modelcontextprotocol/inspector@2.8.0 --cli http://localhost:31313/ \ --transport http --method tools/call --tool-name list_servers \ --tool-arg namespace=com.example --tool-arg search=fetch For VS Code, agentregistry's docs say the gallery reads the organization Copilot policy McpGalleryServiceUrl and appends /v0.1/servers to it. I did not test VS Code for this post. Gotchas Why is /v0.1/servers empty? {"servers":[],"metadata":{"count":0}} That was my first result, with three servers applied. My first manifests set metadata.tag to a version on every entry, which seemed like good hygiene. The compatibility API's default listing only returns rows whose tag is the literal string latest, so all three were stored, visible to arctl get, and absent from /v0.1/servers. GET .../versions/latest returned 404 too. The repo's servers.yaml omits metadata.tag, so every entry gets latest. registry/pinned-tag.yaml reproduces the problem: arctl apply -f registry/pinned-tag.yaml curl -s 'localhost:12121/v0.1/servers?search=pinned' # empty curl -s 'localhost:12121/v0.1/servers?search=pinned&version=2026.8.18' # found The registry's MCP list_servers tool returns every tag by default. So the same entry is visible over MCP and invisible over the v0.1 API. arctl get mcps says "No mcps found" arctl get mcps lists the default namespace, and these servers live in com.example. Ask by qualified name: arctl get mcp com.example/fetch Descriptions over 100 characters agentregistry accepted a 132-character spec.description and served it through /v0.1/servers unchanged. The official server.json schema caps description at 100 characters, so the conformance check fails: FAIL server com.example/longdesc vs 2025-12-11/server.schema.json: description: 'Answers questions about public GitHub repositories. This se... acters.' is too long A client that validates against the official schema can reject that entry. Keep descriptions under 100 characters, and run the conformance check in CI against the manifests you are about to apply. Before you expose it The compatibility API is off by default for a reason. In the OSS build it is anonymous and flattens every namespace into one catalogue. In this compose file, the native API and the MCP server on 31313 also answered without any credentials. That is fine on localhost. Anywhere else, put it behind a gateway that authenticates callers, and treat the list of server names as information about your internal tooling. Two limits of a registry entry are worth stating plainly. A registry entry pins a package version. It does not pin what the server says about its tools at runtime. The server.json schema has no field for tool definitions at all. A server whose tool descriptions change after approval still passes a name check, which is the problem in MCP tool poisoning. The registry tells a client which package to run. Pinning the tool definitions it returns is a separate control. A registry is also a write path and a list path, and that pair can carry data in both directions, as the RubyGems incident in Your Artifact Registry Is a Two-Way Channel for Agents showed. Here the write path is arctl apply. Keep it in CI, under one identity, and do not hand agents write access to the registry they discover from. What's next The demo stops at discovery. agentregistry v0.4.0 can also deploy what it catalogs, through Deployment and Runtime resources for local Docker and Kubernetes, and compose agents from MCP servers, skills and prompts. The next step is taking one approved server from this catalogue to a running deployment, with the conformance check as a gate in front of arctl apply. The code is at themsquared/private-mcp-registry. Frequently asked questions How do I run a private MCP registry? Run agentregistry v0.4.0 and its Postgres database with Docker Compose, declare each approved server as an ar.dev/v1alpha1 MCPServer resource in YAML, and publish the file with arctl apply. Set AGENT_REGISTRY_MCP_REGISTRY_COMPAT_ENABLED=true and the registry also serves GET /v0.1/servers in the official MCP Registry shape. Run as a script, the full quickstart took 49 seconds on my laptop. Is agentregistry compatible with the official MCP Registry API? For reads, yes. With the compatibility API enabled, agentregistry v0.4.0 serves the three v0.1 read endpoints: list servers, list versions, and get one version. Its responses passed validation against the official OpenAPI document's ServerList and ServerResponse schemas and the 2025-12-11 server.json schema. There is no publish endpoint, and a description over 100 characters is served as-is even though the official schema rejects it. Why does agentregistry's /v0.1/servers return an empty list? The compatibility API's default listing only returns entries whose tag is the literal string latest. An MCPServer published with metadata.tag set to a version and nothing else is stored, and arctl get finds it, but GET /v0.1/servers skips it and GET .../versions/latest returns 404. Omit metadata.tag so the entry gets latest, or have clients pass ?version=<tag>. Can an AI agent discover MCP servers from agentregistry? Two ways. Any client that speaks the official v0.1 read API can list and fetch servers from /v0.1/servers, and the same client code works against the public MCP Registry. agentregistry also runs its own MCP server, on port 31313 in the demo, with tools such as list_servers and get_server, so an agent can query the catalogue over MCP directly. --- *Canonical version, with machine-readable markdown at https://webofmike.com/private-mcp-registry/index.md: webofmike.com