生产 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]>
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 / 停用 / 允许列表拒绝、统计与清单端点)。