2025-06-26 18:24:33 +08:00

313 lines
9.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\api\controller\mcp;
class Mcp extends Base
{
// 工具定义常量
private const ADD_FUNCTION = [
[
'name' => 'add',
'description' => "计算两个数字的和",
'inputSchema' => [
'type' => 'object',
'properties' => [
'a' => [
'description' => '第一个加数(必填)',
'example' => 10,
'type' => 'number',
],
'b' => [
'description' => '第二个加数(必填)',
'example' => 20,
'type' => 'number',
]
]
]
],
[
'name' => 'sub',
'description' => "计算两个数字的减法",
'inputSchema' => [
'type' => 'object',
'properties' => [
'a' => [
'description' => '第一个减数(必填)',
'example' => 10,
'type' => 'number',
],
'b' => [
'description' => '第二个减数(必填)',
'example' => 20,
'type' => 'number',
]
]
]
],
];
public function sse()
{
// 设置 CORS 头
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
// 处理 OPTIONS 预检请求
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
// 设置 SSE 响应头
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
header('X-Accel-Buffering: no');
// 获取原始输入
$input = file_get_contents('php://input');
file_put_contents('mcp_debug.log', date('Y-m-d H:i:s').' Raw input: '.$input.PHP_EOL, FILE_APPEND);
// 解析 JSON-RPC 请求
$request = json_decode($input, true);
// 处理请求
if (isset($request['method'])) {
switch($request['method']) {
case 'initialize':
$this->handleInitializeRequest($request);
break;
case 'function':
case 'tools/call': // 新增支持tools/call方法
$this->handleFunctionRequest($request);
break;
case 'ping':
$this->sendJsonRpcResponse($request['id'] ?? null, ['result' => 'pong']);
break;
case 'notifications/initialized':
$this->sendJsonRpcResponse($request['id'] ?? null, ['result' => 'success']);
break;
case 'tools/list':
$this->sendJsonRpcResponse($request['id'] ?? null, ['tools' => self::ADD_FUNCTION]);
break;
default:
$this->handleOtherRequests($request);
}
} else {
$this->handleOtherRequests($request);
}
}
/**
* 处理初始化请求
*/
private function handleInitializeRequest($request)
{
$id = $request['id'] ?? null;
// 记录完整的初始化请求内容
file_put_contents('mcp_debug.log', "Received initialize request: " . json_encode($request) . PHP_EOL, FILE_APPEND);
// 发送初始化响应
$this->sendJsonRpcResponse($id, [
'capabilities' => [
'progress' => true,
'functions' => self::ADD_FUNCTION
],
'serverInfo' => [
'name' => 'Calculator Server',
'version' => '1.0.0',
'vendor' => 'MyCompany',
'license' => 'MIT'
],
'protocolVersion' => '2025-03-26'
]);
}
/**
* 处理函数调用请求
*/
public function handleFunctionRequest($request)
{
$id = $request['id'] ?? null;
$function = $request['params']['function'] ?? '';
$arguments = $request['params']['arguments'] ?? [];
// // 恢复函数名验证
// if ($function !== 'add') {
// $this->sendJsonRpcResponse($id, [
// 'error' => [
// 'code' => -32601,
// 'message' => 'Unsupported function',
// 'data' => "Function not found: $function"
// ]
// ]);
// return;
// }
// // 提取参数验证为独立方法
// $validationResult = $this->validateAddParameters($arguments, $id);
// if ($validationResult !== true) {
// $this->sendJsonRpcResponse($id, $validationResult);
// return;
// }
// 解构参数
$a = $arguments['a'] ?? 0;
$b = $arguments['b'] ?? 0;
// 发送进度通知
$this->sendNotification('progress', ['id' => $id, 'message' => '正在计算...']);
// 模拟处理延迟
sleep(1);
$result = $this->calculateAdd($a, $b);
$this->sendJsonRpcResponse($id, [
// '' => [
'operation' => 'add',
'result' => $result,
'expression' => "$a + $b = $result",
'timestamp' => time()
// ]
]);
}
/**
* 验证加法参数
* @param array $arguments
* @param mixed $id
* @return array|bool 返回true表示验证通过否则返回错误响应数组
*/
private function validateAddParameters(array $arguments, $id)
{
if (!isset($arguments['a'], $arguments['b'])) {
return [
'error' => [
'code' => -32602,
'message' => 'Invalid params',
'data' => 'Missing required parameters: a and b'
]
];
}
if (!is_numeric($arguments['a']) || !is_numeric($arguments['b'])) {
return [
'error' => [
'code' => -32602,
'message' => 'Invalid params',
'data' => 'Parameters must be numbers'
]
];
}
return true;
}
/**
* 处理其他类型的请求
*/
private function handleOtherRequests($request)
{
if (!$request || !isset($request['function'])) {
$this->sendJsonRpcResponse($request['id'] ?? null, ['error' => 'Invalid request format']);
return;
}
if ($request['function'] === 'add' || $request['function'] === 'tools/call') {
$a = $request['params']['a'] ?? 0;
$b = $request['params']['b'] ?? 0;
if (!is_numeric($a) || !is_numeric($b)) {
$this->sendEvent('error', 'Parameters must be numbers');
return;
}
// 模拟处理延迟
sleep(1);
$result = $this->calculateAdd($a, $b);
$this->sendEvent('result', [
'operation' => 'add',
'result' => $result,
'expression' => "$a + $b = $result"
]);
} else {
$this->sendEvent('error', 'Unsupported function: ' . $request['function']);
}
}
/**
* 执行加法计算
*/
private function calculateAdd(float $a, float $b): float
{
return $a + $b;
}
/**
* 发送 JSON-RPC 响应
*/
private function sendJsonRpcResponse($id, $resultOrError)
{
$response = [
'jsonrpc' => '2.0',
'id' => $id
];
if (isset($resultOrError['error'])) {
$response['error'] = $resultOrError['error'];
} else {
$response['result'] = $resultOrError;
}
$this->sendStreamData(json_encode($response, JSON_UNESCAPED_UNICODE));
}
/**
* 发送通知(用于进度更新等)
*/
private function sendNotification($method, $params)
{
$notification = [
'jsonrpc' => '2.0',
'method' => $method,
'params' => $params
];
$this->sendStreamData(json_encode($notification, JSON_UNESCAPED_UNICODE));
}
/**
* 发送标准 SSE 事件
*/
private function sendEvent($type, $data)
{
$jsonData = json_encode($data, JSON_UNESCAPED_UNICODE);
echo "event: $type\n";
echo "data: $jsonData\n\n";
$this->flushBuffers();
file_put_contents('mcp_debug.log', "Sent event: $type - $jsonData".PHP_EOL, FILE_APPEND);
}
/**
* 发送流数据
*/
private function sendStreamData(string $data)
{
echo "data: $data\n\n";
$this->flushBuffers();
file_put_contents('mcp_debug.log', "JSON-RPC Response: $data".PHP_EOL, FILE_APPEND);
}
/**
* 刷新输出缓冲区
*/
private function flushBuffers()
{
ob_flush();
flush();
}
}