編集 | 伊風
あなたの MCP、もしかしたら間違った使い方をしているかもしれません?
MCPは大規模モデルの「USBインターフェース」と見なされることがよくあります。多くの開発者はまず、より多くの専用ツール(grep、sed、tmuxなど)を積み重ねれば、AIがより強力になると考えがちです。
しかし、Hacker Newsで話題になった投稿では、全く逆の結論が示されています。
👉 ツールが増えれば増えるほど混乱し、MCPの最適解は——コード実行環境を一つだけ残すことです。
開発者なら誰でも知っていますが、コマンドラインツールは実際には非常に「脆い」ものです。
• クロスプラットフォーム/バージョン互換性の問題
• 改行コードや特殊文字で頻繁にエラーが発生
• セッションが混乱すると、プロセスが暴走する
著者は、これらが小さなバグではなく、根本的な構造的問題であると鋭く認識しました。
そこで疑問が生じます。コマンドラインの問題は一体どこにあるのでしょうか?なぜ、より多くの小さなツールではなく、「スーパーツール」—Python/JSを直接実行できるインタープリタ—が解決策なのでしょうか?
MCPがコマンドラインツールを呼び出すとなぜ常にクラッシュするのか?
著者は、コマンドラインツールの呼び出しで最もイライラするのは、次の点だと述べています。
AIが一度エラーを起こすと、小さな詳細が正しく処理されていないだけで、最初からやり直すか、別のツールに切り替えるしかありません。
この背景には2つの明確な欠陥があります。
第一に、プラットフォームとバージョンの互換性が低いこと。
コマンドラインツールは特定の環境に依存することが多く、時にはドキュメントのサポートも不足しています。その結果、ほぼ毎回、最初の呼び出しで問題に遭遇します。
より典型的な例は、非ASCII文字の処理です。Claude SonnetやOpusは、シェルで改行コードや制御文字をどのように渡すべきか区別できないことがあります。
このような状況は珍しくなく、C言語のコンパイル時に、末尾に改行文字を残す必要があることがよくありますが、AIツールはここで固まってしまい、「驚くべき」ツールループを延々と繰り返して解決しようとします。
第二に、呼び出しチェーンが長すぎ、状態管理が困難であること。
一部のエージェント(特にClaude Code)は、シェル呼び出しを実行する前に、さらに「セキュリティ事前チェック」を行います。Claudeはまず、小さなモデルHaikuを使って、その呼び出しが危険かどうかを判断し、実行するかどうかを決定します。
さらに厄介なのが、複数回の呼び出しです。例えば、tmuxを使ってLLDBを遠隔操作させる場合、理論的には可能ですが、AIはしばしば「記憶喪失」を起こします。セッション名を途中で変更したり、自身がセッションを持っていることを忘れたりして、タスクを正常に終了できなくなります。
全体として、コマンドラインツールが複数回の呼び出しシナリオに入ると、安定性が最大の弱点となります。
そして、これはCLIツールの本来の利点をかえって隠してしまうことになります。
コマンドラインの強みは「組み合わせ」であり、MCPはそれを弱体化させている
コマンドラインツールは本質的に単一のツールではなく、プログラミング言語(bash)を介して組み合わせることができる一連のツールです。
bashでは、grep、awk、sed、tmuxといった小さなツールを連結し、前のツールの出力が次のツールの入力となることで、一行のコマンドで複雑な問題を解決できます。
これがコマンドラインの「組み合わせ性」です。
しかし、MCPに移行すると、このような追加の推論を必要としない組み合わせは失われます(少なくとも現在の実装では)。
なぜでしょうか?
MCPの呼び出しモデルは、ツールをブラックボックスとして扱うためです。一度に一つのツールだけを呼び出し、結果を得て、次の推論フェーズに入ります。
これは、AIがbashのような柔軟な組み合わせを再現しようとすると、自身で推論をやり直し、段階的に呼び出す必要があり、そのプロセスは遅く、エラーも発生しやすいことを意味します。
古典的な例は、tmuxでlldbを遠隔操作する場合です。CLIでは、AIは次のように処理を連結します。
• まず tmux send-keys を使ってコマンドを入力します
• 次に tmux capture-pane を使って出力を取得します
• さらに sleep を挿入して待機し、早すぎる結果読み取りを避けるために再度キャプチャを続けます
複雑な文字エンコーディングの問題に遭遇した際には、base64に変換してからデコードするなど、別の方法に切り替えることもあります。
一方MCPでは、このプロセスは多くのラウンドに分割され、各ステップで状態を再推論する必要があります(例:セッション名、ブレークポイントの位置、前回の出力スニペット)。チェーンのどこか一箇所でも失敗すると、全体をやり直すことになります。
著者はまた、CLIのもう一つの強みとして、AIにまず小さなスクリプトを書かせ、それを再利用し、組み立てさせることで、最終的に安定した自動化スクリプト一式を構築できる点を強調しました。
しかし、MCPのブラックボックス呼び出しでは、このような「スクリプト化+再利用」という自己成長パスは、現在のところ自然には現れにくいのが現状です。
より良いMCPの方法
著者の抜本的な提案:何十ものツールを扱うのではなく、MCPには一つの「スーパーツール」だけがあればよい。
このスーパーツールとは、状態を持ち、コードを実行するPython/JSインタープリタのことです。
シェルツールには限界があります。特にエージェントが複雑なセッションを維持する必要がある場合、いずれはツールとの「格闘」状態に陥るでしょう。
MCPは本質的に状態を持っています。より実用的な考え方は、「スーパーツール」—状態を持つPythonインタープリタ—を一つだけ公開することです。これはeval()を介してコードを実行し、コンテキストを維持することで、エージェントが慣れた方法で操作できるようにします。
著者の実験はpexpect-mcpです。表面上はpexpect_toolと呼ばれていますが、本質的にはMCPサーバー上で動作し、pexpectライブラリがプリインストールされた永続的なPythonインタープリタ環境です。pexpectは古典的なexpectツールのPython移植版であり、コマンドラインとスクリプトで対話することができます。
このようにして、MCPサーバーは状態を持つPythonインタープリタとなり、公開されるツールインターフェースは非常にシンプルかつ直接的です。渡されたPythonコードスニペットを実行し、以前のすべての呼び出しによって蓄積されたコンテキスト状態を継承します。
ツールインターフェースの概要は以下の通りです。
pexpectセッションでPythonコードを実行し、プロセスを起動して対話できます。
パラメータ:
code: 実行するPythonコード。変数 child を使ってプロセスと対話します。
pexpectは既にインポートされており、pexpect.spawn(...) を直接使って起動できます。
timeout: オプション、タイムアウト時間(秒)、デフォルトは30秒。
例:
child = pexpect.spawn('lldb ./mytool')
child.expect("(lldb)")
戻り値:
コード実行結果またはエラー情報
このモデルの下では、MCPの役割はもはや「ツールセット」ではなく、コード実行環境となり、いくつかの直接的なメリットをもたらします。
• MCPがセッション管理と対話を担当する
• エージェントが記述するコードは、ほぼスクリプトそのものになる
• セッション終了後、再利用可能なデバッグスクリプトとして整理できる
実証実験:効率と再利用性の飛躍
pexpect-mcpの効果を検証するため、著者は既知のクラッシュするCプログラム(demo-buggy)のデバッグにこれを使用しました。
プロセスは以下の通りです。
1. 初回デバッグ(従来のMCPモードのシミュレーション):AIはpexpect_toolを介してLLDBと対話し、クラッシュの原因(メモリ未割り当て、配列の範囲外アクセス)を特定しました。これには約45秒かかり、7回のツール呼び出しが含まれました。
2. スクリプト化:AIはデバッグプロセス全体を、独立した読みやすいPythonスクリプト(debug_demo.py)として自動的にエクスポートしました。
3. 再利用検証:新しいセッションで、uv run debug_demo.pyを1回のツール呼び出しだけで実行しました。スクリプトは5秒以内にクラッシュ分析を再現し、問題の根本原因を正確に特定しました。
著者は、最も重要なことは、このスクリプトが独立しており、人間である私でも直接実行でき、MCPに全く依存しないことです!と述べています。
pexpect-mcpの成功事例は、より汎用的なMCP設計の方向性を示唆しています。断片的でエラーが発生しやすいブラックボックスツールを多数公開するよりも、プログラミング言語自体をインタラクションインターフェースとして用いる方が良いということです。
革新:小型MCPを自作する
MCPの共通の問題の一つは、ツールが増えれば増えるほど、コンテキストが腐敗しやすく、入力の制約も大きくなることです。
しかし、MCPが公開するのがツールの集まりではなく、プログラミング言語であるならば、モデルが学習時に習得した全ての能力を間接的に開放することになります。
何か新しいものを構築する必要があるとき、少なくともプログラミング言語はAIにとって馴染み深いものです。あなたは完全に小さなMCPを自作し、次のようにすることができます。
• アプリケーションの内部状態をエクスポートする
• データベースクエリの補助を提供する(シャーディングアーキテクチャに対応していても)
• データ読み取りAPIを提供する
以前は、AIはコードを読んでこれらのインターフェースを理解するしかありませんでした。しかし今では、状態を持つPython/JavaScriptセッションを介して直接呼び出し、さらに探索することもできます。
さらに素晴らしいのは、これによりエージェントがMCP自体をデバッグする機会も得られることです。PythonとJavaScriptの柔軟性のおかげで、MCPの内部状態のトラブルシューティングまで助けてくれる可能性があります。
ネットユーザーの論争:AIはどのようにコードを操作すべきか?
このブログの議論は、実はAIプログラミングの根底にある哲学に触れています。
AIは一体どのようにコードを操作すべきなのでしょうか?
テキストレベル(文字列)にとどまるべきか、それともより構造化されたインターフェースを通じて理解し操作すべきか?
私たちは、CLIツールの脆さ(改行コードのエラー、セッション管理の混乱)が、本質的に文字列操作に基づく限界であることを知っています。
では、AIが「本物のコード」を書く方が良いのであれば、さらに一歩進んで、ASTを理解させるべきなのでしょうか?注:AST(抽象構文木):コードを木構造に変換して表現する方法。各ノードは変数、関数、または文を表します。コンパイラやIDEにとって、ASTは純粋なテキストよりも正確な構造化インターフェースです。
一部のネットユーザーは次のように考えています。
エディタは、grep、sed、awkといった古いツールでエージェントを振り回すのではなく、言語サーバーなどの構造化機能をより活用すべきです。そして、ほとんどの言語では、操作すべきは文字列ではなく、トークンストリームやASTであるべきです。
別の意見では次のように指摘しています。
現実は、AIがコード自体を操作する方が依然として適していると決定づけています。現在のツールの使用方法が非効率であることには同意しますが、AIが主に構文木ではなくコードを操作するのにはいくつかの理由があります。
1.訓練データセットには、構文木よりもはるかに多くのコードが含まれています。
2.コードはほぼ常に、より簡潔な表現形式です。
これまでグラフニューラルネットワークやトランスフォーマーを使ってASTの辺情報を学習させる試みがありましたが、主流のLLMを超えるには大きなブレークスルー(と莫大な資金)が必要となるでしょう。エージェントにast-grep(構文認識の検索・置換ツール)を使わせる実験はうまくいっており、本質的には全てをコードとして扱いつつも、構文認識の方法で置換しています。
さらに、文字列の普遍性を強調する人もいます。
文字列は依存性のない汎用インターフェースです。任意の言語、任意のファイルにわたって、ほとんど何でも実行できます。他の抽象化は、実行できることを厳しく制限してしまうでしょう。さらに、大規模言語モデル(LLM)はASTで訓練されているのではなく、プログラマーと同じように文字列で訓練されています。
これは一つの問題を示唆しています。
LLMが学習したのは「人間がコードを書く」方法であり、機械にとって最適な構造化された方法ではありません。
もし将来、本当に誰かがASTを使って大規模にモデルを訓練するならば、それは途方もない計算能力と資金を必要とし、さらに汎用的な世界知識を犠牲にする可能性もあります。
しかし、もしかしたら将来、より効率的で機械に密接した新しいパラダイムが登場するかもしれません。
この考え方が、今日のAI IDEのプログラミング体験を覆すと思いますか?コメント欄でぜひ議論してください。