見つかるModel Context Protocolのチュートリアルのほとんどは、公式SDKのある言語であるTypeScriptやPythonで書かれています。それにより、PHPアプリケーションを何年も運用してビジネスデータを保持している私たちのような多くの人が、プロトコルが利用可能かどうか疑問に思うことになります。

利用可能です。MCPはワイヤープロトコルであり、ライブラリではありません。言語が標準入力から1行を読み取ってJSONを書き戻すことができれば、それでサーバーを実装できます。この投稿では、プロトコルが実際に要求するもの、PHP実装の概要、そして過小評価していた部分である内部システムをモデルに公開したときに変わることを説明します。

MCPが実際に何であるか

Model Context Protocolは、AIアシスタントを外部システム(データ、ツール、API)に接続するためのオープンスタンダードです。これが解決する問題は組み合わせの問題です。標準が存在する前は、すべてのアシスタントがすべてのデータソースと独自の統合を必要としていました。MCPは1つのインターフェースを定義することで、準拠したクライアントが準拠したサーバーと通信できるようにします。

内部的にはJSON-RPC 2.0です。リクエストにはjsonrpcバージョン、methodparamsオブジェクト、idが含まれます。レスポンスには対応するidresultまたはerrorが含まれます。通知はidのないリクエストで返信は期待されません。以前にJSON-RPCサービスを実装したことがあれば、トランスポートの話の80%はすでに知っています。

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

  • ツール — モデルが呼び出せるアクション。それぞれに名前、説明、および入力を記述するJSON Schemaがあります。これは何かを実際に行うプリミティブです:データベースのクエリ、レコードの作成、リクエストの送信。モデルはいつ呼び出すかを選択します。
  • リソース — モデルが読み取れるデータで、URIでアドレス指定されます。ファイル、レコード、生成されたドキュメント。これらはアクションではなくコンテキスト用で、クライアントが一般的に何を取り込むかを決定します。
  • プロンプト — ユーザーが意図的に呼び出せる再利用可能なプロンプトテンプレートで、クライアントのUIでスラッシュコマンドやメニュー項目として表示されることがよくあります。

ツールとリソースの区別は、最初に思えるよりも重要です。ざっくりしたルール:ツールはモデル制御、リソースはアプリケーション制御。モデルが何かをフェッチするかどうかを決定すべき場合はツールにし、ユーザーまたはホストアプリケーションが決定する場合はリソースにします。

2つのトランスポート

MCPは2つの標準トランスポートを定義しており、適切なものを選ぶことが最初のアーキテクチャ上の決定です。

stdio。 クライアントはサーバーをサブプロセスとして起動し、標準入出力経由で通信します — 改行区切りのJSONで、1行が1つのメッセージです。これは可能な限りシンプルなセットアップです:ポート、HTTPサーバー、認証レイヤーは不要で、プロセスと通信できるのはそれを生成した親プロセスだけです。クライアントと同じマシンで実行されるものにはこれが適切です。

stdioには2つのルールがあり、どちらもPHPで破りやすいものです:

  1. プロトコルメッセージ以外をstdoutに書き込まない。 誤ったecho、デバッグ用に残したvar_dump、stdoutに出力されるPHP警告はメッセージストリームを破損させ、クライアントはそれをパースできません。代わりに診断はstderrに送信してください。これはクライアントが通常ログに転送するものです。
  2. 出力バッファリングをオフにし、書き込み後に必ずフラッシュする。そうしないとレスポンスがバッファに留まり、クライアントが待機することになります。

Streamable HTTP。 サーバーは通常のHTTPエンドポイントとして実行されます。クライアントはJSON-RPCメッセージをPOSTし、サーバーは単一のJSONレスポンスを返すか、1つのリクエストに対して複数のメッセージをプッシュする必要がある場合はサーバー送信イベントのストリームを返します。これはユーザーのマシン以外でサーバーを実行する場合のトランスポートです — ほとんどのPHPショップにとって興味深いケースです。なぜならこれはすでに運用方法を知っているデプロイメントモデルだからです。

