コンテンツにスキップ

STUDY NOTES

第33回: Agent Client Protocol (ACP) で Zed エディタに deepagents を統合 — 連載の総まとめ

Agent Client Protocol (ACP) という新標準で、Zed エディタから自作 deepagents エージェントを呼び出す最小構成を作る。MCP(第19回)と双子の関係にある新プロトコル。

比喩: - MCP (Model Context Protocol) = 「LLM ホストが外部ツールを呼ぶ」プロトコル(Cursor → My MCP Server) - ACP (Agent Client Protocol) = 「エディタが外部エージェントを呼ぶ」プロトコル(Zed → My Agent Server)

どちらも JSON-RPC over stdio で動き、外部システムをサブプロセスとして立ち上げて疎結合に統合する LSP 系設計。MCP は「機能の提供」、ACP は「会話の提供」と思うと違いが分かりやすい。

サンプルコードはたった 28 行server.ts)。deepagents-acp パッケージが全てを吸収しているので、最小限の boilerplate でエディタ統合が成立する。

観点 第32回 (deepagents + CopilotKit) 第33回 (deepagents + ACP)
クライアント Next.js React UI Zed エディタ
プロトコル HTTP (CopilotRuntime) stdio + JSON-RPC (ACP)
エージェント側 deepagents + LocalShellBackend deepagents + ACPFilesystemBackend
起動 mise run dev → localhost:3000 Zed が サブプロセスとして起動
UI CopilotChat Zed のエージェントパネル
行数 〜数百行 28 行のみ

位置付け: 第19回 (MCP basics) → 第20回 (MCP × LangGraph) → 第32回 (deepagents × CopilotKit) と来た流れの 最終形。連載が示してきた「LLM アプリは独立サーバとして提供し、ホストが薄いプロトコルで統合する」設計思想を エディタ統合で完結する。


全体像

33/
├── server.ts             ← ★ 28 行で完結。DeepAgentsServer をビルドして start
├── package.json          ← @langchain/anthropic + deepagents-acp の 2 依存だけ
├── bun.lock
└── README.md             ← Zed settings.json への登録方法

ランタイムフロー(Zed エディタからのメッセージ送信時):

sequenceDiagram
    participant User
    participant Zed as Zed Editor
    participant Sub as bun run server.ts<br/>(subprocess)
    participant Srv as DeepAgentsServer
    participant Agent as deepagents エージェント<br/>(Claude Sonnet 4.6)
    participant FS as ACPFilesystemBackend<br/>(virtualMode)
    participant Files as ワークスペースファイル

    Note over User,Zed: settings.json で<br/>"Deep Agents" を登録済み

    User->>Zed: New Deep Agents Thread
    Zed->>Sub: subprocess 起動 (stdio パイプ)
    Sub->>Srv: server.start()
    Sub-->>Zed: initialize (capabilities 交換)

    User->>Zed: 「このバグ修正して」
    Zed->>Sub: ACP message (JSON-RPC)
    Sub->>Srv: route to agent
    Srv->>Agent: invoke

    loop ReAct ループ
        Agent->>FS: readFile("./src/foo.ts")
        FS->>Files: 実ファイル読み
        Files-->>FS: 内容
        FS-->>Agent: ファイル内容
        Agent->>FS: writeFile("./src/foo.ts", patched)
        FS->>Files: 書き込み
        FS-->>Agent: OK
    end

    Agent-->>Srv: 最終応答
    Srv-->>Sub: streaming chunks
    Sub-->>Zed: ACP message (JSON-RPC)
    Zed-->>User: エージェントパネルに表示

ACP の本質: Zed エディタは ACP プロトコルさえ守れば、どんなエージェント(Claude / GPT / Gemini / 自作)も統合できる。LSP が「言語サーバを切り替えれば任意の言語を扱える」のと同じく、エディタ × エージェントの組合せ爆発を防ぐ標準。


使用ライブラリ・原理

deepagents-acp パッケージ — ACP プロトコルを完全カプセル化
import { DeepAgentsServer, ACPFilesystemBackend } from "deepagents-acp";

const server = new DeepAgentsServer({
  agents: {
    name: "coding-assistant",
    description: "Deep Agentsベースのコーディングアシスタント",
    model: "claude-sonnet-4-6",
  },
  debug: true,
});

await server.start();

