0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Docker MCP Catalogに申請してみた

0
Last updated at Posted at 2026-09-12

application-counter-hero-1024x300.png

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 / Gateway / Toolkit と MCPクライアント(LibreChat・Dify)の関係図

  • 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_svgrender_png の2つ。

plantuml-mcp-serverのアーキテクチャ: MCP Client → (Streamable HTTP) → Docker MCP Gateway → (stdio) → コンテナ内のJava MCP Server → PlantUML core API (in-process) → Graphviz dot (外部プロセス)

特徴は次のとおりです。

  • 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 (MIT)・server.yamlを整備し、GHCRへマルチアーチ(amd64/arm64)イメージをpublish、CIで受け入れテストを実行

必須要件は実はシンプルです。

  • 公開GitHubリポジトリ
  • リポジトリルートDockerfile
  • LICENSE.txt(MIT)
  • server.yaml(フィールドの詳細は次章)

加えて、必須ではないものの、やっておいて良かったことがあります。

タグ駆動リリースでマルチアーチイメージをGHCRにpublish。 レビュアーが動かせる参照イメージになる上、マルチアーチビルドが通ることの証明になります。そして実際、ここで事故が起きました — ランタイムに使っていた eclipse-temurin:17-jre-alpineamd64のみの提供で、リリースワークフローの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が次を実行します。

  1. マルチアーチ(amd64/arm64)イメージをビルドしてGHCRへpush
  2. pushしたばかりのイメージのdigestをビルドステップの出力(steps.build.outputs.digest)から受け取る
  3. その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.yamldocker 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.yamlabout.titlePlantuml (TODO)、descriptionも TODO、iconはリポジトリオーナーのアバター、さらに不要な config: セクション(TODO付き)まで付いてくる
  • 原因: task create はDockerfileとリポジトリのメタデータだけを見て生成する。手元に用意した参照用 server.yaml は読んでくれない
  • 回避策: 生成後の手直しを必須工程として組み込む。title / description / icon を差し替え、config: を削除。ただし source.commit のピンは正しく入っているので、消さずに残すこと

2. GITHUB_TOKEN 未設定だと謎のJSONパースエラー

  • 症状: task createtask cataloginvalid 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を出した直後のマージステータスはこうなっています。
提出PRのマージステータス: Review required(コードオーナーのレビュー必須)、2 workflows awaiting approval(ワークフローの実行にメンテナー承認が必要)、Merging is blocked(docker/ai-tools-teamのレビュー待ち)

画面の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)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?