1. はじめに
自作のPlantUML MCPサーバーを、Dockerの公式MCPカタログ(Docker MCP Catalog)に申請しました。本記事はその実録です。
この記事で分かること:
- 公式カタログに載せる意義 — 「野良MCPサーバー」が企業に採用されない理由と、カタログがそれにどう答えるか
- 登録要件 — 特に最大の関門であるライセンス
- 申請の具体的手順 — docker/mcp-registry へのPull Request
- 実際にハマった点とその回避策 — おそらく本記事の一番の価値です
なお、内容は執筆時点(2026年8月)の情報です。要件やツールは変わり得るので、申請時は必ず一次情報である docker/mcp-registry の CONTRIBUTING.md を確認してください。
2. Docker MCP Catalog / Registry とは
まず前提を説明します。MCP(Model Context Protocol)は、LLMに外部ツールを接続するための標準プロトコルです。MCPサーバーは「LLMから呼び出せるツールの提供者」で、たとえば本記事のサーバーは「PlantUMLのテキストを渡すと図を返す」ツールを提供します。ClaudeやLibreChat、DifyといったMCP対応クライアントから、同じ作法で呼び出せます。
- Docker MCP Catalog は、MCPサーバーの配布カタログです。ここに載ったサーバーは、検証済みのコンテナイメージとして誰でも取得できます
- カタログの消費側は2通りあります。Docker Desktopに同梱のMCP Toolkit(GUI)と、Docker MCP Gateway(CLI/ヘッドレス)です
- 今回の構成では Docker Desktop / MCP Toolkitは使っていません。Docker Composeで動かせる無料のDocker MCP Gatewayのみを使っています(図のグレー破線が「不使用」の部分です)。LibreChatやDifyなどのMCPクライアントはStreamable HTTPでGatewayに接続し、Gatewayがカタログ定義に従って各MCPサーバーのコンテナ(PlantUML等)をstdioで起動・仲介します
カタログにサーバーを載せる(配る)ルートは2つあります。
| ルート | 方法 | 特徴 |
|---|---|---|
| A: 公式カタログ |
docker/mcp-registry へのPR |
Dockerの審査あり。全ユーザーのDocker Desktop / Gatewayに掲載される。本記事の主題 |
| B: カスタムカタログ | docker mcp catalog import |
審査なし。自分・自組織のGatewayに直接取り込む。申請前の動作確認にも使える |
さらにルートAでは、イメージの提供方式を選べます。Docker-built(Dockerがソースからビルドして mcp/ namespaceでホスト。署名・SBOM・自動セキュリティ更新付き)と、self-built(自前のイメージを登録)です。推奨はDocker-builtですが、これを選ぶには条件があります — それがライセンスです(後述)。
3. なぜ公式カタログへの登録を目指すのか
「便利なMCPサーバーを作った。GitHubで公開した。」— それで終わりにしなかった理由から書きます。個人開発のMCPサーバー、いわば「野良MCPサーバー」は、どれだけ便利でも企業ではまず採用されないからです。
企業が「野良MCPサーバー」を採用しない理由
企業が個人開発のMCPサーバーの採用をためらう理由は、主に次の4点に集約されます。
| No | 採用障壁 | 具体的な内容 |
|---|---|---|
| ① | セキュリティリスク | MCPサーバーはLLMと社内データ・外部APIの橋渡しを行う位置にいる。悪意あるコードや脆弱性の混入は情報漏洩・不正操作に直結するが、個人開発のものはコード監査もサプライチェーン検証もされておらず、安全性を担保できない |
| ② | 保守継続性の不安 | 開発が個人の善意に依存しており、更新停止や突然のリポジトリ削除のリスクがある。MCP仕様の変更や脆弱性発覚時に修正が提供される保証がない |
| ③ | 信頼性・品質の不透明さ | テストの有無、動作実績、エラー処理の品質が外から分からない。業務システムに組み込める水準かを判断する材料がない |
| ④ | 責任の所在と契約の問題 | 障害・事故時の問い合わせ先やSLAがなく、ライセンスや法的責任も曖昧なため、企業の調達基準を満たせない |
Docker MCP Catalogが解決すること
Docker MCP Catalogに登録されたMCPサーバーは、Dockerによる審査を経て、署名付きコンテナイメージとして配布されます。出所の検証と改ざん防止が保証され(①への回答)、コンテナ隔離による安全な実行環境が前提になり、脆弱性スキャンや継続的なイメージ更新の仕組みも提供されます(①②への回答)。
つまり「誰が作ったか分からず、安全かも保証されない」という野良MCP最大の弱点を、信頼できる第三者による検証・配布基盤で補うことができ、企業が採用判断できる土台が整うわけです。
③(品質の不透明さ)については、カタログ任せにせず開発側でも埋められます。今回のサーバーでは受け入れ要件を明文化してCIで自動検証し、結果を公開しています(後述)。
正直に書いておくと、④(責任・SLA)はカタログ登録で完全に解決するわけではありません。登録されてもOSSであることに変わりはなく、SLAが生まれるわけではないからです。それでも「Dockerが審査・配布している」という事実は、調達側の評価において野良との決定的な差になります。
4. 申請したサーバー: plantuml-mcp-server
PlantUMLのソーステキストを受け取り、SVGまたはPNGにレンダリングして返すMCPサーバーです。ツールは render_svg と render_png の2つ。
特徴は次のとおりです。
- in-processレンダリング — MIT版PlantUMLライブラリをJVM内で直接呼ぶため、別プロセスのPlantUMLサーバーが不要
- CJKフォント同梱 — 日本語・中国語・韓国語のラベルがそのまま描画できる(Noto Sans/Serif CJK)
- 非rootコンテナ、レンダリングは100,000文字・60秒の上限つき
リポジトリ: https://github.com/potofo/plantuml-mcp-server
5. 最大の壁はライセンスだった
公式カタログの要件で最初に確認すべきはライセンスです。CONTRIBUTINGには次のように書かれています。
MIT or Apache 2 are great, GPL is not.
実はこのプロジェクトの初期設計は、既存の plantuml/plantuml-server(GPL-3.0)のDockerイメージをベースにしていました。この構成では配布物全体がGPL-3.0になり、公式カタログには申請できません。
解決策は、レンダリングエンジンを net.sourceforge.plantuml:plantuml-mit(MIT版PlantUML)に載せ替え、in-processで呼び出す設計への変更でした。plantuml-mitは、GPL版と同一ソース・同一リリースサイクルで、GPL成分だけを除去してビルドされた公式アーティファクトです。失う機能はditaa・AsciiMath/LaTeX数式などごく一部で、UML全種・JSON/YAML・mindmap・ガントチャートなどは通常どおり使えます。
もう1つ整理が必要だったのがGraphviz(EPL-1.0)です。クラス図などのレイアウトに必須ですが、リンクせず外部プロセスとして起動する構成なら、イメージへの同梱は「単なる集積」であり本体のMITライセンスに影響しません。
教訓: 申請を考えているなら、ライセンス設計は最初にやってください。 後からの載せ替えは、依存ライブラリの差し替えではなくアーキテクチャ変更になります。
GHCR(GitHub Container Registry、
ghcr.io)— GitHubが提供するコンテナイメージのレジストリ。GitHubリポジトリと同じアカウント配下でイメージを公開でき、GitHub ActionsからGITHUB_TOKENだけでpushできるため、OSSのイメージ配布先としてよく使われる。
6. 申請前に整えたもの
必須要件は実はシンプルです。
- 公開GitHubリポジトリ
-
リポジトリルートに
Dockerfile -
LICENSE.txt(MIT) -
server.yaml(フィールドの詳細は次章)
加えて、必須ではないものの、やっておいて良かったことがあります。
タグ駆動リリースでマルチアーチイメージをGHCRにpublish。 レビュアーが動かせる参照イメージになる上、マルチアーチビルドが通ることの証明になります。そして実際、ここで事故が起きました — ランタイムに使っていた eclipse-temurin:17-jre-alpine はamd64のみの提供で、リリースワークフローのarm64ビルドが no match for platform in manifest で失敗したのです。TemurinのAlpineイメージがARM64に対応するのはJDK 21からで、ランタイムを 21-jre-alpine に変更して解決しました(コンパイルターゲットはJava 17のまま)。CIの単一アーチビルドでは絶対に見つからない問題で、リリースを一度実走させた価値がありました。
受け入れテストの整備。 Docker MCP Catalogの受け入れ基準とサーバー固有の要件から、EARS形式の受け入れ要件23項目を定義し、CIで毎回、実際にビルドしたイメージに対して自動検証しています(--network none で本物のMCPセッションを張る方式)。前章の「品質の不透明さ」への、開発側からの回答のつもりです。
descriptionへの非公認明記。 PlantUML の名前とロゴを使う以上、ブランド面の指摘は起こり得ます。「Community project, not affiliated with or endorsed by the PlantUML project.」の一文をマニフェストに入れて先回りしました。
7. 登録マニフェスト(server.yaml)を理解する
この申請では「マニフェスト」と呼べるファイルが3つ登場して紛らわしいので、まず役割を整理します。ここが本章の肝です。
| ファイル | 置き場所 | 役割 |
|---|---|---|
server.yaml(参照用) |
自リポジトリのルート | 申請フォーマットの下敷き。後述の task create は読んでくれないが、手直しの際の「正」になる |
servers/<name>/server.yaml |
docker/mcp-registry(レジストリ側) | 審査対象の実体。PRで提出するのはこれだけ |
catalog.yaml |
自リポジトリのGitHub Release添付 | ルートB(カスタムカタログ)用。docker mcp catalog import に直接食わせる。digest固定 |
補足すると、上2つの server.yaml は「バックアップ」の関係ではなく、「手元の原稿」と「提出版」の関係です。時系列は自リポジトリ側が先で、申請前に「レジストリに載せたい内容」をあらかじめcontribution形式で書いておきます。提出時には、後述の task create が生成したレジストリ側エントリのTODO(title / description / icon)をこの原稿の内容で埋めることになります。提出後に「正」となるのは審査を経たレジストリ側(こちらには source.commit ピンも付きます)で、自リポジトリ側は内容の原本として残し、次に更新PRを出すときの下敷きに使います。
レジストリ側 server.yaml の主なフィールドは次のとおりです。
| フィールド | 内容 |
|---|---|
name |
カタログ上の識別子。servers/<name>/ のディレクトリ名と一致必須 |
image |
Docker-builtなら mcp/<name>(Dockerがこのnamespaceでビルド・ホスト) |
type |
server(ローカルコンテナ型)。リモート型サーバーは remote
|
meta.category / meta.tags
|
categoryは既存の値から選ぶ(今回は productivity) |
about.title / about.icon / about.description
|
カタログUIにそのまま表示される。iconは公開URLで、実際に画像が返ることを確認する |
source.project |
公開GitHubリポジトリのURL。Dockerはここからビルドする |
source.commit |
コミットピン。どの時点のソースをビルドするかを固定する。レジストリの慣例で、既存エントリの大半が付けている。「申請後にmainを更新してもカタログ版には反映されない」という意味も持つ |
config: / secrets:
|
環境変数や認証が必要なサーバーのみ。不要なら書かない(今回は不要) |
補足として、tools.json というファイルもあります。これはserver.yamlのフィールドではなく隣に置く別ファイルで、設定なしではツール一覧が取得できないサーバーだけに必要です(今回は自動検出で済むため不要)。
実際に提出したエントリの全文がこちらです。
name: plantuml
image: mcp/plantuml
type: server
meta:
category: productivity
tags:
- plantuml
- uml
- diagrams
about:
title: PlantUML
icon: https://avatars.githubusercontent.com/u/33107703?s=200&v=4
description: >-
Render PlantUML source to SVG or PNG through Model Context Protocol.
Community project, not affiliated with or endorsed by the PlantUML project.
source:
project: https://github.com/potofo/plantuml-mcp-server
commit: a8ad36cd233be722bf737ca42755cafe54956c80
書き方のコツを2つ。
- descriptionに非公認の一文(not affiliated)を入れておく — ブランド系のレビュー指摘の先回りになります
-
自リポジトリ側にも同フォーマットの
server.yamlを置いておく — 次章で触れるとおり、生成ツールの出力には手直しが必須なので、「正解データ」が手元にあると作業が確実になります
catalog.yaml(ルートB用)はどう作るか
ルートB用の catalog.yaml は手で書かず、リリースワークフローで自動生成しています。リポジトリにはコミットせず、GitHub Releaseの添付ファイルとしてだけ配布する運用です。
仕組みはシンプルで、v* タグをpushするとGitHub Actionsが次を実行します。
- マルチアーチ(amd64/arm64)イメージをビルドしてGHCRへpush
-
pushしたばかりのイメージのdigestをビルドステップの出力(
steps.build.outputs.digest)から受け取る - そのdigestを埋め込んだ
catalog.yamlを組み立て、GitHub Releaseに添付する
生成される catalog.yaml はこの形です。
version: 2
name: plantuml-custom
displayName: PlantUML Custom Catalog
registry:
plantuml:
title: PlantUML
type: server
image: ghcr.io/potofo/plantuml-mcp-server@sha256:<ビルド直後のdigest>
description: Render PlantUML source to SVG or PNG through Model Context Protocol.
longLived: false
tools:
- name: render_svg
- name: render_png
ここで注意したいのが2点。
-
server.yamlとはスキーマが別物です。こちらはGatewayが直接読むversion: 2/registry:形式で、toolsの列挙やlongLivedといったGateway向けのフィールドを含みます。公式カタログ申請用のserver.yamlをdocker mcp catalog importに食わせても動きません -
imageはタグではなくdigest参照(@sha256:...)です。ビルド直後のdigestはワークフローの中でしか分からないので、人手で書くのではなく自動生成にしています。digest固定なので「importしたカタログが指すイメージがいつの間にか変わっている」ことが起きません
自動生成にしておくと、公式カタログの審査を待たずに docker mcp catalog import で自分のGatewayですぐ使い始められますし、申請前の検証にもそのまま使えました。実装は release.yml の「Generate digest-pinned catalog.yaml」ステップにあります。
8. 申請手順(実際の流れ)
前提ツールは Docker Engine / Go 1.24+ / Task / GitHub CLI(gh)です。流れは6ステップ。
# 1. レジストリをforkしてclone
gh repo fork docker/mcp-registry --clone
cd mcp-registry
# 2. サーバーエントリを生成(対話式のtask wizardではなく非対話のtask createを使う)
export GITHUB_TOKEN=$(gh auth token) # 理由は次章
task create -- --category productivity https://github.com/potofo/plantuml-mcp-server
task create はソースリポジトリのDockerfileからイメージをビルドし、コンテナを起動してツールを自動検出します。2 tools found. と表示され、servers/plantuml/server.yaml が生成されました。
# 3. 生成物の手直し(必須! 詳細は次章)
# 4. ローカル検証
task validate -- --name plantuml # CIに最も近いチェック。全項目✅を確認
task build -- --tools plantuml # ツール検出がrender_svg / render_pngと一致するか
task catalog -- plantuml # ローカルカタログ生成 → docker mcp catalog importで実機確認
# 5. servers/plantuml/ だけをコミットしてpush
git add servers/plantuml
git commit -m "Add PlantUML MCP server"
git push -u origin add-plantuml
# 6. PRを作成(タイトルがそのままsquashコミットのメッセージになる)
gh pr create --repo docker/mcp-registry \
--title "Add PlantUML MCP server" --body-file pr-body.md
認証が不要なサーバーなので、テスト用クレデンシャルのフォーム提出は不要でした。あとはCIの通過とDockerチームのレビューを待ちます。
提出したPR: https://github.com/docker/mcp-registry/pull/4657
9. ハマりどころ集
ここが本記事のメインです。実際に踏んだ罠を「症状 → 原因 → 回避策」で並べます。
1. task create は自リポジトリの server.yaml を読まない
-
症状: 生成された
servers/plantuml/server.yamlのabout.titleがPlantuml (TODO)、descriptionもTODO、iconはリポジトリオーナーのアバター、さらに不要なconfig:セクション(TODO付き)まで付いてくる -
原因:
task createはDockerfileとリポジトリのメタデータだけを見て生成する。手元に用意した参照用server.yamlは読んでくれない -
回避策: 生成後の手直しを必須工程として組み込む。title / description / icon を差し替え、
config:を削除。ただしsource.commitのピンは正しく入っているので、消さずに残すこと
2. GITHUB_TOKEN 未設定だと謎のJSONパースエラー
-
症状:
task createやtask catalogがinvalid character '<' looking for beginning of valueで失敗する - 原因: これらのコマンドはGitHub APIを呼ぶ。未認証だとレート制限に当たり、エラーページのHTMLが返ってきてJSONパースが壊れる
-
回避策: 実行前に
export GITHUB_TOKEN=$(gh auth token)
3. task validate の引数形式
-
症状:
task validate -- plantumlが「空のname」として失敗する - 原因: 引数はフラグ形式で渡す仕様
-
回避策:
task validate -- --name plantumlと書く。これで名前・ディレクトリ・タイトル・YAML整形・commitピン・ライセンス・アイコンなど、CI相当のチェックが一括で走る
4. task catalog がCloudflareにブロックされる環境がある
-
症状: 2と同じ
invalid character '<'エラーが、トークン設定後もtask catalogでだけ出る - 原因: hub.docker.com の手前にいるCloudflareが、GoのHTTP/2クライアントのTLSフィンガープリントを弾いてHTMLを返す環境がある。エントリの内容は無関係(既存の他エントリでも再現する)
-
回避策:
GODEBUG=http2client=0 task catalog -- plantumlでHTTP/1.1を強制する
5. (申請前の話ですが)マルチアーチの罠
-
症状: CIは全部グリーンなのに、リリースのマルチアーチビルドだけ
no match for platform in manifestで失敗 -
原因: ベースイメージにarm64版が存在しない(前述の
eclipse-temurin:17-jre-alpine問題)。単一アーチのCIでは決して発覚しない -
回避策: 申請前に一度、amd64/arm64両方のビルドを実走させておく。
docker manifest inspect <image>で対応アーキテクチャを事前確認するのも有効
10. (小ネタ)申請作業は自律型AIエージェントにやらせた
実はこの申請、fork〜PR作成まで別環境の自律型AIエージェントが実行しています。やったことは、手順をRunbook(手順書)として整備したことだけです。
Runbookには、入力情報のテーブル(リポジトリURL・カテゴリ・ツール名・アイコンなど)、判断ポイント(名前が衝突していたときのフォールバック名)、そしてガードレール(servers/plantuml/ 以外は変更禁止・fork以外へのpush禁止・同一エラーが2回出たら停止して報告)を明文化しました。エージェントが実走して失敗した箇所はRunbookにフィードバックして精度を上げる、というループを回しています。前章のハマりどころは、まさにこの実走の産物です。
Runbookの実物はリポジトリに公開しています: runbook/submit-to-docker-mcp-registry.ja-JP.md
やってみての所感: 人間が手でやっても同じ罠を踏みます。手順書に「期待結果」と「失敗パターン」を書いておくことは、AIにも人間にも同じように効きます。
11. 申請結果
執筆時点のステータス: 審査待ち(PRはオープン)です。
- 提出PR: https://github.com/docker/mcp-registry/pull/4657 (2026-08-08提出)
画面の3つの表示は、それぞれ次の意味です。
-
Review required — このリポジトリはコードオーナー制で、write権限を持つレビュアー(
docker/ai-tools-team)の承認がないとマージできません - 2 workflows awaiting approval — 初回コントリビューターのPRは、CIの実行自体にメンテナーの承認が必要です。つまり提出直後は「CI通過済み」にすらなれず、CIが走るのもDockerチームの操作待ちになります
- Merging is blocked — 上記が揃うまでマージ不可。提出者側にできることはありません
要するに、PRを出したら待つしかありません。docker/mcp-registryには世界中から申請が集まっており(このPRの番号が4657であることからも規模が分かります)、審査には相応の時間がかかります。急かさず、レビューコメントが付いたら迅速に対応する、が正しい姿勢です。
マージされると、約24時間以内にMCP catalog・Docker DesktopのMCP Toolkit・Docker Hubの mcp/ namespaceに反映されるとのことです。反映確認はこのコマンドでできます。
docker mcp catalog show | grep -i plantuml
結果が出たら本記事に追記します。レビューで指摘(名称変更の要求など)があれば、それも「ハマりどころ」に足す予定です。
12. まとめ
- 公式カタログ掲載は、「野良MCPサーバー」の採用障壁(セキュリティ・保守・品質・責任)への現実的な回答になる
- ライセンスが最初で最大の関門。MIT / Apache-2で設計する。後からの載せ替えはアーキテクチャ変更になる
- 最低要件は「公開リポジトリ+ルートのDockerfile+server.yaml」とシンプル
-
task createの生成物は必ず手直しし、task validate -- --name <name>でCI相当のチェックを事前に通す - 環境起因の罠(
GITHUB_TOKEN未設定、CloudflareのHTTP/2ブロック)は先に潰しておく - 手順書化しておくと再現性が上がる。AIエージェントに任せることもできる
次にやりたいこと: カタログ反映後の利用状況の観測、そして実運用(Dify / LibreChat)からのフィードバックを受けたツール改善のサイクルを回していくことです。
本記事のMCPサーバー: https://github.com/potofo/plantuml-mcp-server (MIT License)


