2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Agent Plugins 1.0.0 を使ってみた

2
Posted at

Introduction

AIコーディングエージェントが当たり前になった今、「エージェントを拡張するプラグイン」もクライアントごとにバラバラの形式で乱立しています。同じスキル、同じMCPサーバーなのに、VS Code用・Cursor用・Codex用……と、クライアントごとにパッケージを作り直した経験のある方も多いのではないでしょうか。

この問題に対する答えとして登場したのが Agent Plugins です。2026年に公開されたバージョン1.0.0の仕様は、AIエージェントを拡張する再利用可能なコンポーネントを「ポータブルなプラグイン」として1つの形式にまとめるための、オープンでベンダー中立な標準です。

本記事では、公式ドキュメント(https://agent-plugins.org/)を読み込んだうえで、実際にプラグインを作成し、仕様に書かれた挙動をスクリプトで検証してみた結果をまとめます。なお、執筆時点(2026年8月)の仕様バージョンは 1.0.0、ステータスは Working Draft です。

Agent Plugins とは何か

Agent Plugins は、一言でいえば 「AIエージェント向けプラグインの共通パッケージ形式」 です。仕様が定義するのは次の2点だけです。

  1. Agent Skills (https://agentskills.io/ で定義されるスキル形式)をプラグイン内のどこに置くか
  2. MCPサーバー(Model Context Protocol)への接続設定をどう記述するか

重要なのは、この仕様が意図的に「小さな相互運用性の床(interoperability floor)」しか定めていない点です。配布方法、インストール、権限管理、UI、クライアント固有の機能は、すべて各クライアントの裁量に残されています。スキルの中身の書き方は Agent Skills 仕様が、MCPのワイヤープロトコルは MCP 仕様がそれぞれ正であり、Agent Plugins はあくまで「パッケージの箱と発見ルール」を標準化する仕様です。

プロジェクトはオープンライセンスで公開されており、GitHub上でパブリックに開発されています。初期の Technical Steering Committee には Amazon、Cursor、Microsoft、OpenAI、Vercel のコアメンテナーが名を連ねており、実際に対応クライアントとして VS Code、Cursor、GitHub Copilot、ChatGPT & Codex、Kiro、Hermes Agent、OpenClaw が公式サイトに掲載されています。主要なエージェント開発元が揃って参加している点で、単なる一社の独自形式とは一線を画しています。

なぜ v1 はスキルと MCP だけなのか

「コマンドやフック、サブエージェント定義は含まれないの?」と思った方もいるでしょう。仕様の Design Decisions によれば、Agent Skills と MCP はどちらもプロジェクト外に確立された仕様があり、クライアント横断の採用実績があるため v1 の対象に選ばれました。一方、コマンド・フック・エージェント・ルール・LSPサーバーなどはまだクライアント固有色が強く、形式が収斂するまで v1 の対象外とされています。「ポータブルにできる部分だけを標準化する」という割り切りが明確です。

パッケージの全体像

Agent Plugin の実体は、ただのディレクトリです。zip でもレジストリ配布バンドルでもなく、ls や cat や git でそのまま中身を確認・編集・バージョン管理できることが設計意図として明記されています。

my-plugin/
├── plugin.json              # 必須: マニフェスト
├── skills/                  # 任意: Agent Skills(固定ロケーション)
│   └── summarize/
│       ├── SKILL.md
│       ├── scripts/
│       └── references/
├── mcp.json                 # 任意: MCPサーバー設定(固定ロケーション)
└── com.example.client/      # 任意: クライアント拡張ディレクトリ
    └── hooks/

ポイントは3つです。

  • 必須なのはルートの plugin.json だけで、skills/ と mcp.json は任意です。
  • コンポーネントの場所は固定です。plugin.json で「スキルはここにある」などと場所を上書きすることはできません。発見のための間接参照を排除し、どのクライアントでも同じ場所を見れば済むようにするための設計です。
  • パッケージ内のファイルは、シンボリックリンク等を解決した後もプラグインルート内に収まらなければなりません。../ でルートの外を参照するような設定は拒否されます。

最小のプラグインを作る

まずは公式ガイドどおりの最小構成を作ってみます。必要なのはファイル2つだけです。

hello-plugin/
├── plugin.json
└── skills/
    └── greet/
        └── SKILL.md

plugin.json は次のとおりです。

{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
  "name": "hello-plugin"
}

スキルは Agent Skills 形式の SKILL.md です。フロントマターに name と description を書きます。

---
name: greet
description: Greet the user and offer help.
---

Greet the user and offer help.

これだけで、スキル対応クライアントが読み込める正規のプラグインになります。GitHub には公式のサンプルパッケージ(agentplugins/agent-plugins-example)も公開されており、こちらは既存のクライアント固有プラグインを Agent Plugins v1 構造へ移行するためのスキルを同梱した、実用的なリファレンスになっています。

plugin.json を理解する

マニフェストは一見シンプルですが、仕様を読み込むと設計の丁寧さが見えてきます。

閉じたスキーマ

plugin.json に書けるトップレベルフィールドは、次の10個に限定されています(closed schema)。

フィールド 必須 内容
$schema ✔ 対象とする Agent Plugins バージョンの正規スキーマ識別子
name ✔ プラグイン名
version バージョン(Semantic Versioning 推奨)
description 説明
author name / email / url を持てるオブジェクト
homepage ドキュメントURL
repository リポジトリURL
license ライセンス(SPDX 識別子推奨)
keywords 検索用タグの配列
extensions クライアント固有データ(後述)

$schema の値は 1.0.0 では https://agent-plugins.org/schemas/1.0.0/plugin.schema.json という正規識別子でなければなりません。面白いのは、クライアントはプラグイン読み込み時にこのURLからスキーマを取得してはならないと明記されている点です。$schema はあくまで「どのバージョンの検証・解釈ルールをローカルで適用するか」を選ぶためのスイッチであり、オフラインでも検証が完結するように設計されています。

名前の制約

name には明確な制約があります。1〜64文字、使えるのは小文字英数字・ハイフン・ピリオドのみ、先頭と末尾は英数字、-- と .. の連続は禁止です。

エラーの扱いが2段階

マニフェストのエラー処理は仕様上、致命的なものと非致命的なものに明確に分かれています。

  • 非致命(報告して無視、読み込みは継続): 未知のトップレベルフィールド、オブジェクトでない extensions
  • 致命(プラグイン全体を拒否): それ以外のすべてのスキーマ違反(必須フィールド欠落、名前制約違反、未対応の $schema など)

未知フィールドを「スキーマ違反ではあるが、それだけでプラグイン全体を落とさない」と扱うバランス感覚は、タイポ検出の厳格さとエコシステムの寛容さを両立させる工夫だと感じました。なお version が semver でない、homepage がURLとして不正、といった「意味的な」不備では拒否してはならないことも明記されています。検証されるのはJSON型だけです。

extensions: クライアント固有機能の逃がし先

クライアント固有の設定は、逆ドメイン名前空間をキーとする extensions の下に置きます。ファイルベースの拡張は、名前空間と同名のトップレベルディレクトリに置きます。

{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
  "name": "example-plugin",
  "extensions": {
    "com.example.client": { "setting": true }
  }
}

クライアントは、自分が実装していない名前空間を中身を検証せずに無視します。中央レジストリを持たずに衝突を避けつつ、各クライアントが独自機能を実験できる仕組みです。ポータブルなコアを汚さずに拡張の余地を残す、この仕様全体を貫く思想がよく表れている部分です。

skills/ の発見ルール

スキルの形式自体は Agent Skills 仕様が正であり、Agent Plugins が定めるのは「プラグイン内での発見方法」だけです。ルールは単純です。

skills/ の直下の子ディレクトリのうち、SKILL.md という通常ファイルを持つものが1スキルとして発見されます。それより深い階層は再帰的に探索されません。また、発見されたスキルが Agent Skills 仕様を満たさない場合、クライアントはそのスキルだけをスキップし、他のスキルやコンポーネントの読み込みを続けます。

mcp.json を理解する

MCPサーバーの接続設定は、ルートの mcp.json に書きます。トップレベルに置けるのは $schema と mcpServers の2つだけという、こちらも閉じた形式です。

{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
  "mcpServers": {
    "local-validator": {
      "type": "stdio",
      "command": "./bin/validator",
      "args": ["--data", "${PLUGIN_DATA}/validator"],
      "env": { "CONFIG": "${PLUGIN_ROOT}/config.json" },
      "cwd": "${PLUGIN_ROOT}"
    },
    "deployment-api": {
      "type": "streamable-http",
      "url": "https://deploy.example.com/mcp",
      "headers": { "X-Tenant": "public-tenant" }
    }
  }
}

3つのトランスポート

各サーバーエントリは type でトランスポートを宣言し、そのトランスポートに許されたフィールドだけを持てます(未知フィールドや他形式のフィールド混在はそのエントリを無効にします)。

type 必須フィールド 備考
stdio command 任意で args / env / cwd。ローカルプロセス起動
streamable-http url 現行のリモートMCPトランスポート。任意で headers
sse url 非推奨の旧 HTTP+SSE。クライアント対応は任意

MCP対応クライアントは stdio と streamable-http の少なくとも一方に対応すれば適合であり、両対応が推奨(SHOULD)です。クライアントは宣言されたトランスポートで初回接続を試み、失敗時のフォールバック動作は仕様の範囲外です。ちなみに公式サイトの対応クライアント一覧を見ると、VS Code・Cursor・GitHub Copilot・Kiro・OpenClaw は旧 sse まで含めて3方式に対応し、ChatGPT & Codex と Hermes Agent は stdio と streamable-http の2方式対応と記載されており、まさに仕様が許す「差のつき方」になっています。

stdio の command は「1トークン」

個人的に注目したいのが command の扱いです。command はシェルコマンド文字列ではなく、実行ファイルを指す1トークンでなければなりません。許されるのは次の2形式だけです。

  1. python3 のような素の実行ファイル名(プラットフォームの実行ファイル探索ルールで解決)
  2. ./bin/server のようなプラグイン相対パス(プラグインルート基準で解決、ルート内に収まること)

クライアントにユーザー記述のシェル文字列のパースやエスケープを強いないための設計で、command にはプレースホルダー展開も適用されません。パッケージに実行ファイルを同梱する場合は必ず ./ 形式を使います。

cwd を省略するとプラグインルートが作業ディレクトリになります。明示する場合は ./ 相対、${PLUGIN_ROOT} 起点、${PLUGIN_DATA} 起点の3形式のみが有効で、解決後もそれぞれのルート内に収まる必要があります。

リモート接続とセキュリティ

url は絶対 HTTP/HTTPS URL で、ユーザー情報とフラグメントは禁止。非ループバックのエンドポイントは HTTPS 必須で、HTTP が許されるのはホストが localhost かループバックIPリテラルの場合だけです。

そして重要な注意として、headers や env の値は誰でも読めるパッケージデータであり、資格情報やシークレットを埋め込んではならないと明記されています。v1 にはポータブルな OAuth 設定や資格情報参照フィールドは存在せず、認証はクライアント管理です。認証失敗は「そのサーバーの接続失敗」であって「プラグイン設定の不正」ではない、という区別も仕様に書かれています。

PLUGIN_ROOT と PLUGIN_DATA

stdio サーバーを起動するクライアントは、サブプロセスに2つの環境変数を必ず提供します。

  • PLUGIN_ROOT: プラグインルートの絶対パス。同梱スクリプトや設定ファイルの参照に使う
  • PLUGIN_DATA: クライアントが管理する、そのプラグイン専用の書き込み可能なデータディレクトリ。プラグインの更新をまたいで内容が保持される

node_modules や仮想環境、キャッシュ、生成コードなど「更新で消えてほしくない状態」は PLUGIN_DATA に置く、という使い分けです。クライアントはサブプロセス起動前にこのディレクトリを作成する義務があります。

設定値の中では ${PLUGIN_ROOT} と ${PLUGIN_DATA} の2つのプレースホルダーが使えます。展開ルールはかなり厳密に定義されています。

  • 展開対象は args の各要素、env の値、cwd のみ(env のキーと command は対象外)
  • 置換は単一パスかつ非再帰。置換で持ち込まれたテキストは再走査されない
  • 未知の ${OTHER} のようなテキストはリテラルのまま残り、他の環境変数展開は一切行われない
  • env に PLUGIN_ROOT / PLUGIN_DATA というキーを書くと、そのサーバーエントリ自体が無効になる(予約変数はクライアントだけが設定する)

実際に検証してみた

ここからが本題です。仕様書には MUST / MUST NOT が細かく定義されているので、「本当にその通りに動くと嬉しい設計になっているか」を確かめるため、次の2つを作って手元で検証しました。

  1. 検証用プラグイン review-helper: スキル1つと、レビューメモを保存する最小の stdio MCPサーバー(約80行のPython、改行区切りJSON-RPC)を同梱
  2. 検証用ミニローダー miniloader.py: 仕様のクライアント側ルール(マニフェスト検証、スキル発見、mcp.json の2段階検証、プレースホルダー展開、stdioサーバー起動とMCPハンドシェイク)を約200行のPythonで最小実装

検証には公式の JSON Schema(plugin.schema.json / mcp.schema.json)をダウンロードして使用しました。MCPスキーマが #/$defs/server を公開しているのは、まさに「サーバーエントリを個別に検証して、失敗をエントリ単位に分離する」ためだと仕様に書かれており、ミニローダーでもそのまま活用しています。

検証用プラグインの構成は次のとおりです。このレポジトリで公開しています。仕様確認のため、わざと skills/ の深い階層にも SKILL.md を置いています。

review-helper/
├── plugin.json
├── skills/
│   ├── summarize-pr/
│   │   ├── SKILL.md
│   │   └── references/checklist.md
│   └── nested-group/
│       └── deep/SKILL.md        # 発見されない「はず」のスキル
├── mcp.json
└── servers/
    └── notes_server.py          # 同梱の最小MCPサーバー

mcp.json では、素の実行ファイル名(python3)とプレースホルダーを組み合わせています。

{
  "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
  "mcpServers": {
    "review-notes": {
      "type": "stdio",
      "command": "python3",
      "args": ["${PLUGIN_ROOT}/servers/notes_server.py"],
      "env": { "NOTES_FILE": "${PLUGIN_DATA}/notes.json" },
      "cwd": "${PLUGIN_ROOT}"
    },
    "remote-example": {
      "type": "streamable-http",
      "url": "https://tools.example.com/mcp",
      "headers": { "X-Tenant": "public-tenant" }
    }
  }
}

検証1: 正常系 — 発見・起動・ツール呼び出し

ミニローダーで読み込んだ結果がこちらです。

== プラグイン読み込み: review-helper
  manifest OK: name=review-helper version=0.1.0
  [report] 未実装の extensions 名前空間 'com.example.myclient' は中身を検証せず無視(§8.1)
  発見されたスキル: ['summarize-pr']
  有効なMCPサーバー: ['review-notes', 'remote-example']
  [review-notes] initialize -> serverInfo={'name': 'review-notes', 'version': '0.1.0'}
  [review-notes] tools/list -> ['add_note', 'list_notes']
  [review-notes] add_note -> 追加しました(計1件)
  [review-notes] list_notes ->
      - エラーハンドリングの確認

注目ポイントは2つあります。まず、発見されたスキルは summarize-pr だけで、skills/nested-group/deep/SKILL.md は仕様どおり発見されませんでした。「直下1階層のみ、再帰探索しない」というルールが、そのままプラグイン作者への「スキルは skills/ 直下に並べる」という明確な指針になっています。

もうひとつは、stdio サーバーが PLUGIN_ROOT / PLUGIN_DATA 入りの環境で起動し、${PLUGIN_ROOT}/servers/notes_server.py の展開もそのまま機能して、MCPの initialize からツール呼び出しまで一気通貫で動いたことです。

検証2: PLUGIN_DATA は本当に永続するか

同じプラグインをもう一度読み込むと、前回のメモが残ったままカウントが増えました。

  [review-notes] add_note -> 追加しました(計2件)
  [review-notes] list_notes ->
      - エラーハンドリングの確認
      - エラーハンドリングの確認

PLUGIN_DATA 側の notes.json を直接見ても2件保存されており、「クライアント管理の永続ディレクトリ」という契約が、プラグイン側から見て素直に使えることを確認できました。パッケージ本体(PLUGIN_ROOT)は更新のたびに丸ごと置き換わる前提なので、状態の置き場所がプロトコルとして保証されているのは実運用上とても大きいと思います。

検証3: 失敗境界 — どこまでが「致命的」か

この仕様のいちばんの読みどころは、エラー時に**「最も狭い失敗境界」を適用せよ**と定めている点だと思います。異常系フィクスチャを一通り作って確かめました。結果は次のとおり、すべて仕様の記述どおりでした。

# 入力 仕様上の扱い 実測結果
A1 name: "My-Plugin"(大文字) プラグイン拒否(致命的) 拒否された
A2 name: "has--double"(連続ハイフン) プラグイン拒否(致命的) 拒否された
A3 必須の name 欠落 プラグイン拒否(致命的) 拒否された
A4 $schema が未対応の 2.0.0 プラグイン拒否(致命的) 拒否された
B1 未知フィールド hooks 報告して無視、継続 スキルは読み込まれた
B2 extensions が文字列 報告して無視、継続 スキルは読み込まれた
C1 8サーバー中6件が不正な mcp.json 不正エントリのみスキップ 有効2件だけが残った
D1 mcp.json の $schema バージョン不一致 そのプラグインのMCPのみ無効化 スキルは生き残った
E1 description のないスキル そのスキルのみスキップ 兄弟スキルは読み込まれた

C1 で仕込んだ不正エントリの内訳は、env での予約変数 PLUGIN_ROOT の上書き、./ で始まらない cwd、stdio エントリへの url フィールド混在、../../bin/evil というルート脱出 command、未知トランスポート websocket、非ループバックの http:// URL の6種類です。ログの一部を抜粋します。

  [report] server 'bad-reserved-env' をスキップ: スキーマ違反 …(他のサーバーは継続)
  [report] server 'bad-escape-command' をスキップ: command は素の実行ファイル名か './' 始まりの相対パスのみ
  [report] server 'bad-http-remote' をスキップ: 非ループバックの endpoint は HTTPS 必須です
  有効なMCPサーバー: ['ok-server', 'ok-loopback-http']

「1つのMCPサーバーが落ちてもプラグイン全体は死なない」「マニフェストの本質的な不正だけがプラグイン全体を殺す」という階層構造が、実際にテストしてみると非常に扱いやすく、プラグイン作者にもクライアント実装者にも優しい設計だと実感しました。

検証4: プレースホルダー展開は本当に「単一パス・非再帰」か

最後に、展開の細則を直接確認しました。

>>> expand("root=${PLUGIN_ROOT}/cfg data=${PLUGIN_DATA}/db keep=${OTHER}", "/p/root", "/p/data")
'root=/p/root/cfg data=/p/data/db keep=${OTHER}'

>>> # 置換で持ち込まれた ${PLUGIN_DATA} は再走査されない
>>> expand("${PLUGIN_ROOT}/x", "/tricky/${PLUGIN_DATA}", "/p/data")
'/tricky/${PLUGIN_DATA}/x'

未知のプレースホルダーはリテラルのまま残り、置換結果に含まれるプレースホルダー風の文字列も展開されません。展開の連鎖による予期しない挙動(インジェクションの温床)を仕様レベルで塞いでいるわけで、地味ですが好感の持てる決めごとです。

検証して見えてきたこと

JSON Schema は最小限で、仕様本文が正。 検証中に面白い発見がありました。command: "../../bin/evil" は、公式の JSON Schema 上は「1文字以上の文字列」なので通ってしまいます。しかし仕様本文は「素の実行ファイル名か ./ 始まりのみ」と明確に禁じています。仕様にも「スキーマと矛盾する場合は仕様本文が正」と書かれており、クライアント実装者はスキーマ検証だけで済ませてはいけない、という具体例になっています。パス封じ込め(シンボリックリンク解決後の containment 検査)も同様にスキーマでは表現できない要件です。

「決めないこと」を決めている仕様。 配布・インストール・権限・UI・認証・トランスポートのフォールバックはすべて明示的にスコープ外です。おかげで仕様本体は短く読み切れるサイズに収まっており、その分「クライアントごとに体験が違う」余地は残ります。たとえば認証情報の扱いは完全にクライアント任せなので、リモートMCPサーバー付きプラグインを配る場合は、各クライアントでの認証手順を別途案内する必要があるでしょう。

エコシステムの現在地。 対応クライアント一覧には Microsoft(VS Code / GitHub Copilot)、OpenAI(ChatGPT & Codex)、Cursor、AWSの Kiro などが並ぶ一方、執筆時点では Anthropic の Claude Code は掲載されていません。一方で、コンポーネントとして採用された Agent Skills(SKILL.md)や MCP はすでに広く使われている形式なので、既存資産の多くは大きな書き換えなしに載せ替えられるはずです。公式サンプルリポジトリが「既存のクライアント固有プラグインを v1 構造へ移行するスキル」になっているのも象徴的で、移行こそがこの仕様の当面の主戦場なのだと思います。

まとめ

Agent Plugins 1.0.0 を一通り読み、作り、壊して検証してみました。感想をまとめると次のとおりです。

  • 仕様が定めるのは「ディレクトリ構造 + plugin.json + 固定ロケーションの skills/ と mcp.json」だけで、プラグイン作者の学習コストは非常に低いです。最小構成はファイル2つで済みます。
  • 失敗境界(致命的 / 報告して無視 / エントリ単位スキップ)の設計が精密で、検証した限りすべて仕様どおりに扱えました。壊れ方まで標準化されているのがこの仕様の真価だと感じます。
  • PLUGIN_ROOT / PLUGIN_DATA の契約により、同梱バイナリの参照と更新をまたぐ状態管理がポータブルに書けます。
  • 一方で JSON Schema はあくまで補助であり、パス封じ込めや command の形式など仕様本文にしかない要件が複数あります。実装や検証ツールを作る人は仕様本文を必ず読みましょう。

クライアント間の断片化に疲れていたプラグイン作者にとって、「まずこの形で作っておけば主要クライアントで動く」という共通の床ができたのは素直に嬉しい進展です。Working Draft ゆえ今後の変更はあり得ますが、スキーマ識別子の再割り当て禁止など後方互換への配慮も仕様に組み込まれており、追いかける価値は十分にあると思います。

参考リンク

本記事の検証コード(ミニローダー・検証用プラグイン)は学習目的の最小実装であり、実運用のクライアント実装には仕様書§11の適合要件とAppendix Aのチェックリストを参照してください。

2
2
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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?