Files
ulthon_admin/.agents/skills/ulthon-mcp/SKILL.md

187 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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`
- 分发使用一次性 token5 分钟有效期,用完即删)并携带 `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 workerFPM 部署需保证 `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 依赖,可正常使用。