Files
UHD_Media_Tool/docs/runbook/mcp.md
T
rootandClaude Opus 5.5 ceb66f8c77 feat: v0.3.8–v0.3.14 工作区汇总(依赖升级、整理来源、手动入库、原地入库等)
生产 v0.3.8~v0.3.13 均由 8013196 加工作区修改构建部署,此前未提交;本提交补齐源码,
并含未部署的 v0.3.14 原地入库。中间各版本源码状态未保存,无法逐版拆分,详见
docs/plan/17-full-upgrade.md 与 docs/plan/18-organize-release.md。

- v0.3.8 全面升级:Deno workspace 依赖、Router 8 / Vite 8、camofox 1.18.0 / Node 24、
  PostgreSQL 18.6、备份客户端 18.6、演示 qB 5.2.4
- v0.3.9 媒体整理:分类规则、整理来源与目录快照(0059)、文件管理整理入口
- v0.3.10 手动下载与入库:独立手动下载、详情页资源下载、TMDB / Local 元数据兜底
  (metadata.resolve_organize)、正常多版本 / 一次性洗版(0060)
- v0.3.11 搜索详情与桌面黑边修复
- v0.3.12 列表分页、搜索历史、首页概况
- v0.3.13 统一整理弹窗与队列
- v0.3.14 原地入库(0061,未部署)

同批审计修复:
- 刮削入队:确认原记录存在后才删重放产生的新记录;写回 run 关联失败时保持 queued,
  由处理器补关联(对齐 MP 先登记、只在入队失败时记失败)
- 识别升级链只在结果更确定时采用,不再用 no_match 顶替原有歧义候选
- bluray_dir 资产以原盘目录本身为刮削根(MP 同做法)
- metadata.resolve_organize 登记到入库排障 Skill 与能力地图
- db/src/types.ts 按迁移重新生成;contracts 整理动作枚举顺序与数据库对齐
- 测试:原地入库用例改为直接查记录表;domain 测试模板补 version
- 前端:通知铃铛弹层与用户菜单统一样式;站点卡片共用卡头、底栏贴底、分页间距

依赖升级证据只提交摘要文件,registry-snapshot.json 移至 .local/(SHA-256 记于文档)。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-02 15:47:17 +08:00

6.8 KiB

MCP 服务端运行手册(AG-1)

实现:apps/server/src/modules/mcp/(Streamable HTTP 端点、会话表、资源与提示模板、能力统计)、 packages/contracts/src/mcp.ts(合同)、迁移 0039_capability_stats(mcp_server_config + audit_event 能力索引)、 界面 设置 → Agent → MCP 服务端 / 能力清单(apps/web/app/components/settings/agent-tools.tsx)。 依据:REQ R13、§10.3、§12.3。SDK:npm:@modelcontextprotocol/[email protected],传输用 WebStandardStreamableHTTPServerTransport(Deno 下直接吃 Request / 返回 Response)。 SDK 的子路径类型靠 typesVersions 映射,deno check 不认,所以每个 @modelcontextprotocol/sdk/…js import 上方带 // @ts-types="…"(去掉 .js 的同名子路径)。

接入

端点:<站点根>/api/v1/mcp,Streamable HTTP(POST 发消息,GET 开服务端推送流,DELETE 结束会话)。 鉴权:Authorization: Bearer <API Key>。在「设置 → API Key」新建 Key,明文只显示一次。

Claude Code(或其它支持 Streamable HTTP 的客户端)配置,界面上「复制接入配置」给的就是这一段,密钥用环境变量占位:

{
  "mcpServers": {
    "uhd-media-tool": {
      "type": "http",
      "url": "https://umt.example.lan/api/v1/mcp",
      "headers": { "Authorization": "Bearer ${UMT_API_KEY}" }
    }
  }
}

命令行等价写法:claude mcp add --transport http uhd-media-tool https://umt.example.lan/api/v1/mcp --header "Authorization: Bearer $UMT_API_KEY"。

自检(不需要客户端):

curl -s -X POST https://umt.example.lan/api/v1/mcp \
  -H "Authorization: Bearer $UMT_API_KEY" -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' -i

响应头里的 mcp-session-id 就是会话 ID,之后的请求都要带上。

暴露什么

MCP 来源
tools 能力注册中心里对这个 Key 可见的能力。工具名是能力 ID 把 . 换成 __(download.submit → download__submit);描述、inputSchema 取描述符与输入 zod schema;annotations 标 readOnly / destructive / idempotent
resources umt://settings/summary(自动化开关、预算、Agent 上限,不含凭证)、umt://decisions/pending(未结束的待处理,最多 50 条)、umt://subscriptions/overview(各状态计数 + 最近 20 个订阅);需要 read scope
prompts triage_pending、acquire_title(title, year?)、diagnose_task(taskId)

