aontu 0.52.0 → 0.53.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 (273) hide show
  1. package/README.md +88 -0
  2. package/bin/aontu-mcp.js +4 -0
  3. package/dist/agentsmd.d.ts +16 -0
  4. package/dist/agentsmd.js +107 -0
  5. package/dist/agentsmd.js.map +1 -0
  6. package/dist/aontu.d.ts +14 -3
  7. package/dist/aontu.js +145 -4
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +44 -1
  10. package/dist/cli.js +2401 -44
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +16 -0
  13. package/dist/ctx.js +44 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/diff.d.ts +22 -0
  16. package/dist/diff.js +141 -0
  17. package/dist/diff.js.map +1 -0
  18. package/dist/err.d.ts +3 -1
  19. package/dist/err.js +48 -8
  20. package/dist/err.js.map +1 -1
  21. package/dist/graph.d.ts +16 -0
  22. package/dist/graph.js +73 -0
  23. package/dist/graph.js.map +1 -0
  24. package/dist/hcanon.d.ts +3 -0
  25. package/dist/hcanon.js +146 -0
  26. package/dist/hcanon.js.map +1 -0
  27. package/dist/hints.js +223 -5
  28. package/dist/hints.js.map +1 -1
  29. package/dist/jsonschema.d.ts +20 -0
  30. package/dist/jsonschema.js +391 -0
  31. package/dist/jsonschema.js.map +1 -0
  32. package/dist/lang.js +698 -35
  33. package/dist/lang.js.map +1 -1
  34. package/dist/lsp.d.ts +9 -2
  35. package/dist/lsp.js +262 -46
  36. package/dist/lsp.js.map +1 -1
  37. package/dist/mcp-server.d.ts +20 -0
  38. package/dist/mcp-server.js +147 -0
  39. package/dist/mcp-server.js.map +1 -0
  40. package/dist/mcp.d.ts +42 -0
  41. package/dist/mcp.js +814 -0
  42. package/dist/mcp.js.map +1 -0
  43. package/dist/mod-tool.d.ts +58 -0
  44. package/dist/mod-tool.js +498 -0
  45. package/dist/mod-tool.js.map +1 -0
  46. package/dist/mod.d.ts +31 -0
  47. package/dist/mod.js +250 -0
  48. package/dist/mod.js.map +1 -0
  49. package/dist/patch.d.ts +44 -0
  50. package/dist/patch.js +506 -0
  51. package/dist/patch.js.map +1 -0
  52. package/dist/provenance.d.ts +40 -0
  53. package/dist/provenance.js +335 -0
  54. package/dist/provenance.js.map +1 -0
  55. package/dist/query.d.ts +27 -0
  56. package/dist/query.js +294 -0
  57. package/dist/query.js.map +1 -0
  58. package/dist/reach.d.ts +14 -0
  59. package/dist/reach.js +140 -0
  60. package/dist/reach.js.map +1 -0
  61. package/dist/relation.d.ts +19 -0
  62. package/dist/relation.js +305 -0
  63. package/dist/relation.js.map +1 -0
  64. package/dist/report-sarif.d.ts +14 -0
  65. package/dist/report-sarif.js +102 -0
  66. package/dist/report-sarif.js.map +1 -0
  67. package/dist/site.d.ts +4 -0
  68. package/dist/site.js +31 -0
  69. package/dist/site.js.map +1 -1
  70. package/dist/std.d.ts +1 -0
  71. package/dist/std.js +73 -0
  72. package/dist/std.js.map +1 -0
  73. package/dist/subsume.d.ts +39 -0
  74. package/dist/subsume.js +526 -0
  75. package/dist/subsume.js.map +1 -0
  76. package/dist/trim.d.ts +19 -0
  77. package/dist/trim.js +155 -0
  78. package/dist/trim.js.map +1 -0
  79. package/dist/tsconfig.tsbuildinfo +1 -1
  80. package/dist/type.d.ts +17 -1
  81. package/dist/type.js.map +1 -1
  82. package/dist/unify.d.ts +3 -1
  83. package/dist/unify.js +287 -18
  84. package/dist/unify.js.map +1 -1
  85. package/dist/utility.d.ts +9 -1
  86. package/dist/utility.js +122 -1
  87. package/dist/utility.js.map +1 -1
  88. package/dist/val/AggFuncVal.d.ts +33 -0
  89. package/dist/val/AggFuncVal.js +202 -0
  90. package/dist/val/AggFuncVal.js.map +1 -0
  91. package/dist/val/ArithFuncVal.d.ts +31 -0
  92. package/dist/val/ArithFuncVal.js +62 -0
  93. package/dist/val/ArithFuncVal.js.map +1 -0
  94. package/dist/val/BagVal.d.ts +5 -0
  95. package/dist/val/BagVal.js +96 -5
  96. package/dist/val/BagVal.js.map +1 -1
  97. package/dist/val/CloseFuncVal.js +9 -1
  98. package/dist/val/CloseFuncVal.js.map +1 -1
  99. package/dist/val/ConjunctVal.d.ts +1 -1
  100. package/dist/val/ConjunctVal.js +19 -0
  101. package/dist/val/ConjunctVal.js.map +1 -1
  102. package/dist/val/ConstraintVal.d.ts +48 -1
  103. package/dist/val/ConstraintVal.js +1501 -110
  104. package/dist/val/ConstraintVal.js.map +1 -1
  105. package/dist/val/CopyFuncVal.d.ts +1 -2
  106. package/dist/val/CopyFuncVal.js +7 -0
  107. package/dist/val/CopyFuncVal.js.map +1 -1
  108. package/dist/val/Decimal.d.ts +1 -0
  109. package/dist/val/Decimal.js +13 -0
  110. package/dist/val/Decimal.js.map +1 -1
  111. package/dist/val/DeprecateFuncVal.d.ts +11 -0
  112. package/dist/val/DeprecateFuncVal.js +47 -0
  113. package/dist/val/DeprecateFuncVal.js.map +1 -0
  114. package/dist/val/DisjunctVal.js +130 -21
  115. package/dist/val/DisjunctVal.js.map +1 -1
  116. package/dist/val/EachFuncVal.d.ts +15 -0
  117. package/dist/val/EachFuncVal.js +75 -0
  118. package/dist/val/EachFuncVal.js.map +1 -0
  119. package/dist/val/ExpectVal.d.ts +1 -0
  120. package/dist/val/ExpectVal.js +41 -4
  121. package/dist/val/ExpectVal.js.map +1 -1
  122. package/dist/val/FeatureVal.js +1 -1
  123. package/dist/val/FeatureVal.js.map +1 -1
  124. package/dist/val/FilterFuncVal.d.ts +15 -0
  125. package/dist/val/FilterFuncVal.js +91 -0
  126. package/dist/val/FilterFuncVal.js.map +1 -0
  127. package/dist/val/FuncBaseVal.d.ts +6 -1
  128. package/dist/val/FuncBaseVal.js +178 -2
  129. package/dist/val/FuncBaseVal.js.map +1 -1
  130. package/dist/val/HideFuncVal.js.map +1 -1
  131. package/dist/val/IdFuncVal.d.ts +13 -0
  132. package/dist/val/IdFuncVal.js +54 -0
  133. package/dist/val/IdFuncVal.js.map +1 -0
  134. package/dist/val/JunctionVal.js +7 -1
  135. package/dist/val/JunctionVal.js.map +1 -1
  136. package/dist/val/KeyFuncVal.d.ts +1 -1
  137. package/dist/val/KeyFuncVal.js +38 -30
  138. package/dist/val/KeyFuncVal.js.map +1 -1
  139. package/dist/val/ListVal.js +117 -17
  140. package/dist/val/ListVal.js.map +1 -1
  141. package/dist/val/LowerFuncVal.js.map +1 -1
  142. package/dist/val/MapVal.js +102 -8
  143. package/dist/val/MapVal.js.map +1 -1
  144. package/dist/val/MatchFuncVal.d.ts +15 -0
  145. package/dist/val/MatchFuncVal.js +107 -0
  146. package/dist/val/MatchFuncVal.js.map +1 -0
  147. package/dist/val/MoveFuncVal.js.map +1 -1
  148. package/dist/val/NilVal.js +24 -0
  149. package/dist/val/NilVal.js.map +1 -1
  150. package/dist/val/OpBaseVal.d.ts +1 -1
  151. package/dist/val/OpBaseVal.js +24 -2
  152. package/dist/val/OpBaseVal.js.map +1 -1
  153. package/dist/val/OpenFuncVal.js +4 -1
  154. package/dist/val/OpenFuncVal.js.map +1 -1
  155. package/dist/val/PackFuncVal.d.ts +15 -0
  156. package/dist/val/PackFuncVal.js +108 -0
  157. package/dist/val/PackFuncVal.js.map +1 -0
  158. package/dist/val/PathFuncVal.js.map +1 -1
  159. package/dist/val/PlaceVal.d.ts +13 -0
  160. package/dist/val/PlaceVal.js +131 -0
  161. package/dist/val/PlaceVal.js.map +1 -0
  162. package/dist/val/PlusOpVal.js +11 -2
  163. package/dist/val/PlusOpVal.js.map +1 -1
  164. package/dist/val/PrefFuncVal.js.map +1 -1
  165. package/dist/val/PrefVal.d.ts +2 -2
  166. package/dist/val/PrefVal.js +78 -23
  167. package/dist/val/PrefVal.js.map +1 -1
  168. package/dist/val/RefVal.d.ts +1 -1
  169. package/dist/val/RefVal.js +158 -30
  170. package/dist/val/RefVal.js.map +1 -1
  171. package/dist/val/ReferFuncVal.d.ts +36 -0
  172. package/dist/val/ReferFuncVal.js +303 -0
  173. package/dist/val/ReferFuncVal.js.map +1 -0
  174. package/dist/val/ScalarKindVal.d.ts +1 -2
  175. package/dist/val/ScalarKindVal.js +0 -11
  176. package/dist/val/ScalarKindVal.js.map +1 -1
  177. package/dist/val/TopVal.js.map +1 -1
  178. package/dist/val/TypeFuncVal.js.map +1 -1
  179. package/dist/val/UpperFuncVal.js.map +1 -1
  180. package/dist/val/Val.d.ts +9 -2
  181. package/dist/val/Val.js +150 -4
  182. package/dist/val/Val.js.map +1 -1
  183. package/dist/val/VarVal.js.map +1 -1
  184. package/dist/val/arith.d.ts +6 -0
  185. package/dist/val/arith.js +170 -0
  186. package/dist/val/arith.js.map +1 -0
  187. package/dist/vet.d.ts +45 -0
  188. package/dist/vet.js +776 -0
  189. package/dist/vet.js.map +1 -0
  190. package/dist/walk.d.ts +2 -0
  191. package/dist/walk.js +91 -0
  192. package/dist/walk.js.map +1 -0
  193. package/grammar/aontu.gbnf +130 -0
  194. package/grammar/aontu.lark +113 -0
  195. package/package.json +30 -15
  196. package/skill/SKILL.md +37 -0
  197. package/skill/error-codes.md +62 -0
  198. package/skill/examples.md +99 -0
  199. package/skill/grammar-card.md +57 -0
  200. package/src/agentsmd.ts +135 -0
  201. package/src/aontu.ts +192 -4
  202. package/src/cli.ts +2858 -71
  203. package/src/ctx.ts +81 -0
  204. package/src/diff.ts +196 -0
  205. package/src/err.ts +52 -8
  206. package/src/graph.ts +135 -0
  207. package/src/hcanon.ts +169 -0
  208. package/src/hints.ts +271 -5
  209. package/src/jsonschema.ts +511 -0
  210. package/src/lang.ts +779 -37
  211. package/src/lsp.ts +281 -47
  212. package/src/mcp-server.ts +187 -0
  213. package/src/mcp.ts +993 -0
  214. package/src/mod-tool.ts +679 -0
  215. package/src/mod.ts +344 -0
  216. package/src/patch.ts +624 -0
  217. package/src/provenance.ts +430 -0
  218. package/src/query.ts +379 -0
  219. package/src/reach.ts +184 -0
  220. package/src/relation.ts +395 -0
  221. package/src/report-sarif.ts +137 -0
  222. package/src/site.ts +36 -1
  223. package/src/std.ts +73 -0
  224. package/src/subsume.ts +690 -0
  225. package/src/trim.ts +195 -0
  226. package/src/tsconfig.json +10 -4
  227. package/src/type.ts +51 -2
  228. package/src/unify.ts +311 -16
  229. package/src/utility.ts +139 -1
  230. package/src/val/AggFuncVal.ts +319 -0
  231. package/src/val/ArithFuncVal.ts +108 -0
  232. package/src/val/BagVal.ts +101 -4
  233. package/src/val/CloseFuncVal.ts +9 -1
  234. package/src/val/ConjunctVal.ts +20 -0
  235. package/src/val/ConstraintVal.ts +1699 -116
  236. package/src/val/CopyFuncVal.ts +7 -1
  237. package/src/val/Decimal.ts +15 -0
  238. package/src/val/DeprecateFuncVal.ts +84 -0
  239. package/src/val/DisjunctVal.ts +139 -28
  240. package/src/val/EachFuncVal.ts +133 -0
  241. package/src/val/ExpectVal.ts +43 -6
  242. package/src/val/FeatureVal.ts +1 -1
  243. package/src/val/FilterFuncVal.ts +154 -0
  244. package/src/val/FuncBaseVal.ts +200 -3
  245. package/src/val/HideFuncVal.ts +0 -2
  246. package/src/val/IdFuncVal.ts +91 -0
  247. package/src/val/JunctionVal.ts +7 -1
  248. package/src/val/KeyFuncVal.ts +39 -35
  249. package/src/val/ListVal.ts +125 -18
  250. package/src/val/LowerFuncVal.ts +0 -1
  251. package/src/val/MapVal.ts +110 -8
  252. package/src/val/MatchFuncVal.ts +176 -0
  253. package/src/val/MoveFuncVal.ts +0 -2
  254. package/src/val/NilVal.ts +25 -0
  255. package/src/val/OpBaseVal.ts +26 -3
  256. package/src/val/OpenFuncVal.ts +4 -2
  257. package/src/val/PackFuncVal.ts +175 -0
  258. package/src/val/PathFuncVal.ts +0 -1
  259. package/src/val/PlaceVal.ts +193 -0
  260. package/src/val/PlusOpVal.ts +11 -2
  261. package/src/val/PrefFuncVal.ts +0 -1
  262. package/src/val/PrefVal.ts +79 -36
  263. package/src/val/RefVal.ts +163 -30
  264. package/src/val/ReferFuncVal.ts +387 -0
  265. package/src/val/ScalarKindVal.ts +0 -13
  266. package/src/val/TopVal.ts +0 -1
  267. package/src/val/TypeFuncVal.ts +0 -2
  268. package/src/val/UpperFuncVal.ts +0 -1
  269. package/src/val/Val.ts +213 -3
  270. package/src/val/VarVal.ts +0 -1
  271. package/src/val/arith.ts +316 -0
  272. package/src/vet.ts +992 -0
  273. package/src/walk.ts +99 -0
