序文:二段階の誤解を越えて
AIエージェントに「道具(ツール)」を渡すための共通規格、MCP(Model Context Protocol)。 自作のMCPサーバー(stdioモード)を構築する中で、私は二つの大きな誤解を段階的に経験し、ようやくその「正体」に辿り着きました。
この記事では、ローカル環境におけるMCPサーバーの技術的実態と、開発者が知っておくべき「ライフサイクル」の正体について整理します。
第1の誤解:HTTPのような「Webサーバー」?
「サーバー」という呼称から、まず最初に陥るのがこの誤解です。
- 誤解の内容: 特定のポートをListenしており、ツールを呼び出すたびにリクエスト(HTTPなど)を投げてレスポンスをもらう。
- 実態: ローカルMCP(stdioモード)はネットワークを使いません。IDEがサーバーを「子プロセス」として直接起動し、標準入出力(stdin/stdout)のパイプを通じて会話します。
第2の誤解:単なる「CLIツールのラッパー」?
ネットワークを使わないと分かった次に陥るのが、「じゃあ単にIDEが裏でコマンドを叩くだけなんだな」という誤解です。
- 誤解の内容: AIが記事を書きたい瞬間に、IDEが
python server.pyを実行して終了する。つまりツール呼び出しは「都度起動」の使い捨てである。 - 実態: これも違いました。
もし「使い捨て」なら、ソースコードを書き換えた瞬間に次の呼び出しで反映されるはずです。しかし実際には、「IDEを再起動(または接続をリセット)しない限り、コードの変更が反映されない」という事象に直面します。
正解:IDEの『延長線上』にある常駐プロセス
ローカルMCP(stdioモード)の本当の姿は、「IDEによって起動され、セッションが続く限りメモリに居座り続ける、長命な常駐子プロセス」です。
なぜ再起動が必要なのか?
IDE(MCPクライアント)は、設定ファイルに書かれた実行コマンドを一度だけ叩き、そのプロセスとの通信路を掴みっぱなしにします。
- プロセスの維持: ツール呼び出しのたびにプロセスを作るのではなく、一度起動したプロセスとずっと会話を続けます。
- インメモリの持続: プロセスが生き続けているため、OSによって古いコードがメモリにロードされたままになります。ディスク上の
.pyファイルをいくら書き換えても、プロセスを殺して読み直させない限り、新しいコードは走りません。 - 副作用(State)の漏出: 設計上の理想は「ステートレス(副作用なし)」ですが、実態としてはプロセスが生きているため、グローバル変数などに書き込んでしまうと、その状態が意図せず次の呼び出しにまで残るリスクがあります。
つまり、ローカルMCPとは「便利なツール集」というインターフェースを持ちながら、その実体は 「IDEのプロセスと運命共同体な常駐システム」 なのです。
開発の突破口:『ターミナルA・B』分割による自律的テスト環境
「コードを書き換えるたびにIDEを再起動する」というワークフローは、人間にとっても苦行ですが、AIエージェントにとっては致命的です。なぜなら、エージェントは自分自身の親プロセスであるIDEを再起動することができないからです。
この運命共同体な制約を回避し、エージェントが自律的にコードを検証するために編み出されたのが、「ターミナルの役割分割」による開発手法です。
ターミナルA:サーバーの常駐(生身のstdio)
まず、IDEのMCP接続とは別に、ターミナルAで直接サーバーを立ち上げます。こうすることで、サーバーはIDEのライフサイクルから切り離され、単独で標準入力(stdin)を待機する状態になります。
# ターミナルA:サーバーを単独で起動し、待機させる
$ uv run python web/scripts/a3ro_server.py
ターミナルB:外部からの「遠隔操作」(JSON-RPCの送出)
次に、ターミナルB(またはエージェントの操作画面)から、ターミナルAで動いているサーバーに対して JSON-RPC コマンドを流し込みます。エージェントが自ら echo や send_command_input のような仕組みを使って標準入力に文字列を叩き込むことで、IDEを介さずにツールの挙動を100%検証できるのです。
# JSON-RPCリクエストを外部から流し込む(概念例)
$ echo '{"jsonrpc": "2.0", "method": "tools/call", "params": {"name": "draft_post", "arguments": {...}}, "id": 1}'
この「分離」がもたらす自律性
この手法を導入したことで、開発サイクルは劇的に変わりました。
- 実装: エージェントが MCP サーバーのコードを修正。
- 検証: ターミナル A・B を使い分け、JSON-RPC を直接叩いて「新しいコード」の挙動を自律的にテスト。
- 確信: 期待通りの出力が得られたら、そこで初めて人間に「IDEのリロード」を依頼する。
「IDEのリロード」を最終的な同期作業(定着)としてのみ扱い、そこに至るまでの試行錯誤を「分離されたターミナル」で完遂する。この戦略こそが、目に見えない常駐プロセスと戦いながら、エージェントとの協調開発を成功させるための正解でした。
結論:自環境の「リロード方法」が開発の鍵
今回得られた最大の教訓は、ローカルMCPにおいて「コードを書くこと」と「コードを反映させること」はセットである、ということです。
AntigravityのようにIDE全体の再起動が必須な環境もあれば、個別に接続をリセットできる機能を持つツールもあるかもしれません。しかし、自分が使っている環境において 「どうすればMCPサーバーのプロセスを殺して、新しいコードを読み直させられるか」 というリロード手段を知らなれば、まともな開発は不可能です。
「コードを変えたら、プロセスをリセットする」。 このローカルMCP特有の、ステートフルなライフサイクルを理解することこそが、環境構築の泥沼を抜け出し、AIエージェントに最高の「手足」を与えるための第一歩となります。