mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-08-30 12:45:32 +08:00
187 lines
8.1 KiB
Markdown
187 lines
8.1 KiB
Markdown
---
|
||
name: "ulthon-mcp"
|
||
description: "内置 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 一条命令添加:
|
||
|
||
```bash
|
||
claude mcp add --transport http <name> https://域名/mcp --header "Authorization: Bearer <key>"
|
||
```
|
||
|
||
或 JSON 配置:
|
||
|
||
```json
|
||
{
|
||
"mcpServers": {
|
||
"<name>": {
|
||
"type": "http",
|
||
"url": "https://域名/mcp",
|
||
"headers": {
|
||
"Authorization": "Bearer ${MCP_KEY}"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Cursor
|
||
|
||
```json
|
||
{
|
||
"url": "https://域名/mcp",
|
||
"headers": {
|
||
"Authorization": "Bearer ${env:MCP_KEY}"
|
||
}
|
||
}
|
||
```
|
||
|
||
注意插值语法差异:Cursor 用 `${env:VAR}`,Claude Code 用 `${VAR}`,两者不通用。
|
||
|
||
### Cline
|
||
|
||
```json
|
||
{
|
||
"type": "streamableHttp",
|
||
"url": "https://域名/mcp",
|
||
"headers": {
|
||
"Authorization": "Bearer <key>"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Claude Desktop
|
||
|
||
Claude Desktop 不直接支持 HTTP MCP,需要 mcp-remote 桥接:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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`:
|
||
|
||
```powershell
|
||
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 依赖:
|
||
|
||
```bash
|
||
composer require mcp/sdk guzzlehttp/psr7
|
||
```
|
||
|
||
- 依赖要求 PHP >= 8.1。
|
||
- 未安装依赖时 `/mcp` 返回 503(带友好提示),不影响站点其他功能;后台密钥管理页零 SDK 依赖,可正常使用。
|