長年PHPアプリケーション内でビジネスロジックの保守に携わってきた人にとって、Model Context Protocol(MCP)のチュートリアルを見ることは、鍵のかかったドアの外に立っているような感覚かもしれません。ほとんどのガイドはTypeScriptやPythonを前提としています。公式SDK、npmのインストール、pipパッケージの手順が説明されます。その結果、顧客レコード、注文履歴、在庫システムといった膨大な量のビジネスデータが、現在のAIツールの波からは見えない存在であるかのように、PHPのコードベースの中に置き去りにされてしまいます。

幸いなことに、MCPにおいてそれらのSDKは必須ではありません。MCPはライブラリではなく、ワイヤプロトコル(通信規約)です。実行環境が標準入力からテキストを読み取り、JSONを解析し、JSONを書き戻すことができれば、そのプロトコルを扱うことができます。PHPは、LLMが登場するずっと前から、まさにそのことを行ってきたのです。

MCPの正体

MCPはModel Context Protocolの略称です。その核心は、AIアシスタントをデータ、ツール、および外部APIに接続するためのオープンスタンダードです。アシスタントやモデルごとにカスタムの統合を構築する代わりに、準拠したインターフェースを1つ構築します。MCPを理解しているクライアントであれば、PHPやLaravel、あるいは特定のデータベーススキーマについて何も知らなくても、あなたのサーバーと通信できます。

内部的には、MCPはJSON-RPC 2.0を使用しています。つまり、すべてのリクエストは、メソッド名、パラメータ、およびIDを含む単純なJSONオブジェクトです。サーバーは、結果またはエラーを保持する別のJSONオブジェクトで応答します。

サーバーは3つのプリミティブを公開します:

  • Tools(ツール): モデルが呼び出すことができるアクション。データベースへのクエリ、ステータスの更新、サードパーティAPIの呼び出しなどが考えられます。
  • Resources(リソース): モデルがURIを通じて参照できる静的または半静的なデータ。ファイル、設定ドキュメント、リファレンスデータセットなどを想定してください。
  • Prompts(プロンプト): ユーザーがシステムと対話するのを助ける、定義済みのテンプレート。

覚えておくべき重要な制御上の違いがあります。ツールは「モデル制御」です。いつ呼び出すかはアシスタントが決定します。リソースは「アプリケーション制御」です。どのデータが利用可能かはサーバーが決定し、モデルは提供されたものを読み取るだけです。この違いを正しく理解することで、アーキテクチャの予測可能性を維持できます。ツールであるべきものをリソースとして探させたり、その逆が起きたりするような事態は避けたいものです。

トランスポートの仕組み

MCPは2つのトランスポート方式を定義しており、どちらを選択するかによってPHP側の書き方が決まります。

stdio は最もシンプルです。MCPクライアントは、あなたのPHPスクリプトをサブプロセスとして起動します。クライアントはスクリプトの標準入力にJSON-RPCメッセージを書き込み、スクリプトは標準出力にレスポンスを書き込みます。管理すべきソケットも、開くべきポートも、解析すべき認証ヘッダーもありません。ツールとクライアントが同じマシン上にある場合は、通常、ここから始めるのが適切です。

stdioで実行する場合、PHPプロセスには2つの厳格なルールが課されます。第一に、アプリケーションはプロトコル以外のデータを決してstdoutに書き込んではなりません。デバッグ用のecho文を出力したり、PHPのNoticeが漏れたりすると、クライアントのパーサーが壊れてしまいます。すべてのロギングと診断情報はstderrにルーティングしてください。第二に、出力バッファリングを完全に無効にします。PHPは、特にCGIやWebのコンテキストにおいてstdoutをバッファリングする傾向がありますが、CLIスクリプトであってもデータを保持することがあります。すべてのレスポンスを即座にフラッシュしてください。ストリームを使用している場合は、stream_set_write_buffer(STDOUT, 0) を設定するか、暗黙的なバッファリングをオフにして、送信した瞬間にクライアントが改行を受け取れるようにしてください。

Streamable HTTP は仕組みが異なります。PHPアプリケーションは、通常POSTリクエストを介してアクセスされる、永続的なHTTPエンドポイントとして動作します。これは、サーバーが異なるホストにある場合や、複数のクライアントからアクセス可能な長時間実行されるデーモンを作成したい場合に有用です。PHPにおいてこれは通常、呼び出しごとに終了する従来の「リクエスト・レスポンス・サイクル」ではなく、RoadRunnerやFrankenPHP、あるいは同様のプロセス管理下で実行することを意味します。

PHPでの構築

開始するのにフレームワークは必要ありません。PHPによる最小限のMCPサーバーは、STDINから読み取り、JSONをデコードし、ハンドラーにディスパッチし、結果をエンコードするループです。

while ($line = fgets(STDIN)) {
    $request = json_decode($line, true);
    // route to tool or resource handler
    // write JSON-RPC response to STDOUT
}

そのループの中で、モデルにとって意味のあるインターフェースを構築することこそが、本当の作業となります。

