PHP MCP Server について
PHP MCP Server は、PHP で MCP サーバーを構築するための SDK です。既存の PHP アプリケーションの機能を AI アシスタントに公開したい開発者を対象としています。属性による自動検出と、stdio や HTTP など複数のトランスポートに対応します。
主な機能
- 01PHP 8 属性によるツールとリソースの自動登録
- 02stdio と Streamable HTTP のトランスポート
- 03PSR-11 コンテナによる依存性注入
- 04JSON-RPC バッチリクエストの処理
- 05検出結果のキャッシュと手動登録の優先
活用シーン
- 01既存の PHP アプリケーションの機能を MCP ツールとして公開
- 02Cursor などのクライアント向けにローカルの stdio サーバーを実行
- 03リモートアクセス可能な Streamable HTTP の MCP サーバーを構築
MCP 対応
本プロジェクトは MCP サーバーを構築するための SDK であり、ツール、リソース、プロンプトを MCP クライアントに公開します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
PHP MCP サーバー SDK
Model Context Protocol(MCP)サーバーを構築するための包括的な PHP SDK です。最新のアーキテクチャ、充実したテスト、柔軟なトランスポートオプションを備え、本番環境に対応した MCP サーバーを PHP で作成できます。
この SDK を使うと、PHP アプリケーションの機能を標準化された MCP のツール、リソース、プロンプトとして公開でき、AI アシスタント(Anthropic の Claude、Cursor IDE、OpenAI の ChatGPT など)が MCP 標準を使用してバックエンドと連携できるようになります。
🚀 主な機能
- 🏗️ モダンなアーキテクチャ:PHP 8.1 以降の機能、PSR 標準、モジュール設計を採用しています
- 📡 複数のトランスポート:
stdio、http+sse、および再開機能を備えた新しいストリーミング可能な HTTPをサポートしています - 🎯 属性ベースの定義:PHP 8 の属性(
#[McpTool]、#[McpResource]など)を使用して、設定不要で要素を登録できます - 🔧 柔軟なハンドラー:クロージャー、クラスメソッド、静的メソッド、呼び出し可能クラスをサポートしています
- 📝 スマートなスキーマ生成:メソッドシグネチャーから JSON スキーマを自動生成します。オプションで
#[Schema]属性による拡張も可能です - ⚡ セッション管理:複数のストレージバックエンドを利用できる高度なセッション処理を備えています
- 🔄 イベント駆動型:ReactPHP ベースで、高い並行性とノンブロッキング処理を実現します
- 📊 バッチ処理:JSON-RPC のバッチリクエストを全面的にサポートしています
- 💾 スマートキャッシュ:検出した要素をインテリジェントにキャッシュし、手動設定を優先します
- 🧪 補完プロバイダー:ツールやプロンプトの引数補完を標準でサポートしています
- 🔌 依存性注入:自動配線に対応した PSR-11 コンテナーを全面的にサポートしています
- 📋 包括的なテスト:すべてのトランスポートを対象とした統合テストを含む、充実したテストスイートを備えています
このパッケージは、Model Context Protocol の2025-03-26バージョンを後方互換性とともにサポートしています。
📋 要件
- PHP >= 8.1
- Composer
- HTTP トランスポートを使用する場合:イベント駆動型の PHP 環境(CLI を推奨)
- 拡張機能:
json、mbstring、pcre(通常はデフォルトで有効です)
📦 インストール
composer require php-mcp/server
💡 Laravel ユーザーの方へ:フレームワークとの連携強化、設定管理、Artisan コマンドの利用には、
php-mcp/laravelの使用をご検討ください。
⚡ クイックスタート:検出機能を使った Stdio サーバー
この例では、属性による検出機能を使用した stdio サーバーの最も一般的な使用パターンを紹介します。
1. MCP 要素を定義する
src/CalculatorElements.php を作成します:
<?php
namespace App;
use PhpMcp\Server\Attributes\McpTool;
use PhpMcp\Server\Attributes\Schema;
class CalculatorElements
{
/**
* Adds two numbers together.
*
* @param int $a The first number
* @param int $b The second number
* @return int The sum of the two numbers
*/
#[McpTool(name: 'add_numbers')]
public function add(int $a, int $b): int
{
return $a + $b;
}
/**
* Calculates power with validation.
*/
#[McpTool(name: 'calculate_power')]
public function power(
#[Schema(type: 'number', minimum: 0, maximum: 1000)]
float $base,
#[Schema(type: 'integer', minimum: 0, maximum: 10)]
int $exponent
): float {
return pow($base, $exponent);
}
}
2. サーバースクリプトを作成する
mcp-server.php を作成します:
#!/usr/bin/env php
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use PhpMcp\Server\Server;
use PhpMcp\Server\Transports\StdioServerTransport;
try {
// Build server configuration
$server = Server::make()
->withServerInfo('PHP Calculator Server', '1.0.0')
->build();
// Discover MCP elements via attributes
$server->discover(
basePath: __DIR__,
scanDirs: ['src']
);
// Start listening via stdio transport
$transport = new StdioServerTransport();
$server->listen($transport);
} catch (\Throwable $e) {
fwrite(STDERR, "[CRITICAL ERROR] " . $e->getMessage() . "\n");
exit(1);
}
3. MCP クライアントを設定する
クライアント設定に追加します(例:.cursor/mcp.json):
{
"mcpServers": {
"php-calculator": {
"command": "php",
"args": ["/absolute/path/to/your/mcp-server.php"]
}
}
}
4. サーバーをテストする
AI アシスタントから次のツールを呼び出せるようになります:
add_numbers- 2 つの整数を加算しますcalculate_power- 制約を検証しながらべき乗を計算します
🏗️ アーキテクチャの概要
PHP MCP サーバーは、モダンで疎結合なアーキテクチャを採用しています:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ MCP Client │◄──►│ Transport │◄──►│ Protocol │
│ (Claude, etc.) │ │ (Stdio/HTTP/SSE) │ │ (JSON-RPC) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
┌─────────────────┐ │
│ Session Manager │◄──────────────┤
│ (Multi-backend) │ │
└─────────────────┘ │
│
┌─────────────────┐ ┌──────────────────┐ │
│ Dispatcher │◄───│ Server Core │◄─────────────┤
│ (Method Router) │ │ Configuration │ │
└─────────────────┘ └──────────────────┘ │
│ │
▼ │
┌─────────────────┐ ┌──────────────────┐ │
│ Registry │ │ Elements │◄─────────────┘
│ (Element Store)│◄──►│ (Tools/Resources │
└─────────────────┘ │ Prompts/etc.) │
└──────────────────┘
コアコンポーネント
ServerBuilder:流れるように設定できるインターフェース(Server::make()->...->build())Server:設定済みのすべてのコンポーネントを含む中心的な調整役Protocol:トランスポートとコアロジックをつなぐ JSON-RPC 2.0 ハンドラーSessionManager:複数のバックエンドに対応したセッションストレージ(array、cache、custom)Dispatcher:メソッドのルーティングとリクエスト処理Registry:スマートキャッシュと優先順位ルールを備えた要素ストレージElements:登録された MCP コンポーネント(ツール、リソース、プロンプト、テンプレート)
トランスポートのオプション
StdioServerTransport:クライアントの直接起動に使用する標準入出力HttpServerTransport:Web 連携のための HTTP + Server-Sent EventsStreamableHttpServerTransport:再開機能とイベントソーシングを備えた拡張 HTTP
⚙️ サーバーの設定
基本設定
use PhpMcp\Server\Server;
use PhpMcp\Schema\ServerCapabilities;
$server = Server::make()
->withServerInfo('My App Server', '2.1.0')
->withCapabilities(ServerCapabilities::make(
resources: true,
resourcesSubscribe: true,
prompts: true,
tools: true
))
->withPaginationLimit(100)
->build();
依存関係を含む高度な設定
use Psr\Log\Logger;
use Psr\SimpleCache\CacheInterface;
use Psr\Container\ContainerInterface;
$server = Server::make()
->withServerInfo('Production Server', '1.0.0')
->withLogger($myPsrLogger) // PSR-3 Logger
->withCache($myPsrCache) // PSR-16 Cache
->withContainer($myPsrContainer) // PSR-11 Container
->withSession('cache', 7200) // Cache-backed sessions, 2hr TTL
->withPaginationLimit(50) // Limit list responses
->build();
セッション管理のオプション
// In-memory sessions (default, not persistent)
->withSession('array', 3600)
// Cache-backed sessions (persistent across restarts)
->withSession('cache', 7200)
// Custom session handler (implement SessionHandlerInterface)
->withSessionHandler(new MyCustomSessionHandler(), 1800)
🎯 MCP 要素の定義
サーバーでは、MCP 要素を定義する強力な方法を 2 つ用意しています。属性ベースの検出(推奨)と手動登録です。両方を組み合わせることもでき、その場合は手動登録が優先されます。
要素の種類
- 🔧 ツール:実行可能な関数/アクション(例:
calculate、send_email、query_database) - 📄 リソース:静的なコンテンツ/データ(例:
config://settings、file://readme.txt) - 📋 リソーステンプレート:URI パターンを持つ動的リソース(例:
user://{id}/profile) - 💬 プロンプト:会話の開始文/テンプレート(例:
summarize、translate)
1. 🏷️ 属性ベースの検出(推奨)
PHP 8 の属性を使用して、メソッドや呼び出し可能なクラスを MCP 要素としてマークします。サーバーはファイルシステムをスキャンしてこれらを検出します。
use PhpMcp\Server\Attributes\{McpTool, McpResource, McpResourceTemplate, McpPrompt};
class UserManager
{
/**
* Creates a new user account.
*/
#[McpTool(name: 'create_user')]
public function createUser(string $email, string $password, string $role = 'user'): array
{
// Create user logic
return ['id' => 123, 'email' => $email, 'role' => $role];
}
/**
* Get user configuration.
*/
#[McpResource(
uri: 'config://user/settings',
mimeType: 'application/json'
)]
public function getUserConfig(): array
{
return ['theme' => 'dark', 'notifications' => true];
}
/**
* Get user profile by ID.
*/
#[McpResourceTemplate(
uriTemplate: 'user://{userId}/profile',
mimeType: 'application/json'
)]
public function getUserProfile(string $userId): array
{
return ['id' => $userId, 'name' => 'John Doe'];
}
/**
* Generate welcome message prompt.
*/
#[McpPrompt(name: 'welcome_user')]
public function welcomeUserPrompt(string $username, string $role): array
{
return [
['role' => 'user', 'content' => "Create a welcome message for {$username} with role {$role}"]
];
}
}
検出プロセス:
// Build server first
$server = Server::make()
->withServerInfo('My App Server', '1.0.0')
->build();
// Then discover elements
$server->discover(
basePath: __DIR__,
scanDirs: ['src/Handlers', 'src/Services'], // Directories to scan
excludeDirs: ['src/Tests'], // Directories to skip
saveToCache: true // Cache results (default: true)
);
利用可能な属性:
#[McpTool]:実行可能なアクション#[McpResource]:URI からアクセスできる静的コンテンツ#[McpResourceTemplate]:URI テンプレートを持つ動的リソース#[McpPrompt]:会話テンプレートとプロンプト生成器
2. 🔧 手動登録
ServerBuilder を使用して、build() の呼び出し前に要素をプログラムで登録します。動的な登録やクロージャの使用、または明示的な制御を好む場合に便利です。
use App\Handlers\{EmailHandler, ConfigHandler, UserHandler, PromptHandler};
use PhpMcp\Schema\{ToolAnnotations, Annotations};
$server = Server::make()
->withServerInfo('Manual Registration Server', '1.0.0')
// Register a tool with handler method
->withTool(
[EmailHandler::class, 'sendEmail'], // Handler: [class, method]
name: 'send_email', // Tool name (optional)
description: 'Send email to user', // Description (optional)
annotations: ToolAnnotations::make( // Annotations (optional)
title: 'Send Email Tool'
)
)
// Register invokable class as tool
->withTool(UserHandler::class) // Handler: Invokable class
// Register a closure as tool
->withTool(
function(int $a, int $b): int { // Handler: Closure
return $a + $b;
},
name: 'add_numbers',
description: 'Add two numbers together'
)
// Register a resource with closure
->withResource(
function(): array { // Handler: Closure
return ['timestamp' => time(), 'server' => 'php-mcp'];
},
uri: 'config://runtime/status', // URI (required)
mimeType: 'application/json' // MIME type (optional)
)
// Register a resource template
->withResourceTemplate(
[UserHandler::class, 'getUserProfile'],
uriTemplate: 'user://{userId}/profile' // URI template (required)
)
// Register a prompt with closure
->withPrompt(
function(string $topic, string $tone = 'professional'): array {
return [
['role' => 'user', 'content' => "Write about {$topic} in a {$tone} tone"]
];
},
name: 'writing_prompt' // Prompt name (optional)
)
->build();
サーバーでは、3 種類の柔軟なハンドラー形式をサポートしています。クラスメソッドのハンドラーには [ClassName::class, 'methodName']、呼び出し可能なクラスのハンドラーには InvokableClass::class(__invoke メソッドを持つクラス)を使用できます。また、クロージャ、[SomeClass::class, 'staticMethod'] のような静的メソッド、関数名など、あらゆる PHP の呼び出し可能要素を使用できます。クラスベースのハンドラーは、依存性注入のため、設定された PSR-11 コンテナを通じて解決されます。手動登録はキャッシュされず、同じ識別子を持つ検出済み要素より常に優先されます。
重要
ハンドラーとしてクロージャを使用する場合、docblock やクラスのコンテキストを利用できないため、サーバーは PHP の型ヒントのみに基づいて最小限の JSON スキーマを生成します。検証制約、説明、形式を含む、より詳細なスキーマを作成するには、次の 2 つの方法があります。
#[Schema]属性を使用してスキーマ生成を拡張する->withTool()でツールを登録する際に、カスタムの$inputSchemaパラメーターを指定する
🏆 要素の優先順位と検出
優先順位ルール:
- 手動登録は、同じ識別子を持つ検出済み/キャッシュ済みの要素より常に優先されます
- 検出済み要素はパフォーマンス向上のためキャッシュされます(設定可能)
- 新たに検出を実行すると、キャッシュは自動的に無効化されます
検出プロセス:
$server->discover(
basePath: __DIR__,
scanDirs: ['src/Handlers', 'src/Services'], // Scan these directories
excludeDirs: ['tests', 'vendor'], // Skip these directories
force: false, // Force re-scan (default: false)
saveToCache: true // Save to cache (default: true)
);
キャッシュの動作:
- キャッシュされるのは検出済み要素のみです(手動登録はキャッシュされません)
- 利用可能なキャッシュは、
build()の実行時に自動的に読み込まれます - 新たに
discover()を呼び出すと、キャッシュが消去されて再構築されます - 検出済みかどうかのチェックを回避するには、
force: trueを使用します
🚀 サーバーの実行(トランスポート)
サーバーコアはトランスポートに依存しません。デプロイのニーズに応じてトランスポートを選択してください。
1. 📟 Stdio トランスポート
最適な用途:クライアントからの直接実行、コマンドラインツール、シンプルなデプロイ
use PhpMcp\Server\Transports\StdioServerTransport;
$server = Server::make()
->withServerInfo('Stdio Server', '1.0.0')
->build();
$server->discover(__DIR__, ['src']);
// Create stdio transport (uses STDIN/STDOUT by default)
$transport = new StdioServerTransport();
// Start listening (blocking call)
$server->listen($transport);
クライアントの設定:
{
"mcpServers": {
"my-php-server": {
"command": "php",
"args": ["/absolute/path/to/server.php"]
}
}
}
⚠️ 重要:stdio トランスポートを使用する場合、ハンドラー内で
STDOUTに書き込まないでください(デバッグにはSTDERRを使用してください)。STDOUTはJSON-RPC通信専用です。
2. 🌐 HTTP + Server-Sent Events トランスポート(非推奨)
⚠️ 注意:このトランスポートは最新のMCPプロトコルバージョンでは非推奨ですが、後方互換性のため引き続き利用できます。新しいプロジェクトでは、機能が強化され、プロトコルへの準拠性も向上したStreamableHttpServerTransportを使用してください。
最適な用途:後方互換性が必要なレガシーアプリケーション
use PhpMcp\Server\Transports\HttpServerTransport;
$server = Server::make()
->withServerInfo('HTTP Server', '1.0.0')
->withLogger($logger) // Recommended for HTTP
->build();
$server->discover(__DIR__, ['src']);
// Create HTTP transport
$transport = new HttpServerTransport(
host: '127.0.0.1', // MCP protocol prohibits 0.0.0.0
port: 8080, // Port number
mcpPathPrefix: 'mcp' // URL prefix (/mcp/sse, /mcp/message)
);
$server->listen($transport);
クライアントの設定:
{
"mcpServers": {
"my-http-server": {
"url": "http://localhost:8080/mcp/sse"
}
}
}
エンドポイント:
- SSE接続:
GET /mcp/sse - メッセージ送信:
POST /mcp/message?clientId={clientId}
3. 🔄 Streamable HTTP トランスポート(推奨)
最適な用途:本番環境へのデプロイ、リモート MCP サーバー、複数のクライアント、再開可能な接続
use PhpMcp\Server\Transports\StreamableHttpServerTransport;
$server = Server::make()
->withServerInfo('Streamable Server', '1.0.0')
->withLogger($logger)
->withCache($cache) // Required for resumability
->build();
$server->discover(__DIR__, ['src']);
// Create streamable transport with resumability
$transport = new StreamableHttpServerTransport(
host: '127.0.0.1', // MCP protocol prohibits 0.0.0.0
port: 8080,
mcpPathPrefix: 'mcp',
enableJsonResponse: false, // Use SSE streaming (default)
stateless: false // Enable stateless mode for session-less clients
);
$server->listen($transport);
JSONレスポンスモード:
enableJsonResponseオプションは、レスポンスの配信方法を制御します。
false(デフォルト):レスポンスにServer-Sent Events(SSE)ストリームを使用します。処理に時間がかかることがあるツールに最適です。true:SSEストリームを開かずに、即時JSONレスポンスを返します。ツールの実行が速く、ストリーミングが不要な場合に使用してください。
// For fast-executing tools, enable JSON mode
$transport = new StreamableHttpServerTransport(
host: '127.0.0.1',
port: 8080,
enableJsonResponse: true // Immediate JSON responses
);
ステートレスモード:
セッション管理に問題があるクライアントでは、ステートレスモードを有効にしてください。
$transport = new StreamableHttpServerTransport(
host: '127.0.0.1',
port: 8080,
stateless: true // Each request is independent
);
ステートレスモードでは、セッションIDは内部で生成されますが、クライアントには公開されません。また、各リクエストは永続的なセッション状態を持たない独立したリクエストとして扱われます。
機能:
- 再開可能な接続 - クライアントは再接続して、受信できなかったイベントを再生できます
- イベントソーシング - すべてのイベントが再生用に保存されます
- JSONモード - 高速なツール向けに、JSONのみのレスポンスを選択できます
- 強化されたセッション管理 - セッション状態が永続化されます
- 複数クライアントのサポート - 複数のクライアントによる同時利用を想定しています
- ステートレスモード - シンプルなクライアント向けのセッション不要の動作
📋 スキーマの生成と検証
サーバーは、PHPの型ヒント、docblockの情報、任意の#[Schema]属性を組み合わせた高度な優先順位システムを使用して、ツールのパラメーター用JSONスキーマを自動生成します。生成されたスキーマは、入力の検証とMCPクライアントへのスキーマ情報の提供の両方に使用されます。
スキーマ生成の優先順位
スキーマの生成では、次の優先順位が適用されます。
definitionを指定した#[Schema]属性 - スキーマ全体を上書きします(最優先)- パラメーターレベルの
#[Schema]属性 - パラメーター固有のスキーマを拡張します - メソッドレベルの
#[Schema]属性 - メソッド全体のスキーマ設定を行います - PHPの型ヒント + docblock - コードから自動推論します(最も優先度が低い)
Schema属性にdefinitionを指定すると、自動推論はすべて省略され、指定した定義がそのまま使用されます。
パラメーターレベルのスキーマ属性
use PhpMcp\Server\Attributes\{McpTool, Schema};
#[McpTool(name: 'validate_user')]
public function validateUser(
#[Schema(format: 'email')] // PHP already knows it's string
string $email,
#[Schema(
pattern: '^[A-Z][a-z]+$',
description: 'Capitalized name'
)]
string $name,
#[Schema(minimum: 18, maximum: 120)] // PHP already knows it's integer
int $age
): bool {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
メソッドレベルのスキーマ
/**
* Process user data with nested validation.
*/
#[McpTool(name: 'create_user')]
#[Schema(
properties: [
'profile' => [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string', 'minLength' => 2],
'age' => ['type' => 'integer', 'minimum' => 18],
'email' => ['type' => 'string', 'format' => 'email']
],
'required' => ['name', 'email']
]
],
required: ['profile']
)]
public function createUser(array $userData): array
{
// PHP type hint provides base 'array' type
// Method-level Schema adds object structure validation
return ['id' => 123, 'status' => 'created'];
}
スキーマ全体の上書き(メソッドレベルのみ)
#[McpTool(name: 'process_api_request')]
#[Schema(definition: [
'type' => 'object',
'properties' => [
'endpoint' => ['type' => 'string', 'format' => 'uri'],
'method' => ['type' => 'string', 'enum' => ['GET', 'POST', 'PUT', 'DELETE']],
'headers' => [
'type' => 'object',
'patternProperties' => [
'^[A-Za-z0-9-]+$' => ['type' => 'string']
]
]
],
'required' => ['endpoint', 'method']
])]
public function processApiRequest(string $endpoint, string $method, array $headers): array
{
// PHP type hints are completely ignored when definition is provided
// The schema definition above takes full precedence
return ['status' => 'processed', 'endpoint' => $endpoint];
}
⚠️ 重要:スキーマ定義全体の上書きは、ほとんどの場合、使用する必要はありません。すべての自動スキーマ推論が省略され、JSON Schema全体を手動で定義する必要があります。この方法は、JSON Schema仕様に精通しており、優先順位システムでは実現できない複雑な検証要件がある場合にのみ使用してください。ほとんどの場合、パラメーターレベルおよびメソッドレベルの
#[Schema]属性で十分な柔軟性が得られます。
🎨 戻り値のフォーマット
サーバーは、ハンドラーからの戻り値を適切なMCPコンテンツタイプに自動でフォーマットします。
自動フォーマット
// Simple values are auto-wrapped in TextContent
public function getString(): string { return "Hello World"; } // → TextContent
public function getNumber(): int { return 42; } // → TextContent
public function getBool(): bool { return true; } // → TextContent
public function getArray(): array { return ['key' => 'value']; } // → TextContent (JSON)
// Null handling
public function getNull(): ?string { return null; } // → TextContent("(null)")
public function returnVoid(): void { /* no return */ } // → Empty content
高度なコンテンツタイプ
use PhpMcp\Schema\Content\{TextContent, ImageContent, AudioContent, ResourceContent};
public function getFormattedCode(): TextContent
{
return TextContent::code('<?php echo "Hello";', 'php');
}
public function getMarkdown(): TextContent
{
return TextContent::make('# Title\n\nContent here');
}
public function getImage(): ImageContent
{
return ImageContent::make(
data: base64_encode(file_get_contents('image.png')),
mimeType: 'image/png'
);
}
public function getAudio(): AudioContent
{
return AudioContent::make(
data: base64_encode(file_get_contents('audio.mp3')),
mimeType: 'audio/mpeg'
);
}
ファイルとストリームの処理
// File objects are automatically read and formatted
public function getFileContent(): \SplFileInfo
{
return new \SplFileInfo('/path/to/file.txt'); // Auto-detects MIME type
}
// Stream resources are read completely
public function getStreamContent()
{
$stream = fopen('/path/to/data.json', 'r');
return $stream; // Will be read and closed automatically
}
// Structured resource responses
public function getStructuredResource(): array
{
return [
'text' => 'File content here',
'mimeType' => 'text/plain'
];
// Or for binary data:
// return [
// 'blob' => base64_encode($binaryData),
// 'mimeType' => 'application/octet-stream'
// ];
}
🔄 バッチ処理
サーバーは JSON-RPC のバッチリクエストを自動的に処理します。
// Client can send multiple requests in a single HTTP call:
[
{"jsonrpc": "2.0", "id": "1", "method": "tools/call", "params": {...}},
{"jsonrpc": "2.0", "method": "notifications/ping"}, // notification
{"jsonrpc": "2.0", "id": "2", "method": "tools/call", "params": {...}}
]
// Server returns batch response (excluding notifications):
[
{"jsonrpc": "2.0", "id": "1", "result": {...}},
{"jsonrpc": "2.0", "id": "2", "result": {...}}
]
🔧 高度な機能
補完プロバイダー
補完プロバイダーを使用すると、MCP クライアントはユーザーインターフェースで自動補完候補を提示できます。補完プロバイダーは、リソーステンプレートとプロンプト向けに特別に設計されており、テンプレート変数やプロンプト引数などの動的な部分で、ユーザーが利用可能な選択肢を見つけられるよう支援します。
注:ツールとリソースは標準の MCP コマンド(
tools/list、resources/list)で検出できるため、これらに補完プロバイダーは必要ありません。補完プロバイダーは、リソーステンプレート(URI 変数)とプロンプト引数にのみ使用されます。
#[CompletionProvider] 属性では、次の 3 種類の補完ソースをサポートしています。
1. カスタムプロバイダークラス
複雑な補完ロジックには、CompletionProviderInterface を実装します。
use PhpMcp\Server\Contracts\CompletionProviderInterface;
use PhpMcp\Server\Contracts\SessionInterface;
use PhpMcp\Server\Attributes\{McpResourceTemplate, CompletionProvider};
class UserIdCompletionProvider implements CompletionProviderInterface
{
public function __construct(private DatabaseService $db) {}
public function getCompletions(string $currentValue, SessionInterface $session): array
{
// Dynamic completion from database
return $this->db->searchUsers($currentValue);
}
}
class UserService
{
#[McpResourceTemplate(uriTemplate: 'user://{userId}/profile')]
public function getUserProfile(
#[CompletionProvider(provider: UserIdCompletionProvider::class)] // Class string - resolved from container
string $userId
): array {
return ['id' => $userId, 'name' => 'John Doe'];
}
}
あらかじめ設定したプロバイダーインスタンスを渡すこともできます。
class DocumentService
{
#[McpPrompt(name: 'document_prompt')]
public function generatePrompt(
#[CompletionProvider(provider: new UserIdCompletionProvider($database))] // Pre-configured instance
string $userId,
#[CompletionProvider(provider: $this->categoryProvider)] // Instance from property
string $category
): array {
return [['role' => 'user', 'content' => "Generate document for user {$userId} in {$category}"]];
}
}
2. シンプルなリスト補完
静的な補完リストには、values パラメーターを使用します。
use PhpMcp\Server\Attributes\{McpPrompt, CompletionProvider};
class ContentService
{
#[McpPrompt(name: 'content_generator')]
public function generateContent(
#[CompletionProvider(values: ['blog', 'article', 'tutorial', 'guide', 'documentation'])]
string $contentType,
#[CompletionProvider(values: ['beginner', 'intermediate', 'advanced', 'expert'])]
string $difficulty
): array {
return [['role' => 'user', 'content' => "Create a {$difficulty} level {$contentType}"]];
}
}
3. Enum ベースの補完
Enum クラスには、enum パラメーターを使用します。
enum Priority: string
{
case LOW = 'low';
case MEDIUM = 'medium';
case HIGH = 'high';
case CRITICAL = 'critical';
}
enum Status // Unit enum (no backing values)
{
case DRAFT;
case PUBLISHED;
case ARCHIVED;
}
class TaskService
{
#[McpTool(name: 'create_task')]
public function createTask(
string $title,
#[CompletionProvider(enum: Priority::class)] // String-backed enum uses values
string $priority,
#[CompletionProvider(enum: Status::class)] // Unit enum uses case names
string $status
): array {
return ['id' => 123, 'title' => $title, 'priority' => $priority, 'status' => $status];
}
}
補完プロバイダーを使用した手動登録
$server = Server::make()
->withServerInfo('Completion Demo', '1.0.0')
// Using provider class (resolved from container)
->withPrompt(
[DocumentHandler::class, 'generateReport'],
name: 'document_report'
// Completion providers are auto-discovered from method attributes
)
// Using closure with inline completion providers
->withPrompt(
function(
#[CompletionProvider(values: ['json', 'xml', 'csv', 'yaml'])]
string $format,
#[CompletionProvider(enum: Priority::class)]
string $priority
): array {
return [['role' => 'user', 'content' => "Export data in {$format} format with {$priority} priority"]];
},
name: 'export_data'
)
->build();
補完プロバイダーの解決
サーバーはプロバイダーの解決を自動的に処理します。
- クラス文字列(
MyProvider::class)→ 依存性注入を使用して PSR-11 コンテナーから解決されます - インスタンス(
new MyProvider())→ そのまま使用されます - 値の配列(
['a', 'b', 'c'])→ 自動的にListCompletionProviderでラップされます - Enum クラス(
MyEnum::class)→ 自動的にEnumCompletionProviderでラップされます
重要:補完プロバイダーは MCP クライアントのインターフェースでユーザーに候補を提示するだけです。ユーザーは任意の値を入力できるため、補完プロバイダーの制約にかかわらず、ハンドラー内で必ずパラメーターを検証してください。
カスタム依存性注入
MCP 要素のハンドラーでは、コンストラクターによる依存性注入を使用して、データベース、API、その他のビジネスロジックなどのサービスにアクセスできます。ハンドラーにコンストラクターの依存関係がある場合、それらの依存関係を含む、あらかじめ設定した PSR-11 コンテナーを用意する必要があります。
デフォルトでは、サーバーは BasicContainer を使用します。これは、引数のないコンストラクターでクラスをインスタンス化して依存関係の自動配線を試みるシンプルな実装です。データベース接続など、設定が必要な依存関係については、手動で BasicContainer に追加するか、PHP-DI や Laravel のコンテナーなど、より高度な PSR-11 コンテナーを使用できます。
use Psr\Container\ContainerInterface;
class DatabaseService
{
public function __construct(private \PDO $pdo) {}
#[McpTool(name: 'query_users')]
public function queryUsers(): array
{
$stmt = $this->pdo->query('SELECT * FROM users');
return $stmt->fetchAll();
}
}
// Option 1: Use the basic container and manually add dependencies
$basicContainer = new \PhpMcp\Server\Defaults\BasicContainer();
$basicContainer->set(\PDO::class, new \PDO('sqlite::memory:'));
// Option 2: Use any PSR-11 compatible container (PHP-DI, Laravel, etc.)
$container = new \DI\Container();
$container->set(\PDO::class, new \PDO('mysql:host=localhost;dbname=app', $user, $pass));
$server = Server::make()
->withContainer($basicContainer) // Handlers get dependencies auto-injected
->build();
リソースのサブスクリプション
use PhpMcp\Schema\ServerCapabilities;
$server = Server::make()
->withCapabilities(ServerCapabilities::make(
resourcesSubscribe: true, // Enable resource subscriptions
prompts: true,
tools: true
))
->build();
// In your resource handler, you can notify clients of changes:
#[McpResource(uri: 'file://config.json')]
public function getConfig(): array
{
// When config changes, notify subscribers
$this->notifyResourceChange('file://config.json');
return ['setting' => 'value'];
}
再開機能とイベントストア
StreamableHttpServerTransport を使用する本番環境へのデプロイでは、カスタムイベントストアを指定することで、イベントソーシングによる再開機能を実装できます。
use PhpMcp\Server\Contracts\EventStoreInterface;
use PhpMcp\Server\Defaults\InMemoryEventStore;
use PhpMcp\Server\Transports\StreamableHttpServerTransport;
// Use the built-in in-memory event store (for development/testing)
$eventStore = new InMemoryEventStore();
// Or implement your own persistent event store
class DatabaseEventStore implements EventStoreInterface
{
public function storeEvent(string $streamId, string $message): string
{
// Store event in database and return unique event ID
return $this->database->insert('events', [
'stream_id' => $streamId,
'message' => $message,
'created_at' => now()
]);
}
public function replayEventsAfter(string $lastEventId, callable $sendCallback): void
{
// Replay events for resumability
$events = $this->database->getEventsAfter($lastEventId);
foreach ($events as $event) {
$sendCallback($event['id'], $event['message']);
}
}
}
// Configure transport with event store
$transport = new StreamableHttpServerTransport(
host: '127.0.0.1',
port: 8080,
eventStore: new DatabaseEventStore() // Enable resumability
);
カスタムセッションハンドラー
SessionHandlerInterface を実装するクラスを作成して、カスタムのセッションストレージを実装します。
use PhpMcp\Server\Contracts\SessionHandlerInterface;
class DatabaseSessionHandler implements SessionHandlerInterface
{
public function __construct(private \PDO $db) {}
public function read(string $id): string|false
{
$stmt = $this->db->prepare('SELECT data FROM sessions WHERE id = ?');
$stmt->execute([$id]);
$session = $stmt->fetch(\PDO::FETCH_ASSOC);
return $session ? $session['data'] : false;
}
public function write(string $id, string $data): bool
{
$stmt = $this->db->prepare(
'INSERT OR REPLACE INTO sessions (id, data, updated_at) VALUES (?, ?, ?)'
);
return $stmt->execute([$id, $data, time()]);
}
public function destroy(string $id): bool
{
$stmt = $this->db->prepare('DELETE FROM sessions WHERE id = ?');
return $stmt->execute([$id]);
}
public function gc(int $maxLifetime): array
{
$cutoff = time() - $maxLifetime;
$stmt = $this->db->prepare('DELETE FROM sessions WHERE updated_at < ?');
$stmt->execute([$cutoff]);
return []; // Return array of cleaned session IDs if needed
}
}
// Use custom session handler
$server = Server::make()
->withSessionHandler(new DatabaseSessionHandler(), 3600)
->build();
ミドルウェアのサポート
HttpServerTransport と StreamableHttpServerTransport はどちらも、HTTP リクエストとレスポンスをインターセプトして変更するための PSR-7 互換ミドルウェアをサポートしています。ミドルウェアを使用すると、認証、ロギング、CORS 処理、リクエスト検証などの共通機能を、再利用可能なコンポーネントに切り出せます。
ミドルウェアは、有効な PHP 呼び出し可能オブジェクトである必要があります。第 1 引数として PSR-7 の ServerRequestInterface を受け取り、第 2 引数として callable を受け取ります。
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use React\Promise\PromiseInterface;
class AuthMiddleware
{
public function __invoke(ServerRequestInterface $request, callable $next)
{
$apiKey = $request->getHeaderLine('Authorization');
if (empty($apiKey)) {
return new Response(401, [], 'Authorization required');
}
$request = $request->withAttribute('user_id', $this->validateApiKey($apiKey));
$result = $next($request);
return match (true) {
$result instanceof PromiseInterface => $result->then(fn($response) => $this->handle($response)),
$result instanceof ResponseInterface => $this->handle($result),
default => $result
};
}
private function handle($response)
{
return $response instanceof ResponseInterface
? $response->withHeader('X-Auth-Provider', 'mcp-server')
: $response;
}
}
$middlewares = [
new AuthMiddleware(),
new LoggingMiddleware(),
function(ServerRequestInterface $request, callable $next) {
$result = $next($request);
return match (true) {
$result instanceof PromiseInterface => $result->then(function($response) {
return $response instanceof ResponseInterface
? $response->withHeader('Access-Control-Allow-Origin', '*')
: $response;
}),
$result instanceof ResponseInterface => $result->withHeader('Access-Control-Allow-Origin', '*'),
default => $result
};
}
];
$transport = new StreamableHttpServerTransport(
host: '127.0.0.1',
port: 8080,
middlewares: $middlewares
);
重要な考慮事項:
- レスポンスの処理:ReactPHP は非同期で動作するため、ミドルウェアでは
$next($request)から返される同期的なResponseInterfaceと非同期のPromiseInterfaceの両方を処理する必要があります - 呼び出し可能クラスのパターン:推奨されるパターンは、呼び出し可能クラスと、レスポンスを処理する独立した
handle()メソッドを使用することです。これにより、非同期ロジックを再利用できます - 実行順序:ミドルウェアは指定された順に実行され、最後のミドルウェアが MCP ハンドラーに最も近い位置になります
SSL コンテキストの設定
StreamableHttpServerTransport を HTTPS でデプロイする場合は、SSL コンテキストオプションを設定します。
$sslContext = [
'ssl' => [
'local_cert' => '/path/to/certificate.pem',
'local_pk' => '/path/to/private-key.pem',
'verify_peer' => false,
'allow_self_signed' => true,
]
];
$transport = new StreamableHttpServerTransport(
host: '0.0.0.0',
port: 8443,
sslContext: $sslContext
);
SSL コンテキストのリファレンス:SSL コンテキストオプションの全項目については、PHP の SSL コンテキストオプションのドキュメントをご覧ください。
🔍 エラー処理とデバッグ
サーバーは、包括的なエラー処理およびデバッグ機能を備えています。
例外処理
エラーが発生した場合、ツールハンドラーは任意の PHP 例外をスローできます。サーバーは、これらの例外を MCP クライアント向けの適切な JSON-RPC エラーレスポンスに自動的に変換します。
#[McpTool(name: 'divide_numbers')]
public function divideNumbers(float $dividend, float $divisor): float
{
if ($divisor === 0.0) {
// Any exception with descriptive message will be sent to client
throw new \InvalidArgumentException('Division by zero is not allowed');
}
return $dividend / $divisor;
}
#[McpTool(name: 'calculate_factorial')]
public function calculateFactorial(int $number): int
{
if ($number < 0) {
throw new \InvalidArgumentException('Factorial is not defined for negative numbers');
}
if ($number > 20) {
throw new \OverflowException('Number too large, factorial would cause overflow');
}
// Implementation continues...
return $this->factorial($number);
}
サーバーは、これらの例外を MCP クライアントが理解し、ユーザーに表示できる適切な JSON-RPC エラーレスポンスに変換します。
ログとデバッグ
use Psr\Log\LoggerInterface;
class DebugAwareHandler
{
public function __construct(private LoggerInterface $logger) {}
#[McpTool(name: 'debug_tool')]
public function debugTool(string $data): array
{
$this->logger->info('Processing debug tool', ['input' => $data]);
// For stdio transport, use STDERR for debug output
fwrite(STDERR, "Debug: Processing data length: " . strlen($data) . "\n");
return ['processed' => true];
}
}
🚀 本番環境へのデプロイ
$server->listen() はプロセスを継続的に実行するため、インフラストラクチャのニーズに合った任意の方法でデプロイできます。VPS、クラウドインスタンス、コンテナー、または長時間実行プロセスに対応した任意の環境にデプロイできます。
検討に値する、一般的なデプロイ方法を 2 つ紹介します。
オプション 1:Supervisor と Nginx を使用した VPS(推奨)
最適な用途:大半の本番環境へのデプロイ、費用対効果、完全な制御
# 1. Install your application on VPS
git clone https://github.com/yourorg/your-mcp-server.git /var/www/mcp-server
cd /var/www/mcp-server
composer install --no-dev --optimize-autoloader
# 2. Install Supervisor
sudo apt-get install supervisor
# 3. Create Supervisor configuration
sudo nano /etc/supervisor/conf.d/mcp-server.conf
Supervisor の設定:
[program:mcp-server]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/mcp-server/server.php --transport=http --host=127.0.0.1 --port=8080
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/log/mcp-server.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=3
SSL を使用した Nginx の設定:
# /etc/nginx/sites-available/mcp-server
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name mcp.yourdomain.com;
# SSL configuration
ssl_certificate /etc/letsencrypt/live/mcp.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mcp.yourdomain.com/privkey.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# MCP Server proxy
location / {
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Important for SSE connections
proxy_buffering off;
proxy_cache off;
proxy_pass http://127.0.0.1:8080/;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name mcp.yourdomain.com;
return 301 https://$server_name$request_uri;
}
サービスの起動:
# Enable and start supervisor
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start mcp-server:*
# Enable and start nginx
sudo systemctl enable nginx
sudo systemctl restart nginx
# Check status
sudo supervisorctl status
クライアントの設定:
{
"mcpServers": {
"my-server": {
"url": "https://mcp.yourdomain.com/mcp"
}
}
}
オプション 2:Docker によるデプロイ
最適な用途:コンテナー化環境、Kubernetes、クラウドプラットフォーム
本番用 Dockerfile:
FROM php:8.3-fpm-alpine
# Install system dependencies
RUN apk --no-cache add \
nginx \
supervisor \
&& docker-php-ext-enable opcache
# Install PHP extensions for MCP
RUN docker-php-ext-install pdo_mysql pdo_sqlite opcache
# Create application directory
WORKDIR /var/www/mcp
# Copy application code
COPY . /var/www/mcp
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/supervisord.conf /etc/supervisord.conf
COPY docker/php.ini /usr/local/etc/php/conf.d/production.ini
# Install Composer dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Set permissions
RUN chown -R www-data:www-data /var/www/mcp
# Expose port
EXPOSE 80
# Start supervisor
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
docker-compose.yml:
services:
mcp-server:
build: .
ports:
- "8080:80"
environment:
- MCP_ENV=production
- MCP_LOG_LEVEL=info
volumes:
- ./storage:/var/www/mcp/storage
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
# Optional: Add database if needed
database:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secure_password
MYSQL_DATABASE: mcp_server
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped
volumes:
mysql_data:
セキュリティのベストプラクティス
- ファイアウォールの設定:
# Only allow necessary ports
sudo ufw allow ssh
sudo ufw allow 80
sudo ufw allow 443
sudo ufw deny 8080 # MCP port should not be publicly accessible
sudo ufw enable
- SSL/TLS の設定:
# Install Certbot for Let's Encrypt
sudo apt install certbot python3-certbot-nginx
# Generate SSL certificate
sudo certbot --nginx -d mcp.yourdomain.com
📚 サンプルとユースケース
examples/ ディレクトリで、包括的なサンプルをご覧ください。
利用可能なサンプル
01-discovery-stdio-calculator/- 属性検出機能を備えた基本的な stdio 計算機02-discovery-http-userprofile/- ユーザープロフィール管理機能を備えた HTTP サーバー03-manual-registration-stdio/- 要素を手動で登録するパターン04-combined-registration-http/- 手動登録と検出された要素の組み合わせ05-stdio-env-variables/- 環境変数の処理06-custom-dependencies-stdio/- タスク管理を伴う依存性注入07-complex-tool-schema-http/- 高度なスキーマ検証の例08-schema-showcase-streamable/- 包括的なスキーマ機能の紹介
サンプルの実行
# Navigate to an example directory
cd examples/01-discovery-stdio-calculator/
# Make the server executable
chmod +x server.php
# Run the server (or configure it in your MCP client)
./server.php
🚧 v2.x からの移行
バージョン 2.x から移行する場合は、次の主な変更点にご注意ください。
スキーマの更新
- 内部クラスの代わりに、DTO 用の
php-mcp/schemaパッケージを使用 - コンテンツタイプを
PhpMcp\Schema\Content\*名前空間に移動 - 型安全性を高めるため、メソッドシグネチャを更新
セッション管理
- 複数のバックエンドに対応した新しいセッション管理
- 設定には
->withSession()または->withSessionHandler()を使用 - セッションは再接続後も保持されるようになりました(キャッシュバックエンド使用時)
トランスポートの変更
- 再開可能な新しい
StreamableHttpServerTransport - エラー処理とイベントソーシングを強化
- バッチリクエスト処理を改善
🧪 テスト
# Install development dependencies
composer install --dev
# Run the test suite
composer test
# Run tests with coverage (requires Xdebug)
composer test:coverage
# Run code style checks
composer lint
🤝 コントリビュート
コントリビュートを歓迎します!ガイドラインについては、CONTRIBUTING.mdをご覧ください。
📄 ライセンス
MIT ライセンス(MIT)です。詳細はLICENSEをご覧ください。
🙏 謝辞
- Model Context Protocol の仕様に基づいて構築
- 非同期処理に ReactPHP を採用
- 相互運用性を最大限に高めるため、PSR standards を使用
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。