(古いHTTP+SSEトランスポートは以前のリビジョンの仕様に存在します。新しい作業はStreamable HTTPを対象とすべきです。)

ハンドシェイク

どちらのトランスポートを選んでも、会話の開始方法は同じです。クライアントは使用するプロトコルバージョンとサポートする機能を指定してinitializeを送信します。サーバーは自身のプロトコルバージョン、機能、名前とバージョンを返信します。クライアントはその後initialized通知を送信し、通常の運用が開始されます。

機能は両側がネゴシエートする方法です。サーバーがリソースを実装していない場合、リソース機能をアドバタイズせず、適切に動作するクライアントはresources/listを呼び出しません。構築していない機能をアドバタイズしないでください。

ハンドシェイク後、重要なメソッドは予測可能です:

メソッド 内容
tools/list 公開するツールを説明と入力スキーマとともに返す
tools/call 指定された引数で1つのツールを実行し、結果を返す
resources/list 利用可能なリソースをURIとともに返す
resources/read 1つのリソースの内容を返す
prompts/list 利用可能なプロンプトテンプレートを返す
prompts/get 1つの入力済みプロンプトを返す

最小限だが実際に有用なサーバーはinitializetools/listtools/callです。それ以外はすべてオプションです。

PHP側の実装

トランスポートに依存しない構造:

JSON-RPCメッセージを読み取る
  → `method`でディスパッチ
  → 結果(またはエラー)を構築
  → 同じ`id`でレスポンスを書き込む

全画面モードに入る 全画面モードを終了

stdioの場合、fgets(STDIN)のループ、json_decode、メソッド名でのmatchfwrite(STDOUT, json_encode($response) . "\n")です。Streamable HTTPの場合、リクエストボディをデコードしてエンコードされたレスポンスを返す単一のエンドポイントです。中央のディスパッチレイヤーは同一で、読み書きの端だけが変わります。最初からこのように書いておけば、1つのコードベースから両方をサポートできます。

開始前に知っておくべきPHP固有の3つのポイント:

ツールスキーマ。 すべてのツールは入力用のJSON Schemaを必要とします。ネストした配列として手書きするのはすぐに面倒になります。すでに保守しているもの(バリデーションルールセット、DTO、リフレクションで読み取った型付きコンストラクタパラメータ)から導出することで、スキーマと実際の実装が乖離するのを防げます。スキーマの乖離は「モデルがツールを誤って呼び続けている」という最も一般的な原因です。

エラーハンドリング。 2種類の失敗を区別します。不正なリクエストや未知のメソッドはプロトコルエラーで、JSON-RPCのerrorオブジェクトを返します。ツールが実行されたが失敗した(レコードが見つからない、バリデーションが入力拒否した)場合はツールエラーで、エラーフラグを設定した通常の結果と人間が読めるメッセージを返します。この区別は重要です。2番目の種類はモデルに戻り、モデルはメッセージを読んで調整できます。プロトコルエラーは単に何かが壊れたことを伝えるだけです。モデルが回復できそうな失敗の場合、PHPの例外を2番目の種類に変換してください。

長時間実行される作業。 PHPのリクエスト毎プロセスモデルはstdioには適しています(プロセスはセッションの間存続)が、ツールに数分かかるHTTPではやや扱いにくいものです。ツール呼び出しは短く保ってください。ツールが遅い処理を開始する場合、すぐにジョブIDを返し、ステータスを報告する2番目のツールを公開してください。モデルはこのパターンをうまく扱えます。タイムアウトするリクエストはうまく扱えません。

Claudeへの接続

サーバーが実行されたら、接続する方法は大まかに3つあります。

ローカル、stdio経由。 デスクトップおよびCLIクライアント(Claude Desktop、Claude Code、さまざまなIDE統合)は、実行するコマンドと引数(phpとサーバースクリプトのパス)を指定することでサーバーを登録でき、サブプロセスを管理してくれます。これは「tools/listに応答する」から「実際に使用する」までの最速の方法です。

