メインコンテンツまでスキップ

Cursor での Agent Client Protocol(ACP)

Agent Client Protocol with Cursor

Agent Client Protocol(ACP)は、エディタクライアントとコーディングエージェントが、共有された JSON-RPC ベースのインターフェースを介して対話できるようにするオープンスタンダードです。すべてのエディタがすべてのエージェントへの独自のブリッジを構築する代わりに、クライアントとエージェントの両方が ACP を話し、その組み合わせが交換可能になります。Cursor は cursor-agent を通じてその会話のエージェント側を担うことができ、つまり ACP 準拠のクライアントであれば Cursor のエージェントを動かせます。本ガイドでは、プロトコル、セッションライフサイクル、ACP 経由で Cursor を公開する方法、実際のメッセージフロー、独自のクライアントの構築方法、そして現在のギャップがどこにあるかを説明します。

Agent Client Protocol とは​

ACP は、エディタクライアントがコーディングエージェントとどのように対話するかを定義するオープンスタンダードです。このプロトコルは Zed によって広められ、同エディタがさまざまなエージェントを動かすために使用していますが、意図的にエディタ非依存かつエージェント非依存です。

中核となる設計上の選択は単純です:

  • トランスポートは stdio 上の JSON-RPC。 クライアントはエージェントをサブプロセスとして起動し、stdin と stdout で改行区切りの JSON-RPC メッセージを交換します。
  • どちらの側もリクエストと通知を送信できる。 クライアントはエージェントにセッションの開始とプロンプトの送信を求め、エージェントはクライアントにツール実行の許可を求め、進捗をストリームで返します。
  • セッションは明示的。 作業は newSession で作成されたセッション内で行われ、作業ディレクトリとオプションを伴います。

トランスポートが素の stdio でメッセージが素の JSON であるため、ACP はプロセスを起動できるあらゆる場所で機能します。ローカルのエディタ、ターミナルツール、ヘッドレスの CI ランナーです。

ACP は MCP ではありません

MCP はエージェントを外部ツールとデータソースに接続します。ACP はエディタクライアントをエージェントに接続します。両者は異なる問題を解決し、一般的に一緒に使われます。ACP エージェント自身が MCP クライアントであることもあります。

ACP のアーキテクチャ​

アーキテクチャには 3 つの部分があります。クライアント、エージェント、そしてそれらの間のトランスポートです。クライアントは UI と権限を所有し、エージェントはモデルとツールを所有します。

レイヤー役割例
クライアント会話をレンダリングし、ユーザー入力を受け取り、権限リクエストに応答するZed、カスタムエディタ、ヘッドレススクリプト
エージェントモデルを実行し、ツールを実行し、更新をストリームするcursor-agent acp、その他の ACP エージェント
トランスポートJSON-RPC メッセージを双方向に運ぶstdio 上の改行区切り JSON

セッションライフサイクルは固定された順序です。一度理解すれば、メッセージフローは台本のように読めます。

initialize -> client and agent agree on protocol version and capabilities
newSession -> client asks the agent to create a session with a cwd and options
prompt -> client sends user input into the session
session/update -> agent streams progress, tool calls, and messages back (notification)
permission request-> agent asks the client to approve a tool action
cancel -> client aborts the current turn

エージェントは、まずクライアントに尋ねることなく保護されたリソースに作用することはありません。その権限の往復こそが、UI を自律エージェントの制御下に保つものです。

ACP が重要な理由​

ACP が重要であるのは、エディタをエージェントから切り離すからです。その価値は 3 か所に現れます。

エディタの可搬性​

Cursor と別のクライアントが両方とも ACP を話すなら、特注の統合なしに同じエージェントをどちらでも使えます。エージェントの設定、モデルの選択、ルールはエディタではなくエージェントに付いてきます。

準拠した任意のクライアントで Cursor のエージェントを再利用する​

