公式 PHP SDK を使って最小の MCP サーバーを作り、STDIO で動かします。
Laravel や HTTP transport へ広げる前に、まず Tool 1本 を動かして Inspector で確認するところまで進めます。
PHP で MCP サーバーを始める最短ルートは次です。
Windows 11 + WSL2 + Dockerでデモ環境を作るcomposer require mcp/sdkで公式 SDK を入れる#[McpTool]を付けた Tool を 1 本だけ作るserver.phpでStdioTransportを起動するInspectorから、そのserver.phpをコマンドとして呼ぶ
執筆時点の公式 README では、PHP SDK は official SDK として案内されています。
一方で active development、初回メジャー前は experimental とも明記されています。
そのため今回は、まず server 側の最小構成だけに絞ります。
この記事で扱わないもの:
Streamable HTTP transportResourceやPrompt- Laravel / Symfony / Slim 連携
- 本番配備やリモート公開
- クライアント比較
- Claude Desktop 設定
1. ゴールと前提
到達する状態は次のとおりです。
- 公式 PHP SDK で最小の MCP server を作れる
helloTool 1 本をSTDIOで起動できるInspectorで Tool を確認できる
環境前提は次で固定します。
- Windows 11
- WSL2(Ubuntu)
- VS Code(Remote - WSL)
- Docker Desktop
- Node.js(Windows 側、Inspector を使う章だけ)
この前提にしている理由は、Windows 側に PHP や Composer を直接入れなくても進められるからです。
実際の PHP 実行と composer require は、すべてコンテナ内で行います。
ただし 6章の Inspector だけは Windows 側の PowerShell で npx を使うため、Node.js を前提にします。
2. 新規デモ環境を作る
WSL 側のターミナルで、デモ用ディレクトリを作ります。
mkdir -p ~/projects/php-mcp-server-intro-demo
cd ~/projects/php-mcp-server-intro-demo
mkdir -p docker/php src
code .
compose.yml を作成します。
services:
app:
build:
context: .
dockerfile: docker/php/Dockerfile
working_dir: /workspace
volumes:
- ./:/workspace
command: ["sleep", "infinity"]
docker/php/Dockerfile を作成します。
FROM php:8.5-cli
RUN apt-get update \
&& apt-get install -y --no-install-recommends git unzip \
&& rm -rf /var/lib/apt/lists/*
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /workspace
今回は検証時に動作確認できた php:8.5-cli を使います。
SDK 自体は php ^8.1 を要求します。執筆時点の要件は composer show mcp/sdk でも確認できます。
composer.json は最小状態で始めます。
{
"name": "example/php-mcp-server-intro-demo",
"type": "project",
"require": {},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
起動して、コンテナ内の PHP と Composer を確認します。
docker compose up -d --build
docker compose exec app php -v
docker compose exec app composer --version
docker compose exec app php -m | grep fileinfo
執筆時点の確認例:
PHP 8.5.3 (cli)
Composer version 2.9.5
ここで fileinfo も出れば、SDK が要求する ext-fileinfo も満たしています。
ここでコンテナが起動すれば、以後の composer require もそのまま進められます。
Dockerfile を修正したときは、docker compose up -d --build を再実行してください。
3. 公式 PHP SDK を入れる
次に mcp/sdk を導入します。
docker compose exec app composer require mcp/sdk
docker compose exec app composer show mcp/sdk --latest
執筆時点の確認例:
versions : * v0.4.0
released : 2026-02-23
requires
php ^8.1
ext-fileinfo *
今回の一時ディレクトリ検証では、2026-03-07 時点で composer require mcp/sdk 実行時に v0.4.0 が解決されました。
ただし、ここは時点依存です。記事内の版番号は「執筆時点の確認結果」として読んでください。
autoload の更新は GreetingElements.php を追加したあとに実行したいので、4章の末尾でまとめて実行します。
オフライン環境では --latest が使いにくいので、その場合は次で十分です。
docker compose exec app composer show mcp/sdk
4. 最小 Tool と server.php を作る
ここでは hello という Tool を 1 本だけ作ります。
src/GreetingElements.php を作成してください。
<?php
declare(strict_types=1);
namespace App;
use Mcp\Capability\Attribute\McpTool;
final class GreetingElements
{
#[McpTool]
public function hello(string $name): string
{
return "Hello, {$name}!";
}
}
#[McpTool] を付けると、この method が Tool として discovery 対象になります。
今回は名前を明示せず、method 名の hello をそのまま Tool 名として使います。
続いて、プロジェクトのルートディレクトリに server.php を作成します。
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use Mcp\Server;
use Mcp\Server\Transport\StdioTransport;
$server = Server::builder()
->setServerInfo('Greeting Server', '0.1.0')
->setDiscovery(__DIR__, ['src'])
->build();
$status = $server->run(new StdioTransport());
exit($status);
server.php で押さえるのは次の 3 つです。
setServerInfo(...)で server 名と版を渡すsetDiscovery(__DIR__, ['src'])でsrc/をスキャンするStdioTransportでSTDIOserver として起動する
ここで一つ重要なのが、STDOUT に echo や var_dump を出さないことです。
公式 transports.md でも、STDIO transport では STDOUT は JSON-RPC 用なので使わないよう明記されています。
デバッグしたいときは STDERR を使ってください。
fwrite(STDERR, "debug\n");
最後に、念のため autoload を再生成します。
特に composer.json を変更した場合や、autoload 周りの切り分けをしたいときに有効です。
docker compose exec app composer dump-autoload
コードのポイント
① #[McpTool] で Tool を宣言する
#[McpTool]
public function hello(string $name): string
{
return "Hello, {$name}!";
}
#[McpTool] を付けるだけで、この method が Tool として discovery 対象になる。引数の型ヒント(string $name)は Inspector や client が表示するスキーマとして使われる。名前を省略すると method 名(hello)がそのまま Tool 名になる。
② setDiscovery でスキャン対象を指定し、StdioTransport で起動する
$server = Server::builder()
->setServerInfo('Greeting Server', '0.1.0')
->setDiscovery(__DIR__, ['src'])
->build();
$status = $server->run(new StdioTransport());
setDiscovery(__DIR__, ['src']) が src/ 配下を再帰スキャンして #[McpTool] の付いた method を収集する。run(new StdioTransport()) が STDIO server としての待受エントリポイントになる。STDOUT に出力が混入すると JSON-RPC が壊れるため、デバッグは fwrite(STDERR, ...) に限定する。
5. STDIO server として起動する
単体で起動します。
docker compose exec -T app php /workspace/server.php
ここで -T を付けているのは、STDIO 連携に余計な疑似 TTY を入れないためです。
このコマンドを実行すると、server は待受に入り、目立った出力なしで止まって見えるはずです。
それで正常です。
止めるときは Ctrl+C です。
もしここで即座に終了したり、予期しない出力が出るなら、次を見直してください。
composer require mcp/sdkを実行したかcomposer dump-autoloadを実行したかserver.phpやGreetingElements.phpに構文エラーがないかechovar_dumpprint_rを入れていないか
6. Inspector で Tool を確認する
次は公式 Inspector で確認します。
ここからのコマンドは Windows 側の PowerShell で実行してください。
Inspector は npx で起動するので、この章だけは Windows 側に Node.js が必要です。
Inspector から見ると、接続の流れは次のようになります。入口は wsl.exe ですが、最終的にはコンテナ内の php /workspace/server.php が STDIO で JSON-RPC を返します。
sequenceDiagram
participant Inspector
participant WSL as wsl.exe
participant Compose as Docker Compose
participant Server as MCP server
participant Tool as hello Tool
Inspector->>WSL: launch command
WSL->>Compose: docker compose exec -T app php /workspace/server.php
Compose->>Server: start STDIO server
Inspector->>Server: initialize and list tools
Server-->>Inspector: server info and hello tool
Inspector->>Server: call hello with name=Taro
Server->>Tool: invoke GreetingElements::hello
Tool-->>Server: Hello, Taro!
Server-->>Inspector: tool result
この図のどこかでパスやコマンドが崩れると、接続自体は始まっても Tool が見えなかったり、起動直後に落ちたりします。6 章ではその流れを崩さない形でコマンドをそのまま通します。
前提を確認します。
node --version
npm --version
wsl -l -v
wsl.exe -d Ubuntu -- whoami
Ubuntu は自分の distro 名に置き換えてください。
whoami の結果が、このあとの /home/<your-user>/... に入る WSL ユーザー名です。
# <your-user> は自身の WSL ユーザー名に置換してください
npx -y @modelcontextprotocol/inspector wsl.exe -d Ubuntu -- docker compose --project-directory /home/<your-user>/projects/php-mcp-server-intro-demo -f /home/<your-user>/projects/php-mcp-server-intro-demo/compose.yml exec -T app php /workspace/server.php
この 1 行で、Inspector の起動と接続先の指定をまとめて行えます。
ここでは bash -lc を使わず、wsl.exe から docker compose --project-directory ... を直接呼びます。
これなら Arguments 欄でも区切りが崩れにくく、Inspector 側の接続確認に集中できます。
見落としやすいのは、--project-directory と -f の両方で WSL 側の絶対パスを指定することです。
ここも C:\... ではなく、/home/<your-user>/... の形にします。
もし Inspector を npx -y @modelcontextprotocol/inspector だけで起動して、あとから UI で接続先を入れたい場合は、左側を次のように設定します。
Transport Type:STDIOCommand:wsl.exeArguments:-d Ubuntu -- docker compose --project-directory /home/<your-user>/projects/php-mcp-server-intro-demo -f /home/<your-user>/projects/php-mcp-server-intro-demo/compose.yml exec -T app php /workspace/server.php
起動できたら、Inspector 側で hello Tool が見えるはずです。
name に Taro を入れて実行すると、戻り値は次のようになります。
Hello, Taro!
ここまで確認できれば、最初の 1 本としては十分です。
7. 詰まりどころと次の一歩
詰まりやすい点と対処は次のとおりです。
| 症状 | まず見る場所 |
|---|---|
Mcp\\... が見つからない | docker compose exec app composer require mcp/sdk と composer dump-autoload |
| server が起動直後に落ちる | php -l server.php と php -l src/GreetingElements.php |
| Inspector で接続できない | wsl.exe -d Ubuntu -- docker compose --project-directory /home/<your-user>/projects/php-mcp-server-intro-demo -f /home/<your-user>/projects/php-mcp-server-intro-demo/compose.yml exec -T app php /workspace/server.php を単体で実行 |
| protocol error のような挙動になる | STDOUT に echo や var_dump を出していないか |
| パスが見つからない | --project-directory と -f の /home/<your-user>/projects/... が WSL 側の絶対パスになっているか |
ここまでできれば、PHP で MCP server を始める最初の 1 本は完了です。
次に広げるなら、Resource や Prompt を足す、HTTP transport に進む、別の MCP client へつなぐ、といった順で広げると把握しやすいです。