@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.
- package/dist/audit/catalog.d.ts +13 -3
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +30 -3
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +9 -2
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/fetch.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +6 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +95 -0
- package/dist/audit/secrets.d.ts.map +1 -0
- package/dist/audit/wrangler.d.ts +33 -0
- package/dist/audit/wrangler.d.ts.map +1 -0
- package/dist/build.d.ts +3 -3
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +23 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +31 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +12 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +32 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -0
- package/dist/cli/handlers/scenario.d.ts +39 -0
- package/dist/cli/handlers/scenario.d.ts.map +1 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts +35 -2
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/tools/explain.d.ts +6 -0
- package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
- package/dist/cli/mcp/types.d.ts +29 -1
- package/dist/cli/mcp/types.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +1 -1
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +14 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/reporters/stylish.d.ts +15 -1
- package/dist/cli/reporters/stylish.d.ts.map +1 -1
- package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
- package/dist/components/auto-release.d.ts +4 -0
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/capability.d.ts +17 -2
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +7 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +15 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/starter-plugin.d.ts +2 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/verbs/ensure-secret.d.ts +50 -0
- package/dist/components/verbs/ensure-secret.d.ts.map +1 -0
- package/dist/components/verbs/index.d.ts +13 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/r2-sync.d.ts +76 -0
- package/dist/components/verbs/r2-sync.d.ts.map +1 -0
- package/dist/components/verbs/run-agent.d.ts +499 -0
- package/dist/components/verbs/run-agent.d.ts.map +1 -0
- package/dist/components/verbs/sign.d.ts +30 -0
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/components/verbs/wrangler.d.ts +108 -0
- package/dist/components/verbs/wrangler.d.ts.map +1 -0
- package/dist/composite.d.ts +6 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/deep-observation.d.ts +14 -0
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-rank.d.ts +66 -0
- package/dist/discovery/fold-rank.d.ts.map +1 -0
- package/dist/discovery/index.d.ts +15 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +17 -0
- package/dist/discovery/param-deps.d.ts.map +1 -0
- package/dist/effect-receipt.d.ts +177 -0
- package/dist/effect-receipt.d.ts.map +1 -0
- package/dist/fold/fold.d.ts +55 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/subset.d.ts +20 -0
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +2 -0
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +137 -3
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/change-set.d.ts +33 -5
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +90 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
- package/dist/lifecycle/deep-diff.d.ts +18 -0
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts +9 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +33 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +145 -21
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +6 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +113 -0
- package/dist/lifecycle/lease.d.ts.map +1 -0
- package/dist/lifecycle/observation-baseline.d.ts +21 -3
- package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
- package/dist/lifecycle/receipt-plan.d.ts +62 -0
- package/dist/lifecycle/receipt-plan.d.ts.map +1 -0
- package/dist/lifecycle/release-ledger.d.ts +20 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/scenario-eval.d.ts +42 -0
- package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
- package/dist/lifecycle/scenario.d.ts +163 -0
- package/dist/lifecycle/scenario.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +63 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -0
- package/dist/lifecycle/teardown.d.ts +6 -4
- package/dist/lifecycle/teardown.d.ts.map +1 -1
- package/dist/lifecycle/unobserved-gate.d.ts +67 -0
- package/dist/lifecycle/unobserved-gate.d.ts.map +1 -0
- package/dist/lint/knowledge-checks.d.ts +48 -0
- package/dist/lint/knowledge-checks.d.ts.map +1 -0
- package/dist/lint/output-checks.d.ts +5 -0
- package/dist/lint/output-checks.d.ts.map +1 -0
- package/dist/lint/output-docs.d.ts +94 -0
- package/dist/lint/output-docs.d.ts.map +1 -0
- package/dist/lint/pipeline-change-gate.d.ts +101 -0
- package/dist/lint/pipeline-change-gate.d.ts.map +1 -0
- package/dist/lint/post-synth.d.ts +41 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/receipt-checks.d.ts +9 -0
- package/dist/lint/receipt-checks.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
- package/dist/lint/rules/cor022-receipt-leaf.d.ts +13 -0
- package/dist/lint/rules/cor022-receipt-leaf.d.ts.map +1 -0
- package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts +3 -0
- package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts.map +1 -0
- package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -1
- package/dist/lint/rules/index.d.ts +3 -1
- package/dist/lint/rules/index.d.ts.map +1 -1
- package/dist/lsp/lexicon-providers.d.ts +7 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/okf-read.d.ts +78 -0
- package/dist/okf-read.d.ts.map +1 -0
- package/dist/op/activity-contract.d.ts +139 -0
- package/dist/op/activity-contract.d.ts.map +1 -0
- package/dist/op/builders.d.ts +140 -3
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/converge-rule.d.ts +161 -0
- package/dist/op/converge-rule.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +39 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -0
- package/dist/op/index.d.ts +18 -2
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +2 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts +42 -0
- package/dist/op/op-verb-class.d.ts.map +1 -0
- package/dist/op/operator.d.ts +128 -0
- package/dist/op/operator.d.ts.map +1 -0
- package/dist/op/receipt-store.d.ts +138 -0
- package/dist/op/receipt-store.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +187 -0
- package/dist/op/step-output-ref.d.ts.map +1 -0
- package/dist/op/types.d.ts +49 -2
- package/dist/op/types.d.ts.map +1 -1
- package/dist/provenance.d.ts +73 -3
- package/dist/provenance.d.ts.map +1 -1
- package/dist/runtime-adapter.d.ts +7 -1
- package/dist/runtime-adapter.d.ts.map +1 -1
- package/dist/secret-materialization.d.ts +138 -0
- package/dist/secret-materialization.d.ts.map +1 -0
- package/dist/secret-provenance.d.ts +218 -0
- package/dist/secret-provenance.d.ts.map +1 -0
- package/dist/serializer.d.ts +29 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/toml.d.ts +40 -5
- package/dist/toml.d.ts.map +1 -1
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/audit/catalog.test.ts +1 -1
- package/src/audit/catalog.ts +75 -3
- package/src/audit/core.test.ts +57 -0
- package/src/audit/core.ts +0 -0
- package/src/audit/detect-bundle.test.ts +1 -1
- package/src/audit/discover.test.ts +24 -0
- package/src/audit/discover.ts +40 -4
- package/src/audit/fetch.test.ts +216 -3
- package/src/audit/fetch.ts +270 -59
- package/src/audit/report-html.ts +5 -2
- package/src/audit/report-model.ts +9 -0
- package/src/audit/report.test.ts +22 -0
- package/src/audit/report.ts +3 -2
- package/src/audit/rules-doc.ts +13 -1
- package/src/audit/secrets.test.ts +303 -0
- package/src/audit/secrets.ts +406 -0
- package/src/audit/wrangler.test.ts +230 -0
- package/src/audit/wrangler.ts +290 -0
- package/src/build.test.ts +41 -0
- package/src/build.ts +39 -6
- package/src/cli/command-group.ts +1 -1
- package/src/cli/commands/__fixtures__/audit-fountain/agents/fleet.yaml +27 -0
- package/src/cli/commands/__fixtures__/audit-fountain/k8s/deploy.yaml +16 -0
- package/src/cli/commands/__fixtures__/audit-fountain-clean/fleet.yaml +20 -0
- package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
- package/src/cli/commands/audit.test.ts +268 -1
- package/src/cli/commands/audit.ts +87 -18
- package/src/cli/commands/build.test.ts +245 -0
- package/src/cli/commands/build.ts +210 -21
- package/src/cli/commands/lint.ts +15 -3
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.test.ts +199 -1
- package/src/cli/handlers/components.ts +160 -3
- package/src/cli/handlers/explain.test.ts +70 -1
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +12 -3
- package/src/cli/handlers/lifecycle.test.ts +115 -1
- package/src/cli/handlers/lifecycle.ts +96 -15
- package/src/cli/handlers/operator.test.ts +255 -0
- package/src/cli/handlers/operator.ts +240 -0
- package/src/cli/handlers/scenario.test.ts +456 -0
- package/src/cli/handlers/scenario.ts +330 -0
- package/src/cli/main.test.ts +23 -0
- package/src/cli/main.ts +72 -1
- package/src/cli/mcp/resource-handlers.ts +38 -1
- package/src/cli/mcp/server.test.ts +323 -3
- package/src/cli/mcp/server.ts +84 -7
- package/src/cli/mcp/tools/explain.ts +51 -2
- package/src/cli/mcp/types.ts +27 -1
- package/src/cli/plugins.ts +4 -2
- package/src/cli/registry.ts +14 -2
- package/src/cli/reporters/stylish.test.ts +154 -0
- package/src/cli/reporters/stylish.ts +154 -33
- package/src/codegen/docs-rule-scanning.test.ts +42 -0
- package/src/codegen/docs-rule-scanning.ts +25 -2
- package/src/components/README.md +7 -0
- package/src/components/auto-release.ts +6 -0
- package/src/components/capability.ts +17 -2
- package/src/components/cli-support.test.ts +17 -0
- package/src/components/cli-support.ts +13 -1
- package/src/components/component-schema.test.ts +32 -0
- package/src/components/component.schema.json +6 -0
- package/src/components/component.test.ts +21 -0
- package/src/components/component.ts +15 -0
- package/src/components/driver.ts +12 -4
- package/src/components/registry.test.ts +7 -2
- package/src/components/starter-plugin.ts +17 -0
- package/src/components/verbs/ensure-secret.test.ts +130 -0
- package/src/components/verbs/ensure-secret.ts +79 -0
- package/src/components/verbs/index.ts +13 -0
- package/src/components/verbs/r2-sync.test.ts +107 -0
- package/src/components/verbs/r2-sync.ts +124 -0
- package/src/components/verbs/run-agent.test.ts +683 -0
- package/src/components/verbs/run-agent.ts +786 -0
- package/src/components/verbs/sign.test.ts +19 -0
- package/src/components/verbs/sign.ts +34 -2
- package/src/components/verbs/wrangler.test.ts +170 -0
- package/src/components/verbs/wrangler.ts +241 -0
- package/src/composite.ts +31 -2
- package/src/config.test.ts +15 -0
- package/src/config.ts +30 -0
- package/src/deep-observation.test.ts +19 -0
- package/src/deep-observation.ts +17 -0
- package/src/discovery/collect.ts +11 -2
- package/src/discovery/fold-import.test.ts +54 -0
- package/src/discovery/fold-import.ts +178 -38
- package/src/discovery/fold-rank.test.ts +197 -0
- package/src/discovery/fold-rank.ts +346 -0
- package/src/discovery/index.ts +16 -1
- package/src/discovery/param-deps.test.ts +118 -0
- package/src/discovery/param-deps.ts +170 -0
- package/src/effect-receipt-exclusion.test.ts +190 -0
- package/src/effect-receipt.test.ts +419 -0
- package/src/effect-receipt.ts +412 -0
- package/src/fold/fold.test.ts +6 -2
- package/src/fold/fold.ts +184 -3
- package/src/fold/subset.test.ts +95 -6
- package/src/fold/subset.ts +66 -2
- package/src/index.ts +4 -0
- package/src/lexicon-schema.ts +3 -0
- package/src/lexicon.ts +151 -5
- package/src/lifecycle/change-set.ts +46 -7
- package/src/lifecycle/converge-ledger.test.ts +199 -0
- package/src/lifecycle/converge-ledger.ts +179 -0
- package/src/lifecycle/deep-diff.test.ts +79 -1
- package/src/lifecycle/deep-diff.ts +23 -0
- package/src/lifecycle/deep-observe.ts +13 -2
- package/src/lifecycle/gate-ledger.test.ts +103 -0
- package/src/lifecycle/gate-ledger.ts +140 -0
- package/src/lifecycle/git.test.ts +430 -0
- package/src/lifecycle/git.ts +446 -84
- package/src/lifecycle/index.ts +6 -0
- package/src/lifecycle/lease.test.ts +343 -0
- package/src/lifecycle/lease.ts +270 -0
- package/src/lifecycle/observation-baseline.test.ts +46 -0
- package/src/lifecycle/observation-baseline.ts +33 -1
- package/src/lifecycle/receipt-plan.test.ts +250 -0
- package/src/lifecycle/receipt-plan.ts +249 -0
- package/src/lifecycle/release-ledger.ts +20 -0
- package/src/lifecycle/scenario-eval.test.ts +199 -0
- package/src/lifecycle/scenario-eval.ts +158 -0
- package/src/lifecycle/scenario.test.ts +195 -0
- package/src/lifecycle/scenario.ts +321 -0
- package/src/lifecycle/symptoms.test.ts +116 -0
- package/src/lifecycle/symptoms.ts +126 -0
- package/src/lifecycle/teardown.test.ts +31 -0
- package/src/lifecycle/teardown.ts +6 -4
- package/src/lifecycle/unobserved-gate.test.ts +109 -0
- package/src/lifecycle/unobserved-gate.ts +102 -0
- package/src/lint/knowledge-checks.test.ts +80 -0
- package/src/lint/knowledge-checks.ts +74 -0
- package/src/lint/output-checks.test.ts +85 -0
- package/src/lint/output-checks.ts +99 -0
- package/src/lint/output-docs.test.ts +220 -0
- package/src/lint/output-docs.ts +204 -0
- package/src/lint/pipeline-change-gate.test.ts +144 -0
- package/src/lint/pipeline-change-gate.ts +153 -0
- package/src/lint/post-synth.test.ts +97 -0
- package/src/lint/post-synth.ts +60 -0
- package/src/lint/receipt-checks.test.ts +101 -0
- package/src/lint/receipt-checks.ts +93 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
- package/src/lint/rules/comp/comp.test.ts +49 -1
- package/src/lint/rules/cor022-receipt-leaf.test.ts +116 -0
- package/src/lint/rules/cor022-receipt-leaf.ts +130 -0
- package/src/lint/rules/cor024-receipt-secret-pointer.test.ts +121 -0
- package/src/lint/rules/cor024-receipt-secret-pointer.ts +218 -0
- package/src/lint/rules/evl001-non-literal-expression.test.ts +35 -3
- package/src/lint/rules/evl001-non-literal-expression.ts +7 -0
- package/src/lint/rules/index.ts +7 -1
- package/src/lsp/lexicon-providers.test.ts +44 -0
- package/src/lsp/lexicon-providers.ts +11 -1
- package/src/okf-read.test.ts +149 -0
- package/src/okf-read.ts +197 -0
- package/src/op/activity-contract.test.ts +180 -0
- package/src/op/activity-contract.ts +278 -0
- package/src/op/builders-exports.test.ts +17 -1
- package/src/op/builders.ts +198 -6
- package/src/op/converge-rule.test.ts +179 -0
- package/src/op/converge-rule.ts +311 -0
- package/src/op/effect-step.test.ts +311 -0
- package/src/op/generate-pipeline.test.ts +53 -0
- package/src/op/generate-pipeline.ts +99 -0
- package/src/op/index.ts +40 -3
- package/src/op/local-executor.test.ts +92 -0
- package/src/op/local-executor.ts +212 -29
- package/src/op/op-verb-class.test.ts +126 -0
- package/src/op/op-verb-class.ts +115 -0
- package/src/op/op.test.ts +25 -2
- package/src/op/operator.test.ts +346 -0
- package/src/op/operator.ts +213 -0
- package/src/op/receipt-store.ts +211 -0
- package/src/op/step-output-ref.test.ts +334 -0
- package/src/op/step-output-ref.ts +453 -0
- package/src/op/types.ts +51 -2
- package/src/provenance.test.ts +151 -4
- package/src/provenance.ts +118 -4
- package/src/runtime-adapter.ts +31 -10
- package/src/secret-materialization.test.ts +199 -0
- package/src/secret-materialization.ts +235 -0
- package/src/secret-provenance.test.ts +388 -0
- package/src/secret-provenance.ts +475 -0
- package/src/serializer.ts +30 -0
- package/src/toml.test.ts +157 -384
- package/src/toml.ts +371 -5
- package/src/yaml.test.ts +88 -0
- 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
|
-
|
|
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
|
|
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(
|
|
1480
|
+
expect(resources).toHaveLength(8);
|
|
1161
1481
|
});
|
|
1162
1482
|
|
|
1163
1483
|
test("server with empty plugins array works", async () => {
|
package/src/cli/mcp/server.ts
CHANGED
|
@@ -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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("");
|
package/src/cli/mcp/types.ts
CHANGED
|
@@ -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
|
|
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
|
/**
|
package/src/cli/plugins.ts
CHANGED
|
@@ -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<
|
|
109
|
+
): Array<import("../lexicon").BuildRootContributor> {
|
|
110
110
|
return (plugins ?? [])
|
|
111
111
|
.filter((plugin) => typeof plugin.buildRoots === "function")
|
|
112
|
-
|
|
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
|
/**
|