mcpField
P
PHP MCP Server

PHP MCP Server

本地服务
github.com/php-mcp开发工具87564 周前更新

关于 PHP MCP Server

PHP MCP Server 是用于构建 MCP 服务的 PHP SDK,面向希望把 PHP 应用功能暴露给 AI 助手的开发者。它支持基于 PHP 8 属性的自动发现,并提供 stdio 与 HTTP 等多种传输方式。

主要功能

  1. 01基于 PHP 8 属性自动注册工具与资源
  2. 02支持 stdio 与 Streamable HTTP 传输
  3. 03通过 PSR-11 容器实现依赖注入
  4. 04支持 JSON-RPC 批处理请求
  5. 05结果可缓存,手动注册优先

适用场景

  1. 01将现有 PHP 应用的功能暴露为 MCP 工具
  2. 02为 Cursor 等客户端提供本地 stdio 形式的 PHP 服务
  3. 03构建需要远程访问的 Streamable HTTP MCP 服务

MCP 相关

该项目是用于构建 MCP 服务的 SDK,可将工具、资源和提示词暴露给 MCP 客户端。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

暂未收录安装方式

我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。

代码仓库

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

PHP MCP 服务器 SDK

Latest Version on Packagist Total Downloads Tests License

一款功能全面的 PHP SDK,用于构建 Model Context Protocol (MCP) 服务。借助现代架构、全面测试和灵活的传输选项,在 PHP 中创建可用于生产环境的 MCP 服务。

借助此 SDK,您可以将 PHP 应用的功能作为标准化的 MCP 工具、资源和提示词公开,使 AI 助手(如 Anthropic 的 Claude、Cursor IDE、OpenAI 的 ChatGPT 等)能够使用 MCP 标准与您的后端交互。

🚀 主要功能

  • 🏗️ 现代架构:基于 PHP 8.1+ 特性、PSR 标准和模块化设计构建
  • 📡 多种传输方式:支持 stdio、http+sse 和新增的、可恢复连接的流式 HTTP
  • 🎯 基于属性的定义:使用 PHP 8 属性(#[McpTool]、#[McpResource] 等)注册元素,无需配置
  • 🔧 灵活的处理器:支持闭包、类方法、静态方法和可调用类
  • 📝 智能 Schema 生成:根据方法签名自动生成 JSON schema,并可使用 #[Schema] 属性进行增强
  • ⚡ 会话管理:高级会话处理,支持多种存储后端
  • 🔄 事件驱动:基于 ReactPHP,支持高并发和非阻塞操作
  • 📊 批处理:全面支持 JSON-RPC 批量请求
  • 💾 智能缓存:智能缓存发现的元素,并优先采用手动覆盖项
  • 🧪 补全提供程序:内置对工具和提示词参数补全的支持
  • 🔌 依赖注入:全面支持 PSR-11 容器和自动装配
  • 📋 全面测试:包含集成测试的广泛测试套件,覆盖所有传输方式

此软件包支持 2025-03-26 版本的 Model Context Protocol,并保持向后兼容。

📋 要求

  • 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 - 将两个整数相加
  • 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:支持多种后端的会话存储(数组、缓存、自定义)
  • Dispatcher:方法路由与请求处理
  • Registry:具备智能缓存和优先级规则的元素存储
  • Elements:已注册的 MCP 组件(工具、资源、提示词、模板)
传输选项
  1. StdioServerTransport:用于直接启动客户端的标准输入/输出
  2. HttpServerTransport:用于 Web 集成的 HTTP + 服务器发送事件
  3. StreamableHttpServerTransport:支持可恢复性和事件溯源的增强型 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 元素定义方式:基于 Attribute 的发现(推荐)和手动注册。两种方式可以结合使用,手动注册的优先级更高。

元素类型
  • 🔧 工具:可执行的函数/操作(例如,calculate、send_email、query_database)
  • 📄 资源:静态内容/数据(例如,config://settings、file://readme.txt)
  • 📋 资源模板:带 URI 模式的动态资源(例如,user://{id}/profile)
  • 💬 提示词:对话开场白/模板(例如,summarize、translate)
1. 🏷️ 基于 Attribute 的发现(推荐)

使用 PHP 8 Attribute 将方法或可调用类标记为 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)
);

可用的 Attribute:

  • #[McpTool]:可执行操作
  • #[McpResource]:可通过 URI 访问的静态内容
  • #[McpResourceTemplate]:带 URI 模板的动态资源
  • #[McpPrompt]:对话模板和提示词生成器
2. 🔧 手动注册

在调用 build() 之前,使用 ServerBuilder 以编程方式注册元素。适用于动态注册、闭包,或偏好显式控制的场景。

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();

服务支持三种灵活的处理器格式:[ClassName::class, 'methodName'] 用于类方法处理器,InvokableClass::class 用于可调用类处理器(包含 __invoke 方法的类),以及任何 PHP 可调用项,包括闭包、[SomeClass::class, 'staticMethod'] 这样的静态方法,或函数名。基于类的处理器通过配置的 PSR-11 容器解析,以实现依赖注入。手动注册的内容永不缓存,并且对于具有相同标识符的已发现元素,手动注册始终优先。

重要

将闭包用作处理器时,由于没有 docblock 或类上下文,服务只会根据 PHP 类型提示生成最简 JSON Schema。若要生成包含验证约束、描述和格式的更详细 Schema,有以下两种方式:

  • 使用 #[Schema] attribute 增强 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:立即返回 JSON 响应,不会打开 SSE 流。适用于执行速度快且不需要流式传输的工具。
// 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 类型提示、文档块信息和可选的 #[Schema] 属性,自动为工具参数生成 JSON 架构。这些生成的架构既用于输入验证,也用于向 MCP 客户端提供架构信息。

架构生成优先级

服务按照以下优先级生成架构:

  1. 带有 definition 的 #[Schema] 属性——完全覆盖架构(最高优先级)
  2. 参数级 #[Schema] 属性——增强特定参数的架构
  3. 方法级 #[Schema] 属性——配置整个方法的架构
  4. PHP 类型提示 + 文档块——从代码自动推断(最低优先级)

如果 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 架构。只有在你熟悉 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] 属性支持三种补全来源:

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 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
  • 枚举类(MyEnum::class)→ 自动包装为 EnumCompletionProvider

