Skip to content

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

相关资源

社区支持

🚀 探索模型上下文协议的无限可能 | 连接 AI 与世界的桥梁 | 让智能更智能