ACP
概要
Cursor CLI は、高度な連携向けに **ACP (Agent Client Protocol) **をサポートしています。agent acp を実行すると、JSON-RPC を使用して stdio 経由でカスタムクライアントを接続できます。
詳細は、公式の Agent Client Protocol ドキュメントをご覧ください。
ACP はカスタムクライアントや連携を構築するためのものです。通常のターミナル
ワークフローでは、agent を使用する対話型 CLI を使ってください。
ACP サーバーを起動する
ACP モードで Cursor CLI を起動します。
agent acpトランスポートとメッセージ形式
- トランスポート:
stdio - プロトコルエンベロープ: JSON-RPC 2.0
- フレーミング: 改行区切り JSON (1行につき1メッセージ)
- 方向:
- クライアントはリクエスト/通知を
stdinに書き込む - Cursor CLIはレスポンス/通知を
stdoutに書き込む - ログは
stderrに書き込まれる場合がある
- クライアントはリクエスト/通知を
リクエストフロー
一般的な ACP セッションのフロー:
initializemethodId: "cursor_login"を指定してauthenticatesession/new(またはsession/load)session/prompt- モデルが出力をストリーミングしている間に、
session/update通知を処理 - 判断を返して
session/request_permissionを処理 - 必要に応じて
session/cancelを送信
認証
Cursor CLI では、ACP の認証方法として cursor_login が通知されます。実際には、既存の CLI 認証方法を使って、起動前にあらかじめ認証できます。
agent login--api-key(またはCURSOR_API_KEY)--auth-token(またはCURSOR_AUTH_TOKEN)
ルート CLI コマンドからエンドポイントと TLS のオプションを指定することもできます。
agent --api-key "$CURSOR_API_KEY" acpagent -e https://api2.cursor.sh acpagent -k acpセッション、モード、権限
セッション
session/newでセッションを作成session/loadで既存の会話を再開
モード
ACP セッションでは、CLI と同じ基本モードを利用できます。
agent(すべてのツールを使用可能)plan(プランニング、参照専用)ask(Q&A、参照専用)
権限
ツールの実行に承認が必要な場合、Cursor は session/request_permission を送信します。クライアントは次のいずれかを返す必要があります。
allow-onceallow-alwaysreject-once
クライアントが権限リクエストに応答しない場合、ツールの実行がブロックされる可能性があります。
MCP サーバー
ACP は、プロジェクトレベルまたはユーザーレベルの .cursor/mcp.json で定義された MCP サーバー をサポートします。プロジェクトディレクトリから agent を起動し、使用するサーバーを承認してください。
Cursor ダッシュボードで設定したチームレベルの MCP サーバーは、ACP モードではサポートされていません。
Cursor 拡張メソッド
Cursor は、より充実したクライアント UX を実現するために ACP 拡張メソッドを送信します。メソッドには 2 種類あります。
- ブロッキングメソッド (
cursor/ask_question、cursor/create_plan) :エージェントはレスポンスを受け取るまで待機します。クライアントは JSON-RPC レスポンスを返す必要があります。 - 通知メソッド (
cursor/update_todos、cursor/task、cursor/generate_image) :エージェントはこれらを fire-and-forget 通知として送信します。クライアントで表示できますが、応答は不要です。
| メソッド | 種類 | 用途 |
|---|---|---|
cursor/ask_question | ブロッキング | ユーザーに選択式の質問をする |
cursor/create_plan | ブロッキング | プランの明示的な承認をリクエストする |
cursor/update_todos | 通知 | todo の状態更新をクライアントに通知する |
cursor/task | 通知 | サブエージェントのタスク完了をクライアントに通知する |
cursor/generate_image | 通知 | 生成画像の出力をクライアントに通知する |
cursor/ask_question
ユーザーに選択肢付きの質問を提示します。クライアントから応答があるまで、エージェントは待機します。
リクエスト:
interface CursorAskQuestionRequest { toolCallId: string; title?: string; questions: Array<{ id: string; prompt: string; options: Array<{ id: string; label: string }>; allowMultiple?: boolean; }>;}レスポンス:
interface CursorAskQuestionResponse { outcome: | { outcome: "answered"; answers: Array<{ questionId: string; selectedOptionIds: string[]; }>; } | { outcome: "skipped"; reason?: string } | { outcome: "cancelled" };}リクエスト例:
{ "toolCallId": "call_123", "title": "Need input", "questions": [ { "id": "q1", "prompt": "Which mode should I use?", "options": [ { "id": "agent", "label": "Agent" }, { "id": "plan", "label": "Plan" } ], "allowMultiple": false } ]}cursor/create_plan
ユーザーにプランの承認を求めます。クライアントがプランを承認または拒否するまで、エージェントは待機します。
リクエスト:
interface CursorCreatePlanRequest { toolCallId: string; name?: string; overview?: string; plan: string; todos: Array<{ id: string; content: string; status: "pending" | "in_progress" | "completed" | "cancelled"; }>; isProject?: boolean; phases?: Array<{ name: string; todos: Array<{ id: string; content: string; status: "pending" | "in_progress" | "completed" | "cancelled"; }>; }>;}plan: 完全なプランを説明する markdown 文字列。phases: 大規模なプラン向けに todo を名前付きフェーズに任意でグループ化します。
レスポンス:
interface CursorCreatePlanResponse { outcome: | { outcome: "accepted"; planUri?: string } | { outcome: "rejected"; reason?: string } | { outcome: "cancelled" };}リクエストの例:
{ "toolCallId": "call_124", "name": "Refactor tabs layout", "overview": "Tighten layout behavior and preserve existing UX.", "plan": "1. Inspect current tab sizing logic.\n2. Update layout calculations.\n3. Verify editor behavior.", "todos": [ { "id": "todo-1", "content": "Inspect current tab sizing logic", "status": "completed" }, { "id": "todo-2", "content": "Update layout calculations", "status": "in_progress" }, { "id": "todo-3", "content": "Verify editor behavior", "status": "pending" } ], "isProject": false}cursor/update_todos
クライアントのtodoリストを更新します。通知として送信されるため、レスポンスは不要です。
リクエスト:
interface CursorUpdateTodosRequest { toolCallId: string; todos: Array<{ id: string; content: string; status: "pending" | "in_progress" | "completed" | "cancelled"; }>; merge: boolean;}merge:trueの場合、これらのtodoを既存のリストに統合します。falseの場合、リスト全体を置き換えます。
レスポンス:
interface CursorUpdateTodosResponse { outcome: | { outcome: "accepted"; todos: Array<{ id: string; content: string; status: "pending" | "in_progress" | "completed" | "cancelled"; }>; } | { outcome: "rejected"; reason?: string } | { outcome: "cancelled" };}リクエスト例:
{ "toolCallId": "call_125", "todos": [ { "id": "1", "content": "Set up project structure", "status": "completed" }, { "id": "2", "content": "Add authentication", "status": "in_progress" }, { "id": "3", "content": "Write unit tests", "status": "pending" } ], "merge": true}cursor/task
サブエージェントのタスクをクライアントに通知します。通知として送信されるため、レスポンスは不要です。
リクエスト:
interface CursorTaskRequest { toolCallId: string; description: string; prompt: string; subagentType: | "unspecified" | "computer_use" | "explore" | "video_review" | "browser_use" | "shell" | "vm_setup_helper" | { custom: string }; model?: string; agentId?: string; durationMs?: number;}subagentType: 実行するサブエージェントの種類。カスタムサブエージェントタイプには{ custom: "your_type" }を使用します。agentId: 以前に作成したサブエージェントを再開するには、これを設定します。durationMs: タスクの実行時間。レスポンスに含まれます。
レスポンス:
interface CursorTaskResponse { outcome: | { outcome: "completed"; agentId?: string; durationMs?: number } | { outcome: "rejected"; reason?: string } | { outcome: "cancelled" };}リクエスト例:
{ "toolCallId": "call_126", "description": "Explore codebase", "prompt": "Find where authentication is handled and report the file paths.", "subagentType": "explore"}cursor/generate_image
生成された画像をクライアントに通知します。通知として送信されるため、レスポンスは不要です。
リクエスト:
interface CursorGenerateImageRequest { toolCallId: string; description: string; filePath?: string; referenceImagePaths?: string[];}filePath: 生成画像の推奨ファイルパス。referenceImagePaths: 入力として使用するリファレンス画像のパス。
レスポンス:
interface CursorGenerateImageResponse { outcome: | { outcome: "generated"; filePath: string; imageData?: string } | { outcome: "rejected"; reason?: string } | { outcome: "cancelled" };}リクエスト例:
{ "toolCallId": "call_127", "description": "Minimal flat app icon for a note-taking app", "filePath": "/tmp/icon.png", "referenceImagePaths": ["/tmp/reference.png"]}最小限の Node.js クライアント
この例では、カスタム ACP クライアントの最小限の制御フローを示します。
import { spawn } from "node:child_process";import readline from "node:readline";const agent = spawn("agent", ["acp"], { stdio: ["pipe", "pipe", "inherit"] });let nextId = 1;const pending = new Map();function send(method, params) { const id = nextId++; agent.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n"); return new Promise((resolve, reject) => pending.set(id, { resolve, reject }));}function respond(id, result) { agent.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, result }) + "\n");}const rl = readline.createInterface({ input: agent.stdout });rl.on("line", line => { const msg = JSON.parse(line); if (msg.id && (msg.result || msg.error)) { const waiter = pending.get(msg.id); if (!waiter) return; pending.delete(msg.id); msg.error ? waiter.reject(msg.error) : waiter.resolve(msg.result); return; } if (msg.method === "session/update") { const update = msg.params?.update; if (update?.sessionUpdate === "agent_message_chunk" && update.content?.text) { process.stdout.write(update.content.text); } return; } if (msg.method === "session/request_permission") { respond(msg.id, { outcome: { outcome: "selected", optionId: "allow-once" } }); }});const init = async () => { await send("initialize", { protocolVersion: 1, clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false }, clientInfo: { name: "acp-minimal-client", version: "0.1.0" } }); await send("authenticate", { methodId: "cursor_login" }); const { sessionId } = await send("session/new", { cwd: process.cwd(), mcpServers: [] }); const result = await send("session/prompt", { sessionId, prompt: [{ type: "text", text: "Say hello in one sentence." }] }); console.log(`\n\n[stopReason=${result.stopReason}]`);};init().finally(() => { agent.stdin.end(); agent.kill();});IDE 連携
ACP を使用すると、Cursor の AI エージェントを Cursor デスクトップアプリ以外のエディターでも利用できます。好みの開発環境向けにサードパーティ連携を構築または利用できます。
使用例
-
JetBrains IDEs — IntelliJ IDEA、WebStorm、PyCharmなどのJetBrains IDEをCursorのエージェントに接続できます。セットアップ手順はJetBrains連携ガイドを参照してください。
-
Neovim (avante.nvim) — avante.nvimを使用して、ACP経由でNeovimをCursorのエージェントに接続できます。下記のNeovimのセットアップを参照してください。
-
Zed —
agent acpを起動してstdio経由で通信することで、Zedのモダンなエディターと連携できます。Zedの拡張機能では、ACPクライアントプロトコルを実装してAIリクエストをCursorにルーティングできます。 -
カスタムエディター — 拡張機能をサポートする任意のエディターでACPクライアントを実装できます。エージェントプロセスを起動し、stdio経由でJSON-RPCメッセージを送信して、エディターのUIでレスポンスを処理します。
Neovim (avante.nvim)
avante.nvim は、AI 搭載のコーディングアシスタントを提供する Neovim プラグインです。ACP をサポートしているため、Neovim 内でエージェント型コーディングを行うために Cursor のエージェントに接続できます。
以下を lazy.nvim のプラグイン設定 (例:~/.config/nvim/lua/plugins/avante.lua) に追加します。
return { { "yetone/avante.nvim", event = "VeryLazy", version = false, build = "make", opts = { provider = "cursor", mode = "agentic", acp_providers = { cursor = { command = os.getenv("HOME") .. "/.local/bin/agent", args = { "acp" }, auth_method = "cursor_login", env = { HOME = os.getenv("HOME"), PATH = os.getenv("PATH"), }, }, }, }, dependencies = { "nvim-lua/plenary.nvim", "MunifTanjim/nui.nvim", "nvim-tree/nvim-web-devicons", { "MeanderingProgrammer/render-markdown.nvim", opts = { file_types = { "markdown", "Avante" }, }, ft = { "markdown", "Avante" }, }, }, },}主な設定:
provider: リクエストを Cursor のエージェント経由で送信するには"cursor"に設定します。mode: ファイル編集やターミナルコマンドを含むすべてのツールを使用するには"agentic"に設定します。チャットのみを使用する場合は"normal"を指定します。command:agentバイナリへのパスを指定します。デフォルトのインストールパスは~/.local/bin/agentです。別の場所にインストールした場合は変更してください。auth_method:"cursor_login"を使用します。認証するには、まずターミナルでagent loginを実行してください。
連携を作る
agent acpを子プロセスとして起動する- JSON-RPC を使用して stdin/stdout 経由で通信する
session/update通知を処理してストリーミングレスポンスを表示する- ツールに承認が必要な場合は
session/request_permissionに応答する - より充実した UX のために、必要に応じて Cursor 拡張メソッドを実装する
動作するリファレンス実装については、上記の最小限の Node.js クライアントを参照してください。