Files
zyt/server/app/mcp/controller/ConsoleController.php
T
2026-09-24 16:18:52 +08:00

84 lines
3.6 KiB
PHP

<?php
declare(strict_types=1);
namespace app\mcp\controller;
use app\BaseController;
use app\mcp\service\AuditLogger;
use app\mcp\service\ConsoleService;
use app\mcp\service\GrantService;
use app\mcp\service\Guard;
use app\mcp\service\McpConfig;
use app\mcp\service\McpException;
use think\Response;
/**
* AI 后台浏览器(供行知服务器上的内置浏览器调用,都要求 Bearer AI 授权令牌):
* POST /mcp/console/open 换取“AI 浏览器”专用终端的后台登录(令牌只给行知服务器,写进浏览器,不给模型)
* POST /mcp/console/close 作废该会话(行知关闭浏览器或空闲超时时调用;授权已失效时也照样注销)
* 失败时 data.reason 为 feature_disabled / console_disabled / no_console_permission / ip_not_allowed 等。
*/
class ConsoleController extends BaseController
{
public function open(): Response
{
$blocked = $this->blocked();
if ($blocked) {
return $blocked;
}
try {
$identity = GrantService::authenticate($this->request);
} catch (McpException $e) {
return Guard::envelope(-1, $e->getMessage(), ['reason' => $e->reason], 401);
}
$entry = ['grant_id' => $identity->grant['id'], 'admin_id' => $identity->adminId, 'tool' => 'console.open', 'resource' => 'console',
'client_task_id' => (string) $this->request->header('x-xingzhi-task-id', ''), 'ip' => $this->request->ip()];
try {
$data = ConsoleService::open($identity);
AuditLogger::log($entry + ['status' => 'ok']);
return Guard::envelope(1, '', $data);
} catch (McpException $e) {
AuditLogger::log($entry + ['status' => 'denied', 'message' => $e->reason]);
return Guard::envelope(0, $e->getMessage(), ['reason' => $e->reason], 200, 1);
}
}
public function close(): Response
{
$blocked = $this->blocked();
if ($blocked) {
return $blocked;
}
try {
$identity = GrantService::authenticate($this->request);
[$grantId, $adminId, $note] = [(int) $identity->grant['id'], $identity->adminId, ''];
} catch (McpException $e) {
// 授权已撤销、过期或账号失去权限时也照样注销它换来的后台会话:收回登录不需要授权仍然有效
$grant = GrantService::findByToken($this->request);
if (!$grant) {
return Guard::envelope(-1, $e->getMessage(), ['reason' => $e->reason], 401);
}
[$grantId, $adminId, $note] = [(int) $grant['id'], (int) $grant['admin_id'], ' (grant ' . $e->reason . ')'];
}
$closed = ConsoleService::closeForAdmin($adminId);
AuditLogger::log(['grant_id' => $grantId, 'admin_id' => $adminId, 'tool' => 'console.close', 'resource' => 'console',
'status' => 'ok', 'message' => ($closed ? 'closed' : 'none') . $note, 'ip' => $this->request->ip()]);
return Guard::envelope(1, $closed ? '已关闭' : '没有需要关闭的会话', ['closed' => $closed]);
}
private function blocked(): ?Response
{
if (!McpConfig::enabled()) {
return Guard::envelope(0, 'AI 助手接口未启用', ['reason' => 'feature_disabled'], 503, 1);
}
if ($this->request->method(true) !== 'POST') {
return response('', 405)->header(['Allow' => 'POST']);
}
$guard = Guard::check($this->request);
if ($guard !== null) {
return Guard::envelope(0, $guard[1], ['reason' => $guard[2]], 200, 1);
}
return null;
}
}