@intentius/chant 0.46.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/dist/audit/catalog.d.ts +13 -3
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/core.d.ts +30 -3
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts +9 -2
  6. package/dist/audit/discover.d.ts.map +1 -1
  7. package/dist/audit/fetch.d.ts.map +1 -1
  8. package/dist/audit/report-html.d.ts.map +1 -1
  9. package/dist/audit/report-model.d.ts +6 -0
  10. package/dist/audit/report-model.d.ts.map +1 -1
  11. package/dist/audit/report.d.ts.map +1 -1
  12. package/dist/audit/rules-doc.d.ts.map +1 -1
  13. package/dist/audit/secrets.d.ts +95 -0
  14. package/dist/audit/secrets.d.ts.map +1 -0
  15. package/dist/audit/wrangler.d.ts +33 -0
  16. package/dist/audit/wrangler.d.ts.map +1 -0
  17. package/dist/build.d.ts +3 -3
  18. package/dist/build.d.ts.map +1 -1
  19. package/dist/cli/commands/audit.d.ts +7 -0
  20. package/dist/cli/commands/audit.d.ts.map +1 -1
  21. package/dist/cli/commands/build.d.ts +23 -0
  22. package/dist/cli/commands/build.d.ts.map +1 -1
  23. package/dist/cli/commands/lint.d.ts.map +1 -1
  24. package/dist/cli/handlers/build.d.ts.map +1 -1
  25. package/dist/cli/handlers/components.d.ts +31 -0
  26. package/dist/cli/handlers/components.d.ts.map +1 -1
  27. package/dist/cli/handlers/lifecycle.d.ts +12 -1
  28. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  29. package/dist/cli/handlers/operator.d.ts +32 -0
  30. package/dist/cli/handlers/operator.d.ts.map +1 -0
  31. package/dist/cli/handlers/scenario.d.ts +39 -0
  32. package/dist/cli/handlers/scenario.d.ts.map +1 -0
  33. package/dist/cli/main.d.ts.map +1 -1
  34. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  35. package/dist/cli/mcp/server.d.ts +35 -2
  36. package/dist/cli/mcp/server.d.ts.map +1 -1
  37. package/dist/cli/mcp/tools/explain.d.ts +6 -0
  38. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  39. package/dist/cli/mcp/types.d.ts +29 -1
  40. package/dist/cli/mcp/types.d.ts.map +1 -1
  41. package/dist/cli/plugins.d.ts +1 -1
  42. package/dist/cli/plugins.d.ts.map +1 -1
  43. package/dist/cli/registry.d.ts +14 -2
  44. package/dist/cli/registry.d.ts.map +1 -1
  45. package/dist/cli/reporters/stylish.d.ts +15 -1
  46. package/dist/cli/reporters/stylish.d.ts.map +1 -1
  47. package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
  48. package/dist/components/auto-release.d.ts +4 -0
  49. package/dist/components/auto-release.d.ts.map +1 -1
  50. package/dist/components/capability.d.ts +17 -2
  51. package/dist/components/capability.d.ts.map +1 -1
  52. package/dist/components/cli-support.d.ts +7 -0
  53. package/dist/components/cli-support.d.ts.map +1 -1
  54. package/dist/components/component.d.ts +15 -0
  55. package/dist/components/component.d.ts.map +1 -1
  56. package/dist/components/driver.d.ts.map +1 -1
  57. package/dist/components/starter-plugin.d.ts +2 -0
  58. package/dist/components/starter-plugin.d.ts.map +1 -1
  59. package/dist/components/verbs/ensure-secret.d.ts +50 -0
  60. package/dist/components/verbs/ensure-secret.d.ts.map +1 -0
  61. package/dist/components/verbs/index.d.ts +13 -0
  62. package/dist/components/verbs/index.d.ts.map +1 -1
  63. package/dist/components/verbs/r2-sync.d.ts +76 -0
  64. package/dist/components/verbs/r2-sync.d.ts.map +1 -0
  65. package/dist/components/verbs/run-agent.d.ts +499 -0
  66. package/dist/components/verbs/run-agent.d.ts.map +1 -0
  67. package/dist/components/verbs/sign.d.ts +30 -0
  68. package/dist/components/verbs/sign.d.ts.map +1 -1
  69. package/dist/components/verbs/wrangler.d.ts +108 -0
  70. package/dist/components/verbs/wrangler.d.ts.map +1 -0
  71. package/dist/composite.d.ts +6 -1
  72. package/dist/composite.d.ts.map +1 -1
  73. package/dist/config.d.ts +26 -0
  74. package/dist/config.d.ts.map +1 -1
  75. package/dist/deep-observation.d.ts +14 -0
  76. package/dist/deep-observation.d.ts.map +1 -1
  77. package/dist/discovery/collect.d.ts.map +1 -1
  78. package/dist/discovery/fold-import.d.ts +15 -1
  79. package/dist/discovery/fold-import.d.ts.map +1 -1
  80. package/dist/discovery/fold-rank.d.ts +66 -0
  81. package/dist/discovery/fold-rank.d.ts.map +1 -0
  82. package/dist/discovery/index.d.ts +15 -0
  83. package/dist/discovery/index.d.ts.map +1 -1
  84. package/dist/discovery/param-deps.d.ts +17 -0
  85. package/dist/discovery/param-deps.d.ts.map +1 -0
  86. package/dist/effect-receipt.d.ts +177 -0
  87. package/dist/effect-receipt.d.ts.map +1 -0
  88. package/dist/fold/fold.d.ts +55 -2
  89. package/dist/fold/fold.d.ts.map +1 -1
  90. package/dist/fold/subset.d.ts +20 -0
  91. package/dist/fold/subset.d.ts.map +1 -1
  92. package/dist/index.d.ts +4 -0
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/lexicon-schema.d.ts +2 -0
  95. package/dist/lexicon-schema.d.ts.map +1 -1
  96. package/dist/lexicon.d.ts +137 -3
  97. package/dist/lexicon.d.ts.map +1 -1
  98. package/dist/lifecycle/change-set.d.ts +33 -5
  99. package/dist/lifecycle/change-set.d.ts.map +1 -1
  100. package/dist/lifecycle/converge-ledger.d.ts +90 -0
  101. package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
  102. package/dist/lifecycle/deep-diff.d.ts +18 -0
  103. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  104. package/dist/lifecycle/deep-observe.d.ts +9 -1
  105. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  106. package/dist/lifecycle/gate-ledger.d.ts +33 -0
  107. package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
  108. package/dist/lifecycle/git.d.ts +145 -21
  109. package/dist/lifecycle/git.d.ts.map +1 -1
  110. package/dist/lifecycle/index.d.ts +6 -0
  111. package/dist/lifecycle/index.d.ts.map +1 -1
  112. package/dist/lifecycle/lease.d.ts +113 -0
  113. package/dist/lifecycle/lease.d.ts.map +1 -0
  114. package/dist/lifecycle/observation-baseline.d.ts +21 -3
  115. package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
  116. package/dist/lifecycle/receipt-plan.d.ts +62 -0
  117. package/dist/lifecycle/receipt-plan.d.ts.map +1 -0
  118. package/dist/lifecycle/release-ledger.d.ts +20 -0
  119. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  120. package/dist/lifecycle/scenario-eval.d.ts +42 -0
  121. package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
  122. package/dist/lifecycle/scenario.d.ts +163 -0
  123. package/dist/lifecycle/scenario.d.ts.map +1 -0
  124. package/dist/lifecycle/symptoms.d.ts +63 -0
  125. package/dist/lifecycle/symptoms.d.ts.map +1 -0
  126. package/dist/lifecycle/teardown.d.ts +6 -4
  127. package/dist/lifecycle/teardown.d.ts.map +1 -1
  128. package/dist/lifecycle/unobserved-gate.d.ts +67 -0
  129. package/dist/lifecycle/unobserved-gate.d.ts.map +1 -0
  130. package/dist/lint/knowledge-checks.d.ts +48 -0
  131. package/dist/lint/knowledge-checks.d.ts.map +1 -0
  132. package/dist/lint/output-checks.d.ts +5 -0
  133. package/dist/lint/output-checks.d.ts.map +1 -0
  134. package/dist/lint/output-docs.d.ts +94 -0
  135. package/dist/lint/output-docs.d.ts.map +1 -0
  136. package/dist/lint/pipeline-change-gate.d.ts +101 -0
  137. package/dist/lint/pipeline-change-gate.d.ts.map +1 -0
  138. package/dist/lint/post-synth.d.ts +41 -0
  139. package/dist/lint/post-synth.d.ts.map +1 -1
  140. package/dist/lint/receipt-checks.d.ts +9 -0
  141. package/dist/lint/receipt-checks.d.ts.map +1 -0
  142. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
  143. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
  144. package/dist/lint/rules/cor022-receipt-leaf.d.ts +13 -0
  145. package/dist/lint/rules/cor022-receipt-leaf.d.ts.map +1 -0
  146. package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts +3 -0
  147. package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts.map +1 -0
  148. package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -1
  149. package/dist/lint/rules/index.d.ts +3 -1
  150. package/dist/lint/rules/index.d.ts.map +1 -1
  151. package/dist/lsp/lexicon-providers.d.ts +7 -0
  152. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  153. package/dist/okf-read.d.ts +78 -0
  154. package/dist/okf-read.d.ts.map +1 -0
  155. package/dist/op/activity-contract.d.ts +139 -0
  156. package/dist/op/activity-contract.d.ts.map +1 -0
  157. package/dist/op/builders.d.ts +140 -3
  158. package/dist/op/builders.d.ts.map +1 -1
  159. package/dist/op/converge-rule.d.ts +161 -0
  160. package/dist/op/converge-rule.d.ts.map +1 -0
  161. package/dist/op/generate-pipeline.d.ts +39 -0
  162. package/dist/op/generate-pipeline.d.ts.map +1 -0
  163. package/dist/op/index.d.ts +18 -2
  164. package/dist/op/index.d.ts.map +1 -1
  165. package/dist/op/local-executor.d.ts +2 -1
  166. package/dist/op/local-executor.d.ts.map +1 -1
  167. package/dist/op/op-verb-class.d.ts +42 -0
  168. package/dist/op/op-verb-class.d.ts.map +1 -0
  169. package/dist/op/operator.d.ts +128 -0
  170. package/dist/op/operator.d.ts.map +1 -0
  171. package/dist/op/receipt-store.d.ts +138 -0
  172. package/dist/op/receipt-store.d.ts.map +1 -0
  173. package/dist/op/step-output-ref.d.ts +187 -0
  174. package/dist/op/step-output-ref.d.ts.map +1 -0
  175. package/dist/op/types.d.ts +49 -2
  176. package/dist/op/types.d.ts.map +1 -1
  177. package/dist/provenance.d.ts +73 -3
  178. package/dist/provenance.d.ts.map +1 -1
  179. package/dist/runtime-adapter.d.ts +7 -1
  180. package/dist/runtime-adapter.d.ts.map +1 -1
  181. package/dist/secret-materialization.d.ts +138 -0
  182. package/dist/secret-materialization.d.ts.map +1 -0
  183. package/dist/secret-provenance.d.ts +218 -0
  184. package/dist/secret-provenance.d.ts.map +1 -0
  185. package/dist/serializer.d.ts +29 -0
  186. package/dist/serializer.d.ts.map +1 -1
  187. package/dist/toml.d.ts +40 -5
  188. package/dist/toml.d.ts.map +1 -1
  189. package/dist/yaml.d.ts.map +1 -1
  190. package/package.json +4 -1
  191. package/src/audit/catalog.test.ts +1 -1
  192. package/src/audit/catalog.ts +75 -3
  193. package/src/audit/core.test.ts +57 -0
  194. package/src/audit/core.ts +0 -0
  195. package/src/audit/detect-bundle.test.ts +1 -1
  196. package/src/audit/discover.test.ts +24 -0
  197. package/src/audit/discover.ts +40 -4
  198. package/src/audit/fetch.test.ts +216 -3
  199. package/src/audit/fetch.ts +270 -59
  200. package/src/audit/report-html.ts +5 -2
  201. package/src/audit/report-model.ts +9 -0
  202. package/src/audit/report.test.ts +22 -0
  203. package/src/audit/report.ts +3 -2
  204. package/src/audit/rules-doc.ts +13 -1
  205. package/src/audit/secrets.test.ts +303 -0
  206. package/src/audit/secrets.ts +406 -0
  207. package/src/audit/wrangler.test.ts +230 -0
  208. package/src/audit/wrangler.ts +290 -0
  209. package/src/build.test.ts +41 -0
  210. package/src/build.ts +39 -6
  211. package/src/cli/command-group.ts +1 -1
  212. package/src/cli/commands/__fixtures__/audit-fountain/agents/fleet.yaml +27 -0
  213. package/src/cli/commands/__fixtures__/audit-fountain/k8s/deploy.yaml +16 -0
  214. package/src/cli/commands/__fixtures__/audit-fountain-clean/fleet.yaml +20 -0
  215. package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
  216. package/src/cli/commands/audit.test.ts +268 -1
  217. package/src/cli/commands/audit.ts +87 -18
  218. package/src/cli/commands/build.test.ts +245 -0
  219. package/src/cli/commands/build.ts +210 -21
  220. package/src/cli/commands/lint.ts +15 -3
  221. package/src/cli/handlers/build.ts +2 -0
  222. package/src/cli/handlers/components.test.ts +199 -1
  223. package/src/cli/handlers/components.ts +160 -3
  224. package/src/cli/handlers/explain.test.ts +70 -1
  225. package/src/cli/handlers/graph.test.ts +20 -0
  226. package/src/cli/handlers/graph.ts +12 -3
  227. package/src/cli/handlers/lifecycle.test.ts +115 -1
  228. package/src/cli/handlers/lifecycle.ts +96 -15
  229. package/src/cli/handlers/operator.test.ts +255 -0
  230. package/src/cli/handlers/operator.ts +240 -0
  231. package/src/cli/handlers/scenario.test.ts +456 -0
  232. package/src/cli/handlers/scenario.ts +330 -0
  233. package/src/cli/main.test.ts +23 -0
  234. package/src/cli/main.ts +72 -1
  235. package/src/cli/mcp/resource-handlers.ts +38 -1
  236. package/src/cli/mcp/server.test.ts +323 -3
  237. package/src/cli/mcp/server.ts +84 -7
  238. package/src/cli/mcp/tools/explain.ts +51 -2
  239. package/src/cli/mcp/types.ts +27 -1
  240. package/src/cli/plugins.ts +4 -2
  241. package/src/cli/registry.ts +14 -2
  242. package/src/cli/reporters/stylish.test.ts +154 -0
  243. package/src/cli/reporters/stylish.ts +154 -33
  244. package/src/codegen/docs-rule-scanning.test.ts +42 -0
  245. package/src/codegen/docs-rule-scanning.ts +25 -2
  246. package/src/components/README.md +7 -0
  247. package/src/components/auto-release.ts +6 -0
  248. package/src/components/capability.ts +17 -2
  249. package/src/components/cli-support.test.ts +17 -0
  250. package/src/components/cli-support.ts +13 -1
  251. package/src/components/component-schema.test.ts +32 -0
  252. package/src/components/component.schema.json +6 -0
  253. package/src/components/component.test.ts +21 -0
  254. package/src/components/component.ts +15 -0
  255. package/src/components/driver.ts +12 -4
  256. package/src/components/registry.test.ts +7 -2
  257. package/src/components/starter-plugin.ts +17 -0
  258. package/src/components/verbs/ensure-secret.test.ts +130 -0
  259. package/src/components/verbs/ensure-secret.ts +79 -0
  260. package/src/components/verbs/index.ts +13 -0
  261. package/src/components/verbs/r2-sync.test.ts +107 -0
  262. package/src/components/verbs/r2-sync.ts +124 -0
  263. package/src/components/verbs/run-agent.test.ts +683 -0
  264. package/src/components/verbs/run-agent.ts +786 -0
  265. package/src/components/verbs/sign.test.ts +19 -0
  266. package/src/components/verbs/sign.ts +34 -2
  267. package/src/components/verbs/wrangler.test.ts +170 -0
  268. package/src/components/verbs/wrangler.ts +241 -0
  269. package/src/composite.ts +31 -2
  270. package/src/config.test.ts +15 -0
  271. package/src/config.ts +30 -0
  272. package/src/deep-observation.test.ts +19 -0
  273. package/src/deep-observation.ts +17 -0
  274. package/src/discovery/collect.ts +11 -2
  275. package/src/discovery/fold-import.test.ts +54 -0
  276. package/src/discovery/fold-import.ts +178 -38
  277. package/src/discovery/fold-rank.test.ts +197 -0
  278. package/src/discovery/fold-rank.ts +346 -0
  279. package/src/discovery/index.ts +16 -1
  280. package/src/discovery/param-deps.test.ts +118 -0
  281. package/src/discovery/param-deps.ts +170 -0
  282. package/src/effect-receipt-exclusion.test.ts +190 -0
  283. package/src/effect-receipt.test.ts +419 -0
  284. package/src/effect-receipt.ts +412 -0
  285. package/src/fold/fold.test.ts +6 -2
  286. package/src/fold/fold.ts +184 -3
  287. package/src/fold/subset.test.ts +95 -6
  288. package/src/fold/subset.ts +66 -2
  289. package/src/index.ts +4 -0
  290. package/src/lexicon-schema.ts +3 -0
  291. package/src/lexicon.ts +151 -5
  292. package/src/lifecycle/change-set.ts +46 -7
  293. package/src/lifecycle/converge-ledger.test.ts +199 -0
  294. package/src/lifecycle/converge-ledger.ts +179 -0
  295. package/src/lifecycle/deep-diff.test.ts +79 -1
  296. package/src/lifecycle/deep-diff.ts +23 -0
  297. package/src/lifecycle/deep-observe.ts +13 -2
  298. package/src/lifecycle/gate-ledger.test.ts +103 -0
  299. package/src/lifecycle/gate-ledger.ts +140 -0
  300. package/src/lifecycle/git.test.ts +430 -0
  301. package/src/lifecycle/git.ts +446 -84
  302. package/src/lifecycle/index.ts +6 -0
  303. package/src/lifecycle/lease.test.ts +343 -0
  304. package/src/lifecycle/lease.ts +270 -0
  305. package/src/lifecycle/observation-baseline.test.ts +46 -0
  306. package/src/lifecycle/observation-baseline.ts +33 -1
  307. package/src/lifecycle/receipt-plan.test.ts +250 -0
  308. package/src/lifecycle/receipt-plan.ts +249 -0
  309. package/src/lifecycle/release-ledger.ts +20 -0
  310. package/src/lifecycle/scenario-eval.test.ts +199 -0
  311. package/src/lifecycle/scenario-eval.ts +158 -0
  312. package/src/lifecycle/scenario.test.ts +195 -0
  313. package/src/lifecycle/scenario.ts +321 -0
  314. package/src/lifecycle/symptoms.test.ts +116 -0
  315. package/src/lifecycle/symptoms.ts +126 -0
  316. package/src/lifecycle/teardown.test.ts +31 -0
  317. package/src/lifecycle/teardown.ts +6 -4
  318. package/src/lifecycle/unobserved-gate.test.ts +109 -0
  319. package/src/lifecycle/unobserved-gate.ts +102 -0
  320. package/src/lint/knowledge-checks.test.ts +80 -0
  321. package/src/lint/knowledge-checks.ts +74 -0
  322. package/src/lint/output-checks.test.ts +85 -0
  323. package/src/lint/output-checks.ts +99 -0
  324. package/src/lint/output-docs.test.ts +220 -0
  325. package/src/lint/output-docs.ts +204 -0
  326. package/src/lint/pipeline-change-gate.test.ts +144 -0
  327. package/src/lint/pipeline-change-gate.ts +153 -0
  328. package/src/lint/post-synth.test.ts +97 -0
  329. package/src/lint/post-synth.ts +60 -0
  330. package/src/lint/receipt-checks.test.ts +101 -0
  331. package/src/lint/receipt-checks.ts +93 -0
  332. package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
  333. package/src/lint/rules/comp/comp.test.ts +49 -1
  334. package/src/lint/rules/cor022-receipt-leaf.test.ts +116 -0
  335. package/src/lint/rules/cor022-receipt-leaf.ts +130 -0
  336. package/src/lint/rules/cor024-receipt-secret-pointer.test.ts +121 -0
  337. package/src/lint/rules/cor024-receipt-secret-pointer.ts +218 -0
  338. package/src/lint/rules/evl001-non-literal-expression.test.ts +35 -3
  339. package/src/lint/rules/evl001-non-literal-expression.ts +7 -0
  340. package/src/lint/rules/index.ts +7 -1
  341. package/src/lsp/lexicon-providers.test.ts +44 -0
  342. package/src/lsp/lexicon-providers.ts +11 -1
  343. package/src/okf-read.test.ts +149 -0
  344. package/src/okf-read.ts +197 -0
  345. package/src/op/activity-contract.test.ts +180 -0
  346. package/src/op/activity-contract.ts +278 -0
  347. package/src/op/builders-exports.test.ts +17 -1
  348. package/src/op/builders.ts +198 -6
  349. package/src/op/converge-rule.test.ts +179 -0
  350. package/src/op/converge-rule.ts +311 -0
  351. package/src/op/effect-step.test.ts +311 -0
  352. package/src/op/generate-pipeline.test.ts +53 -0
  353. package/src/op/generate-pipeline.ts +99 -0
  354. package/src/op/index.ts +40 -3
  355. package/src/op/local-executor.test.ts +92 -0
  356. package/src/op/local-executor.ts +212 -29
  357. package/src/op/op-verb-class.test.ts +126 -0
  358. package/src/op/op-verb-class.ts +115 -0
  359. package/src/op/op.test.ts +25 -2
  360. package/src/op/operator.test.ts +346 -0
  361. package/src/op/operator.ts +213 -0
  362. package/src/op/receipt-store.ts +211 -0
  363. package/src/op/step-output-ref.test.ts +334 -0
  364. package/src/op/step-output-ref.ts +453 -0
  365. package/src/op/types.ts +51 -2
  366. package/src/provenance.test.ts +151 -4
  367. package/src/provenance.ts +118 -4
  368. package/src/runtime-adapter.ts +31 -10
  369. package/src/secret-materialization.test.ts +199 -0
  370. package/src/secret-materialization.ts +235 -0
  371. package/src/secret-provenance.test.ts +388 -0
  372. package/src/secret-provenance.ts +475 -0
  373. package/src/serializer.ts +30 -0
  374. package/src/toml.test.ts +157 -384
  375. package/src/toml.ts +371 -5
  376. package/src/yaml.test.ts +88 -0
  377. package/src/yaml.ts +76 -6
