基于Hyperf框架实现的Model Context Protocol (MCP)服务器,提供了完整的工具、提示和资源管理能力,支持Redis会话管理和基于注解的配置。基于dtyq/php-mcp核心库构建。
composer require hyperf/mcp-server-incubator
在路由文件(如config/routes.php)中添加MCP路由器:
<?php
use Hyperf\Context\ApplicationContext;
use Hyperf\HttpServer\Router\Router;
use Hyperf\McpServer\Server;
Router::addRoute(['POST', 'GET', 'DELETE'], '/mcp', function () {
return ApplicationContext::getContainer()->get(Server::class)->handler();
});
注意 配置提供者将被Hyperf自动加载,无需手动注册
config/config.php。
注册MCP工具、提示和资源最简单的方法是使用注解。此方法将根据方法签名自动生成模式并处理注册。
#[McpTool] - 注册工具使用 #[McpTool] 注解:将方法注册为MCP工具:
<?php
declare(strict_types=1);
namespace App\Service;
use Hyperf\McpServer\Collector\Annotations\McpTool;
class CalculatorService
{
#[McpTool]
public function calculate(string $operation, int $a, int $b): array
{
$result = match ($operation) {
'add' => $a + $b,
'subtract' => $a - $b,
'multiply' => $a * $b,
'divide' => $a / $b,
default => null,
};
return [
'operation' => $operation,
'operands' => [$a, $b],
'result' => $result,
];
}
#[McpTool(
name: 'advanced_calc',
description: '高级数学计算',
group: 'math'
)]
public function advancedCalculate(string $formula, array $variables = []): float
{
// 复杂计算逻辑
return 42.0;
}
}
注解参数:
name: 工具名称(默认为方法名)description: 工具描述inputSchema 自定义输入模式(当为空时自动生成)group 工具分组,用于组织enabled: 启用工具(默认:true)#[McpPrompt] - 注册提示使用 #[McpPrompt] 注解将方法注册为提示模板:
<?php
declare(strict_types=1);
namespace App\Service;
use Dtyq\PhpMcp\Types\Prompts\GetPromptResult;
use Dtyq\PhpMcp\Types\Prompts\PromptMessage;
use Dtyq\PhpMcp\Types\Content\TextContent;
use Dtyq\PhpMcp\Types\Core\ProtocolConstants;
use Hyperf\McpServer\Collector\Annotations\McpPrompt;
class PromptService
{
#[McpPrompt]
public function greeting(string $name, string $language = 'chinese'): GetPromptResult
{
$greetings = [
'english' => "Hello, {$name}! Welcome to the Streamable HTTP MCP server!",
'spanish' => "¡Hola, {$name}! ¡Bienvenido al servidor MCP Streamable HTTP!",
'french' => "Bonjour, {$name}! Bienvenue sur le serveur MCP Streamable HTTP!",
'chinese' => "你好,{$name}!欢迎使用 Streamable HTTP MCP 服务器!",
];
$message = new PromptMessage(
ProtocolConstants::ROLE_USER,
new TextContent($greetings[$language] ?? $greetings['chinese'])
);
return new GetPromptResult('问候提示', [$message]);
}
#[McpPrompt(
name: 'code_review',
description: '生成代码审查提示',
group: 'development'
)]
public function codeReview(string $code, string $language = 'php'): GetPromptResult
{
$prompt = "请审查以下 {$language} 代码:\n\n```{$language}\n{$code}\n```\n\n请提供以下方面的反馈:\n- 代码质量\n- 最佳实践\n- 潜在改进";
$message = new PromptMessage(
ProtocolConstants::ROLE_USER,
new TextContent($prompt)
);
return new GetPromptResult('代码审查提示', [$message]);
}
}
注解参数:
name 提示名称(默认为方法名)description: 提示描述arguments 自定义参数模式(当为空时自动生成)group 提示分组,用于组织enabled: 启用提示(默认:true)#[McpResource] - 注册资源使用 #[McpResource] 注解将方法注册为资源提供者:
<?php
declare(strict_types=1);
namespace App\Service;
use Dtyq\PhpMcp\Types\Resources\TextResourceContents;
use Hyperf\McpServer\Collector\Annotations\McpResource;
class SystemService
{
#[McpResource]
public function systemInfo(): TextResourceContents
{
$info = [
'php_version' => PHP_VERSION,
'os' => PHP_OS,
'memory_usage' => memory_get_usage(true),
'timestamp' => date('c'),
'pid' => getmypid(),
];
return new TextResourceContents(
'mcp://system/info',
json_encode($info, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE),
'application/json'
);
}
#[McpResource(
name: 'server_config',
uri: 'mcp://system/config',
description: '服务器配置数据',
mimeType: 'application/json'
)]
public function serverConfig(): TextResourceContents
{
$config = [
'environment' => env('APP_ENV', 'production'),
'debug' => env('APP_DEBUG', false),
'timezone' => date_default_timezone_get(),
];
return new TextResourceContents(
'mcp://system/config',
json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE),
'application/json'
);
}
}
注解参数:
name: 资源名称(默认为方法名)uri: 资源URI(当为空时自动生成)description: 资源描述mimeType: 资源MIME类型size: 资源大小(以字节为单位)group 资源分组,用于组织enabled: 启用资源(默认:true)isTemplate 是否为模板资源uriTemplate: URI模板参数注解系统将根据方法签名自动生成一个JSON模式:
#[McpTool]
public function processUser(
string $userId, // 必需的字符串参数
int $age = 18, // 可选的整数参数,有默认值
bool $active = true, // 可选的布尔参数,有默认值
array $tags = [] // 可选的数组参数,默认为空数组
): array {
// 实现代码
}
这将生成以下模式:
{
"type": "object",
"properties": {
"userId": {
"type": "string",
"description": "Parameter: userId"
},
"age": {
"type": "integer",
"description": "Parameter: age",
"default": 18
},
"active": {
"type": "boolean",
"description": "Parameter: active",
"default": true
},
"tags": {
"type": "array",
"description": "Parameter: tags",
"items": {"type": "string"},
"default": []
}
},
"required": ["userId"]
}
支持的类型:
| PHP 类型 | JSON Schema 类型 |
|---|---|
string | "type": "string" |
int, integer | "type": "integer" |
float, double | "type": "number" |
bool, boolean | "type": "boolean" |
array | "type": "array" |
注意:复杂类型(类、接口、联合类型)不支持。自动模式生成仅允许基本PHP类型。
可以使用分组来组织注解并加载特定组:
<?php
use Hyperf\Context\ApplicationContext;
use Hyperf\HttpServer\Router\Router;
use Hyperf\McpServer\Server;
// 只注册数学相关工具
Router::addRoute(['POST', 'GET', 'DELETE'], '/mcp/math', function () {
return ApplicationContext::getContainer()->get(Server::class)->handler('math');
});
// 注册开发工具
Router::addRoute(['POST', 'GET', 'DELETE'], '/mcp/dev', function () {
return ApplicationContext::getContainer()->get(Server::class)->handler('development');
});
// 注册所有工具(默认分组)
Router::addRoute(['POST', 'GET', 'DELETE'], '/mcp', function () {
return ApplicationContext::getContainer()->get(Server::class)->handler();
});
这是一个使用所有三种类型注解的完整服务类:
<?php
namespace App\Service;
use Dtyq\PhpMcp\Types\Prompts\GetPromptResult;
use Dtyq\PhpMcp\Types\Prompts\PromptMessage;
use Dtyq\PhpMcp\Types\Content\TextContent;
use Dtyq\PhpMcp\Types\Core\ProtocolConstants;
use Dtyq\PhpMcp\Types\Resources\TextResourceContents;
use Hyperf\McpServer\Collector\Annotations\McpTool;
use Hyperf\McpServer\Collector\Annotations\McpPrompt;
use Hyperf\McpServer\Collector\Annotations\McpResource;
class McpDemoService
{
#[McpTool(description: '回显消息')]
public function echo(string $message): array
{
return [
'echo' => $message,
'timestamp' => time(),
];
}
#[McpPrompt(description: '生成欢迎消息')]
public function welcome(string $username): GetPromptResult
{
$message = new PromptMessage(
ProtocolConstants::ROLE_USER,
new TextContent("欢迎 {$username} 来到我们的 MCP 服务器!")
);
return new GetPromptResult('欢迎消息', [$message]);
}
#[McpResource(description: '当前服务器状态')]
public function status(): TextResourceContents
{
$status = [
'status' => 'healthy',
'uptime' => time() - $_SERVER['REQUEST_TIME'],
'memory' => round(memory_get_usage() / 1024 / 1024, 2) . ' MB',
];
return new TextResourceContents(
'mcp://server/status',
json_encode($status, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE),
'application/json'
);
}
}
如果需要自定义认证,可以实现 AuthenticatorInterface:
<?php
namespace App\Auth;
use Dtyq\PhpMcp\Shared\Auth\AuthenticatorInterface;
use Dtyq\PhpMcp\Shared\Exceptions\AuthenticationError;
use Dtyq\PhpMcp\Types\Auth\AuthInfo;
use Hyperf\HttpServer\Contract\RequestInterface;
class CustomAuthenticator implements AuthenticatorInterface
{
public function __construct(
protected RequestInterface $request,
) {
}
public function authenticate(): AuthInfo
{
$apiKey = $this->request->header('X-API-Key');
// 实现您的认证逻辑
if (!$this->validateApiKey($apiKey)) {
throw new AuthenticationError('认证失败');
}
return AuthInfo::create(
subject: 'user-123',
scopes: ['read', 'write'],
metadata: ['api_key' => $apiKey]
);
}
private function validateApiKey(string $apiKey): bool
{
// 您的 API 密钥验证逻辑
return $apiKey === 'your-secret-api-key';
}
}
然后在配置中绑定:
// config/autoload/dependencies.php
return [
Dtyq\PhpMcp\Shared\Auth\AuthenticatorInterface::class => App\Auth\CustomAuthenticator::class,
];
您可以监听 HttpTransportAuthenticatedEvent 事件来动态注册工具、资源和提示:
<?php
namespace App\Listener;
use App\Service\UserToolService;
use Dtyq\PhpMcp\Server\Transports\Http\Event\HttpTransportAuthenticatedEvent;
use Dtyq\PhpMcp\Types\Tools\Tool;
use Dtyq\PhpMcp\Types\Resources\Resource;
use Dtyq\PhpMcp\Types\Prompts\Prompt;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Psr\Container\ContainerInterface;
#[Listener]
class DynamicMcpResourcesListener implements ListenerInterface
{
public function __construct(
protected ContainerInterface $container,
) {
}
public function listen(): array
{
return [
HttpTransportAuthenticatedEvent::class,
];
}
public function process(object $event): void
{
if (!$event instanceof HttpTransportAuthenticatedEvent) {
return;
}
$transportMetadata = $event->getTransportMetadata();
$authInfo = $event->getAuthInfo();
// 获取认证用户信息
$user = $authInfo->getMetadata('user');
$permissions = $authInfo->getMetadata('permissions', []);
// 动态注册工具
$this->registerDynamicTools($transportMetadata, $user, $permissions);
// 动态注册资源
$this->registerDynamicResources($transportMetadata, $user, $permissions);
// 动态注册提示
$this->registerDynamicPrompts($transportMetadata, $user, $permissions);
}
private function registerDynamicTools($transportMetadata, $user, array $permissions): void
{
$toolManager = $transportMetadata->getToolManager();
// 根据用户权限注册不同的工具
if (in_array('user_management', $permissions)) {
$userTool = new Tool('get_user_info', [
'type' => 'object',
'properties' => [
'user_id' => ['type' => 'integer'],
],
'required' => ['user_id'],
], '获取用户信息');
$toolManager->register($userTool, function(array $args) use ($user) {
// 实现工具逻辑
return $this->container->get(UserToolService::class)->getUserInfo($args['user_id'], $user);
});
}
if (in_array('admin', $permissions)) {
$adminTool = new Tool('admin_operation', [
'type' => 'object',
'properties' => [
'action' => ['type' => 'string'],
'target' => ['type' => 'string'],
],
'required' => ['action'],
], '执行管理员操作');
$toolManager->register($adminTool, function(array $args) {
// 管理员专用工具逻辑
return ['result' => "Admin action: {$args['action']}"];
});
}
}
private function registerDynamicResources($transportMetadata, $user, array $permissions): void
{
$resourceManager = $transportMetadata->getResourceManager();
// 根据权限注册资源
if (in_array('read_users', $permissions)) {
$usersResource = new Resource('users', 'application/json', '用户列表');
$resourceManager->register($usersResource, function() use ($user) {
// 返回用户有权限访问的用户列表
return json_encode(['users' => ['Alice', 'Bob']]);
});
}
if (in_array('read_reports', $permissions)) {
$reportsResource = new Resource('reports', 'application/json', '报告数据');
$resourceManager->register($reportsResource, function() {
return json_encode(['reports' => ['report1', 'report2']]);
});
}
}
private function registerDynamicPrompts($transportMetadata, $user, array $permissions): void
{
$promptManager = $transportMetadata->getPromptManager();
// 根据用户角色注册提示模板
if (in_array('content_creator', $permissions)) {
$contentPrompt = new Prompt('create_content', [
'type' => 'object',
'properties' => [
'topic' => ['type' => 'string'],
'style' => ['type' => 'string'],
],
'required