各エディタでエージェントの挙動を再実装する代わりに、任意の ACP クライアントを cursor-agent に向け、そのクライアントの UI 内で Cursor の推論、ツール使用、モデル選択を得られます。これは Cursor ではないエディタに標準化しているが Cursor のエージェント挙動を望むチームに特に有用です。

ヘッドレス自動化​

トランスポートが stdio であるため、エディタなしでスクリプトや CI ジョブからエージェントを動かせます。それにより、パイプラインでのスクリプト化された再現可能なエージェント実行が可能になります。

# A headless run is just a client that writes JSON-RPC to the agent's stdin
cat initialize.json newSession.json prompt.json | cursor-agent acp
ヘッドレス実行にも権限が必要です

スクリプトからエージェントを動かすとき、ツール呼び出しを「承認」する人間はいません。権限モードを事前に決め、機密の認証情報がある環境では決して自動実行をデフォルトにしないでください。

cursor-agent で Cursor を ACP 経由で公開する​

cursor-agent は、接続の ACP エージェント側として動作できる CLI です。Cursor CLI をインストールするのと同じ方法でインストールします。

# Install via npm
npm install -g cursor-agent

# Or install via the Cursor CLI script
curl https://cursor.com/install -fsS | bash

# Confirm it is on your PATH
cursor-agent --version

ACP モードで実行する​

ACP のエントリポイントはサブコマンドです。実行すると、プロセスは stdio 上で JSON-RPC を話し、クライアントの接続を待ちます。

# Start the agent as an ACP server, talking JSON-RPC over stdio
cursor-agent acp

# Useful flags
cursor-agent acp --cwd /path/to/project # set the working directory
cursor-agent acp --model claude-sonnet-4-5 # pin a model for the session
cursor-agent acp --verbose # log messages for debugging

認証​

エージェントはプロンプトを提供する前に認証情報が必要です。一度ログインすると認証情報はローカルに保存されます。

# Interactive login, opens a browser flow
cursor-agent login

# Check current auth status
cursor-agent status

# Log out and clear stored credentials
cursor-agent logout

エージェントが起動するのにもかかわらずすべてのプロンプトが失敗する場合、原因はほぼ常に認証の欠落または期限切れです。他の何かをデバッグする前に cursor-agent status を実行してください。

セッションの設定​

セッションは作業ディレクトリと、その会話でエージェントがどう振る舞うかを左右するオプションを伴って作成されます。

モデルの選択​

モデルは起動時に固定することも、セッションごとに選ぶこともできます。セッションごとに渡すことで、単一のエージェントプロセスが異なるモデル設定を持つ異なるクライアントにサービスを提供できます。

{
"method": "session/new",
"params": {
"cwd": "/home/dev/project",
"model": "claude-sonnet-4-5",
"mcpServers": []
}
}

コンテキストウィンドウ使用量のレポート​

一部のクライアントはコンテキストウィンドウがどれだけ埋まっているかを表示します。エージェントは、クライアントがサポートする場合、セッション更新の一部としてトークン使用量をレポートします。

{
"method": "session/update",
"params": {
"sessionId": "sess_01HXYZ",
"update": {
"type": "usage",
"inputTokens": 12480,
"outputTokens": 2310,
"contextLimit": 200000
}
}
}

権限モードと作業ディレクトリ​

権限モードは、ツールを実行する前にエージェントが尋ねるかどうかを決めます。対話型クライアントでは、デフォルトは尋ねることです。ヘッドレス自動化では、意図的に選びます。

  • Ask-per-tool: エージェントは権限リクエストを送って待ちます。最も安全で、対話型エディタに最適です。
  • Auto-run: エージェントは尋ねずに作業ディレクトリ内で実行します。高速で、サンドボックスでのみ適切です。
  • Read-only: エージェントは読み取りと検索はできますが決して書き込みません。レビュー専用のセッションに適しています。

newSession の cwd は、エージェントがプロジェクトとして扱うものを区切ります。その外のファイルに触れるべきではありません。

JSON-RPC メッセージフローの例​

以下は最小限ですが完全な会話です。すべてのメッセージはワイヤー上の 1 行の JSON です。