@@ -47,7 +47,8 @@ describe("McpServer", () => {
47
47
 
48
48
  expect(response.error).toBeUndefined();
49
49
  const result = response.result as Record<string, unknown>;
50
- expect(result.protocolVersion).toBe("2024-11-05");
50
+ // No requested version → negotiates to the latest supported revision (#1194).
51
+ expect(result.protocolVersion).toBe("2026-07-28");
51
52
  expect(result.capabilities).toBeDefined();
52
53
  expect((result.serverInfo as Record<string, unknown>).name).toBe("chant");
53
54
  expect((result.serverInfo as Record<string, unknown>).version).toBe("0.1.0");
@@ -60,11 +61,273 @@ describe("McpServer", () => {
60
61
  method: "initialize",
61
62
  params: {},
62
63
  });
63
- const result = response.result as Record<string, unknown>;
64
+ const result = response!.result as Record<string, unknown>;
64
65
  const caps = result.capabilities as Record<string, unknown>;
65
66
  expect(caps.tools).toBeDefined();
66
67
  expect(caps.resources).toBeDefined();
67
68
  });
69
+
70
+ // -----------------------------------------------------------------------
71
+ // Dual-revision negotiation (#1194)
72
+ // -----------------------------------------------------------------------
73
+
74
+ test("a 2024-11-05 client requesting its own version gets it back unchanged", async () => {
75
+ const response = await server.handleRequest({
76
+ jsonrpc: "2.0",
77
+ id: 1,
78
+ method: "initialize",
79
+ params: { protocolVersion: "2024-11-05", clientInfo: { name: "old-client", version: "1.0" } },
80
+ });
81
+ expect(response!.error).toBeUndefined();
82
+ const result = response!.result as Record<string, unknown>;
83
+ expect(result.protocolVersion).toBe("2024-11-05");
84
+ });
85
+
86
+ test("a 2026-07-28 client requesting its version gets the latest revision", async () => {
87
+ const response = await server.handleRequest({
88
+ jsonrpc: "2.0",
89
+ id: 1,
90
+ method: "initialize",
91
+ params: { _meta: { protocolVersion: "2026-07-28" } },
92
+ });
93
+ expect(response!.error).toBeUndefined();
94
+ const result = response!.result as Record<string, unknown>;
95
+ expect(result.protocolVersion).toBe("2026-07-28");
96
+ });
97
+
98
+ test("an unrecognized requested version falls back to the latest supported", async () => {
99
+ const response = await server.handleRequest({
100
+ jsonrpc: "2.0",
101
+ id: 1,
102
+ method: "initialize",
103
+ params: { protocolVersion: "1999-01-01" },
104
+ });
105
+ const result = response!.result as Record<string, unknown>;
106
+ expect(result.protocolVersion).toBe("2026-07-28");
107
+ });
108
+
109
+ test("no requested version at all defaults to the latest supported", async () => {
110
+ const response = await server.handleRequest({
111
+ jsonrpc: "2.0",
112
+ id: 1,
113
+ method: "initialize",
114
+ params: {},
115
+ });
116
+ const result = response!.result as Record<string, unknown>;
117
+ expect(result.protocolVersion).toBe("2026-07-28");
118
+ });
119
+ });
120
+
121
+ // -----------------------------------------------------------------------
122
+ // _meta parsing (#1194)
123
+ // -----------------------------------------------------------------------
124
+
125
+ describe("_meta parsing", () => {
126
+ test("reads requested protocol version from _meta on initialize", async () => {
127
+ const response = await server.handleRequest({
128
+ jsonrpc: "2.0",
129
+ id: 1,
130
+ method: "initialize",
131
+ params: { _meta: { protocolVersion: "2024-11-05" } },
132
+ });
133
+ const result = response!.result as Record<string, unknown>;
134
+ expect(result.protocolVersion).toBe("2024-11-05");
135
+ });
136
+
137
+ test("_meta protocolVersion takes precedence over a legacy top-level one", async () => {
138
+ const response = await server.handleRequest({
139
+ jsonrpc: "2.0",
140
+ id: 1,
141
+ method: "initialize",
142
+ params: { protocolVersion: "2024-11-05", _meta: { protocolVersion: "2026-07-28" } },
143
+ });
144
+ const result = response!.result as Record<string, unknown>;
145
+ expect(result.protocolVersion).toBe("2026-07-28");
146
+ });
147
+
148
+ test("client identity in _meta does not interfere with tool dispatch", async () => {
149
+ const response = await server.handleRequest({
150
+ jsonrpc: "2.0",
151
+ id: 1,
152
+ method: "tools/call",
153
+ params: {
154
+ name: "search",
155
+ arguments: { query: "bucket" },
156
+ _meta: {
157
+ protocolVersion: "2026-07-28",
158
+ "io.modelcontextprotocol/clientInfo": { name: "some-agent", version: "3.0" },
159
+ },
160
+ },
161
+ });
162
+ expect(response!.error).toBeUndefined();
163
+ const result = response!.result as { content: Array<{ text: string }>; isError?: boolean };
164
+ expect(result.isError).toBeUndefined();
165
+ const parsed = JSON.parse(result.content[0].text);
166
+ expect(parsed.query).toBe("bucket");
167
+ });
168
+
169
+ test("a request with no _meta and no legacy fields is unaffected", async () => {
170
+ const response = await server.handleRequest({
171
+ jsonrpc: "2.0",
172
+ id: 1,
173
+ method: "tools/list",
174
+ params: {},
175
+ });
176
+ expect(response!.error).toBeUndefined();
177
+ });
178
+ });
179
+
180
+ // -----------------------------------------------------------------------
181
+ // Notification silence (#1194)
182
+ // -----------------------------------------------------------------------
183
+
184
+ describe("notifications", () => {
185
+ test("a message with no id gets no response", async () => {
186
+ const response = await server.handleRequest({
187
+ jsonrpc: "2.0",
188
+ method: "notifications/initialized",
189
+ params: {},
190
+ });
191
+ expect(response).toBeNull();
192
+ });
193
+
194
+ test("an id-less message is not routed through dispatch as Unknown method", async () => {
195
+ // notifications/initialized isn't a dispatch case; silence must come
196
+ // from the id check, not from swallowing a dispatch error.
197
+ const response = await server.handleRequest({
198
+ jsonrpc: "2.0",
199
+ method: "notifications/anything",
200
+ });
201
+ expect(response).toBeNull();
202
+ });
203
+
204
+ test("a message with id 0 (falsy but present) still gets a response", async () => {
205
+ const response = await server.handleRequest({
206
+ jsonrpc: "2.0",
207
+ id: 0,
208
+ method: "tools/list",
209
+ });
210
+ expect(response).not.toBeNull();
211
+ expect(response!.id).toBe(0);
212
+ });
213
+ });
214
+
215
+ // -----------------------------------------------------------------------
216
+ // server/discover (#1194)
217
+ // -----------------------------------------------------------------------
218
+
219
+ describe("server/discover", () => {
220
+ test("a 2026-07-28 client can discover capabilities without calling initialize", async () => {
221
+ const response = await server.handleRequest({
222
+ jsonrpc: "2.0",
223
+ id: 1,
224
+ method: "server/discover",
225
+ params: { _meta: { protocolVersion: "2026-07-28" } },
226
+ });
227
+ expect(response!.error).toBeUndefined();
228
+ const result = response!.result as Record<string, unknown>;
229
+ expect(result.protocolVersion).toBe("2026-07-28");
230
+ expect(result.serverInfo).toBeDefined();
231
+ expect(result.capabilities).toBeDefined();
232
+ expect(Array.isArray(result.tools)).toBe(true);
233
+ expect(Array.isArray(result.resources)).toBe(true);
234
+ });
235
+
236
+ test("discover's tools list matches tools/list", async () => {
237
+ const discover = await server.handleRequest({ jsonrpc: "2.0", id: 1, method: "server/discover", params: {} });
238
+ const list = await server.handleRequest({ jsonrpc: "2.0", id: 2, method: "tools/list" });
239
+ const discoverNames = (discover!.result as { tools: Array<{ name: string }> }).tools.map((t) => t.name).sort();
240
+ const listNames = (list!.result as { tools: Array<{ name: string }> }).tools.map((t) => t.name).sort();
241
+ expect(discoverNames).toEqual(listNames);
242
+ });
243
+
244
+ test("discover's resources list matches resources/list, including plugin contributions", async () => {
245
+ const plugin = createMockPlugin({
246
+ name: "test-lex",
247
+ mcpResources: () => [
248
+ {
249
+ uri: "catalog",
250
+ name: "Test Catalog",
251
+ description: "Test resource catalog",
252
+ mimeType: "application/json",
253
+ handler: async () => "[]",
254
+ },
255
+ ],
256
+ });
257
+ const s = new McpServer([plugin]);
258
+ const discover = await s.handleRequest({ jsonrpc: "2.0", id: 1, method: "server/discover", params: {} });
259
+ const list = await s.handleRequest({ jsonrpc: "2.0", id: 2, method: "resources/list" });
260
+ const discoverUris = (discover!.result as { resources: Array<{ uri: string }> }).resources.map((r) => r.uri).sort();
261
+ const listUris = (list!.result as { resources: Array<{ uri: string }> }).resources.map((r) => r.uri).sort();
262
+ expect(discoverUris).toEqual(listUris);
263
+ expect(discoverUris).toContain("chant://test-lex/catalog");
264
+ });
265
+
266
+ test("negotiates protocol version the same way as initialize", async () => {
267
+ const response = await server.handleRequest({
268
+ jsonrpc: "2.0",
269
+ id: 1,
270
+ method: "server/discover",
271
+ params: { protocolVersion: "2024-11-05" },
272
+ });
273
+ const result = response!.result as Record<string, unknown>;
274
+ expect(result.protocolVersion).toBe("2024-11-05");
275
+ });
276
+ });
277
+
278
+ // -----------------------------------------------------------------------
279
+ // Structured tool output (#1194)
280
+ // -----------------------------------------------------------------------
281
+
282
+ describe("structured tool output", () => {
283
+ test("an object-returning handler gets structuredContent alongside text", async () => {
284
+ const response = await server.handleRequest({
285
+ jsonrpc: "2.0",
286
+ id: 1,
287
+ method: "tools/call",
288
+ params: { name: "search", arguments: { query: "bucket" } },
289
+ });
290
+ const result = response!.result as { content: Array<{ text: string }>; structuredContent?: Record<string, unknown> };
291
+ expect(result.structuredContent).toBeDefined();
292
+ expect(result.structuredContent!.query).toBe("bucket");
293
+ const parsedText = JSON.parse(result.content[0].text);
294
+ expect(result.structuredContent).toEqual(parsedText);
295
+ });
296
+
297
+ test("a string-returning handler has no structuredContent", async () => {
298
+ const plugin = createMockPlugin({
299
+ name: "test-lex",
300
+ mcpTools: () => [
301
+ {
302
+ name: "greet",
303
+ description: "Greet",
304
+ inputSchema: { type: "object", properties: {} },
305
+ handler: async () => "hello from plugin",
306
+ },
307
+ ],
308
+ });
309
+ const s = new McpServer([plugin]);
310
+ const response = await s.handleRequest({
311
+ jsonrpc: "2.0",
312
+ id: 1,
313
+ method: "tools/call",
314
+ params: { name: "test-lex:greet", arguments: {} },
315
+ });
316
+ const result = response!.result as { structuredContent?: unknown };
317
+ expect(result.structuredContent).toBeUndefined();
318
+ });
319
+
320
+ test("an isError response has no structuredContent", async () => {
321
+ const response = await server.handleRequest({
322
+ jsonrpc: "2.0",
323
+ id: 1,
324
+ method: "tools/call",
325
+ params: { name: "unknown-tool", arguments: {} },
326
+ });
327
+ const result = response!.result as { isError: boolean; structuredContent?: unknown };
328
+ expect(result.isError).toBe(true);
329
+ expect(result.structuredContent).toBeUndefined();
330
+ });
68
331
  });
