2025-06-25 18:31:47 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace app\api\controller\mcp;
|
|
|
|
|
|
|
|
|
|
class Mcp extends Base
|
|
|
|
|
{
|
|
|
|
|
// 工具定义常量
|
|
|
|
|
private const ADD_FUNCTION = [
|
2025-06-26 18:24:33 +08:00
|
|
|
|
[
|
2025-06-25 18:31:47 +08:00
|
|
|
|
'name' => 'add',
|
2025-06-26 18:24:33 +08:00
|
|
|
|
'description' => "计算两个数字的和",
|
|
|
|
|
'inputSchema' => [
|
|
|
|
|
'type' => 'object',
|
|
|
|
|
'properties' => [
|
2025-06-25 18:31:47 +08:00
|
|
|
|
'a' => [
|
|
|
|
|
'description' => '第一个加数(必填)',
|
2025-06-26 18:24:33 +08:00
|
|
|
|
'example' => 10,
|
|
|
|
|
'type' => 'number',
|
2025-06-25 18:31:47 +08:00
|
|
|
|
],
|
|
|
|
|
'b' => [
|
|
|
|
|
'description' => '第二个加数(必填)',
|
2025-06-26 18:24:33 +08:00
|
|
|
|
'example' => 20,
|
|
|
|
|
'type' => 'number',
|
2025-06-25 18:31:47 +08:00
|
|
|
|
]
|
2025-06-26 18:24:33 +08:00
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'name' => 'sub',
|
|
|
|
|
'description' => "计算两个数字的减法",
|
|
|
|
|
'inputSchema' => [
|
|
|
|
|
'type' => 'object',
|
|
|
|
|
'properties' => [
|
|
|
|
|
'a' => [
|
|
|
|
|
'description' => '第一个减数(必填)',
|
|
|
|
|
'example' => 10,
|
|
|
|
|
'type' => 'number',
|
|
|
|
|
],
|
|
|
|
|
'b' => [
|
|
|
|
|
'description' => '第二个减数(必填)',
|
|
|
|
|
'example' => 20,
|
|
|
|
|
'type' => 'number',
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
]
|
2025-06-25 18:31:47 +08:00
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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':
|
2025-06-26 18:24:33 +08:00
|
|
|
|
$this->sendJsonRpcResponse($request['id'] ?? null, ['tools' => self::ADD_FUNCTION]);
|
2025-06-25 18:31:47 +08:00
|
|
|
|
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,
|
2025-06-26 18:24:33 +08:00
|
|
|
|
'functions' => self::ADD_FUNCTION
|
2025-06-25 18:31:47 +08:00
|
|
|
|
],
|
|
|
|
|
'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, [
|
2025-06-26 18:24:33 +08:00
|
|
|
|
// '' => [
|
2025-06-25 18:31:47 +08:00
|
|
|
|
'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();
|
|
|
|
|
}
|
|
|
|
|
}
|