工具可见性(mcpToolVisible):Key 的 scope 覆盖能力的 scope;没有 organize.execute / admin 时只给 read / preview 类。agent.*(派生子会话等)只在 Agent 会话内可用,不经 MCP 暴露。当前 41 个能力下:read 18 个,read + organize.execute 28 个,admin 37 个(2026-10-01 按 mcpToolVisible 核对;界面「可见工具」按实时注册表计算)。

安全边界:MCP 不绕过授权

  • 每次工具调用都走 ctx.capabilities.invoke(source="mcp", actorId="api_key:<id>"),和 Web、内置 Agent 是同一条路径:输入 schema 校验 → 授权回调 → 服务端幂等键 → 操作账本 → 输出 schema → 审计。
  • 授权只看库里的记录:propose / approve / execute 类要有覆盖它的持续授权(automation_grant)或有效的单次审批。API Key 不享有控制台管理员的"手动即授权",客户端或模型在参数里声称"已同意"不起作用。缺授权时工具返回 isError + {"error":{"code":"unauthorized",...}},不执行。
  • 单次审批只能在控制台登录后申请和批准(POST/PATCH /approvals 只接受管理员会话),MCP 客户端不能给自己开审批。
  • 自动化总开关关闭或只读对账时,execute 类一律拒绝。
  • 幂等键按会话归属(id:v版本:mcp:<sessionId>:sha256(输入)):同一会话同参数的有副作用调用只执行一次,重复调用直接返回账本里的结果。
  • 输出与资源内容出服务端前再过一遍 redactForModel(cookie / passkey / token / apiKey 等键一律遮盖)。
  • 审计:audit_event 里 capability.invoke / capability.denied,payload.source = "mcp";会话建立写 mcp.session.open,配置变更写 mcp.config.update。

会话、限速与开关

  • 会话与建立它的 API Key 绑定:换一个 Key 带同一个 mcp-session-id 返回 403。控制台 cookie 会话不能连 MCP(403)。
  • 空闲 30 分钟回收;每个 Key 最多 8 个会话,全进程 256 个;超出返回 429 / 503。
  • 限速(每个 Key、滑动一分钟):工具调用 60 次,HTTP 请求 600 次。工具调用超限返回 rate_limited,请求超限返回 HTTP 429。站点实时搜索、TMDB 等外部 IO 能力另受进程级并发与 15 s 超时约束(与 REST 共用 sharedInvokeLimiter)。
  • 启用开关与允许的 API Key 在 mcp_server_config(PATCH /mcp/config,admin scope)。停用或把某个 Key 移出允许列表时,它的现有会话立即断开。列表为 null 表示所有未撤销的 Key;撤销 Key 后下一个请求即 401。
  • 会话表在 API 进程内存里,重启后客户端需要重新 initialize(按规范收到 404 即重建会话)。多 API 进程部署时需要按会话粘滞路由。

能力统计与清单

端点 说明
GET /capabilities/stats?days=7 按能力汇总 audit_event:调用数、失败(error / invalid)、被拒绝、平均耗时、最近一次与调用来源(内置 Agent = agent / task,MCP,REST = web / cli)。capability_invocation 只记有副作用的调用,不用于统计
GET /capabilities/tools 描述符 + 工具名 + 输入 / 输出 JSON Schema + 授权状态(无需授权 / 有持续授权 / 需审批 / 仅 Agent 会话)+ 有效持续授权数
GET /mcp/status 启用、允许的 Key、在线会话(会话 ID 只回前 8 位)、最近 20 次 MCP 调用、限速、各 Key 组合可见工具数(admin scope)

排障

现象 处理
initialize 返回 403「MCP 服务端已停用」/「不在 MCP 允许列表里」 设置 → Agent → MCP 服务端,打开开关或勾选这个 Key
工具列表里没有 execute 类工具 Key 缺 organize.execute(或 admin)scope
工具返回 unauthorized 没有覆盖该能力的持续授权。发布收录策略会生成持续授权,或在控制台为计划申请单次审批后带 approvalId 调用
404「会话不存在或已过期」 空闲超时或进程重启,重新 initialize
429 超过每分钟上限或会话数上限,稍后重试 / 关闭不用的会话

测试:apps/server/test/mcp/mcp.test.ts(官方 SDK client 经 Streamable HTTP 实连:工具列表按 scope 过滤、read 能力调用与脱敏、execute 无授权被拒与有授权后记账本、资源与提示模板、会话绑定、cookie / 停用 / 允许列表拒绝、统计与清单端点)。