feat(mcp): skill 的 instructions 半边投影为 MCP prompts 原语;tool-binding 半边标注 cloud-runtime-only (#3905) - #6077
Conversation
…3905) ADR-0063 §2 names skills the only third-party extension primitive, but the open (BYO-AI) distribution consumed them nowhere: SkillSchema was authorable and lint-validated with no code path reading it. The MCP server now implements prompts/list + prompts/get from registered skill metadata, so a skill's instructions half is reachable by any MCP client; the tool-binding half (tools/surface/triggerConditions) is documented cloud-runtime-only rather than faked. Also fixes the in-repo name collision: packages/mcp/src/skill.ts (the ADR-0036 Amendment C SKILL.md distributable) is now skill-md.ts, next to the new skill-prompts.ts that owns the metadata type. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
… docs (#3905) The dispatcher test asserts buildMcpBridge hands the MCP runtime a listSkills reader bound to the request's environment — the producer without which the prompt surface has no source in the open distribution. content/docs/references is the regenerated output of the skill.zod.ts describe() changes (check:generated --fix, gen:docs only). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
…consumer (#3905) The ledger's own note said the open edition consumes nothing here — true until this PR. name/label/description/instructions/active now cite packages/mcp/src/skill-prompts.ts as in-repo evidence; tools/surface/ triggerConditions are marked cloud-runtime-only in the same words the schema now uses. Statuses unchanged (all were already live via the cloud runtime). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
|
CI 状态如实记录(交报告时刻,2026-08-06 19:05Z):
本 PR 带 changeset( Generated by Claude Code Generated by Claude Code |
📓 Docs Drift CheckThis PR changes 3 package(s): 123 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
|
补圈同步完成(#5837 分片已 MERGED,main 新 head:
Generated by Claude Code Generated by Claude Code |
…erer change (#3905) The os-regen merge driver flagged content/docs/references/ai/skill.mdx pending after merging main: #5606 (6e82972) made docs-gen render a retiredKey tombstone as `never` rather than `any`, and this branch had rewritten the same table's describe column. Regenerated via check:generated --fix (gen:docs only, the one gate it proved stale), so the row now carries main's `never` and this branch's cloud-runtime-only wording. No generated file was hand-edited. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
|
解冲突补圈完成。新 head: 冲突清单与解法(1 项,零手改生成物)
那一行的语义正是两侧叠加: 源码零冲突。 特别核对了 PM 点名的相撞面: 合并后重跑(持 flock): 终 head Generated by Claude Code Generated by Claude Code |
合并队列在 03:25:18Z 把本 PR 踢出,唯一红线是 `@objectstack/mcp`: TEST_DEBT 记 52,`tsc --noEmit` 实测 53(+1)。这 +1 不是队列里排在 前面的 #6077 引入的(该 PR 自己那一代队列构建是绿的),而是一条此前 无人可见的既有漂移 —— ratchet 第一次对着移动的 base 重测,就把它照了 出来。换句话说,这个不变量在自己的引入 PR 上先抓到了一条真实漂移, 这本身就是它有效的证据。 +1 已完全归因:src/skill-prompts.test.ts(185,23),一处把 `SkillPrompt | null` 断言成 `Record< string, unknown >` 的 TS2352 —— 正是 #3905 / PR #6077 投影 skill `instructions` 为 MCP prompt 原语时 新增的文件,测试文件数也因此 8 -> 9。 按裁决走 option A(记实测 +10)而不是再跑一轮精确校准:packages/mcp 今天刚落一个 feature,属于活跃变动包,精确数字大概率会再输一次已经 被裁决判死的那场竞速(option D 连输五次)。RECORDED 63 = 实测 53 @ 34558c2 + 10,note 按顶栏要求重写了成分 —— 旧 note 写的 "`error` is of type unknown, one catch-block idiom" 是错的,51 条 TS18046 全部是响应体 `json` 绑定,与 catch 块无关。 顶栏 BOOTSTRAP MARGINS 段同步从"四条"改为"五条"。 验证:`pnpm check:type-check-debt` 退出 0,34 条台账全部重测、无一条 高于记录值,五条余量各自打印 `ℹ can be lowered`;同一棵树上连测两次 数字逐字一致(mcp 53 / objectql 346 / rest 153 / lint 32 / service-storage 42),故这是校准问题而非 tsc 不确定性。
Fixes #3905
按维护者 2026-08-03 裁 A(+ 2026-08-06 评估批复确认)落地:给 skill 一个开源消费方,把
tool-binding 半边如实标注 cloud-runtime-only,并修掉同仓重名。
1. 前提复核(issue 立于 07-28,已过 9 天)
逐条对
origin/main@9e3709a重验,三条全部仍成立:prompts/*未实现grep 'prompts/list|prompts/get|listPrompts' packages/只命中packages/spec/prompts/README.md(另一个东西:发行的 agent prompt 文本)。HTTP 面handleHttpRequest只声明capabilities: { tools: {} }。git grep SkillSchema origin/main -- packages/除packages/spec/**与 CHANGELOG 外无命中。packages/mcp/src/skill.ts重名packages/mcp?packages/mcp/**或packages/runtime/src/domains/mcp.ts(含 #5837,其 76 个文件里没有packages/mcp)。一处修正:issue 说服务器「未实现 prompts 原语」。更准确的事实是 —— stdio 面早已有
一个
agent_prompt(bridgePrompts,SDKregisterPrompt,所以 grepprompts/list自然为空);网络可达的 HTTP 面才是零 prompts。这不改变结论(skill 元数据在两条
传输上都零消费),但本 PR 因此两条传输都补齐,而不是只补一条。
2.
instructions半边 → MCPprompts原语新模块
packages/mcp/src/skill-prompts.ts拥有投影:name→ prompt 名(不改写),label→title,description原样,instructions→prompts/get的消息体(role: 'user'—— MCP 的prompts/get没有 system 角色,交给模型的上下文即 user)。
instructions的 skill(没有可服务的半边,不列,而不是列一个空的);active: false的 skill。capabilities.prompts只在宿主确实能读到本环境 skill 元数据时声明并注册处理器(与 action 工具同一套按能力接线的优雅降级 —— 不声明「答不了的能力」);
无 skill 时
prompts/list返回空列表而非报错;prompts/get取不存在的名字返回-32602 InvalidParams(MCP 规范对 prompts 的错误指引)。describeObject同一条 per-environment 通道),多租户宿主不会把一个环境的 skill 服务给另一个环境;stdio 面在
bridgePrompts里按 skill 注册(SDK 拥有该 server 的prompts/list),列表是接线时的快照、正文在prompts/get时重读,所以改了 skill 无需重启。agent_prompt时告警并跳过(不静默丢弃),告诉作者代价与改法。认领申报的文件面是
packages/mcp/**+ spec JSDoc + docs + 测试 + changeset。实现中确认:HTTP 面的投影在
packages/mcp内无法自足。handleHttpRequest的一切 per-request 环境数据都来自
opts.bridge,而该 bridge 的唯一生产者是packages/runtime/src/domains/mcp.ts的buildMcpBridge。可选路线:listSkills?座位、不接生产者 → 就是本 issue 要消灭的declared ≠ enforced 形状,自相矛盾;
start()时捕获的 metadata service 供 HTTP 面读 →resolveService存在「shared-kernel 多环境 + scoped 工厂」路径(
http-dispatcher.ts:1720-1729),该 service可能是宿主的而非本环境的 → 跨租户元数据泄漏风险,不可接受;
buildMcpBridge补 8 行listSkills(与listObjects/describeObject同一条per-env 通道)→ 架构正确、安全、可测。
选了 (c) 并在此显式申报。碰撞检查:该文件在飞 0 触碰。这一读是元数据级(与
describeObject同级、不做 EC 过滤),/mcp路由本身要求已认证主体。3. tool-binding 半边:文档明确 cloud-runtime-only
packages/spec/src/ai/skill.zod.ts—— schema 头部新增「skill 的两个半边各跑在哪」;tools/surface/triggerConditions的 JSDoc +describe()标注 CLOUD-RUNTIME-ONLY;instructions标注「两个发行版都跑,开源里作为 MCP prompt 服务」。describe变了,按 os-regen纪律重生成(见 §6)。
content/docs/ai/connect-mcp.mdx—— 新增「Prompts: your skills, served to the client」一节(含
defineSkill例子 +prompts/list/prompts/get报文)。content/docs/ai/agents.mdx—— 把原先一句「skills 只在 ObjectOS 跑」换成两半边的表格:判断力(
instructions)处处跑,接线(tools/surface/triggerConditions)只在 ObjectOS 跑,两半边在两个发行版里都照旧被校验 —— 所以开源里写的 skill 到 cloud 上语义完整。
lint 两条 ai 规则的语义未动(引用完整性在两个发行版里都仍然有效)。
4. 重名修复(方案与论证)
packages/mcp/src/skill.ts从来不是 skill 元数据类型,而是 ADR-0036 Amendment C 的SKILL.md分发物。方案:按各自承载的产物命名,两侧模块头互指。src/skill.tssrc/skill-md.tsSKILL.md分发物src/skill-prompts.tsskill元数据类型(SkillSchema)→ MCP prompts 投影src/skill.test.tssrc/skill-md.test.tssrc/skill-surface-guard.test.tssrc/skill-md-surface-guard.test.ts为什么是改名而不是挪位:该模块与
renderSkill()同包、被 runtime 通过'mcp'服务鸭子调用(
domains/mcp.ts的GET /mcp/skill),挪出去要动服务契约;而重名的成本全部发生在grep 的第一跳,改名即根治。ADR-0036 Amendment C 的引用完整性保住:包的公开导出名
(
renderSkillMarkdown/OBJECTSTACK_SKILL_NAME/OBJECTSTACK_SKILL_DESCRIPTION/RenderSkillOptions)一个未变,ADR 正文引用的是包名与 amendment,不引用文件路径;仓内引用(index.ts / mcp-server-runtime.ts / 两个测试 / 守卫失败提示 / plugin-auth 测试注释)全部跟名。
5. 测试
packages/mcp/src/skill-prompts.test.ts(新,13 例)—— 大部分走真实 JSON-RPC 线(
handleHttpRequest的prompts/list/prompts/get/initialize往返),而不是内部helper 的返回值:本 issue 修的恰恰是「校验通过、lint 通过、客户端永远到不了」的面。
packages/mcp/src/__tests__/mcp-server-runtime.test.ts—— stdio 面用 SDK 的InMemoryTransport+ 真Client做listPrompts()/getPrompt()往返;另加「元数据服务读不了 skill 时不炸 boot、且 warn 而非吞掉」。
packages/runtime/src/http-dispatcher.mcp.test.ts—— 钉住 (c) 那条生产者:bridge 带listSkills且读的是本环境元数据。反向验证(方向事先预测:红,结果与预测一致):把
handleHttpRequest里的registerSkillPrompts+prompts能力声明删掉后 ———— 7 例转红,全部落在
-32601 Method not found;还原后9 passed (9) / 97 passed (97)。剩下几例本就不该动(「宿主读不到 skill 时不声明能力」「工具面照常」「纯投影单测」),
它们保持绿正是正确的。
本地跑过的门:
pnpm lint✅、check:nul-bytes/doc-authoring/role-word/adr-anchors/route-envelope/error-code-casing/engine-double-contract/docs-audit-scope/published-files全 0;@objectstack/spec8309 例、@objectstack/runtime1477 例、@objectstack/mcp97 例全绿;两包typecheck干净。6. 生成物
describe()变化 → 按纪律走check:generated判定 +--fix只重生成它证明为陈旧的那一个(
content/docs/references/**,即gen:docs),随后补跑gen:openapi(#5371,无 diff),再次
check:generated→✓ All 10 generated artifacts are up to date.。未手改任何生成物。Generated by Claude Code