コードからツールのスキーマを生成する。 トラブルを招く最も早い方法の一つは、ツールのパラメータに対してJSONスキーマを手書きし、実際のバリデーションロジックとの乖離(ドリフト)を許してしまうことです。PHPには強力なリフレクション機能があります。メソッドのシグネチャを検査し、フォームやコマンドオブジェクトから既存のバリデーションルールを読み取り、それらの制約に基づいてスキーマを生成してください。もし内部コードが有効なメール形式を必要とするなら、MCPスキーマも同様に定義すべきです。バリデーションルールが変更されれば、スキーマも自動的に更新されます。乖離も、サイレントな失敗も起こりません。

プロトコルエラーとツールエラーを分離する。 JSON-RPCには独自の、エラー空間があります。不正なJSON、未知のメソッド、リクエストIDの欠落といった、プロトコルの不備にはこれを使用してください。ツールは正しく実行されたものの、業務上の問題に直面した場合は、ペイロード内にエラーフラグを含めた通常のレスポンスを返します。顧客検索ツールで一致するレコードが見つからないのは、プロトコルのクラッシュではありません。{"found": false} のような構造化された結果を返すことで、モデルは何が起きたのかを理解し、次のステップを選択できるようになります。モデルはより広範な検索を試みたり、ユーザーに詳細を尋ねたりするかもしれません。代わりにJSON-RPCエラーを投げると、モデルは文脈(コンテキスト)を見失ってしまうことがよくあります。

長時間実行される処理を計画する。 PHPは短時間の要求(リクエスト)向けに構築されています。Webリクエストは30秒でタイムアウトするかもしれませんし、CLIスクリプトであってもメモリや(待機する側の)忍耐を使い果たす可能性があります。もしツールが完了までに数分を要する場合(大規模なレポートの作成や、システム間でのデータ同期など)、モデルを待たせてはいけません。即座にジョブ識別子(ID)を返してください。その上で、そのIDを使ってステータスを確認するための2つ目のツールを用意します。進捗状況は、Redis、データベースのテーブル、あるいはデータ量が少なければフラットファイルに保存することも可能です。モデルはIDを受け取り、後で再度確認を行い、最終的に完了した結果を取得します。

モデルがキーを持つ場合のセキュリティ

AIモデルにツールへのアクセス権を与えることは、人間のユーザーに与えることとは異なります。モデルは文字通り動作が速く、説明を誤解することもあります。公開するすべてのツールを、権限昇格(privilege escalation)のリスクとして扱ってください。

スコープを徹底的に制限してください。汎用的な run_sql ツールの公開は絶対に避けてください。find_customer_by_emailupdate_order_status のように、具体的で限定的なツールを構築します。モデルは、定義したパラメータを用いて、指定した名前通りのことだけができるようにすべきです。

読み取りパスと書き込みパスを分離してください。読み取り専用ツールはリスクが低くなります。破壊的な操作には明示的な確認メカニズムを設け、あるいは完全に別のサーバーに制限してください。クライアントがサポートしている場合は、書き込みツールを実行する前に人間の承認ステップを必須にしてください。

ツールの説明は、追加の指示を書くつもりで記述してください。なぜなら、実際そうだからです。モデルがいつツールを呼び出すべきかを正確に記述してください。価格を検索するツールなら、その旨を伝えます。顧客IDを確認した後にのみ使用すべきなら、それを明確に述べます。曖昧な説明は、曖昧な挙動につながります。

出力をフィルタリングしてください。EloquentモデルやDoctrineエンティティ全体をシリアライズして、そのまま結果として流し込まないでください。モデルが実際に必要とするフィールドのみを返します。原価、従業員向けのメモ、内部に留めておくべきデータベースIDなどの内部フィールドを、通信経路に乗せる必要はありません。返却するデータの構造(shape)を明示的に定義してください。

最後に、すべてをログに記録してください。ツール名、渡された引数、および結果を記録します。モデルが高コストなクエリでループし始めたり、予期しない順序でツールを探索し始めたりした場合、ログだけがそれを検知できる唯一の手段となります。

どこから始めるべきか

PHPアプリケーションをAIアシスタントに接続するために、SDKのメンテナーの許可を得る必要はありません。必要なのは、JSON-RPC、ループ、そしてstdout(標準出力)に関する規律です。

初日からAPI全体をMCPツールとして再構築したいという衝動に駆られないようにしてください。組織内で繰り返し質問されるような、読み取り専用の操作を3つ選んでください。例えば、注文ステータスの確認、顧客サマリーの取得、あるいは最近の請求書一覧の表示などです。それらをツールとしてラップし、stdio経由で提供して、同僚の一人に使わせてみてください。モデルがうまくできること、そして躓くところを観察してください。30個のツールを計画するよりも、その3つのツールから学ぶことの方がずっと多いはずです。

MCPは架け橋であり、アプリケーションの代替品ではありません。あなたのPHPコードはすでにビジネスロジックを理解しています。プロトコルは、単にモデルがその領域を越えて、質問を投げられるようにするためのものです。