DeepAgentsServer がやってくれること:

  1. stdio リスナー起動 (Zed から JSON-RPC を受信)
  2. ACP プロトコルメッセージのパース (initialize, sessions/new, messages/send 等)
  3. 内部で deepagentscreateDeepAgent を呼んで通常のエージェント構築
  4. agents config から model / system prompt を組み立て
  5. ストリーミング応答を ACP 仕様に従って返す
ACPFilesystemBackend — Zed のファイルシステム API を経由したアクセス
const backend = new ACPFilesystemBackend(...);
backend.virtualMode = true;
backend.resolveAbsPath = function (filePath) {
  return this.resolvePath(filePath);
};

特徴:

  • ACP プロトコル経由で Zed が開いているワークスペース内のファイルを読み書き
  • virtualMode で workspace 外への到達を制限
  • Zed が「ユーザに見えるファイル変更」を可視化できる(diff 表示など)
  • LocalShellBackend (32回) が「Node.js プロセスから直接 fs.readFile」だったのに対し、これは エディタ越し にアクセス
server.createBackend フックの上書き (workaround)
const originalCreateBackend = server.createBackend.bind(server);
server.createBackend = function (config) {
  const backend = originalCreateBackend(config);
  if (backend instanceof ACPFilesystemBackend) {
    backend.virtualMode = true;
    backend.resolveAbsPath = function (filePath) {
      return this.resolvePath(filePath);
    };
  }
  return backend;
};

これは ACPFilesystemBackend のバグ回避コード:

  1. deepagents-acp 0.1.8ACPFilesystemBackendvirtualMode: true を public API で設定する手段がない(バグ)
  2. かつ resolveAbsPath メソッドが未実装で、仮想パスを実パスに変換できない
  3. 対策として server.createBackend をオーバーライドし、生成された backend に手動で virtualMode = trueresolveAbsPath を後付けインジェクション

将来の deepagents-acp リリースで修正されれば、この workaround は不要になる。

Agent Client Protocol (ACP) — Zed が標榜する「エージェント統合の LSP」

ACP の主要メッセージ:

メッセージ 役割
initialize Capabilities 交換
sessions/new 新しい会話スレッド作成
messages/send ユーザメッセージ送信
messages/stream エージェントからのストリーミング応答
sessions/abort 実行中の処理を中断
tools/call エージェント側から「Zed のツール」(ファイル読み書き等)を呼ぶ

MCP との対比: - MCP: ホスト(Cursor)→ サーバ(自作ツール)の「機能呼び出し」 - ACP: ホスト(Zed)→ サーバ(自作エージェント)の「会話セッション」 - どちらも JSON-RPC 2.0 over stdio という同じトランスポートで動く


ファイル別の役割

ファイル 役割
server.ts 唯一の実コード。DeepAgentsServer 設定 + バックエンドのバグ workaround + server.start()
package.json @langchain/anthropicdeepagents-acp のみの 2 依存
bun.lock bun の依存ロック
README.md mise install + Zed settings.json への登録方法

行レベルの工夫(中核ロジックの抜粋)

① DeepAgentsServer の最小設定 (server.ts:1-11)
import { DeepAgentsServer, ACPFilesystemBackend } from "deepagents-acp";

const server = new DeepAgentsServer({
  agents: {                                                                  // ①
    name: "coding-assistant",
    description: "Deep Agentsベースのコーディングアシスタント",
    model: "claude-sonnet-4-6",
  },
  debug: true,                                                               // ②
});
やってること なぜそうする
agents config に name/description/model を 1 つだけ指定 内部で createDeepAgent(model: ChatAnthropic(...)) 等を組み立ててくれる。LangChain の boilerplate が消える
debug: true で詳細ログ 開発中は ACP プロトコルの送受信を全部 stderr に出す。Zed のログパネルで監視できる
virtualMode 有効化の workaround (server.ts:15-25)
const originalCreateBackend = server.createBackend.bind(server);             // ①
server.createBackend = function (config) {
  const backend = originalCreateBackend(config);                             // ②
  if (backend instanceof ACPFilesystemBackend) {
    backend.virtualMode = true;                                              // ③
    backend.resolveAbsPath = function (filePath) {                           // ④
      return this.resolvePath(filePath);
    };
  }
  return backend;
};
やってること なぜそうする
server.createBackend をオリジナル参照保持 純粋に「メソッド差し替え」する monkey patch パターン
オリジナルを呼んで通常通り backend 生成 バックエンドの初期化ロジックは触らない
virtualMode = true を後付け代入 deepagents-acp 0.1.8 には public API での設定経路がないため、direct assignment で強制有効化
resolveAbsPathresolvePath 経由でフォールバック実装 内部で resolveAbsPath が呼ばれるが未実装なので、resolvePath で代用
await server.start()server.ts:27
await server.start();

