mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-08-30 04:35:33 +08:00
8.1 KiB
8.1 KiB
name, description
| name | description |
|---|---|
| ulthon-mcp | 内置 MCP Server(/mcp 端点)的使用方式:密钥管理与授权、AI 客户端(Claude Code/Cursor/Cline 等)配置、命令行联调与调用问题排查。 |
内置 MCP Server(/mcp)
何时调用
- 需要让 AI 客户端(Claude Code、Cursor、Cline 等)通过 MCP 协议直接操作后台(查列表、改数据、调用业务接口)。
- 需要创建、授权、禁用 MCP 密钥。
- 需要排查 MCP 调用问题(401/403/503、Tool not found、会话丢失等)。
机制概览
密钥认证
- 客户端请求
/mcp时携带Authorization: Bearer <密钥明文>。 - 服务端对明文做 sha256 后与
ul_system_mcp_key.key比对,仅status=1且未软删的密钥有效。 - 无密钥或密钥错误返回 401;
mcp.enable=false返回 403;服务端未安装 MCP SDK 依赖返回 503。
工具集(权限语义)
- 每个密钥通过授权页绑定一组权限节点(
ul_system_mcp_key_node)。 - 实际可用的工具集 = 密钥授权节点 ∩ 创建者实时权限。权限判定与后台鉴权同源(同一条
getAdminAllowedNodes判定链),不是另写一套规则。 - 创建者权限被回收后,对应工具自动消失(
initialize响应的capabilities.tools键随之消失,调用会返回 Tool not found)。 - 权限变更最多有约 60 秒延迟(节点权限查询缓存)。
调用分发
tools/call不直接执行业务代码,而是以创建者身份自请求对应的 admin 控制器。节点system.quick/index分发到/admin/system.quick/index。- 分发使用一次性 token(5 分钟有效期,用完即删)并携带
mcp_internal标记,供 CSRF 中间件豁免识别。 - 自请求恒带
X-Requested-With: XMLHttpRequest与Content-Type: application/json,保证index这类方法走 JSON 分支。 - 响应判定:HTTP 200 且 body 为 JSON 即成功(layui 的
code=0与 success 的code=200均算成功,原始 JSON 原样返回);200 但非 JSON 加[HTML页面内容]前缀;非 200 加[ERROR]前缀并提取错误 msg。
审计
- 每次调用在
ul_system_mcp_log落一行记录(工具名、参数、成败、耗时),同时密钥的use_num原子自增。 - 越权或权限已回收的调用同样记审计(
is_success=0)且use_num照常自增。use_num的语义是调用次数,不是成功次数。
工具名编码
- 权限节点名不能直接做 MCP 工具名,编码规则:
.替换为-,/替换为--。 - 例:
system.quick/index对应工具名system-quick--index;动作段的驼峰保留,system.auth/toggleUser对应system-auth--toggleUser。 - 工具描述中携带原始节点名,按描述可反查权限节点。
后台操作
入口:系统管理 → MCP密钥管理(system.mcp_key)。
- 新增:保存成功后弹窗展示密钥明文,仅此一次,关闭弹窗后无法再查看(数据库只存哈希)。
- 授权:编辑页内配置可调用的权限节点,只显示创建者本人拥有的节点。
- 禁用/删除:立即失效,客户端再调用返回 401。
- 审计:MCP调用日志(
system.mcp_log)页查看每次调用的参数与结果。
客户端配置
端点地址为 https://域名/mcp,认证方式为 Bearer 密钥。示例中 <key> 替换为密钥明文。
Claude Code
CLI 一条命令添加:
claude mcp add --transport http <name> https://域名/mcp --header "Authorization: Bearer <key>"
或 JSON 配置:
{
"mcpServers": {
"<name>": {
"type": "http",
"url": "https://域名/mcp",
"headers": {
"Authorization": "Bearer ${MCP_KEY}"
}
}
}
}
Cursor
{
"url": "https://域名/mcp",
"headers": {
"Authorization": "Bearer ${env:MCP_KEY}"
}
}
注意插值语法差异:Cursor 用 ${env:VAR},Claude Code 用 ${VAR},两者不通用。
Cline
{
"type": "streamableHttp",
"url": "https://域名/mcp",
"headers": {
"Authorization": "Bearer <key>"
}
}
Claude Desktop
Claude Desktop 不直接支持 HTTP MCP,需要 mcp-remote 桥接:
npx -y mcp-remote https://域名/mcp --transport http-only --header "Authorization:${AUTH_HEADER}"
命令行联调(tools:http:call)
用 tools:http:call 可以不依赖任何 MCP 客户端直接验证 /mcp 端点。两个要点:
- 必须加
--super-token=false,否则 super token 会覆写 Authorization 头,密钥认证永远失败。 - 协议要求除
initialize外的请求必须带Mcp-Session-Id头,取自 initialize 响应头。
第一步,initialize:
php think tools:http:call --url=/mcp --method=POST --super-token=false \
-H '{"Content-Type":"application/json","Authorization":"Bearer <key>"}' \
--body '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"cli","version":"1.0"}}}'
从输出 JSON 的 response.headers 中取出 Mcp-Session-Id。
第二步,tools/list:
php think tools:http:call --url=/mcp --method=POST --super-token=false \
-H '{"Content-Type":"application/json","Authorization":"Bearer <key>","Mcp-Session-Id":"<会话ID>"}' \
--body '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
第三步,tools/call:
php think tools:http:call --url=/mcp --method=POST --super-token=false \
-H '{"Content-Type":"application/json","Authorization":"Bearer <key>","Mcp-Session-Id":"<会话ID>"}' \
--body '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"system-quick--index","arguments":{"page":1,"limit":10}}}'
PowerShell 或多层 docker exec 场景下 JSON 引号会被剥壳,稳妥做法是把请求体写入文件,再引用文件内容传给 --body:
Set-Content -Encoding UTF8 runtime\agents\mcp-body.json '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"system-quick--index","arguments":{"page":1,"limit":10}}}'
$hdr = '{"Content-Type":"application/json","Authorization":"Bearer <key>","Mcp-Session-Id":"<会话ID>"}'
php think tools:http:call --url=/mcp --method=POST --super-token=false -H $hdr --body (Get-Content -Raw runtime\agents\mcp-body.json)
安全注意
- 密钥等价于"创建者权限子集"的凭证:持有密钥的人可以在授权节点范围内执行创建者能做的操作,按敏感凭证保管。
- 授权页只显示创建者拥有的节点,密钥权限不可能超过创建者。
- 创建者权限回收后工具自动收缩,无需重新配置密钥授权。
- 每次调用都有审计记录(含参数),可在 MCP调用日志页追溯。
- 密钥明文只在新增弹窗展示一次,遗失后无法找回,只能删除旧密钥重建。
已知行为与限制
- 每次 MCP 调用产生两行日志:
system_log(以创建者身份记录的系统日志)与system_mcp_log(MCP 审计)。 - 演示模式(IS_DEMO=true)站点 MCP 写操作全部不可用。
tools/call是服务端自请求,一次调用占用两个 FPM worker,FPM 部署需保证pm.max_children > 1。- 生产环境建议显式配置 APP_HOST(
.env的app.app_host):分发回环地址优先取该配置,未配置时按入站请求头还原,反向代理场景可能不准。 - 自定义覆写过 Csrf 中间件的项目,需自行同步保留
mcp_internal豁免逻辑,否则 MCP 写操作会被 CSRF 拦截。 - 页面型节点要拿 assign 数据(下拉选项、默认值等),需在
tools/call的 arguments 里显式传"get_page_data": 1。不要依赖自动注入,自动注入会破坏index的 JSON 分支。 GET /mcp返回 405:只实现 Streamable HTTP 的 POST 请求/响应模式,不做 SSE 监听流,也没有 resources、prompts 能力。- 协议版本协商固定回
2025-11-25。
存量项目升级指引
通过 admin:update 拉取 MCP 功能代码后,需手动安装 SDK 依赖:
composer require mcp/sdk guzzlehttp/psr7
- 依赖要求 PHP >= 8.1。
- 未安装依赖时
/mcp返回 503(带友好提示),不影响站点其他功能;后台密钥管理页零 SDK 依赖,可正常使用。