初期化​

クライアントは initialize で開始し、エージェントはその機能とプロトコルバージョンで応答します。

{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": 1,
"clientCapabilities": {
"fs": { "readTextFile": true, "writeTextFile": true },
"terminal": true
}
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": 1,
"agentCapabilities": {
"promptCapabilities": { "image": false },
"loadSession": true
}
}
}

新しいセッション​

機能について合意されたら、クライアントはセッションを作成し、ID を受け取ります。

{
"jsonrpc": "2.0",
"id": 2,
"method": "session/new",
"params": { "cwd": "/home/dev/project", "mcpServers": [] }
}
{
"jsonrpc": "2.0",
"id": 2,
"result": { "sessionId": "sess_01HXYZ" }
}

プロンプト​

クライアントはユーザーのターンを送ります。これはリクエストであることに注意してください。エージェントはそれを承認しますが、実際の内容はストリーミング通知として届きます。

{
"jsonrpc": "2.0",
"id": 3,
"method": "session/prompt",
"params": {
"sessionId": "sess_01HXYZ",
"content": [{ "type": "text", "text": "Add input validation to the signup route." }]
}
}

ストリーミング更新​

エージェントが作業するにつれて、session/update 通知を ID なしで発行します。これらは一方向だからです。

{
"jsonrpc": "2.0",
"method": "session/update",
"params": {
"sessionId": "sess_01HXYZ",
"update": { "type": "agent_message_chunk", "text": "Reading src/routes/signup.ts" }
}
}

権限リクエスト​

保護されたアクションの前に、エージェントはクライアントに尋ねます。クライアントは標準の JSON-RPC リクエストの仕組みを通じて応答します。

{
"jsonrpc": "2.0",
"id": "perm-1",
"method": "session/request_permission",
"params": {
"sessionId": "sess_01HXYZ",
"toolCall": {
"title": "Write file",
"path": "src/routes/signup.ts"
}
}
}
{
"jsonrpc": "2.0",
"id": "perm-1",
"result": { "outcome": { "outcome": "selected", "optionId": "allow" } }
}

キャンセル​

ターンを中止するには、クライアントは session/cancel を送ります。エージェントは現在の作業を停止し、キャンセルの停止理由でプロンプトリクエストを終えます。

{
"jsonrpc": "2.0",
"method": "session/cancel",
"params": { "sessionId": "sess_01HXYZ" }
}

独自の ACP クライアントを構築する​

最小限のクライアントはエージェントを起動し、stdin に JSON-RPC の行を書き、stdout から改行区切りの JSON を解析します。以下のスニペットは TypeScript での形を示しています。

import { spawn } from "node:child_process";
import { createInterface } from "node:readline";

const agent = spawn("cursor-agent", ["acp", "--cwd", process.cwd()], {
stdio: ["pipe", "pipe", "inherit"],
});

let nextId = 1;

function send(method: string, params: unknown): void {
const message = { jsonrpc: "2.0", id: nextId++, method, params };
agent.stdin.write(JSON.stringify(message) + "\n");
}

// Read newline-delimited JSON-RPC from the agent
const reader = createInterface({ input: agent.stdout });
reader.on("line", (line) => {
if (!line.trim()) return;
const msg = JSON.parse(line);

// Notifications have no id
if (!("id" in msg)) {
if (msg.method === "session/update") {
console.log("update:", JSON.stringify(msg.params.update));
}
return;
}

// A request from the agent needs a reply
if (msg.method === "session/request_permission") {
const reply = {
jsonrpc: "2.0",
id: msg.id,
result: { outcome: { outcome: "selected", optionId: "allow" } },
};
agent.stdin.write(JSON.stringify(reply) + "\n");
return;
}

console.log("response:", JSON.stringify(msg.result ?? msg.error));
});

// Kick off the session
send("initialize", { protocolVersion: 1, clientCapabilities: {} });
send("session/new", { cwd: process.cwd(), mcpServers: [] });

