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

8.1 KiB
Raw Blame History

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 且未软删的密钥有效。
  • 无密钥或密钥错误返回 401mcp.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: XMLHttpRequestContent-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_logMCP 审计)。
  • 演示模式IS_DEMO=true站点 MCP 写操作全部不可用。
  • tools/call 是服务端自请求,一次调用占用两个 FPM workerFPM 部署需保证 pm.max_children > 1
  • 生产环境建议显式配置 APP_HOST.envapp.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 依赖,可正常使用。