package/dist/mcp.js ADDED
@@ -0,0 +1,814 @@
1
+ "use strict";
2
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.MCP_PROTOCOL = void 0;
5
+ exports.servedTrust = servedTrust;
6
+ exports.confinedParseFailure = confinedParseFailure;
7
+ exports.toolList = toolList;
8
+ exports.callTool = callTool;
9
+ exports.serverInstructions = serverInstructions;
10
+ exports.handle = handle;
11
+ exports.parseError = parseError;
12
+ // THE MCP TOOL LIBRARY (G7 phase 6,
13
+ // docs/capability-review/g7-machine-access.md; completed to the full
14
+ // CLI verb surface by the use-case review's MCP recommendation,
15
+ // use-cases/SUPPORT.md): the verbs an agent calls, over the Model
16
+ // Context Protocol, as a transport-free library. The split follows
17
+ // the LSP's (docs/lsp.md): this file is the protocol and the tools,
18
+ // ts/src/mcp-server.ts is stdio and nothing else, and the whole thing
19
+ // is testable without a socket.
20
+ //
21
+ // Every tool returns THE SAME JSON CONTRACT THE CLI PRINTS. That is
22
+ // the point of the surface: an agent that has read `aontu vet
23
+ // --format json` output knows what the `vet` tool answers, and a
24
+ // report copied from one to the other is the same object. The tools
25
+ // add no vocabulary of their own.
26
+ //
27
+ // The server evaluates under a CONFINED resolver (G5, docs/trust.md):
28
+ // a caller hands source text, and text that could reach out through
29
+ // `@"..."` is exactly what a server must not run unconfined. By
30
+ // default every include is denied; a server started with
31
+ // `--root <dir>` (ts/src/mcp-server.ts) resolves includes confined
32
+ // below that root instead — the CLI's `--trust root:<dir>` posture —
33
+ // and lets every document argument arrive as a `<name>Path` file
34
+ // under the same root. The package-resolver leg is never enabled
35
+ // here.
36
+ const node_fs_1 = require("node:fs");
37
+ const node_path_1 = require("node:path");
38
+ const aontu_1 = require("./aontu");
39
+ const vet_1 = require("./vet");
40
+ const query_1 = require("./query");
41
+ const diff_1 = require("./diff");
42
+ const hcanon_1 = require("./hcanon");
43
+ const keyorder_1 = require("./keyorder");
44
+ const subsume_1 = require("./subsume");
45
+ const trim_1 = require("./trim");
46
+ const jsonschema_1 = require("./jsonschema");
47
+ const relation_1 = require("./relation");
48
+ const reach_1 = require("./reach");
49
+ const patch_1 = require("./patch");
50
+ exports.MCP_PROTOCOL = '2024-11-05';
51
+ // JSON-RPC's own codes, the three a server this small can raise.
52
+ const PARSE_ERROR = -32700;
53
+ const METHOD_NOT_FOUND = -32601;
54
+ const INVALID_PARAMS = -32602;
55
+ // The trust profile a served evaluation runs under: no includes at
56
+ // all — or, when the server was started with --root, includes
57
+ // realpath-confined below that root (the CLI's `--trust root:<dir>`
58
+ // semantics; docs/trust.md). The package-resolver leg is enabled by
59
+ // neither.
60
+ //
61
+ // The profile is INJECTED into every tool by callTool rather than
62
+ // applied by each tool for itself. That is deliberate: four of the six
63
+ // original tools once called the library with no profile at all, so a
64
+ // served `@"x.js"` was require()d in the server process, while the
65
+ // module header claimed confinement. A tool that must remember to
66
+ // confine itself is a tool that eventually forgets, and the forgetting
67
+ // is silent. With the profile arriving as an argument, a tool cannot
68
+ // run unconfined without visibly discarding it.
69
+ function servedTrust(root) {
70
+ return null == root
71
+ ? { include: 'none' }
72
+ : { include: { root } };
73
+ }
74
+ function served(trust) {
75
+ return new aontu_1.Aontu({ trust });
76
+ }
77
+ // DOES THIS DOCUMENT STAND UP UNDER THE SERVED PROFILE — or the
78
+ // finding that says why not. This is the confinement gate for the
79
+ // engines that take no trust profile, and parse is the whole include
80
+ // story: `@"..."` resolves at parse time (ts/src/lang.ts), so a
81
+ // document whose confined parse is clean either has no includes at
82
+ // all (capability 'none') or resolves every one below the root — and
83
+ // an engine that then re-resolves the same closure under the default
84
+ // profile reads exactly the files the confined parse proved in
85
+ // bounds. A parse that fails for any reason refuses the call: an
86
+ // engine's own answer for a document this profile cannot read is not
87
+ // an answer this server may compute.
88
+ function confinedParseFailure(src, trust, path) {
89
+ const aontu = served(trust);
90
+ const ctx = aontu.ctx({ collect: true });
91
+ aontu.parse(src, null == path ? undefined : { path }, ctx);
92
+ return 0 < ctx.err.length ? (0, query_1.evalFailure)(ctx) : undefined;
93
+ }
94
+ // Confinement is realpath-then-prefix-check, mirroring the include
95
+ // resolver's own rule (ts/src/lang.ts, docs/trust.md): the file's
96
+ // real path must sit below the root's real path, so a symlink inside
97
+ // the root pointing outside it is an escape, not a loophole.
98
+ //
99
+ // A path that does not (fully) exist cannot be realpath'd whole, and
100
+ // falling back to the LEXICAL form compares apples to oranges when the
101
+ // root itself sits behind a symlink -- on macOS a root under /var
102
+ // realpaths to /private/var, so a merely-missing file inside it read
103
+ // as an escape instead of "cannot read" (the CI failure that bought
104
+ // this comment). Realpath the deepest EXISTING ancestor and re-attach
105
+ // the rest, so both sides of the prefix check are in real coordinates.
106
+ function realpathOf(p) {
107
+ try {
108
+ return (0, node_fs_1.realpathSync)(p);
109
+ }
110
+ catch {
111
+ const parent = (0, node_path_1.dirname)(p);
112
+ if (parent === p) {
113
+ return p;
114
+ }
115
+ return (0, node_path_1.join)(realpathOf(parent), (0, node_path_1.basename)(p));
116
+ }
117
+ }
118
+ function outsideRoot(root, full) {
119
+ const rootReal = realpathOf(root);
120
+ const fullReal = realpathOf(full);
121
+ return fullReal !== rootReal && !fullReal.startsWith(rootReal + node_path_1.sep);
122
+ }
123
+ const TOOLS = [
124
+ {
125
+ name: 'vet',
126
+ description: 'Validate a data document against a schema document. Returns the ' +
127
+ 'vet report: verdict (valid | invalid | incomplete | error), and ' +
128
+ 'findings with codes, paths, sites and a repair hint. A site ' +
129
+ 'names the file whose text it excerpts, so its row and column ' +
130
+ 'are safe to edit at even when the schema loads other files.',
131
+ properties: {
132
+ schema: { type: 'string', description: 'The schema document' },
133
+ data: { type: 'string', description: 'The data document' },
134
+ at: { type: 'string', description: 'Validate at this path ($.a.b)' },
135
+ },
136
+ required: ['schema', 'data'],
137
+ docs: ['schema', 'data'],
138
+ run: (a, trust, paths) => (0, vet_1.vet)(str(a.schema), str(a.data), {
139
+ ...(null == a.at ? {} : { at: str(a.at) }),
140
+ schemaPath: paths.schema,
141
+ dataPath: paths.data,
142
+ schemaUrl: paths.schema,
143
+ dataUrl: paths.data,
144
+ trust,
145
+ }),
146
+ },
147
+ {
148
+ name: 'get',
149
+ description: 'Select one node of an evaluated document by path and render it: ' +
150
+ 'generated JSON by default, or the canon, types, keys or ' +
151
+ 'depth-elided view. A view other than json is a valid Aontu ' +
152
+ 'document that subsumes the truth it summarises.',
153
+ properties: {
154
+ src: { type: 'string', description: 'The document' },
155
+ path: { type: 'string', description: 'The path ($.a.b)' },
156
+ view: {
157
+ type: 'string',
158
+ description: 'json (default), canon, types or keys',
159
+ },
160
+ depth: {
161
+ type: 'number',
162
+ description: 'Structure to this depth; deeper nodes render as top',
163
+ },
164
+ },
165
+ required: ['src', 'path'],
166
+ docs: ['src'],
167
+ run: (a, trust, paths) => (0, query_1.get)(str(a.src), str(a.path), {
168
+ view: a.view,
169
+ depth: 'number' === typeof a.depth ? a.depth : undefined,
170
+ path: paths.src,
171
+ trust,
172
+ }),
173
+ },
174
+ {
175
+ name: 'why',
176
+ description: 'Explain the value at a path: every contribution that met there, ' +
177
+ 'in source order, with its role (literal, spread, ref, pref) and ' +
178
+ 'the site it was written at.',
179
+ properties: {
180
+ src: { type: 'string', description: 'The document' },
181
+ path: { type: 'string', description: 'The path ($.a.b)' },
182
+ },
183
+ required: ['src', 'path'],
184
+ docs: ['src'],
185
+ run: (a, trust, paths) => (0, query_1.why)(str(a.src), str(a.path), { path: paths.src, trust }),
186
+ },
187
+ {
188
+ name: 'diff',
189
+ description: 'What changed, at which paths, between two documents. Compares ' +
190
+ 'the hash form, so reformatting is not a change and closing a ' +
191
+ 'map is. Whether a change is BREAKING is the breaking verb\'s ' +
192
+ 'question, not this one.',
193
+ properties: {
194
+ left: { type: 'string', description: 'The earlier document' },
195
+ right: { type: 'string', description: 'The later document' },
196
+ at: { type: 'string', description: 'Compare at this path ($.a.b)' },
197
+ },
198
+ required: ['left', 'right'],
199
+ docs: ['left', 'right'],
200
+ run: (a, trust, paths) => (0, diff_1.diff)(str(a.left), str(a.right), {
201
+ ...(null == a.at ? {} : { at: str(a.at) }),
202
+ leftPath: paths.left,
203
+ rightPath: paths.right,
204
+ trust,
205
+ }),
206
+ },
207
+ {
208
+ name: 'canon',
209
+ description: 'Normalise a document to its canonical form: the deterministic ' +
210
+ 'text two documents that mean the same thing share.',
211
+ properties: {
212
+ src: { type: 'string', description: 'The document' },
213
+ },
214
+ required: ['src'],
215
+ docs: ['src'],
216
+ run: (a, trust, paths) => canonOf(str(a.src), trust, paths.src),
217
+ },
218
+ {
219
+ name: 'summary',
220
+ description: 'A document at a glance: its canon-hash pin, its root keys, and ' +
221
+ 'the shape of its top tier. The first tier of progressive ' +
222
+ 'disclosure — expand by calling get with a path.',
223
+ properties: {
224
+ src: { type: 'string', description: 'The document' },
225
+ },
226
+ required: ['src'],
227
+ docs: ['src'],
228
+ run: (a, trust, paths) => summaryOf(str(a.src), trust, paths.src),
229
+ },
230
+ // The evolution and change verbs (the use-case review's "MCP is a
231
+ // read-only subset" gap, use-cases/09-agent-tools/README.md gap 11).
232
+ {
233
+ name: 'subsume',
234
+ description: 'Does the general document admit every instance the specific ' +
235
+ 'one admits? Returns the subsume report: verdict (subsumes | ' +
236
+ 'does_not_subsume | undecided | error) and compat findings, ' +
237
+ 'each with a witness at the path that narrowed.',
238
+ properties: {
239
+ general: { type: 'string', description: 'The general document' },
240
+ specific: { type: 'string', description: 'The specific document' },
241
+ profile: {
242
+ type: 'string',
243
+ description: 'values, defaults (default) or gen',
244
+ },
245
+ at: { type: 'string', description: 'Compare at this path ($.a.b)' },
246
+ },
247
+ required: ['general', 'specific'],
248
+ docs: ['general', 'specific'],
249
+ check: (a) => null == a.profile || 'values' === a.profile ||
250
+ 'defaults' === a.profile || 'gen' === a.profile
251
+ ? undefined : 'profile needs values, defaults or gen',
252
+ refuse: (_a, finding) => ({ verdict: 'error', findings: [finding] }),
253
+ run: (a, _trust, paths) => (0, subsume_1.subsume)(str(a.general), str(a.specific), {
254
+ ...(null == a.profile ? {} : { profile: a.profile }),
255
+ ...(null == a.at ? {} : { at: str(a.at) }),
256
+ generalUrl: paths.general,
257
+ specificUrl: paths.specific,
258
+ generalPath: paths.general,
259
+ specificPath: paths.specific,
260
+ }),
261
+ },
262
+ {
263
+ name: 'breaking',
264
+ description: 'Is the new version of a document a breaking change against the ' +
265
+ 'old one? Wraps subsume in the CLI\'s policy logic: the mode ' +
266
+ 'argument, else the document\'s own $.aontu_policy.compat, else ' +
267
+ 'backward. Returns verdict (compatible | breaking | undecided | ' +
268
+ 'error), the mode checked, and the findings.',
269
+ properties: {
270
+ old: { type: 'string', description: 'The old (published) version' },
271
+ new: { type: 'string', description: 'The new (proposed) version' },
272
+ mode: {
273
+ type: 'string',
274
+ description: 'backward, forward or full; overrides the ' +
275
+ 'document\'s own $.aontu_policy.compat declaration',
276
+ },
277
+ },
278
+ required: ['old', 'new'],
279
+ docs: ['old', 'new'],
280
+ check: (a) => null == a.mode || 'backward' === a.mode ||
281
+ 'forward' === a.mode || 'full' === a.mode
282
+ ? undefined : 'mode needs backward, forward or full',
283
+ refuse: (a, finding, trust, paths) => ({
284
+ verdict: 'error',
285
+ mode: breakingMode(a, trust, paths),
286
+ findings: [finding],
287
+ }),
288
+ run: (a, trust, paths) => breakingOf(a, trust, paths),
289
+ },
290
+ {
291
+ name: 'set',
292
+ description: 'Change values by appending to an overlay document (or, with ' +
293
+ 'inPlace, rewriting a pinned literal where that is provably ' +
294
+ 'safe). Returns the vet-class report plus the NEW OVERLAY TEXT: ' +
295
+ 'this server never writes files, so the caller owns the write.',
296
+ properties: {
297
+ entry: {
298
+ type: 'string',
299
+ description: 'The entry document (the truth the overlay must ' +
300
+ 'hold against)',
301
+ },
302
+ overlay: {
303
+ type: 'string',
304
+ description: 'The overlay document as it stands (may be empty)',
305
+ },
306
+ assignments: {
307
+ type: 'array',
308
+ items: {
309
+ type: 'object',
310
+ properties: {
311
+ path: { type: 'string', description: 'The path ($.a.b)' },
312
+ value: {
313
+ type: 'string',
314
+ description: 'The value, as Aontu source text',
315
+ },
316
+ },
317
+ required: ['path', 'value'],
318
+ },
319
+ description: 'The assignments to apply, in order',
320
+ },
321
+ inPlace: {
322
+ type: 'boolean',
323
+ description: 'Rewrite a pinned literal where it was written, ' +
324
+ 'where provably safe; otherwise append as usual',
325
+ },
326
+ },
327
+ required: ['entry', 'overlay', 'assignments'],
328
+ docs: ['entry', 'overlay'],
329
+ check: checkAssignments,
330
+ refuse: (a, finding) => setError(str(a.overlay), finding),
331
+ run: (a, trust, paths) => setOf(a, trust, paths),
332
+ },
333
+ {
334
+ name: 'relations',
335
+ description: 'Check the declared relations of a finished model: acyclicity ' +
336
+ 'and inverse consistency over the entity edge set. Returns ' +
337
+ 'verdict (pass | fail | error) and relation findings.',
338
+ properties: {
339
+ source: { type: 'string', description: 'The document' },
340
+ },
341
+ required: ['source'],
342
+ docs: ['source'],
343
+ // The pre-parse finding rides `errors`, exactly where the engine
344
+ // puts its own reason for a document that does not stand up
345
+ // (ts/src/relation.ts, the review's finding F). NOT `findings`:
346
+ // RelationFinding is its own vocabulary (code, relation, at,
347
+ // detail) and a document with no graph has no graph findings.
348
+ refuse: (_a, finding) => ({ verdict: 'error', findings: [], errors: [finding] }),
349
+ run: (a, _trust, paths) => (0, relation_1.relationCheck)(str(a.source), { path: paths.source }),
350
+ },
351
+ {
352
+ name: 'reaches',
353
+ description: 'Ask whether one entity reaches another over the entity graph, ' +
354
+ 'at any remove — the closure question `relations` cannot ask one ' +
355
+ 'edge at a time (blast radius, containment). Returns verdict ' +
356
+ '(reaches | unreachable | error) and, when it reaches, a shortest ' +
357
+ 'path. Transitive, not reflexive: an entity reaches itself only ' +
358
+ 'through a cycle.',
359
+ properties: {
360
+ source: { type: 'string', description: 'The document' },
361
+ from: { type: 'string', description: 'The entity to start at' },
362
+ to: { type: 'string', description: 'The entity to look for' },
363
+ relation: {
364
+ type: 'string',
365
+ description: 'Follow only edges under this relation (optional)',
366
+ },
367
+ },
368
+ required: ['source', 'from', 'to'],
369
+ docs: ['source'],
370
+ refuse: (_a, finding) => ({ verdict: 'error', errors: [finding] }),
371
+ run: (a, _trust, paths) => (0, reach_1.reachCheck)(str(a.source), str(a.from), str(a.to), {
372
+ path: paths.source,
373
+ relation: null == a.relation ? undefined : str(a.relation),
374
+ }),
375
+ },
376
+ {
377
+ name: 'hash',
378
+ description: 'The canon-hash pin of a document: "aon1-" + ' +
379
+ 'base64url(SHA-256(hash form)). Survives reformatting; moves on ' +
380
+ 'any change of meaning. Pass form: true for the hash form text ' +
381
+ 'the pin digests.',
382
+ properties: {
383
+ source: { type: 'string', description: 'The document' },
384
+ form: {
385
+ type: 'boolean',
386
+ description: 'Include the hash form text as well',
387
+ },
388
+ },
389
+ required: ['source'],
390
+ docs: ['source'],
391
+ run: (a, trust, paths) => hashOf(str(a.source), true === a.form, trust, paths.source),
392
+ },
393
+ {
394
+ name: 'trim',
395
+ description: 'Report redundant map entries — entries whose removal leaves ' +
396
+ 'the evaluated result unchanged, the spread-implied case ' +
397
+ 'included — as paths. Report-only, the CLI\'s trim --check. ' +
398
+ 'Returns verdict (clean | redundant | error).',
399
+ properties: {
400
+ source: { type: 'string', description: 'The document' },
401
+ },
402
+ required: ['source'],
403
+ docs: ['source'],
404
+ // The pre-parse finding rides `errors`, exactly where the engine
405
+ // puts its own reason for a document that does not stand up
406
+ // (ts/src/trim.ts, the review's finding F).
407
+ refuse: (_a, finding) => ({ verdict: 'error', redundant: [], errors: [finding] }),
408
+ run: (a, _trust, paths) => (0, trim_1.trimCheck)(str(a.source), { path: paths.source }),
409
+ },
410
+ {
411
+ name: 'jsonschema',
412
+ description: 'Export a document as a JSON Schema (draft 2020-12), and say ' +
413
+ 'what could not be carried. This is the bridge to a ' +
414
+ 'structured-output API, which constrains generation to JSON ' +
415
+ 'Schema and to nothing else -- and to an MCP tool\'s own ' +
416
+ 'inputSchema, which the protocol requires to be one. Returns ' +
417
+ 'verdict (ok | lossy | error), the schema, and a `lossy` list ' +
418
+ 'naming every construct the schema could not say and what it ' +
419
+ 'says instead. A lossy schema admits MORE than the model does, ' +
420
+ 'so vet the result against the model rather than trusting the ' +
421
+ 'schema alone.',
422
+ properties: {
423
+ source: { type: 'string', description: 'The document' },
424
+ at: {
425
+ type: 'string',
426
+ description: 'Export this path of the document ($.a.b)',
427
+ },
428
+ },
429
+ required: ['source'],
430
+ docs: ['source'],
431
+ refuse: (_a, finding) => ({ verdict: 'error', schema: {}, lossy: [], errors: [finding] }),
432
+ run: (a, _trust, paths) => (0, jsonschema_1.jsonSchema)(str(a.source), {
433
+ at: null == a.at ? undefined : str(a.at), path: paths.source,
434
+ }),
435
+ },
436
+ ];
437
+ function str(v) {
438
+ return 'string' === typeof v ? v : '';
439
+ }
440
+ function canonOf(src, trust, path) {
441
+ const aontu = served(trust);
442
+ const ctx = aontu.ctx({ collect: true });
443
+ const v = aontu.unify(src, null == path ? undefined : { path }, ctx);
444
+ if (0 < ctx.err.length) {
445
+ return { ok: false, canon: '', findings: [(0, query_1.evalFailure)(ctx)] };
446
+ }
447
+ return { ok: true, canon: v.canon, findings: [] };
448
+ }
449
+ function summaryOf(src, trust, path) {
450
+ const aontu = served(trust);
451
+ const ctx = aontu.ctx({ collect: true });
452
+ const v = aontu.unify(src, null == path ? undefined : { path }, ctx);
453
+ if (0 < ctx.err.length) {
454
+ return {
455
+ ok: false, hash: '', keys: [], shape: '', findings: [(0, query_1.evalFailure)(ctx)],
456
+ };
457
+ }
458
+ const keys = true === v.isMap ? Object.keys(v.peg).sort(keyorder_1.cmpCodePoint) : [];
459
+ return {
460
+ ok: true,
461
+ hash: (0, hcanon_1.canonHash)(v),
462
+ keys,
463
+ // The top tier only: every key, with its subtree elided to `top`.
464
+ shape: (0, query_1.get)(src, '$', { view: 'types', depth: 2, path, trust }).out,
465
+ findings: [],
466
+ };
467
+ }
468
+ // The pin, computed under the served profile — this engine is the
469
+ // evaluation itself, so the profile rides in directly and no pre-parse
470
+ // is needed. The error answer follows canonOf's shape; the CLI prints
471
+ // nothing but a message there (ts/src/cli.ts runHash), so this shape
472
+ // is the served superset of it.
473
+ function hashOf(src, form, trust, path) {
474
+ const aontu = served(trust);
475
+ const ctx = aontu.ctx({ collect: true });
476
+ const v = aontu.unify(src, null == path ? undefined : { path }, ctx);
477
+ if (0 < ctx.err.length || null == v || true === v.isNil) {
478
+ return { ok: false, hash: '', findings: [(0, query_1.evalFailure)(ctx)] };
479
+ }
480
+ return {
481
+ ok: true,
482
+ hash: (0, hcanon_1.canonHash)(v),
483
+ ...(form ? { form: (0, hcanon_1.hcanon)(v) } : {}),
484
+ findings: [],
485
+ };
486
+ }
487
+ // Verdict aggregation: an error anywhere makes the run an error;
488
+ // otherwise a witness anywhere makes it breaking; otherwise an open
489
+ // question anywhere leaves it undecided.
490
+ const BREAKING_RANK = {
491
+ subsumes: 0,
492
+ undecided: 1,
493
+ does_not_subsume: 2,
494
+ error: 3,
495
+ };
496
+ const BREAKING_VERDICT = {
497
+ subsumes: 'compatible',
498
+ does_not_subsume: 'breaking',
499
+ undecided: 'undecided',
500
+ error: 'error',
501
+ };
502
+ // The document's own compatibility declaration: `$.aontu_policy.compat`,
503
+ // a disjunction whose default is the declared mode. Undefined when the
504
+ // key is absent or does not spell a mode. The one departure from the
505
+ // cli.ts original: the read runs CONFINED, because here the document
506
+ // came from a caller.
507
+ function policyCompatOf(newSrc, trust, path) {
508
+ const aontu = served(trust);
509
+ const ctx = aontu.ctx({ collect: true });
510
+ const v = aontu.unify(newSrc, null == path ? undefined : { path }, ctx);
511
+ if (0 < ctx.err.length || true === v?.isNil) {
512
+ return undefined;
513
+ }
514
+ let compat = v?.peg?.aontu_policy?.peg?.compat;
515
+ if (null == compat) {
516
+ return undefined;
517
+ }
518
+ if (true === compat.isDisjunct && Array.isArray(compat.peg)) {
519
+ compat = compat.peg.find((m) => true === m?.isPref) ?? compat.peg[0];
520
+ }
521
+ if (true === compat.isPref) {
522
+ compat = compat.peg;
523
+ }
524
+ const m = true === compat?.isString ? compat.peg : undefined;
525
+ return 'backward' === m || 'forward' === m || 'full' === m || 'none' === m
526
+ ? m : undefined;
527
+ }
528
+ // The declared mode: the mode argument overrides the document's own
529
+ // policy; neither means backward (v1-valid documents stay valid).
530
+ function breakingMode(a, trust, paths) {
531
+ return a.mode ?? policyCompatOf(str(a.new), trust, paths.new) ?? 'backward';
532
+ }
533
+ function breakingOf(a, trust, paths) {
534
+ const mode = breakingMode(a, trust, paths);
535
+ if ('none' === mode) {
536
+ // The document declares no compatibility promise: nothing to check.
537
+ return { verdict: 'compatible', mode, findings: [] };
538
+ }
539
+ const sides = {
540
+ old: { src: str(a.old), url: paths.old ?? 'old', path: paths.old },
541
+ new: { src: str(a.new), url: paths.new ?? 'new', path: paths.new },
542
+ };
543
+ // backward: the NEW document is the general side — every old
544
+ // instance must still be admitted. forward: the old one is.
545
+ const checks = [];
546
+ if ('backward' === mode || 'full' === mode) {
547
+ checks.push({ general: sides.new, specific: sides.old });
548
+ }
549
+ if ('forward' === mode || 'full' === mode) {
550
+ checks.push({ general: sides.old, specific: sides.new });
551
+ }
552
+ let worst = 'subsumes';
553
+ const findings = [];
554
+ for (const check of checks) {
555
+ const report = (0, subsume_1.subsume)(check.general.src, check.specific.src, {
556
+ generalUrl: check.general.url,
557
+ specificUrl: check.specific.url,
558
+ generalPath: check.general.path,
559
+ specificPath: check.specific.path,
560
+ });
561
+ if (BREAKING_RANK[worst] < BREAKING_RANK[report.verdict]) {
562
+ worst = report.verdict;
563
+ }
564
+ findings.push(...report.findings);
565
+ }
566
+ return { verdict: BREAKING_VERDICT[worst], mode, findings };
567
+ }
568
+ // ---------------------------------------------------------------------
569
+ // The set tool: the CLI's `set` verb minus the filesystem — the patch
570
+ // engine (ts/src/patch.ts) already answers with the new overlay text,
571
+ // and the caller owns the write.
572
+ // The assignments arrive structured ({path, value}) rather than as the
573
+ // CLI's `<path>=<value>` spelling, and are re-joined for the engine's
574
+ // parseAssignment — so the path must not smuggle a `=` that would move
575
+ // the split.
576
+ function checkAssignments(a) {
577
+ if (0 === a.assignments.length) {
578
+ return 'assignments needs at least one {path, value}';
579
+ }
580
+ for (const x of a.assignments) {
581
+ if ('string' !== typeof x?.path || '' === x.path.trim() ||
582
+ 'string' !== typeof x?.value || '' === x.value.trim()) {
583
+ return 'each assignment needs a path and a value, both strings';
584
+ }
585
+ if (x.path.includes('=')) {
586
+ return `assignment path may not contain "=": ${x.path}`;
587
+ }
588
+ }
589
+ return undefined;
590
+ }
591
+ function setError(overlay, finding) {
592
+ return {
593
+ overlay,
594
+ appended: [],
595
+ replaced: [],
596
+ verdict: 'error',
597
+ findings: [finding],
598
+ };
599
+ }
600
+ function setOf(a, trust, paths) {
601
+ // THE ASSIGNMENT VALUES ARE DOCUMENTS TOO: each one is appended (or
602
+ // spliced) into the overlay and evaluated there by the engine's
603
+ // final vet, so a value that smuggles an include — or a newline and
604
+ // then an include — gets the same confined pre-parse as the
605
+ // documents themselves, wrapped exactly as the engine's own
606
+ // spanValue wraps a fragment (ts/src/patch.ts).
607
+ for (const x of a.assignments) {
608
+ const denied = confinedParseFailure('v: ' + x.value, trust, paths.overlay);
609
+ if (null != denied) {
610
+ return setError(str(a.overlay), denied);
611
+ }
612
+ }
613
+ return (0, patch_1.patch)(str(a.entry), str(a.overlay), a.assignments.map((x) => x.path + '=' + x.value), {
614
+ entryPath: paths.entry,
615
+ overlayPath: paths.overlay,
616
+ inPlace: true === a.inPlace,
617
+ });
618
+ }
619
+ // ---------------------------------------------------------------------
620
+ // The tool list as MCP spells it: a name, a description, and a JSON
621
+ // Schema for the arguments. With a served root, every document
622
+ // property gains its `<name>Path` file alternative — and comes OFF the
623
+ // `required` list, because JSON Schema's `required` cannot say "one of
624
+ // the two"; callTool's own argument check still refuses a call that
625
+ // carries neither.
626
+ function toolList(root) {
627
+ return TOOLS.map((t) => {
628
+ const properties = { ...t.properties };
629
+ let required = t.required;
630
+ if (null != root && null != t.docs) {
631
+ for (const doc of t.docs) {
632
+ properties[doc + 'Path'] = {
633
+ type: 'string',
634
+ description: `File below the server's --root, read as ` +
635
+ `\`${doc}\`; an alternative to inline \`${doc}\` text`,
636
+ };
637
+ }
638
+ required = t.required.filter((r) => !t.docs.includes(r));
639
+ }
640
+ return {
641
+ name: t.name,
642
+ description: t.description,
643
+ inputSchema: {
644
+ type: 'object',
645
+ properties,
646
+ required,
647
+ },
648
+ };
649
+ });
650
+ }
651
+ function refusal(text) {
652
+ return { content: [{ type: 'text', text }], isError: true };
653
+ }
654
+ // One tool call. A tool that REFUSES (an invalid document, a path that
655
+ // names nothing, a document the served profile cannot read) is not a
656
+ // protocol error: it answers with its own report and `isError` false,
657
+ // because the report IS the answer the agent asked for. `isError` is
658
+ // reserved for a call that could not be made at all — an unknown tool,
659
+ // a missing or malformed argument, a file argument the server cannot
660
+ // serve.
661
+ // `opts.tools` is injectable for the same reason the watch loop's
662
+ // waiter is: the catch below is defensive code no document reaches --
663
+ // every verb answers with a report rather than throwing -- and code
664
+ // the suite cannot execute is code the ADR-002 floor cannot hold.
665
+ function callTool(name, args, opts) {
666
+ const root = opts?.root;
667
+ const tools = opts?.tools ?? TOOLS;
668
+ const tool = tools.find((t) => t.name === name);
669
+ if (null == tool) {
670
+ return refusal(`no such tool: ${name}`);
671
+ }
672
+ const a = { ...(args ?? {}) };
673
+ const paths = {};
674
+ // THE FILE ALTERNATIVES (--root). A document that did not arrive as
675
+ // inline text may arrive as a `<name>Path` file — served only when
676
+ // the operator granted a root at startup, confined below it by the
677
+ // same realpath rule the include resolver applies, and recorded in
678
+ // `paths` so the engine resolves the file's own relative includes
679
+ // from its directory (the CLI's rule for a named file). Inline text
680
+ // wins when a caller sends both.
681
+ for (const doc of tool.docs ?? []) {
682
+ if ('string' === typeof a[doc]) {
683
+ continue;
684
+ }
685
+ const rel = a[doc + 'Path'];
686
+ if ('string' !== typeof rel) {
687
+ continue;
688
+ }
689
+ if (null == root) {
690
+ return refusal(`tool ${name}: ${doc}Path needs a server started with ` +
691
+ `--root <dir>; pass ${doc} as document text instead`);
692
+ }
693
+ const full = (0, node_path_1.resolve)(root, rel);
694
+ if (outsideRoot(root, full)) {
695
+ return refusal(`tool ${name}: ${doc}Path escapes the server root: ${rel}`);
696
+ }
697
+ try {
698
+ a[doc] = (0, node_fs_1.readFileSync)(full, 'utf8');
699
+ }
700
+ catch (e) {
701
+ return refusal(`tool ${name}: cannot read ${doc}Path ${rel}: ${e?.message ?? e}`);
702
+ }
703
+ paths[doc] = full;
704
+ }
705
+ for (const req of tool.required) {
706
+ const kind = tool.properties[req]?.type;
707
+ const okv = 'array' === kind
708
+ ? Array.isArray(a[req])
709
+ : 'string' === typeof a[req];
710
+ if (!okv) {
711
+ const alt = null != root && (tool.docs ?? []).includes(req)
712
+ ? ` (or ${req}Path)` : '';
713
+ return refusal(`tool ${name} needs ${'array' === kind ? 'an array' : 'a string'}` +
714
+ ` argument: ${req}${alt}`);
715
+ }
716
+ }
717
+ const bad = null == tool.check ? undefined : tool.check(a);
718
+ if (null != bad) {
719
+ return refusal(`tool ${name}: ${bad}`);
720
+ }
721
+ // The served profile is supplied HERE, once, for every tool.
722
+ // A tool never chooses its own confinement.
723
+ const trust = servedTrust(root);
724
+ let out;
725
+ try {
726
+ // The confined pre-parse, for the engines that cannot take the
727
+ // profile themselves (see ToolDef.refuse).
728
+ if (null != tool.refuse) {
729
+ for (const doc of tool.docs ?? []) {
730
+ if ('string' !== typeof a[doc]) {
731
+ continue;
732
+ }
733
+ const denied = confinedParseFailure(a[doc], trust, paths[doc]);
734
+ if (null != denied) {
735
+ out = tool.refuse(a, denied, trust, paths);
736
+ break;
737
+ }
738
+ }
739
+ }
740
+ if (undefined === out) {
741
+ out = tool.run(a, trust, paths);
742
+ }
743
+ }
744
+ catch (e) {
745
+ // A tool that throws is a call that could not be made, which is
746
+ // what isError means -- and it must not take the server process
747
+ // down with it: a stdio server serves one client for a whole
748
+ // session, so an unhandled throw on one document loses every
749
+ // later call too.
750
+ return refusal(`tool ${name} failed: ${e?.message ?? e}`);
751
+ }
752
+ return {
753
+ content: [{ type: 'text', text: JSON.stringify(out, null, 2) }],
754
+ isError: false,
755
+ };
756
+ }
757
+ // What a connecting client is told once, at the handshake: which
758
+ // confinement mode this server is in. The --root capability is a
759
+ // startup grant, not a per-call negotiation, so initialize is where a
760
+ // caller learns whether `<name>Path` arguments are served.
761
+ function serverInstructions(root) {
762
+ return 'Aontu tools take document TEXT arguments and answer the ' +
763
+ 'same JSON reports the aontu CLI prints with --format json. ' +
764
+ (null == root
765
+ ? 'Evaluation is confined: includes (@"...") are denied, and ' +
766
+ 'file-path arguments (schemaPath, srcPath, sourcePath, ...) ' +
767
+ 'are refused. Start the server with --root <dir> to serve ' +
768
+ 'both, confined below that directory.'
769
+ : `This server was started with --root ${root}: every document ` +
770
+ 'argument also accepts a <name>Path alternative naming a file ' +
771
+ 'below that root, and includes (@"...") resolve confined to it.');
772
+ }
773
+ // Handle one JSON-RPC message. Returns undefined for a NOTIFICATION
774
+ // (no id): MCP sends `notifications/initialized`, and answering a
775
+ // notification is a protocol error in the other direction.
776
+ function handle(msg, version, root) {
777
+ const id = msg.id ?? null;
778
+ if (null == msg.id) {
779
+ return undefined;
780
+ }
781
+ switch (msg.method) {
782
+ case 'initialize':
783
+ return ok(id, {
784
+ protocolVersion: exports.MCP_PROTOCOL,
785
+ capabilities: { tools: {} },
786
+ serverInfo: { name: 'aontu', version },
787
+ instructions: serverInstructions(root),
788
+ });
789
+ case 'ping':
790
+ return ok(id, {});
791
+ case 'tools/list':
792
+ return ok(id, { tools: toolList(root) });
793
+ case 'tools/call': {
794
+ const name = msg.params?.name;
795
+ if ('string' !== typeof name) {
796
+ return err(id, INVALID_PARAMS, 'tools/call needs a tool name');
797
+ }
798
+ return ok(id, callTool(name, msg.params?.arguments ?? {}, { root }));
799
+ }
800
+ default:
801
+ return err(id, METHOD_NOT_FOUND, `no such method: ${msg.method}`);
802
+ }
803
+ }
804
+ // A message that did not decode at all.
805
+ function parseError() {
806
+ return err(null, PARSE_ERROR, 'invalid JSON');
807
+ }
808
+ function ok(id, result) {
809
+ return { jsonrpc: '2.0', id, result };
810
+ }
811
+ function err(id, code, message) {
812
+ return { jsonrpc: '2.0', id, error: { code, message } };
813
+ }
814
+ //# sourceMappingURL=mcp.js.map