これだけで:

  1. stdio リスナー起動(Zed からの subprocess 起動を待ち受け)
  2. ACP プロトコル全メッセージのハンドラ登録
  3. プロセスが alive な限り会話セッション継続

プロセスは Zed が kill するまで生き続ける。subprocess なので、Zed を閉じれば自動終了。

④ Zed settings.json への登録 (README より)
{
  "agent_servers": {
    "Deep Agents": {
      "type": "custom",
      "command": "bun",
      "args": [
        "run",
        "/absolute/path/to/softwaredesign-llm-application/33/server.ts"
      ],
      "env": {
        "ANTHROPIC_API_KEY": "sk-ant-..."
      }
    }
  }
}

ポイント:

  • type: "custom" で「自作 ACP サーバ」と宣言(公式 Claude/Cursor agent 等とは別ジャンル)
  • 絶対パス必須 (ACP 仕様)。相対パスでは Zed が解決できない
  • env で APIキー注入。Zed は subprocess を .env 抜きで起動するので、settings.json で直接渡す必要がある(ここに API キー生書きは要注意

学んだこと(要点)

  • ACP は MCP の双子。MCP が「機能提供」、ACP が「会話提供」。両方とも LSP 系設計 で、stdio + JSON-RPC で疎結合化
  • deepagents-acp パッケージが 28 行で全てを覆い隠す。LangChain + LangGraph + ACP の三層が DeepAgentsServer 1 行で組み立つ
  • Zed エディタが ACP 公式ホスト。VS Code は MCP 寄り、Zed は ACP 寄りという棲み分け
  • ACPFilesystemBackend は Zed 越しにファイル操作するので、エディタ側で diff 表示・編集確認 UI が綺麗に出る
  • 公式ライブラリのバグ workaround を直接書くスタイルは production では避けたいが、サンプルなら可。prototype chain や instance への monkey patch で対応する
  • 「Zed の settings.json に API キー直書き」がデフォルト: production 用途では Zed Secrets API 経由などの secure な仕組みが必要
  • Zed が subprocess として agent を起動するので、.env ファイルは無視される。全環境変数を settings.json か Zed の env 設定で渡す必要がある
  • 連載の総まとめとして、第19回 (MCP basics) → 第20-22回 (MCP + LangGraph) → 第29-31回 (Middleware) → 第32回 (CopilotKit) → 第33回 (ACP) のアーキテクチャ進化が美しい

拡張アイデア

  1. スキルファイルを追加workspace/.agent/skills/ に SKILL.md を置いて、エージェントに「コードレビュー」「リファクタリング案出し」「テスト生成」等のスキルを足す(第32回パターンと同じ)
  2. 複数モデル切替 UI — Zed の settings.jsonclaude-sonnet-4-6claude-opus-4-7claude-haiku-4-5 に切替して比較
  3. MCP との統合 — DeepAgents の tools に MCP ツール(mcp-use パッケージ等)を混ぜる。MCP × ACP の二重統合
  4. エージェント別 server を複数登録coding-assistant / doc-writer / test-generator などを別 ACP サーバとして起動 → Zed で切替
  5. virtualMode バグの upstream PRdeepagents-acp の GitHub に Issue/PR を立てて、virtualMode を public API で設定可能にする
  6. HumanInTheLoop パターン — ファイル書き込み前に Zed 側で承認ダイアログ(第31回パターン)
  7. 会話のスレッド永続化MemorySaver でなく SqliteSaverDeepAgentsServer に注入し、Zed を再起動しても会話継続
  8. 他のエディタへの展開 — ACP は Zed 専用ではない(仕様は公開)。Neovim / Emacs / VS Code のプラグインを書いて同じ ACP サーバを統合

現代版に移植するなら

1. API キー設定は .env.op + op run に切り替える(CLAUDE.md ルール 8)

通常起動なら:

# 33/.env.op
ANTHROPIC_API_KEY=op://Personal/anthropic-api-key/credential
op run --env-file=.env.op -- bun run server.ts

ただし Zed 経由起動には特殊な工夫が必要: Zed が subprocess を .env なしで起動するので、op run を間に挟む必要がある。Zed の settings.json を以下に書き換え:

{
  "agent_servers": {
    "Deep Agents": {
      "type": "custom",
      "command": "op",
      "args": [
        "run",
        "--env-file=/absolute/path/to/softwaredesign-llm-application/33/.env.op",
        "--",
        "bun",
        "run",
        "/absolute/path/to/softwaredesign-llm-application/33/server.ts"
      ]
    }
  }
}

これで op run が API キーを展開してから bun run server.ts を起動 → settings.json に生キーが残らない。

2. claude-sonnet-4-6 の最新化チェック

2026 年現在 claude-sonnet-4-6 は十分新しい。claude-opus-4-7 に上げると推論力が向上するがコスト 5 倍。コーディングタスクでは sonnet-4-6 がコスパ最高なのでこのままで OK。

3. deepagents-acp バージョンアップ追従

0.1.8 から将来 virtualMode が public API 化されたら、server.ts:13-25 の workaround を削除して config 経由に書き換え:

const server = new DeepAgentsServer({
  agents: {...},
  backendConfig: {
    type: "filesystem",
    virtualMode: true,                  // 公式 API 化したら
  },
});
4. await server.start() のエラーハンドル

現状は start() が throw すると bun プロセスが終了。Zed 側で再起動しないと回復しない。try-catch + 再接続ループ を入れると堅い:

while (true) {
  try {
    await server.start();
    break;
  } catch (e) {
    console.error("[server] crashed:", e);
    await new Promise((r) => setTimeout(r, 1000));
  }
}
5. ログを構造化

debug: true で出る生ログを stderr に流すと Zed のログパネルがノイジー。pino 等で JSON 化して info 以上だけ stderr に出すと整理される。


既知の不具合・注意点

  • deepagents-acp 0.1.8ACPFilesystemBackend バグ: 上記の通り virtualModeresolveAbsPath がない。workaround を入れているが、ライブラリ更新時に削除を忘れる可能性
  • settings.json の絶対パス: クローン場所が変わると settings.json も更新必要。マシン間で ~/.config/zed/settings.json を同期している場合は注意
  • 環境変数の二重管理: .env と settings.json の両方に書く運用になりがち(うっかり)。1 つの真の出所(op)に集約推奨
  • agents config の単数指定: README には agents を 1 つだけ書く例。複数 agents 登録時のフォーマットが docs 不明(要確認)
  • stdio バッファリング: Zed が subprocess の stdout を都度フラッシュ前提で待つので、Node.js のデフォルトバッファリングと相性が悪い場面あり。debug: true で発覚しがち
  • エージェントが任意のファイルを編集する: コーディングアシスタントなので 意図せず重要ファイルを書き換える リスク。git で常にコミットしてから依頼する慣習が必要
  • ANTHROPIC_API_KEY を settings.json に生書きする推奨例: README が "ANTHROPIC_API_KEY": "sk-ant-..." を例示。グローバルルール(生キー禁止)に反する ので op run 経由に必ず置換すべき
  • Zed プロセスが死ぬと subprocess も孤児化: bun プロセスが孤児として残る場面あり。手動 kill か process.on('disconnect') ハンドラ実装

記事参照

  • Software Design 2026 年 6 月号(推定)連載第33回(最終回)「ACP で Zed × 自作エージェント」
  • 関連: 第19回 STUDY_NOTES — MCP basics。プロトコル設計の妹分
  • 関連: 第32回 STUDY_NOTESdeepagents を CopilotKit で React と統合
  • 関連: 第20回 STUDY_NOTES — MCP × LangGraph。stdio + JSON-RPC の同じ流儀
  • ACP 公式: https://agentclientprotocol.com/
  • deepagents-acp: https://github.com/langchain-ai/deepagentsjs/tree/main/libs/acp
  • Zed External Agents: https://zed.dev/docs/ai/external-agents
  • 連載全体の振り返り: 第1回 (Chainlit) → 第33回 (ACP + Zed) の 3 年間で 「単発 LLM 呼び出し」から「プロトコル標準化された分散エージェント」まで一直線

作成: 2026-05-25 / 最終更新: 2026-06-22