sse协议demo和mcp服务demo

This commit is contained in:
qinzexin 2025-06-26 18:24:33 +08:00
parent 2e081d5b87
commit a57eb0ff06
3 changed files with 388 additions and 18 deletions

View File

@ -6,29 +6,44 @@ class Mcp extends Base
{
// 工具定义常量
private const ADD_FUNCTION = [
[
'name' => 'add',
'description' => '计算两个数字的和',
'toolType' => 'function',
'parameters' => [
'description' => "计算两个数字的和",
'inputSchema' => [
'type' => 'object',
'properties' => [
'a' => [
'description' => '第一个加数(必填)',
'example' => 10
'example' => 10,
'type' => 'number',
],
'b' => [
'description' => '第二个加数(必填)',
'example' => 20
'example' => 20,
'type' => 'number',
]
]
]
],
[
'name' => 'sub',
'description' => "计算两个数字的减法",
'inputSchema' => [
'type' => 'object',
'properties' => [
'a' => [
'description' => '第一个减数(必填)',
'example' => 10,
'type' => 'number',
],
'b' => [
'description' => '第二个减数(必填)',
'example' => 20,
'type' => 'number',
]
]
]
],
'metadata' => [
'capability' => [
'streaming' => false,
'batch' => true,
'transactional' => false
],
'version' => '1.0.0',
'vendor' => 'MyCompany',
'protocol' => 'json-rpc-2.0'
]
];
@ -75,7 +90,7 @@ class Mcp extends Base
$this->sendJsonRpcResponse($request['id'] ?? null, ['result' => 'success']);
break;
case 'tools/list':
$this->sendJsonRpcResponse($request['id'] ?? null, ['tools' => [self::ADD_FUNCTION]]);
$this->sendJsonRpcResponse($request['id'] ?? null, ['tools' => self::ADD_FUNCTION]);
break;
default:
$this->handleOtherRequests($request);
@ -99,7 +114,7 @@ class Mcp extends Base
$this->sendJsonRpcResponse($id, [
'capabilities' => [
'progress' => true,
'functions' => [self::ADD_FUNCTION]
'functions' => self::ADD_FUNCTION
],
'serverInfo' => [
'name' => 'Calculator Server',
@ -151,7 +166,7 @@ class Mcp extends Base
$result = $this->calculateAdd($a, $b);
$this->sendJsonRpcResponse($id, [
// 'result' => [
// '' => [
'operation' => 'add',
'result' => $result,
'expression' => "$a + $b = $result",

View File

@ -0,0 +1,311 @@
<?php
namespace app\api\controller\mcp;
class My extends Base
{
// 工具定义常量
private const ADD_FUNCTION = [
[
'name' => 'my',
'method' => 'my',
'description' => "介绍焦钰锟是谁的mcp",
'toolType' => 'function',
'inputSchema' => [ // 补充完整的参数定义
'type' => 'object',
'properties' => [
'desc' => [
'description' => '具体想问什么',
'example' => "所有",
'type' => 'string'
]
],
'required' => []
]
]
];
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'] ?? [];
$function = $request['params']['name'] ?? 'my';
switch ($function){
case 'my':
// $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;
// }
// $result = $this->calculateAdd($a, $b);
// 模拟处理延迟
sleep(1);
$result = "我是焦钰锟今年30岁php程序员240斤喜欢玩电脑游戏死亡搁浅艾尔登法环";
//用jsonrpc2.0应答
$this->sendJsonRpcResponse($id, [
'content' => [
[
'type' => 'text',
'text' => $result // 返回格式化的时间和时区信息
]
]
]);
// $this->sendEvent('result', [
// 'content' => [
// [
// 'type' => 'text',
// 'text' => $result // 返回格式化的时间和时区信息
// ]
// ]
// ]);
break;
}
}
/**
* 验证加法参数
* @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'] === 'my' || $request['function'] === 'tools/call') {
$function = $request['params']['name'] ?? 'my';
switch ($function){
case 'my':
// $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;
// }
// $result = $this->calculateAdd($a, $b);
// 模拟处理延迟
sleep(1);
$result = "我是焦钰锟今年30岁php程序员240斤喜欢玩电脑游戏死亡搁浅艾尔登法环";
$this->sendEvent('result', [
'operation' => 'my',
'result' => $result,
'expression' =>$result
]);
break;
}
} 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();
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace app\api\controller\sse;
use think\Controller;
class Demo extends Controller
{
public function index()
{
// 设置SSE响应头
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
//添加 X-Accel-Buffering=no
header('X-Accel-Buffering: no');
// 禁用输出缓冲
if (ob_get_level() == 0) ob_start();
for ($i = 0; $i < 10; $i++) {
// 构建数据
$data = [
'time' => date('Y-m-d H:i:s'),
'message' => 'hallo word ' . ($i+1)
];
// 发送数据
echo "data: " . json_encode($data, JSON_UNESCAPED_UNICODE) . "\n\n";
// 刷新输出缓冲
flush();
ob_flush();
// 休眠1秒
sleep(1);
}
// 结束连接
echo "data: {\"complete\":true}\n\n";
ob_end_flush();
}
}