リモート、HTTP経由。 URLにデプロイされたStreamable HTTPサーバーは、リモート接続をサポートするクライアントに登録できます。Claude APIにもMCPコネクタがあり、リクエストでサーバーのURLを宣言すると、APIがサーバーサイドで接続を行い、クライアントループを書かずにモデルがツールを呼び出せます。ホスト型MCPサーバーは通常、サービスのネイティブAPIキーではなくOAuthベアラートークンで認証することに注意してください — それらは異なる認証システムであり、後者が動作すると仮定するのはよくある初期のつまずきです。

独自のコードから。 既存のクライアントを使用するのではなくエージェントを構築する場合、ほとんどのAI SDKはMCPツール定義をネイティブツール形式に変換できるため、MCPサーバーは制御するループのツールソースになります。

どのルートでも、最初はstdioでデバッグしてください。失敗モードはシンプルです:TLS、認証、プロキシ、CORSはありません。プロトコルを正しくしてからHTTPに移行してください。

過小評価していた部分:露出

既存のシステムの前にMCPサーバーを置いたときに変わるのはこれです。公開するすべてのツールは、少なくとも部分的に外部世界から読み取るテキストによって制御されるモデルに付与される能力です。モデルがツールを呼び出すよう説得できれば、ツールは実行されます。

実際的な結果:

ツールのスコープを狭くする。 任意のSQLを受け入れる汎用的なrun_queryツールは構築するのに最も便利ですが、出荷するのに最悪のものです。型付きパラメータを持つ具体的なツール(find_customer_by_emaillist_orders_in_range)を優先し、匿名のHTTPリクエストから来たかのようにすべての引数をサーバーサイドで検証してください。実質的にそうだからです。

読み書きは異なるリスククラス。 読み取り専用ツールには開示リスクがあります。書き込みツールには実際に起きたリスクがあります。それらを分離し、破壊的または不可逆的なものは、モデルが注意深いことを信頼するのではなく、ホストアプリケーションでの明示的な確認の後ろに置いてください。

説明はセキュリティサーフェスの一部。 ツールの説明はモデルが読む指示です。曖昧な説明は、意図しない状況でモデルがツールを呼び出すよう誘います。ツールがいつ使用されるべきかについて規定することは、何をするかだけでなく、正確性と安全性の両方を明らかに改善します。

公開するつもりのないものを漏らさない。 タスクに必要なフィールドを返し、レコード全体を返さない。顧客向けの回答に含まれるべきでない内部メモ、原価、個人電話番号は、ツールの結果にも含まれるべきではありません。プロンプトではなくサーバーでフィルタリングしてください。

すべての呼び出しをログに記録。 ツール名、引数、呼び出し元、結果。「なぜそれをしたのか」と誰かが尋ねたとき、ログが唯一の答えになります。

これらはどれも特別なものではありません — パブリックAPIエンドポイントに適用するのと同じ規律です。違いは、呼び出し元がドキュメントを読む開発者ではなく言語モデルであるため、曖昧さがサポートチケットとして表面化するのではなく、予期しない方法で解決されることです。

価値はあるか?

何年も蓄積されたビジネスデータの上に構築されたPHPアプリケーションにとって、MCPはそのデータと実際に推論できるアシスタントの間の最も安価な橋渡しだと私は考えています。待つべきSDKはありません。パイプまたはHTTPエンドポイント経由のJSON-RPCです — どちらもPHPが20年間うまくやってきたものです。

組織内の誰かが毎週尋ねる質問に答える3つの読み取り専用ツールから始めましょう。stdio経由で1人に配信し、実際に何を求めているかを見てください。それが事前に設計するよりもはるかに良い仕様です。

公式SDKのない言語でMCPサーバーを構築したことがあるなら、つまずいた点(トランスポート、スキーマ、スコープ設定)を教えていただけると幸いです。