69
332
 
70
333
  describe("error handling", () => {
@@ -590,6 +853,7 @@ describe("McpServer", () => {
590
853
  expect(uris).toContain("chant://ops");
591
854
  expect(uris).toContain("chant://ops/{name}/runs");
592
855
  expect(uris).toContain("chant://ops/{name}/runs/latest");
856
+ expect(uris).toContain("chant://knowledge");
593
857
 
594
858
  // Each resource has required fields
595
859
  for (const resource of result.resources) {
@@ -735,6 +999,62 @@ describe("McpServer", () => {
735
999
  expect(data.error).toBeDefined();
736
1000
  });
737
1001
  });
1002
+
1003
+ describe("chant://knowledge", () => {
1004
+ test("empty gracefully when no bundle exists", async () => {
1005
+ const originalCwd = process.cwd();
1006
+ process.chdir(testDir);
1007
+ try {
1008
+ const response = await server.handleRequest({
1009
+ jsonrpc: "2.0",
1010
+ id: 1,
1011
+ method: "resources/read",
1012
+ params: { uri: "chant://knowledge" },
1013
+ });
1014
+ expect(response.error).toBeUndefined();
1015
+ const result = response.result as { contents: Array<{ text: string; mimeType: string }> };
1016
+ expect(result.contents[0].mimeType).toBe("application/json");
1017
+ const data = JSON.parse(result.contents[0].text);
1018
+ expect(data.index).toBeNull();
1019
+ expect(data.concepts).toEqual([]);
1020
+ } finally {
1021
+ process.chdir(originalCwd);
1022
+ }
1023
+ });
1024
+
1025
+ test("serves the bundle index and concepts when present", async () => {
1026
+ const originalCwd = process.cwd();
1027
+ process.chdir(testDir);
1028
+ try {
1029
+ await mkdir(join(testDir, "knowledge", "decisions"), { recursive: true });
1030
+ await writeFile(join(testDir, "knowledge", "index.md"), "# Knowledge index\n");
1031
+ await writeFile(
1032
+ join(testDir, "knowledge", "decisions", "public-assets.md"),
1033
+ "---\ntype: decision\ntitle: Public assets\nbinds: bucket\n---\nBody text.\n",
1034
+ );
1035
+
1036
+ const response = await server.handleRequest({
1037
+ jsonrpc: "2.0",
1038
+ id: 1,
1039
+ method: "resources/read",
1040
+ params: { uri: "chant://knowledge" },
1041
+ });
1042
+ expect(response.error).toBeUndefined();
1043
+ const result = response.result as { contents: Array<{ text: string }> };
1044
+ const data = JSON.parse(result.contents[0].text);
1045
+ expect(data.index).toBe("# Knowledge index\n");
1046
+ expect(data.concepts).toHaveLength(1);
1047
+ expect(data.concepts[0]).toMatchObject({
1048
+ path: "decisions/public-assets.md",
1049
+ type: "decision",
1050
+ title: "Public assets",
1051
+ binds: ["bucket"],
1052
+ });
1053
+ } finally {
1054
+ process.chdir(originalCwd);
1055
+ }
1056
+ });
1057
+ });
738
1058
  });
739
1059
 
740
1060
  // -----------------------------------------------------------------------
@@ -1157,7 +1477,7 @@ describe("McpServer", () => {
1157
1477
 
1158
1478
  const resourcesRes = await s.handleRequest({ jsonrpc: "2.0", id: 3, method: "resources/list" });
1159
1479
  const resources = (resourcesRes.result as { resources: Array<{ uri: string }> }).resources;
1160
- expect(resources).toHaveLength(7);
1480
+ expect(resources).toHaveLength(8);
1161
1481
  });
1162
1482
 
1163
1483
  test("server with empty plugins array works", async () => {
@@ -7,11 +7,50 @@ import { explainTool, handleExplain } from "./tools/explain";
7
7
  import { scaffoldTool, createScaffoldHandler } from "./tools/scaffold";
8
8
  import { searchTool, createSearchHandler } from "./tools/search";
9
9
  import type { LexiconPlugin } from "../../lexicon";
10
- import type { McpRequest, McpResponse, ToolDefinition, ToolHandler, ResourceDefinition } from "./types";
10
+ import type { McpRequest, McpResponse, McpRequestMeta, ToolDefinition, ToolHandler, ResourceDefinition } from "./types";
11
11
  import { createSnapshotTool, createDiffTool } from "./lifecycle-tools";
12
12
  import { createOpListTool, createOpRunTool, createOpStatusTool, createOpSignalTool, createOpReportTool } from "./op-tools";
13
13
  import { buildResourcesList, handleResourcesRead } from "./resource-handlers";
14
14
 
15
+ /**
16
+ * Protocol versions this server understands, newest first. `initialize` and
17
+ * `server/discover` both negotiate against this list rather than assuming
18
+ * the client's revision (#1194).
19
+ */
20
+ const SUPPORTED_PROTOCOL_VERSIONS = ["2026-07-28", "2024-11-05"] as const;
21
+ const LATEST_PROTOCOL_VERSION = SUPPORTED_PROTOCOL_VERSIONS[0];
22
+
23
+ /**
24
+ * Pick the protocol version to answer with: the client's requested version
25
+ * when we support it, otherwise our latest. A 2024-11-05 client that asks
26
+ * for `2024-11-05` gets it back unchanged; a 2026-07-28 client — or one
27
+ * that never says — gets the latest revision (#1194).
28
+ */
29
+ export function negotiateProtocolVersion(requested: string | undefined): string {
30
+ if (requested && (SUPPORTED_PROTOCOL_VERSIONS as readonly string[]).includes(requested)) {
31
+ return requested;
32
+ }
33
+ return LATEST_PROTOCOL_VERSION;
34
+ }
35
+
36
+ /**
37
+ * Parse the `_meta` envelope the 2026-07-28 revision puts on every request
38
+ * (client identity, requested protocol version), falling back to the
39
+ * top-level `protocolVersion`/`clientInfo` fields a prior-revision client
40
+ * sends on `initialize`. Read-side only — the server holds no handshake
41
+ * state to update (#1194).
42
+ */
43
+ export function parseMeta(params: Record<string, unknown>): { protocolVersion?: string; clientInfo?: { name: string; version?: string } } {
44
+ const meta = (params._meta ?? {}) as McpRequestMeta;
45
+ const protocolVersion =
46
+ (typeof meta.protocolVersion === "string" ? meta.protocolVersion : undefined) ??
47
+ (typeof params.protocolVersion === "string" ? params.protocolVersion : undefined);
48
+ const clientInfo =
49
+ meta["io.modelcontextprotocol/clientInfo"] ??
50
+ (params.clientInfo as { name: string; version?: string } | undefined);
51
+ return { protocolVersion, clientInfo };
52
+ }
53
+
15
54
  /**
16
55
  * The name a lexicon's MCP tool is registered under: `<lexicon>:<verb>`,
17
56
  * whether or not the lexicon already wrote the prefix itself (#1341).
@@ -135,9 +174,19 @@ export class McpServer {
135
174
  }
136
175
 
137
176
  /**
138
- * Handle incoming MCP request
177
+ * Handle incoming MCP request.
178
+ *
179
+ * A message with no `id` is a notification — `notifications/initialized`
180
+ * being the one every client sends — and gets no response in any
181
+ * protocol revision (#1194). Returns `null` for those; callers (see
182
+ * {@link start}) simply skip writing anything back.
139
183
  */
140
- async handleRequest(request: McpRequest): Promise<McpResponse> {
184
+ async handleRequest(request: McpRequest & { id: string | number }): Promise<McpResponse>;
185
+ async handleRequest(request: McpRequest): Promise<McpResponse | null>;
186
+ async handleRequest(request: McpRequest): Promise<McpResponse | null> {
187
+ if (request.id === undefined) {
188
+ return null;
189
+ }
141
190
  try {
142
191
  const result = await this.dispatch(request.method, request.params ?? {});
143
192
  return {
@@ -157,16 +206,35 @@ export class McpServer {
157
206
  }
158
207
  }
159
208
 
209
+ /**
210
+ * The `initialize` result — also the basis of `server/discover` below.
211
+ */
212
+ private buildInitializeResult(params: Record<string, unknown>): Record<string, unknown> {
213
+ const { protocolVersion } = parseMeta(params);
214
+ return {
215
+ protocolVersion: negotiateProtocolVersion(protocolVersion),
216
+ capabilities: { tools: {}, resources: {} },
217
+ serverInfo: { name: "chant", version: "0.1.0" },
218
+ };
219
+ }
220
+
160
221
  /**
161
222
  * Dispatch request to appropriate handler
162
223
  */
163
224
  private async dispatch(method: string, params: Record<string, unknown>): Promise<unknown> {
164
225
  switch (method) {
165
226
  case "initialize":
227
+ // Answered for prior-revision clients too — negotiated, not hard-coded (#1194).
228
+ return this.buildInitializeResult(params);
229
+
230
+ case "server/discover":
231
+ // On-demand capability discovery (#1194): the initialize result
232
+ // merged with the tools/resources listings, so a 2026-07-28 client
233
+ // never has to call `initialize` at all.
166
234
  return {
167
- protocolVersion: "2024-11-05",
168
- capabilities: { tools: {}, resources: {} },
169
- serverInfo: { name: "chant", version: "0.1.0" },
235
+ ...this.buildInitializeResult(params),
236
+ tools: Array.from(this.tools.values()),
237
+ resources: buildResourcesList(this.pluginResources).resources,
170
238
  };
171
239
 
172
240
  case "tools/list":
@@ -203,6 +271,7 @@ export class McpServer {
203
271
 
204
272
  try {
205
273
  const result = await handler(toolParams);
274
+ const isStructured = typeof result === "object" && result !== null;
206
275
  return {
207
276
  content: [
208
277
  {
@@ -210,6 +279,11 @@ export class McpServer {
210
279
  text: typeof result === "string" ? result : JSON.stringify(result, null, 2),
211
280
  },
212
281
  ],
282
+ // Structured output (2025-06-18+): handlers already return plain
283
+ // objects that get stringified above, so agents that want the
284
+ // parsed shape directly get it here too, alongside the text block
285
+ // kept for backward compatibility (#1194).
286
+ ...(isStructured ? { structuredContent: result } : {}),
213
287
  };
214
288
  } catch (error) {
215
289
  return {
@@ -238,7 +312,10 @@ export class McpServer {
238
312
  try {
239
313
  const request = JSON.parse(line) as McpRequest;
240
314
  const response = await this.handleRequest(request);
241
- console.log(JSON.stringify(response));
315
+ // Notifications (#1194) get no response at all — not even an empty one.
316
+ if (response !== null) {
317
+ console.log(JSON.stringify(response));
318
+ }
242
319
  } catch (error) {
243
320
  const errorResponse: McpResponse = {
244
321
  jsonrpc: "2.0",
@@ -1,6 +1,19 @@
1
- import { resolve } from "path";
1
+ import { resolve, dirname } from "path";
2
2
  import { discover } from "../../../discovery/index";
3
3
  import { buildOkfBundle, OKF_VERSION } from "../../../okf";
4
+ import { loadOkfBundle, bindConcepts, type OkfConcept } from "../../../okf-read";
5
+ import { loadChantConfigUpward, resolveKnowledgeDir } from "../../../config";
6
+
7
+ /** One bound concept as surfaced in an explain knowledge section — the trio the #1867 design calls for, not the full {@link OkfConcept}. */
8
+ export interface ExplainKnowledgeEntry {
9
+ type: string;
10
+ title?: string;
11
+ path: string;
12
+ }
13
+
14
+ function toKnowledgeEntry(concept: OkfConcept): ExplainKnowledgeEntry {
15
+ return { type: concept.type, title: concept.title, path: concept.path };
16
+ }
4
17
 
5
18
  /**
6
19
  * Explain tool definition for MCP
@@ -46,6 +59,25 @@ export async function handleExplain(params: Record<string, unknown>): Promise<un
46
59
  };
47
60
  }
48
61
 
62
+ // Authored knowledge (#1867, design #1059): bind the project's OKF bundle
63
+ // (if any) to the discovered entities and carry each bound concept's
64
+ // type/title/path into the summary. A project with no `knowledge/` yet
65
+ // binds nothing — `bound` is empty — so the section is omitted below
66
+ // rather than rendered empty; this is the CLI and MCP tool's one shared
67
+ // code path, so both surfaces match by construction.
68
+ // `loadChantConfigUpward` walks up from `infraPath` (chant #1117) — a
69
+ // `chant explain src/<stack>` two levels below `chant.config.ts` must find
70
+ // the same `knowledge/` a `chant explain .` at the root would, matching
71
+ // `build.ts`'s `configDir` derivation for the same reason.
72
+ const loaded = await loadChantConfigUpward(infraPath);
73
+ const projectRoot = loaded.configPath ? dirname(loaded.configPath) : infraPath;
74
+ const knowledgeDir = resolveKnowledgeDir(loaded.config, projectRoot);
75
+ const bundle = await loadOkfBundle(knowledgeDir);
76
+ const { bound } = bindConcepts(bundle, result.entities);
77
+ const knowledge = new Map<string, ExplainKnowledgeEntry[]>(
78
+ Array.from(bound.entries()).map(([name, concepts]) => [name, concepts.map(toKnowledgeEntry)]),
79
+ );
80
+
49
81
  // Group entities by lexicon and kind
50
82
  const byLexicon = new Map<string, { resources: string[]; properties: string[] }>();
51
83
 
@@ -89,7 +121,11 @@ export async function handleExplain(params: Record<string, unknown>): Promise<un
89
121
  };
90
122
 
91
123
  if (format === "json") {
92
- return summary;
124
+ // Omitted entirely when no entity bound a concept — "cleanly absent" per
125
+ // #1867's AC, not an empty `{}`.
126
+ return knowledge.size > 0
127
+ ? { ...summary, knowledge: Object.fromEntries(knowledge) }
128
+ : summary;
93
129
  }
94
130
 
95
131
  // Markdown format
@@ -133,6 +169,19 @@ export async function handleExplain(params: Record<string, unknown>): Promise<un
133
169
  lines.push("");
134
170
  }
135
171
 
172
+ if (knowledge.size > 0) {
173
+ lines.push("## Knowledge");
174
+ lines.push("");
175
+ for (const [name, entries] of knowledge) {
176
+ lines.push(`### \`${name}\``);
177
+ for (const entry of entries) {
178
+ const title = entry.title ? `${entry.title} ` : "";
179
+ lines.push(`- ${entry.type || "concept"}: ${title}(\`${entry.path}\`)`);
180
+ }
181
+ lines.push("");
182
+ }
183
+ }
184
+
136
185
  if (result.errors.length > 0) {
137
186
  lines.push("## Errors");
138
187
  lines.push("");
@@ -1,9 +1,13 @@
1
1
  /**
2
2
  * MCP message types
3
+ *
4
+ * `id` is optional per JSON-RPC 2.0: a message with no `id` is a
5
+ * notification (e.g. `notifications/initialized`) and gets no response,
6
+ * in every protocol revision (#1194).
3
7
  */
4
8
  export interface McpRequest {
5
9
  jsonrpc: "2.0";
6
- id: string | number;
10
+ id?: string | number;
7
11
  method: string;
8
12
  params?: Record<string, unknown>;
9
13
  }
@@ -19,6 +23,18 @@ export interface McpResponse {
19
23
  };
20
24
  }
21
25
 
26
+ /**
27
+ * The `_meta` envelope the 2026-07-28 revision puts on every request so it
28
+ * is self-contained: client identity and the client's requested protocol
29
+ * version travel here instead of living in handshake state (#1194). A
30
+ * prior-revision client instead sends `protocolVersion`/`clientInfo` as
31
+ * top-level `initialize` params — both forms are read by {@link parseMeta}.
32
+ */
33
+ export interface McpRequestMeta {
34
+ protocolVersion?: string;
35
+ "io.modelcontextprotocol/clientInfo"?: { name: string; version?: string };
36
+ }
37
+
22
38
  /**
23
39
  * Tool definition for MCP
24
40
  */
@@ -30,6 +46,16 @@ export interface ToolDefinition {
30
46
  properties: Record<string, unknown>;
31
47
  required?: string[];
32
48
  };
49
+ /**
50
+ * JSON schema for `structuredContent` (2025-06-18+), when a tool's result
51
+ * shape is obvious and stable enough to declare. Optional and additive —
52
+ * most tools omit it (#1194).
53
+ */
54
+ outputSchema?: {
55
+ type: "object";
56
+ properties: Record<string, unknown>;
57
+ required?: string[];
58
+ };
33
59
  }
34
60
 
35
61
  /**
@@ -106,10 +106,12 @@ export function collectBuildRootContributors(
106
106
  plugins: readonly LexiconPlugin[] | undefined,
107
107
  config: Record<string, unknown>,
108
108
  projectRoot: string,
109
- ): Array<() => Promise<import("../lexicon").BuildRootContribution>> {
109
+ ): Array<import("../lexicon").BuildRootContributor> {
110
110
  return (plugins ?? [])
111
111
  .filter((plugin) => typeof plugin.buildRoots === "function")
112
- .map((plugin) => () => plugin.buildRoots!({ projectRoot, config }));
112
+ // `entities` is not bindable here — discovery has not run — so the merge
113
+ // hands it in when it calls the closure (#1828 / SOPS provenance).
114
+ .map((plugin) => (ctx) => plugin.buildRoots!({ projectRoot, config, entities: ctx?.entities }));
113
115
  }
114
116
 
115
117
  /**