重要:补全提供程序仅在 MCP 客户端界面中向用户提供建议。用户仍可输入任意值,因此无论补全提供程序有哪些限制,都应始终在处理程序中验证参数。

自定义依赖注入

MCP 元素处理程序可以使用构造函数依赖注入来访问数据库、API 或其他业务逻辑等服务。如果处理程序具有构造函数依赖项,则必须提供一个包含这些依赖项的预配置 PSR-11 容器。

默认情况下,服务使用 BasicContainer——这是一个简单的实现,会尝试通过实例化具有无参数构造函数的类来自动装配依赖项。对于需要配置的依赖项(例如数据库连接),你可以手动将它们添加到 BasicContainer,也可以使用更高级的 PSR-11 容器,例如 PHP-DI 或 Laravel 的容器。

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 都支持兼容 PSR-7 的中间件,可拦截并修改 HTTP 请求和响应。中间件可将身份验证、日志记录、CORS 处理和请求验证等通用功能提取为可复用组件。

中间件必须是有效的 PHP 可调用对象,第一个参数接受 PSR-7 ServerRequestInterface,第二个参数接受 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
);

重要注意事项:

  • 响应处理:中间件必须处理 $next($request) 返回的同步 ResponseInterface 和异步 PromiseInterface,因为 ReactPHP 采用异步运行方式
  • 可调用模式:建议使用带有独立 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);
}

服务器会将这些异常转换为相应的 JSON-RPC 错误响应,以便 MCP 客户端理解并向用户显示。

日志记录与调试
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、云实例、容器或任何支持长时间运行进程的环境中。

以下是两种值得考虑的常用部署方式:

选项 1:VPS 搭配 Supervisor + Nginx(推荐)

适用场景:大多数生产环境部署,经济高效且完全可控

# 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:
安全最佳实践
  1. 防火墙配置:
# 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
  1. 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 版本迁移,请注意以下主要变更:

架构更新
  • 使用 php-mcp/schema 包中的 DTO 替代内部类
  • 内容类型已移至 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。

🙏 致谢

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。