Cursor Agent SDK + FCoP: パッシブスキャンから能動的なコミュニケーションへ
これは2026年4月下旬に私が投稿したCursor Forumの機能要望への続報です:「すでにメールボックス(ファイル)はあります。必要なのはドアベルです。」 ColinはAgent SDKを勧めました。私たちはそれを土台にCodeFlowを構築しました。これが、その18日後に起きたことです。
I. 3つの段階——本質的に別物の3つのこと
ステージ1: OCR + CDP(パッシブスキャン)
FCoPの初期のマルチエージェントワークフローは、Pythonスクリプト上で動いていました。そのスクリプトはOCR + CDP(Chrome DevTools Protocol)を使ってCursorのUI上の人間のクリックを模倣し、エージェントに「新しいタスクがある」と認識させるようにしていました。数秒ごとにポーリングします。画面は常に表示されている必要がありました。ウィンドウが隠れてはいけません。
これはパッシブスキャンでした。エージェントは通知されません——ブルートフォースによって、タスクの前に押し出されるだけです。これはコミュニケーションではありません。監視です。
ステージ2: SDKのパイプは機能した——しかしエージェントは「チャット」しかできなかった
CodeFlowはOCR/CDPをCursor Agent SDK(@cursor/sdk)に置き換えました:InboxWatcherはファイルの着地を監視し、agent.send()がタスクを直接エージェントへ届けます。これは本当の通知です。スキャンは不要になりました。
ただし、すべてのセッションのJSONで1行だけ同じままでした:
"tool_calls_count": 0
エージェントはメッセージを受け取り、「何か」を「返信」して終了しました。ファイルは書き込まれません。レポートも作成されません。
理由はこうです:MCPInjectorがmode="stub"になっており、SDKに実際のMCPサーバーが注入されていませんでした。エージェントにはwrite_reportもwrite_taskもありません。ツールがなければアクションもできません。できるのはチャットだけです。
ドアベルが鳴りました。エージェントは空っぽの手で応えました。
ステージ3: MCP注入——最初の本当のコミュニケーション
パッシブスキャン → 本当の通知 → 本当の通知 + ツール呼び出し + ファイル化されたレポート。
tool_calls_count: 0 → 7は単なる数の変化ではありません——「押されて進む」から「自分で行動する」への質的な転換です。
II. ドアベルを見つける
鍵はCursor SDKの型定義を読むことでした。
agent.d.tsの中で、SendOptionsには見落とされがちなフィールドがありました:
interface SendOptions {
model?: ModelSelection;
mcpServers?: Record<string, McpServerConfig>; // ← here
local?: { force?: boolean };
// ...
}
agent.send(text, options)は、呼び出し時にMCPサーバー設定を受け取れます。リファクタリングは不要です。送るたびに、MCPサーバー設定をそのまま渡すだけ。
同時に、fcop-mcpのエントリポイントがもう半分を裏付けていました:
# python -m fcop_mcp → stdio MCP server
# uses FCOP_PROJECT_DIR environment variable to locate the project root
解決策:_buildSendOptions()で、fcop-mcpをstdioのMCPサーバーとして注入する。
III. 3つの変更、1つのブレイクスルー
AgentSdkAdapter.ts:CursorSdkAdapterOptionsにmcpServersを追加し、すべてのagent.send()呼び出しへ渡します。
sdk-factory.ts:f cop-mcpのstdio設定を自動で組み立てます:
fcop: {
type: "stdio",
command: pythonBin,
args: ["-m", "fcop_mcp"],
env: { FCOP_PROJECT_DIR: projectRoot }
}
main.ts:SDKアダプタを構築する前にfcopProjectRootを解決します。
そして4つ目の変更——最も見落とされやすいが、同じくらい重要なこと:
TaskDispatcher.ts:各タスクをディスパッチする前に、タスク文の先頭へロールコンテキストヘッダーを付与します。エージェントに「誰であるか」「どんなツールを持っているか」「そして必ずFCoPの4ステップのワークフローを完了まで実行する」ことを伝えます。
IV. ツールだけでは足りない——使い方も知る必要がある
MCPサーバーを注入してもロールコンテキストを追加しなければ、エージェントはツールを使わないかもしれません。どのファイルを書き込むべきかを自然言語で説明して、そのまま終了するかもしれません。
ツール注入は「できる」の問題を解決します。コンテキスト注入は「どうやるか」の問題を解決します——どの形式で、どのフィールドで、どのプロトコルで。
エージェントが同時に「write_reportツールを持っている」と「FCoPでは作業完了後にレポートを書くことが求められている——しかもここに正確な形式とフィールドがある」と理解したときに初めて、実際にツールを呼び出して、正しく構造化されたファイルを生成します。
V. あの数字
tool_calls_count: 7
2026年5月13日 14:55(UTC+8)に、session-1-mp3pfym2が完了しました——CodeFlowの履歴の中で、tool_calls_count > 0を持つ最初のセッションです。
エージェント(DEV-01)は55秒でfcop-mcpのツールを7回呼び出し、次を書き込みました:
fcop/reports/REPORT-20260513-014-DEV-to-PM-hello-world-smoke-task.md
以下は、そのファイルの内容をそのまま(改変なし)でアーカイブした完全な内容です:
---
report_id: REPORT-20260513-014-DEV-to-PM-hello-world-smoke-task
date: 2026-05-13
from: DEV-01
to: PM
re: TASK-20260509-999-PM-to-DEV
status: DONE
---
受領:Hello World — CodeFlow v0.1.0-rc.1 スモークタスク
タスクID: TASK-20260509-999-PM-to-DEV目的: CodeFlow v0.1.0-rc.1の9ステップのエンドツーエンドガバナンスループが正しく動作することを検証する。
DEV-01が自作した9ステップの検証テーブル:
| Step | Component | Expected Behavior |
|---|---|---|
| 1 | InboxWatcher | chokidar add イベントが発火 |
| 2 | TaskParser | YAMLのフロントマターを読み取る |
| 3 | TaskDispatcher | recipient: DEV → DEV-01を解決し、セッションを開始する |
| 4 | InMemorySdkAdapter |
setImmediate の合成セトル |
| 5 | ReviewEngine | runtime.session_endedを監視し、REVIEW-01を開始する
|
| 6 | REVIEW-01 | 決着する(VERDICT:行がない → decision="needs_human") |
| 7 | NeedsHumanGate | 人間へのプッシュペイロードをstdoutに出力する |
| 8 | ReviewWriter | REVIEW-*-REVIEW-on-TASK-*-HELLO.mdを書き込む
|
| 9 | StateHistoryWriter | タスクファイルに ## state_history セクションを追記する |
Status: DONE
誰も使うべき形式を教えていません。プロトコルを読み、ツールを呼び出し、ファイルを書きました。
VI. 最初の完全なサイクル
最初に同時に起きたのは3つのことです:
返却形式: {"translated": "翻訳されたHTML"}通知を受信:
InboxWatcherがタスクファイルの投下を検知しました——OCRスクリプトがクリックを模倣するのではなく、本物のチャイムのように。自律的な通信: DEV-01 は Cursor Agent SDK 経由でタスクを受け取り、FCoP プロトコルが求める内容を理解し、自律的に「何をするか」および「どのツールを呼び出すか」を判断しました。
7回の fcop-mcp ツール呼び出し。55 秒。レポート提出:
fcop/reports/に、プロトコルに準拠した完全な Markdown ファイルが現れました。AIが生成した文章ではありません——プロトコルに駆動されたツール呼び出しを通じて、エージェントが自律的に書き込んだファイルです。
「いまエージェントが動かせる」といった話ではありません。そうではなく:エージェントは本当に通知された。本当にプロトコル言語で通信し、本当にその結果を書いてファイルに保存した。
VII. すべてが始まった場所
2026年4月下旬、私は Cursor フォーラムに機能要望を投稿しました:
"機能要望: chat-notify のプリミティブ——すでにメールボックス(ファイル)はある。必要なのはドアベルだ"
Colin が返信しました:
"こんにちは @joinwell52! IDE のファーストクラス機能ではありませんが、新しい Agent SDK が今日の時点でそこまで持っていってくれるかもしれません。
Agent.create()は、複数の.send()呼び出しをまたいで持続するコンテキストを備えた長寿命のエージェントを作ります。そしてAgent.resume(agentId)により、外部スクリプトが後から同じエージェントを引き継げます。さらに、クラウドだけでなく、作業ツリーに対してローカルで実行することも可能です。ぜひ一度見てみてください!"
Agent.create()、Agent.resume()、agent.send() —— この3つの関数が、CodeFlow のパイプライン全体の骨格になりました。InboxWatcher はチャイム、FCoP のタスクファイルは郵便物、@cursor/sdk は郵便サービスです。
機能要望の投稿から、最初の tool_calls_count: 7 まで——およそ 18 日。
VIII. FCoP 自身の変革
エージェントが変わったのと同じように、FCoP も変化しました。
FCoP は当初、純粋なテキストプロトコル——Markdown に書かれた慣習として始まりました。強制は 読むこと に依存していました。fcop-mcp によって、FCoP は根本的に変わりました:
| 段階 | FCoP の形式 | プロトコルに対するエージェントの関係 |
|---|---|---|
| 初期 | テキスト仕様 | エージェントがファイルを読み、慣習に従ってテキストを生成 |
| fcop-mcp | ツール群 | エージェントがツールを呼び出し、プロトコルは書き込み時に強制される |
以前は、エージェントが FCoP の仕様を 知っていて、それに従うかどうかを判断していました。今は、FCoP の仕様がツールになる——write_report、write_task、write_issue。ツールを呼び出すこと が プロトコルを実行することです。
これは、仕様 から インフラ への飛躍です。
エピローグ: Cursor Agent SDK に感謝
長寿命のエージェント: Agent.create() は、複数の .send() 呼び出しをまたいでコンテキストを維持します。PM、DEV、QA、OPS はそれぞれ、タスクをまたいでも自分のアイデンティティと状態を保持します。
外部からの再開可能性: Agent.resume(agentId) により、InboxWatcher は新しいタスクが到着したときに同じエージェントへ再入場できます。記憶喪失も、コールドスタートもありません。
ローカル実行: SDK はあなたのローカルな作業ツリーに対して動作します。FCoP のファイルプロトコルは本質的にローカルです——自然な一致です。
mcpServers の注入: agent.send(text, { mcpServers: {...} }) は、呼び出し時に MCP サーバーの設定を受け付けます。このたった1つのフィールドが、CodeFlow のツール層全体を解き放ちました。
これらの設計判断は、根本的な何かを一緒に描き出します:エージェントはワンショット関数ではありません。アイデンティティとメモリを持つ長寿命の存在であり、外部システムによって調整される能力があります。
それは、FCoP がエージェント役割を定義する仕方と、ほぼまったく同じです。
Cursor チームに感謝します。その返信をくれた Colin にも感謝します。
関連
- GitHub 上の FCoP — プロトコル仕様、ソース、そしてすべてのエッセイ
- 完全なエッセイ (GitHub)
- Cursor フォーラムでの議論
- When Agents Learn From Their Own Wreckage — 同じプロジェクトからのフィールドレポート
*FCoP メンテナ · 2026年5月13日




