
Agent Client Protocol (ACP)は、コードエディタ/IDEとコーディングエージェントの通信を標準化するプロトコルです。最近はAIエージェントの選択肢が増えましたが、実際の連携は各エディタの個別実装に依存しがちです。本記事では、エディタ開発者がすぐに統合を始められる粒度で、仕様の要点と実装の入口をまとめます。仕様の出典は公式ドキュメント とGitHubのリポジトリ です。
結論だけ言うと、ACPは「エディタとエージェントの間に共通語を作る」ための仕組みです。結果として、AI やLLM エージェントの乗り換えや、異なるエディタ間での互換性が取りやすくなります。
ACPが解決したい課題
ACPが問題視しているのは、エディタとエージェントの密結合です。具体的には次のような痛みがあります。
- エディタが新しいエージェントを使うたびに個別実装が必要
- エージェントが対応できるエディタが限定される
- ユーザーがエージェントを選ぶと、使えるUIやエディタが縛られる
これは、Language Server Protocol (LSP)がエディタと解析ツールを分離した構図に近いです。ACPも同様に、エディタとエージェントを疎結合にするための共通プロトコルとして設計されています。
ACPの概要: エディタ実装の最短ルート
ACPは「ユーザーは主にエディタ上で作業し、必要に応じてエージェントを呼び出す」前提です。エディタ側の実装に必要な最低限を並べると次の通りです。
- エージェントをサブプロセスとして起動(stdio: 標準入出力)
- JSON-RPC 2.0(JSONで行うRPC仕様)のメッセージを改行区切りで送受信
initialize→session/new→session/promptの順で会話を開始session/update通知をストリームとして受け取ってUIに反映
ACPのメッセージは改行で区切られる1行JSONが基本です。stdoutにACP以外の文字列を出さないこと、stdinにもACP以外を書かないことが重要です(Transports )。
実装目線で重要な仕様を先にまとめます。
- 通知とリクエスト(notification と method): JSON-RPCなので、
idが無いmethodは通知(返答不要)、idがあるmethodはリクエスト(返答が必要)です(Protocol Overview )。 - 双方向: クライアントがエージェントへリクエストを送るだけではなく、エージェントがクライアントへ
session/request_permissionやfs/read_text_file等のリクエストを飛ばします。エディタ側はサーバ実装だと思ってください。 - stdoutはプロトコル専用: エージェントはstdoutにACPメッセージ以外を出してはいけません。ログはstderrに出す、が前提です(Transports)。
- 改行の扱い: メッセージは
\n区切りで、1メッセージの中に生の改行を入れません(JSON文字列の\nエスケープはOK)。JSONのpretty print(インデント付き)はやめた方が安全です。 - 絶対パス: ACP内のファイルパスは絶対パスが前提です(Protocol OverviewのArgument Requirements)。
- 行番号は1-based:
fs/read_text_fileのlineなど、行番号は1始まりです(同じくOverview)。
実装チェックリスト(エディタ側)
ここだけ実装できれば、とりあえず「ACP対応エージェントと会話できる」状態になります。
- stdioでエージェントを起動して、stdin/stdoutで1行JSONを送受信
- JSON-RPCのディスパッチ(通知/リクエスト/レスポンス)
initialize→session/new→session/promptsession/updateをストリームとしてUIに反映(テキスト/ツール呼び出し/差分など)- エージェントからの
session/request_permissionに応答(許可UIの実装) - 必要なら
fs/*やterminal/*を実装してcapabilitiesに載せる
まず実装するメッセージフロー
Protocol Overviewの「Message Flow」が最短の道しるべです。最低限の流れは次の4ステップです。
initialize: プロトコルバージョンとcapabilities(利用機能の宣言)を交渉session/new: セッションを作成してsessionIdを取得session/prompt: ユーザーの入力を送信session/update: エージェントからの進捗/結果を受信
initializeでは、クライアント側が「ファイル読み書きやターミナル操作を提供できるか」をcapabilitiesで宣言します(Initialization
)。ここで宣言した機能だけが、エージェント側から要求されます。
エージェントとクライアントのやり取りは、ざっくりこういうシーケンスです。
sequenceDiagram participant Client as Editor/Client participant Agent as Agent Client->>Agent: initialize Agent-->>Client: initialize result (version/capabilities) Client->>Agent: session/new Agent-->>Client: session/new result (sessionId) Client->>Agent: session/prompt (user message) Agent-->>Client: session/update (plan/message chunks/tool calls...) Agent->>Client: session/request_permission (optional) Client-->>Agent: permission result Agent-->>Client: session/update (tool_call_update) Agent-->>Client: session/prompt result (stopReason)
initialize(バージョン/機能の交渉)
initializeは最初の握手です。クライアントがサポートするプロトコルバージョンと、提供できる機能(capabilities)を送ります。
例(クライアント→エージェント):
| |
ここでcapabilitiesを盛りすぎると、エージェントが当然のようにfs/read_text_fileを呼びます。
最初はterminal: false、fsもfalseにして、会話だけ成立させるのが安全です。
session/new(会話のコンテキスト作成)
次にsession/newでセッションを作ります(Session Setup
)。返ってくるsessionIdを以降のやり取りで使います。
| |
mcpServersは拡張サーバ設定で、使わないなら空配列のままでOKです。
cwdは絶対パスです。ここを間違えると「意図しない場所のファイルをいじる」事故が起きます。
session/prompt と session/update(ストリーミング表示)
プロンプトターン(Prompt Turn)はsession/promptから始まり、session/update通知が大量に流れて、最後にsession/promptレスポンスが返って終わります(Prompt Turn
)。
| |
ストリーミング中に受け取るsession/updateの例:
plan: タスク計画(UIに出すと分かりやすい)agent_message_chunk: テキストのチャンク(逐次追記)tool_call/tool_call_update: ツール実行の開始/進捗/完了(Tool Calls )
ターンの終了はsession/promptのレスポンスで分かります。
| |
session/request_permission(許可UIは必須)
エージェントはツールを実行する前に、クライアントへ許可要求を投げる場合があります(Tool CallsのRequesting Permission)。
ここがACP実装の一番の落とし穴で、重要な観点です。
注意点は、これが「エージェント→クライアント」の双方向JSON-RPCリクエストだという点です。つまりエディタ側は、単にエージェントへ送るクライアントではなく、エージェントからのJSON-RPCリクエストを受けて応答するサーバでもあります。
id付きのsession/request_permissionを受け取ったら、クライアントは必ず同じidでレスポンスを返します- ここを返さないと、エージェントは許可待ちで止まり、
session/updateも進まなくなります - 実装上は「stdoutのストリームを読みながら、通知・レスポンス・逆方向リクエストをディスパッチする」構造が必要です
| |
ユーザーが選択した結果を返します。
| |
最小のクライアント実装例(stdio + JSON-RPC)
以下は、エディタ側がACPエージェントを起動して、initialize → session/new → session/prompt を送りつつ、双方向リクエストも最低限捌くための骨組みです。動作確認は、ACP対応のエージェントに置き換えて使ってください。
前提:
- Python 3.11以上
- エージェントの実行ファイルが用意されている
| |
この例では、session/update をストリームとして受け取り、テキストのチャンクをそのまま表示しています。UI上ではこのストリーミングを「生成中のメッセージ」として扱うと自然です。
また、session/request_permissionやfs/*のような「エージェント→クライアント」リクエストも最低限処理しています。ここを実装しないと、エージェントが許可待ちで止まります。
動作確認用: 最小のモックエージェント(stdio)
エディタ統合を始めるとき、「本物のエージェント」と繋ぐ前に、まずは自分のJSON-RPCディスパッチとUIを確認したくなります。 そのための最小モックを置いておきます。stdoutにはACPメッセージ以外を出さず、ログはstderrに流します。
| |
このモックをpython acp_mock_agent.pyとして起動し、上のクライアント例の/path/to/agentをPythonに差し替えれば、ストリーミング表示の配線確認ができます。
エディタ統合でよく実装するクライアントメソッド
ACPは「ツール呼び出しの実行本体」をエージェント側に寄せつつ、エディタならではの体験(未保存バッファ、差分表示、許可UIなど)をクライアント側のメソッドで実現します。
session/request_permission: 許可UI(必須級)fs/read_text_file/fs/write_text_file: エディタバッファの読み書き(File System )terminal/*: ターミナルの起動/出力取得(Terminals )
まずはsession/request_permissionとsession/updateの表示だけ入れて、ファイル操作系は後から足すのが安全です。
主要言語ごとのクライアントライブラリ
ゼロからJSON-RPCとスキーマを組むのは地味に面倒なので、まずは公式/コミュニティのライブラリを使うのが近道です。
TypeScript / JavaScript
- npm: @agentclientprotocol/sdk
- 公式SDKリポジトリ: agentclientprotocol/typescript-sdk
- APIリファレンス: agentclientprotocol.github.io/typescript-sdk
クライアント側はClientSideConnection、エージェント側はAgentSideConnectionを使って接続を張る設計です(詳細はTypeScript library
)。
Python
- pip:
pip install agent-client-protocol - 公式SDKリポジトリ: agentclientprotocol/python-sdk
- ドキュメント: agentclientprotocol.github.io/python-sdk
Pydanticモデルとasyncの土台が入っていて、クライアント/エージェントの両方に使えます(Python library )。
Rust
- crates.io: agent-client-protocol
- ドキュメント: docs.rs
- 公式リポジトリ: agentclientprotocol/rust-sdk
Client/Agent traitを実装して両側を作れる設計です(Rust library
)。
それ以外(コミュニティ)
公式に載っているコミュニティ実装もあります(Community libraries )。例えば:
- Go: coder/acp-go-sdk
- React: marimo-team/use-acp
- Swift: swift-acp など
コミュニティ実装はメンテ状況が揺れやすいので、エディタ本体に組み込む場合は、最初にリリース頻度と互換性ポリシーを確認しておくと安全です。
Claude CodeをターゲットにするならCCSDKを使うべき?
エディタ開発者が「Claude Codeをエージェントとして使いたい」だけなら、基本的にはCCSDKをエディタに直組みするより、ACPクライアントを実装する方が筋が良いです。
理由はシンプルで、ACPはエディタ統合のためのインターフェイス(セッション、ストリーミング、許可UI、差分など)がすでに規格化されていて、Claude Code側もACPエコシステムに載れるようにアダプタ経由で提供されているからです。
- 例: Zedは「Claude CodeをSDKアダプタで統合し、ACPクライアントから使える」と明記しています(Claude Code - ACP Agent | Zed )。
一方で、CCSDK(現在はClaude Agent SDK として公開されています)を使う選択肢もあります。ただしこれは「自分がエディタ兼クライアント実装も全部持つ」方向です。
- CCSDKを使うのが向くケース: ACPの枠を超えた独自UI/機能が必要、あるいはエディタが単一エージェント専用で相互運用性を捨てられる
- ACPクライアントを作るのが向くケース: 将来のエージェント乗り換えを楽にしたい、他のACPエージェントも同じ統合で扱いたい
どちらにしても、エディタ側が実装すべき中心は「ストリーミング表示」と「許可UI」です。そこができると、エージェントが何であれ体験は一定になります。
まとめ: まずどこを読むか
ACPは「エディタとエージェントを分離して、選択肢を広げる」ための標準です。今後、エージェント実装や対応エディタが増えてくる前提で読む価値があります。
次のアクションとしては、Overview → Initialization → Session Setup → Prompt Turn の順で読むと、そのまま実装に落とせます。
自分がエディタ統合を始めるなら、次の順で作業します。
- stdio JSON-RPCの入出力を安定させる(ログはstderr、stdoutはプロトコル専用)
initialize/session/new/session/promptを通すsession/updateのうちagent_message_chunkを表示できるようにするsession/request_permissionのUIを作るtool_call/tool_call_updateとdiff表示に対応する- 必要になったら
fs/*とterminal/*をcapabilitiesに載せる
参考リンク
- Agent Client Protocol 公式サイト
- ACP Introduction
- ACP Protocol Overview
- Initialization
- Session Setup
- Prompt Turn
- Transports
- File System
- Schema
- Tool Calls
- ACP Libraries (TypeScript/Python/Rust/…)
- ACP Community Libraries
- ACP TypeScript SDK (npm)
- ACP TypeScript SDK (GitHub)
- ACP Python SDK (GitHub)
- agent-client-protocol (crates.io)
- Claude Agent SDK Overview
- JSON-RPC 2.0 Specification