覚えておくべき重要なルールは次のとおりです。すべてのリクエストには id があり、一致する応答を期待します。通知には決して id がありません。エージェントもあなたにリクエストを送れるため、リーダーは双方向の両方を扱わなければなりません。

生のワイヤーをログに記録する

クライアント統合が誤動作したら、ロジックに触れる前に双方向のすべての行をログに記録してください。ACP のバグは通常、欠落した id、失われた改行、または決して送られなかった応答であり、生のワイヤーはその 3 つすべてを明らかにします。

制限と現在のギャップ​

ACP は若く、すべての Cursor の機能がそれを通じて公開されているわけではありません。その上に構築する前に、何を手放すのかを知ってください。

システムプロンプトのカスタマイズなし​

ACP のセッションオプションは現在、クライアントが IDE 内で .cursor/rules が行うようには、エージェントのシステムプロンプトを置き換えたり拡張したりできません。プロジェクトで設定されたルールは依然としてエージェントによって適用されますが、クライアントは独自のものを注入できません。

コンテキストウィンドウレポートが不完全​

使用量レポートは存在しますが、完全またはリアルタイムであることは保証されていません。正確なコンテキストメーターを望むクライアントは、ACP の使用量更新だけに頼るべきではありません。

IDE との機能同等性の違い​

いくつかの IDE 機能は ACP 経由では利用できない、または低下しています:

  • クライアントが実装しない限り、提案された編集の視覚的な差分レビューがクライアント内にない。
  • バックグラウンドエージェントとクラウドエージェントはこの経路では駆動されない。
  • 一部のツールと MCP の挙動が IDE の Agent モードとは異なる。
  • Tab 補完は別個のシステムであり、ACP にはまったく含まれない。

トラブルシューティング​

エージェントが見つからない​

クライアントが cursor-agent を起動して ENOENT が返る場合、バイナリが起動を行うプロセスの PATH にありません。spawn 呼び出しで絶対パスを使用し、GUI クライアントはターミナルとは異なる PATH を継承する可能性があることを覚えておいてください。

# Find the absolute path and use it in the client config
which cursor-agent

認証エラー​

すべてのプロンプトが認証エラーで失敗する場合、エージェントに有効な認証情報がありません。cursor-agent status を実行し、次に cursor-agent login を実行し、クライアントがログインしたユーザーやホームディレクトリとは異なるものの下で動作していないか確認してください。

ストリーミング更新がない​

プロンプトリクエストは返るのに更新がまったく届かない場合、クライアントが通知を正しく解析していない可能性が高いです。通知には id がないため、応答だけを扱うリーダーはそれらを捨てます。行リーダーが改行で発火し、パーサーが id のないメッセージを扱うことを確認してください。

クライアントが権限プロンプトをまったく受け取らない​

エージェントが尋ねずにツールを実行する場合、セッションが自動実行権限モードで作成されたか、クライアントが initialize 中に権限リクエストを扱う機能を通知しなかったためです。セッションオプションと送信した機能の両方を確認してください。

症状原因修正
spawn で ENOENTエージェントバイナリが PATH にないspawn 呼び出しで絶対パスを使用する
すべてのプロンプトが失敗する認証の欠落または期限切れcursor-agent status を実行してから cursor-agent login
session/update メッセージがないリーダーが id 付きの応答のみを扱うid のない通知を扱う
権限プロンプトがない自動実行モード、またはクライアント機能の欠落ask-per-tool を設定し、機能を通知する
プロンプトが永遠にハングするリクエストへの応答が決して送られないすべてのエージェントリクエストに一致する id で返信する
応答の解析エラー改行なしでメッセージが連結される1 行につき正確に 1 つの JSON オブジェクトを書き読みする

ACP はエディタとエージェントの関係を契約に変え、その契約こそが Cursor のエージェントをプロトコルを話すあらゆる場所から使えるようにします。上記のメッセージフローから始め、ワイヤーをログに記録すれば、統合は書き直しではなく少量の配管作業になります。