主题
PHP SDK
Model Context Protocol (MCP) 的 PHP SDK 提供了构建 MCP 服务器和客户端的现代 PHP 解决方案,支持 PHP 8.0+ 和现代 PHP 特性。
安装
Composer 安装
bash
composer require model-context-protocol/php-sdk
可选依赖
bash
# WebSocket 支持
composer require ratchet/pawl
# 异步支持
composer require reactphp/socket
# HTTP 客户端
composer require guzzlehttp/guzzle
快速开始
创建 MCP 服务器
php
<?php
require_once 'vendor/autoload.php';
use ModelContextProtocol\Server;
use ModelContextProtocol\Tool;
use ModelContextProtocol\ToolResult;
// 创建服务器
$server = new Server([
'name' => 'my-php-server',
'version' => '1.0.0',
'description' => 'PHP MCP 服务器示例'
]);
// 定义工具类
class EchoTool extends Tool
{
public function getName(): string
{
return 'echo';
}
public function getDescription(): string
{
return '回显输入的消息';
}
public function getInputSchema(): array
{
return [
'type' => 'object',
'properties' => [
'message' => [
'type' => 'string',
'description' => '要回显的消息'
]
],
'required' => ['message']
];
}
public function call(array $arguments): ToolResult
{
$message = $arguments['message'] ?? null;
if (!$message) {
return ToolResult::error('缺少 message 参数');
}
return ToolResult::success(['echo' => $message]);
}
}
// 注册工具
$server->registerTool(new EchoTool());
// 启动服务器
$server->start();
创建 MCP 客户端
php
<?php
require_once 'vendor/autoload.php';
use ModelContextProtocol\Client;
use ModelContextProtocol\Transport\StdioTransport;
// 创建传输层
$transport = new StdioTransport();
// 创建客户端
$client = new Client([
'transport' => $transport,
'connectTimeout' => 10,
'requestTimeout' => 30
]);
try {
// 连接到服务器
$client->connect();
// 列出可用工具
$tools = $client->listTools();
echo "可用工具: " . implode(', ', array_column($tools, 'name')) . "\n";
// 调用工具
$result = $client->callTool('echo', ['message' => 'Hello from PHP!']);
echo "工具调用结果: " . json_encode($result) . "\n";
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
} finally {
$client->close();
}
核心功能
服务器功能
工具注册
php
<?php
// 计算器工具
class CalculatorTool extends Tool
{
public function getName(): string
{
return 'calculator';
}
public function getDescription(): string
{
return '执行基本数学运算';
}
public function getInputSchema(): array
{
return [
'type' => 'object',
'properties' => [
'operation' => [
'type' => 'string',
'enum' => ['add', 'subtract', 'multiply', 'divide'],
'description' => '运算类型'
],
'a' => [
'type' => 'number',
'description' => '第一个数字'
],
'b' => [
'type' => 'number',
'description' => '第二个数字'
]
],
'required' => ['operation', 'a', 'b']
];
}
public function call(array $arguments): ToolResult
{
$operation = $arguments['operation'] ?? null;
$a = $arguments['a'] ?? null;
$b = $arguments['b'] ?? null;
if (!$operation || !is_numeric($a) || !is_numeric($b)) {
return ToolResult::error('无效的参数');
}
$result = match ($operation) {
'add' => $a + $b,
'subtract' => $a - $b,
'multiply' => $a * $b,
'divide' => $b != 0 ? $a / $b : null,
default => null
};
if ($result === null && $operation === 'divide') {
return ToolResult::error('除数不能为零');
}
if ($result === null) {
return ToolResult::error("不支持的运算: {$operation}");
}
return ToolResult::success(['result' => $result]);
}
}
// 注册工具
$server->registerTool(new CalculatorTool());
// 或者使用闭包
$server->registerTool('simple_tool', function (array $arguments): ToolResult {
return ToolResult::success(['message' => 'Hello from closure tool!']);
});
资源管理
php
<?php
use ModelContextProtocol\ResourceProvider;
use ModelContextProtocol\Resource;
use ModelContextProtocol\ResourceURI;
class FileResourceProvider extends ResourceProvider
{
private string $basePath;
public function __construct(string $basePath)
{
$this->basePath = $basePath;
}
public function getResource(ResourceURI $uri): Resource
{
$filePath = $this->basePath . '/' . ltrim($uri->getPath(), '/');
if (!file_exists($filePath)) {
throw new ResourceNotFoundException("资源不存在: {$uri}");
}
$content = file_get_contents($filePath);
return new Resource(
uri: $uri,
mimeType: 'text/plain',
content: $content
);
}
public function listResources(): array
{
$files = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->basePath)
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$relativePath = str_replace($this->basePath, '', $file->getPathname());
$files[] = new ResourceURI("file://{$relativePath}");
}
}
return $files;
}
}
// 注册资源提供者
$server->registerResourceProvider(new FileResourceProvider('/path/to/files'));
提示模板
php
<?php
use ModelContextProtocol\PromptProvider;
use ModelContextProtocol\Prompt;
class CodeReviewPromptProvider extends PromptProvider
{
public function getPrompt(string $name, array $arguments): Prompt
{
return match ($name) {
'code-review' => $this->createCodeReviewPrompt($arguments),
default => throw new PromptNotFoundException("未知提示: {$name}")
};
}
public function listPrompts(): array
{
return ['code-review'];
}
private function createCodeReviewPrompt(array $arguments): Prompt
{
$code = $arguments['code'] ?? null;
if (!$code) {
throw new InvalidArgumentException('缺少 code 参数');
}
$language = $arguments['language'] ?? 'unknown';
$content = <<<PROMPT
请审查以下 {$language} 代码并提供改进建议:
```{$language}
{$code}
PROMPT;
return new Prompt(
name: 'code-review',
description: '代码审查提示',
content: $content
);
}
}
// 注册提示提供者 $server->registerPromptProvider(new CodeReviewPromptProvider());
### 客户端功能
#### 连接管理
```php
<?php
// 自动重连客户端
$client = new Client([
'transport' => $transport,
'autoReconnect' => true,
'maxReconnectAttempts' => 5,
'reconnectDelay' => 2
]);
// 连接状态回调
$client->onConnected(function () {
echo "已连接到服务器\n";
});
$client->onDisconnected(function () {
echo "与服务器断开连接\n";
});
$client->onError(function (Exception $error) {
echo "连接错误: " . $error->getMessage() . "\n";
});
并发操作
php
<?php
use React\Promise\Promise;
// 并发调用多个工具
$promises = [
$client->callToolAsync('tool1', $args1),
$client->callToolAsync('tool2', $args2),
$client->callToolAsync('tool3', $args3)
];
Promise::all($promises)->then(function (array $results) {
foreach ($results as $index => $result) {
echo "结果 " . ($index + 1) . ": " . json_encode($result) . "\n";
}
});
流式操作
php
<?php
// 流式工具调用
$client->callToolStream('streaming-tool', $args, function (string $chunk) {
echo $chunk;
});
传输协议
Stdio 传输
php
<?php
use ModelContextProtocol\Transport\StdioTransport;
$transport = new StdioTransport([
'command' => ['python', 'server.py'],
'workingDirectory' => '/path/to/server',
'environment' => ['ENV_VAR' => 'value']
]);
WebSocket 传输
php
<?php
use ModelContextProtocol\Transport\WebSocketTransport;
$transport = new WebSocketTransport([
'uri' => 'ws://localhost:8080/mcp',
'headers' => ['Authorization' => 'Bearer token'],
'connectTimeout' => 10
]);
HTTP SSE 传输
php
<?php
use ModelContextProtocol\Transport\SSETransport;
$transport = new SSETransport([
'uri' => 'http://localhost:8080/mcp',
'headers' => ['Authorization' => 'Bearer token'],
'readTimeout' => 30
]);
高级特性
依赖注入支持
php
<?php
use Psr\Container\ContainerInterface;
class DIContainer implements ContainerInterface
{
private array $services = [];
public function set(string $id, callable $factory): void
{
$this->services[$id] = $factory;
}
public function get(string $id): mixed
{
if (!$this->has($id)) {
throw new NotFoundException("服务未找到: {$id}");
}
return $this->services[$id]();
}
public function has(string $id): bool
{
return isset($this->services[$id]);
}
}
// 注册服务
$container = new DIContainer();
$container->set('database', fn() => new DatabaseService());
$container->set('email', fn() => new EmailService());
// 带依赖注入的工具
class DatabaseTool extends Tool
{
private DatabaseService $databaseService;
public function __construct(DatabaseService $databaseService)
{
$this->databaseService = $databaseService;
}
public function getName(): string
{
return 'database-query';
}
public function getDescription(): string
{
return '执行数据库查询';
}
public function getInputSchema(): array
{
return [
'type' => 'object',
'properties' => [
'query' => [
'type' => 'string',
'description' => 'SQL 查询语句'
]
],
'required' => ['query']
];
}
public function call(array $arguments): ToolResult
{
$query = $arguments['query'] ?? null;
if (!$query) {
return ToolResult::error('缺少 query 参数');
}
try {
$result = $this->databaseService->executeQuery($query);
return ToolResult::success($result);
} catch (Exception $e) {
return ToolResult::error("查询执行失败: " . $e->getMessage());
}
}
}
// 创建服务器并注册工具
$server = new Server(['name' => 'my-server', 'version' => '1.0.0']);
$server->registerTool(new DatabaseTool($container->get('database')));
中间件支持
php
<?php
use ModelContextProtocol\Middleware\MiddlewareInterface;
use ModelContextProtocol\Request;
use ModelContextProtocol\Response;
class LoggingMiddleware implements MiddlewareInterface
{
public function process(Request $request, callable $next): Response
{
echo "处理请求: " . $request->getMethod() . "\n";
$startTime = microtime(true);
try {
$response = $next($request);
$duration = round((microtime(true) - $startTime) * 1000);
echo "请求完成,耗时: {$duration}ms\n";
return $response;
} catch (Exception $e) {
$duration = round((microtime(true) - $startTime) * 1000);
echo "请求处理失败,耗时: {$duration}ms\n";
throw $e;
}
}
}
class AuthenticationMiddleware implements MiddlewareInterface
{
public function process(Request $request, callable $next): Response
{
$authHeader = $request->getHeader('Authorization');
if (!$this->isValidToken($authHeader)) {
throw new UnauthorizedException('无效的认证令牌');
}
return $next($request);
}
private function isValidToken(?string $token): bool
{
return $token && str_starts_with($token, 'Bearer ');
}
}
// 注册中间件
$server->addMiddleware(new LoggingMiddleware());
$server->addMiddleware(new AuthenticationMiddleware());
配置管理
php
<?php
class McpConfig
{
public readonly ServerConfig $server;
public readonly LoggingConfig $logging;
public readonly TransportConfig $transport;
public function __construct(array $config)
{
$this->server = new ServerConfig($config['server'] ?? []);
$this->logging = new LoggingConfig($config['logging'] ?? []);
$this->transport = new TransportConfig($config['transport'] ?? []);
}
public static function loadFromFile(string $filePath): self
{
$config = json_decode(file_get_contents($filePath), true);
return new self($config);
}
}
class ServerConfig
{
public readonly string $name;
public readonly string $version;
public readonly string $description;
public readonly int $maxConnections;
public readonly int $requestTimeout;
public function __construct(array $config)
{
$this->name = $config['name'] ?? '';
$this->version = $config['version'] ?? '';
$this->description = $config['description'] ?? '';
$this->maxConnections = $config['maxConnections'] ?? 100;
$this->requestTimeout = $config['requestTimeout'] ?? 30;
}
}
class LoggingConfig
{
public readonly string $level;
public readonly ?string $file;
public function __construct(array $config)
{
$this->level = $config['level'] ?? 'INFO';
$this->file = $config['file'] ?? null;
}
}
class TransportConfig
{
public readonly string $type;
public readonly int $bufferSize;
public function __construct(array $config)
{
$this->type = $config['type'] ?? 'stdio';
$this->bufferSize = $config['bufferSize'] ?? 8192;
}
}
// config.json
// {
// "server": {
// "name": "my-server",
// "version": "1.0.0",
// "description": "我的 MCP 服务器",
// "maxConnections": 100,
// "requestTimeout": 30
// },
// "logging": {
// "level": "INFO",
// "file": "/var/log/mcp-server.log"
// },
// "transport": {
// "type": "stdio",
// "bufferSize": 8192
// }
// }
// 加载配置
$config = McpConfig::loadFromFile('config.json');
// 使用配置创建服务器
$server = new Server([
'name' => $config->server->name,
'version' => $config->server->version,
'description' => $config->server->description,
'maxConnections' => $config->server->maxConnections,
'requestTimeout' => $config->server->requestTimeout
]);
事件系统
php
<?php
use Psr\EventDispatcher\EventDispatcherInterface;
class EventDispatcher implements EventDispatcherInterface
{
private array $listeners = [];
public function dispatch(object $event): object
{
$eventClass = get_class($event);
if (isset($this->listeners[$eventClass])) {
foreach ($this->listeners[$eventClass] as $listener) {
$listener($event);
}
}
return $event;
}
public function addListener(string $eventClass, callable $listener): void
{
$this->listeners[$eventClass][] = $listener;
}
}
class ToolRegisteredEvent
{
public function __construct(
public readonly Tool $tool
) {}
}
class RequestReceivedEvent
{
public function __construct(
public readonly Request $request
) {}
}
class ResponseSentEvent
{
public function __construct(
public readonly Response $response
) {}
}
class McpServerWithEvents extends Server
{
private EventDispatcherInterface $eventDispatcher;
public function __construct(array $config, EventDispatcherInterface $eventDispatcher)
{
parent::__construct($config);
$this->eventDispatcher = $eventDispatcher;
}
public function registerTool(Tool $tool): void
{
parent::registerTool($tool);
$this->eventDispatcher->dispatch(new ToolRegisteredEvent($tool));
}
public function handleRequest(Request $request): Response
{
$this->eventDispatcher->dispatch(new RequestReceivedEvent($request));
$response = parent::handleRequest($request);
$this->eventDispatcher->dispatch(new ResponseSentEvent($response));
return $response;
}
}
// 使用事件
$eventDispatcher = new EventDispatcher();
$server = new McpServerWithEvents(['name' => 'event-server', 'version' => '1.0.0'], $eventDispatcher);
$eventDispatcher->addListener(ToolRegisteredEvent::class, function (ToolRegisteredEvent $event) {
echo "工具已注册: " . $event->tool->getName() . "\n";
});
$eventDispatcher->addListener(RequestReceivedEvent::class, function (RequestReceivedEvent $event) {
echo "收到请求: " . $event->request->getMethod() . "\n";
});
$eventDispatcher->addListener(ResponseSentEvent::class, function (ResponseSentEvent $event) {
echo "发送响应: " . $event->response->getStatus() . "\n";
});
测试
单元测试 (PHPUnit)
php
<?php
use PHPUnit\Framework\TestCase;
class EchoToolTest extends TestCase
{
private EchoTool $tool;
protected function setUp(): void
{
$this->tool = new EchoTool();
}
public function testCallWithValidMessage(): void
{
$result = $this->tool->call(['message' => 'test']);
$this->assertTrue($result->isSuccess());
$this->assertEquals('test', $result->getData()['echo']);
}
public function testCallWithMissingMessage(): void
{
$result = $this->tool->call([]);
$this->assertTrue($result->isError());
$this->assertStringContains('缺少 message 参数', $result->getErrorMessage());
}
}
class CalculatorToolTest extends TestCase
{
private CalculatorTool $tool;
protected function setUp(): void
{
$this->tool = new CalculatorTool();
}
public function testAddition(): void
{
$result = $this->tool->call([
'operation' => 'add',
'a' => 2,
'b' => 3
]);
$this->assertTrue($result->isSuccess());
$this->assertEquals(5, $result->getData()['result']);
}
public function testDivisionByZero(): void
{
$result = $this->tool->call([
'operation' => 'divide',
'a' => 10,
'b' => 0
]);
$this->assertTrue($result->isError());
$this->assertStringContains('除数不能为零', $result->getErrorMessage());
}
}
集成测试
php
<?php
use PHPUnit\Framework\TestCase;
class McpIntegrationTest extends TestCase
{
private Server $server;
private Client $client;
protected function setUp(): void
{
$this->server = new Server(['name' => 'test-server', 'version' => '1.0.0']);
$this->server->registerTool(new EchoTool());
// 启动服务器(在后台进程中)
$this->startServerInBackground();
$transport = new StdioTransport();
$this->client = new Client(['transport' => $transport]);
$this->client->connect();
}
protected function tearDown(): void
{
$this->client->close();
$this->stopBackgroundServer();
}
public function testCallTool(): void
{
$result = $this->client->callTool('echo', ['message' => 'test']);
$this->assertEquals('test', $result['echo']);
}
public function testListTools(): void
{
$tools = $this->client->listTools();
$toolNames = array_column($tools, 'name');
$this->assertContains('echo', $toolNames);
}
private function startServerInBackground(): void
{
// 实现后台服务器启动逻辑
}
private function stopBackgroundServer(): void
{
// 实现后台服务器停止逻辑
}
}
性能测试
php
<?php
// 性能基准测试
function benchmarkToolCalls(Client $client, int $iterations = 1000): void
{
$startTime = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$client->callTool('echo', ['message' => 'benchmark']);
}
$endTime = microtime(true);
$duration = $endTime - $startTime;
echo "总调用次数: {$iterations}\n";
echo "总耗时: " . round($duration, 2) . "秒\n";
echo "平均 QPS: " . round($iterations / $duration, 2) . "\n";
}
// 并发性能测试
function benchmarkConcurrentCalls(Client $client, int $threads = 10, int $callsPerThread = 100): void
{
$startTime = microtime(true);
$promises = [];
for ($i = 0; $i < $threads; $i++) {
for ($j = 0; $j < $callsPerThread; $j++) {
$promises[] = $client->callToolAsync('echo', ['message' => 'concurrent']);
}
}
Promise::all($promises)->wait();
$endTime = microtime(true);
$totalCalls = $threads * $callsPerThread;
$duration = $endTime - $startTime;
echo "总调用次数: {$totalCalls}\n";
echo "总耗时: " . round($duration, 2) . "秒\n";
echo "平均 QPS: " . round($totalCalls / $duration, 2) . "\n";
}
部署
Laravel 集成
php
<?php
// app/Http/Controllers/McpController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use ModelContextProtocol\Server;
class McpController extends Controller
{
private Server $mcpServer;
public function __construct()
{
$this->mcpServer = new Server([
'name' => 'laravel-mcp-server',
'version' => '1.0.0'
]);
$this->mcpServer->registerTool(new EchoTool());
}
public function handle(Request $request): JsonResponse
{
try {
$response = $this->mcpServer->handleRequest($request->getContent());
return response()->json($response);
} catch (Exception $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}
public function health(): JsonResponse
{
return response()->json(['status' => 'ok']);
}
}
// routes/web.php
Route::post('/mcp', [McpController::class, 'handle']);
Route::get('/health', [McpController::class, 'health']);
Symfony 集成
php
<?php
// src/Controller/McpController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use ModelContextProtocol\Server;
class McpController extends AbstractController
{
private Server $mcpServer;
public function __construct()
{
$this->mcpServer = new Server([
'name' => 'symfony-mcp-server',
'version' => '1.0.0'
]);
$this->mcpServer->registerTool(new EchoTool());
}
#[Route('/mcp', methods: ['POST'])]
public function handle(Request $request): JsonResponse
{
try {
$response = $this->mcpServer->handleRequest($request->getContent());
return new JsonResponse($response);
} catch (Exception $e) {
return new JsonResponse(['error' => $e->getMessage()], 500);
}
}
#[Route('/health', methods: ['GET'])]
public function health(): JsonResponse
{
return new JsonResponse(['status' => 'ok']);
}
}
Docker 部署
dockerfile
FROM php:8.2-fpm-alpine
# 安装系统依赖
RUN apk add --no-cache \
git \
curl \
libpng-dev \
oniguruma-dev \
libxml2-dev \
zip \
unzip
# 安装 PHP 扩展
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
# 安装 Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# 设置工作目录
WORKDIR /var/www
# 复制 composer 文件
COPY composer.json composer.lock ./
# 安装依赖
RUN composer install --no-dev --optimize-autoloader
# 复制应用代码
COPY . .
# 设置权限
RUN chown -R www-data:www-data /var/www
# 暴露端口
EXPOSE 9000
# 启动 PHP-FPM
CMD ["php-fpm"]
Systemd 服务
ini
# /etc/systemd/system/mcp-server.service
[Unit]
Description=MCP Server
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/mcp-server
ExecStart=/usr/bin/php server.php
Restart=always
RestartSec=5
Environment=APP_ENV=production
[Install]
WantedBy=multi-user.target
bash
# 启用和启动服务
sudo systemctl enable mcp-server
sudo systemctl start mcp-server
sudo systemctl status mcp-server