@intentius/chant 0.0.1

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 (271) hide show
  1. package/README.md +365 -0
  2. package/package.json +22 -0
  3. package/src/attrref.test.ts +148 -0
  4. package/src/attrref.ts +50 -0
  5. package/src/barrel.test.ts +157 -0
  6. package/src/barrel.ts +101 -0
  7. package/src/bench.test.ts +227 -0
  8. package/src/build.test.ts +437 -0
  9. package/src/build.ts +425 -0
  10. package/src/builder.test.ts +312 -0
  11. package/src/builder.ts +56 -0
  12. package/src/child-project.ts +44 -0
  13. package/src/cli/commands/__fixtures__/init-lexicon-output/README.md +26 -0
  14. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +14 -0
  15. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +16 -0
  16. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +8 -0
  17. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +7 -0
  18. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +10 -0
  19. package/src/cli/commands/__fixtures__/init-lexicon-output/examples/getting-started/.gitkeep +0 -0
  20. package/src/cli/commands/__fixtures__/init-lexicon-output/justfile +26 -0
  21. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +29 -0
  22. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs.ts +25 -0
  23. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +8 -0
  24. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +74 -0
  25. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/naming.ts +33 -0
  26. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +25 -0
  27. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +45 -0
  28. package/src/cli/commands/__fixtures__/init-lexicon-output/src/coverage.ts +11 -0
  29. package/src/cli/commands/__fixtures__/init-lexicon-output/src/generated/.gitkeep +0 -0
  30. package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/generator.ts +10 -0
  31. package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/parser.ts +10 -0
  32. package/src/cli/commands/__fixtures__/init-lexicon-output/src/index.ts +9 -0
  33. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/index.ts +1 -0
  34. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/sample.ts +18 -0
  35. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/completions.ts +14 -0
  36. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/hover.ts +14 -0
  37. package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +110 -0
  38. package/src/cli/commands/__fixtures__/init-lexicon-output/src/serializer.ts +24 -0
  39. package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/fetch.ts +21 -0
  40. package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/parse.ts +25 -0
  41. package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate-cli.ts +4 -0
  42. package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate.ts +24 -0
  43. package/src/cli/commands/__fixtures__/init-lexicon-output/tsconfig.json +10 -0
  44. package/src/cli/commands/__fixtures__/sample-rule.ts +11 -0
  45. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +222 -0
  46. package/src/cli/commands/build.test.ts +149 -0
  47. package/src/cli/commands/build.ts +344 -0
  48. package/src/cli/commands/diff.test.ts +148 -0
  49. package/src/cli/commands/diff.ts +221 -0
  50. package/src/cli/commands/doctor.test.ts +239 -0
  51. package/src/cli/commands/doctor.ts +224 -0
  52. package/src/cli/commands/import.test.ts +379 -0
  53. package/src/cli/commands/import.ts +335 -0
  54. package/src/cli/commands/init-lexicon.test.ts +297 -0
  55. package/src/cli/commands/init-lexicon.ts +993 -0
  56. package/src/cli/commands/init.test.ts +317 -0
  57. package/src/cli/commands/init.ts +505 -0
  58. package/src/cli/commands/licenses.ts +165 -0
  59. package/src/cli/commands/lint.test.ts +332 -0
  60. package/src/cli/commands/lint.ts +408 -0
  61. package/src/cli/commands/list.test.ts +100 -0
  62. package/src/cli/commands/list.ts +108 -0
  63. package/src/cli/commands/update.test.ts +38 -0
  64. package/src/cli/commands/update.ts +207 -0
  65. package/src/cli/conflict-check.test.ts +255 -0
  66. package/src/cli/conflict-check.ts +89 -0
  67. package/src/cli/debug.ts +8 -0
  68. package/src/cli/format.test.ts +140 -0
  69. package/src/cli/format.ts +133 -0
  70. package/src/cli/handlers/build.ts +58 -0
  71. package/src/cli/handlers/dev.ts +38 -0
  72. package/src/cli/handlers/init.ts +46 -0
  73. package/src/cli/handlers/lint.ts +36 -0
  74. package/src/cli/handlers/misc.ts +57 -0
  75. package/src/cli/handlers/serve.ts +26 -0
  76. package/src/cli/index.ts +3 -0
  77. package/src/cli/lsp/capabilities.ts +46 -0
  78. package/src/cli/lsp/diagnostics.ts +52 -0
  79. package/src/cli/lsp/server.test.ts +618 -0
  80. package/src/cli/lsp/server.ts +393 -0
  81. package/src/cli/main.test.ts +257 -0
  82. package/src/cli/main.ts +224 -0
  83. package/src/cli/mcp/resources/context.ts +59 -0
  84. package/src/cli/mcp/server.test.ts +747 -0
  85. package/src/cli/mcp/server.ts +402 -0
  86. package/src/cli/mcp/tools/build.ts +117 -0
  87. package/src/cli/mcp/tools/import.ts +48 -0
  88. package/src/cli/mcp/tools/lint.ts +45 -0
  89. package/src/cli/plugins.test.ts +31 -0
  90. package/src/cli/plugins.ts +94 -0
  91. package/src/cli/registry.ts +73 -0
  92. package/src/cli/reporters/stylish.test.ts +282 -0
  93. package/src/cli/reporters/stylish.ts +186 -0
  94. package/src/cli/watch.test.ts +81 -0
  95. package/src/cli/watch.ts +101 -0
  96. package/src/codegen/case.test.ts +30 -0
  97. package/src/codegen/case.ts +11 -0
  98. package/src/codegen/coverage.ts +167 -0
  99. package/src/codegen/docs.ts +634 -0
  100. package/src/codegen/fetch.test.ts +119 -0
  101. package/src/codegen/fetch.ts +261 -0
  102. package/src/codegen/generate-registry.test.ts +118 -0
  103. package/src/codegen/generate-registry.ts +107 -0
  104. package/src/codegen/generate-runtime-index.test.ts +81 -0
  105. package/src/codegen/generate-runtime-index.ts +99 -0
  106. package/src/codegen/generate-typescript.test.ts +146 -0
  107. package/src/codegen/generate-typescript.ts +161 -0
  108. package/src/codegen/generate.ts +206 -0
  109. package/src/codegen/json-patch.test.ts +113 -0
  110. package/src/codegen/json-patch.ts +151 -0
  111. package/src/codegen/json-schema.test.ts +196 -0
  112. package/src/codegen/json-schema.ts +209 -0
  113. package/src/codegen/naming.ts +201 -0
  114. package/src/codegen/package.ts +161 -0
  115. package/src/codegen/rollback.test.ts +92 -0
  116. package/src/codegen/rollback.ts +115 -0
  117. package/src/codegen/topo-sort.test.ts +69 -0
  118. package/src/codegen/topo-sort.ts +46 -0
  119. package/src/codegen/typecheck.test.ts +37 -0
  120. package/src/codegen/typecheck.ts +74 -0
  121. package/src/codegen/validate.test.ts +86 -0
  122. package/src/codegen/validate.ts +143 -0
  123. package/src/composite.test.ts +426 -0
  124. package/src/composite.ts +243 -0
  125. package/src/config.test.ts +91 -0
  126. package/src/config.ts +87 -0
  127. package/src/declarable.test.ts +160 -0
  128. package/src/declarable.ts +47 -0
  129. package/src/detectLexicon.test.ts +236 -0
  130. package/src/detectLexicon.ts +37 -0
  131. package/src/discovery/cache.test.ts +78 -0
  132. package/src/discovery/cache.ts +86 -0
  133. package/src/discovery/collect.test.ts +269 -0
  134. package/src/discovery/collect.ts +51 -0
  135. package/src/discovery/cycles.test.ts +238 -0
  136. package/src/discovery/cycles.ts +107 -0
  137. package/src/discovery/files.test.ts +154 -0
  138. package/src/discovery/files.ts +61 -0
  139. package/src/discovery/graph.test.ts +476 -0
  140. package/src/discovery/graph.ts +150 -0
  141. package/src/discovery/import.test.ts +199 -0
  142. package/src/discovery/import.ts +20 -0
  143. package/src/discovery/index.test.ts +272 -0
  144. package/src/discovery/index.ts +132 -0
  145. package/src/discovery/resolve.test.ts +267 -0
  146. package/src/discovery/resolve.ts +54 -0
  147. package/src/errors.test.ts +138 -0
  148. package/src/errors.ts +86 -0
  149. package/src/import/base-parser.test.ts +67 -0
  150. package/src/import/base-parser.ts +48 -0
  151. package/src/import/generator.ts +21 -0
  152. package/src/import/ir-utils.test.ts +103 -0
  153. package/src/import/ir-utils.ts +87 -0
  154. package/src/import/parser.ts +41 -0
  155. package/src/index.ts +60 -0
  156. package/src/intrinsic-interpolation.test.ts +91 -0
  157. package/src/intrinsic-interpolation.ts +89 -0
  158. package/src/intrinsic.test.ts +69 -0
  159. package/src/intrinsic.ts +43 -0
  160. package/src/lexicon-integrity.test.ts +94 -0
  161. package/src/lexicon-integrity.ts +69 -0
  162. package/src/lexicon-manifest.test.ts +101 -0
  163. package/src/lexicon-manifest.ts +71 -0
  164. package/src/lexicon-output.test.ts +182 -0
  165. package/src/lexicon-output.ts +82 -0
  166. package/src/lexicon-schema.test.ts +239 -0
  167. package/src/lexicon-schema.ts +144 -0
  168. package/src/lexicon.ts +212 -0
  169. package/src/lint/config-overrides.test.ts +254 -0
  170. package/src/lint/config.test.ts +644 -0
  171. package/src/lint/config.ts +375 -0
  172. package/src/lint/declarative.test.ts +256 -0
  173. package/src/lint/declarative.ts +187 -0
  174. package/src/lint/engine.test.ts +465 -0
  175. package/src/lint/engine.ts +172 -0
  176. package/src/lint/named-checks.test.ts +37 -0
  177. package/src/lint/named-checks.ts +33 -0
  178. package/src/lint/parser.test.ts +129 -0
  179. package/src/lint/parser.ts +42 -0
  180. package/src/lint/post-synth.test.ts +113 -0
  181. package/src/lint/post-synth.ts +76 -0
  182. package/src/lint/presets/relaxed.json +19 -0
  183. package/src/lint/presets/strict.json +19 -0
  184. package/src/lint/rule-loader.test.ts +67 -0
  185. package/src/lint/rule-loader.ts +67 -0
  186. package/src/lint/rule-options.test.ts +141 -0
  187. package/src/lint/rule.test.ts +196 -0
  188. package/src/lint/rule.ts +98 -0
  189. package/src/lint/rules/barrel-import-style.test.ts +80 -0
  190. package/src/lint/rules/barrel-import-style.ts +59 -0
  191. package/src/lint/rules/composite-scope.ts +55 -0
  192. package/src/lint/rules/cor017-composite-name-match.test.ts +107 -0
  193. package/src/lint/rules/cor017-composite-name-match.ts +108 -0
  194. package/src/lint/rules/cor018-composite-prefer-lexicon-type.test.ts +172 -0
  195. package/src/lint/rules/cor018-composite-prefer-lexicon-type.ts +167 -0
  196. package/src/lint/rules/declarable-naming-convention.test.ts +69 -0
  197. package/src/lint/rules/declarable-naming-convention.ts +70 -0
  198. package/src/lint/rules/enforce-barrel-import.test.ts +169 -0
  199. package/src/lint/rules/enforce-barrel-import.ts +81 -0
  200. package/src/lint/rules/enforce-barrel-ref.test.ts +114 -0
  201. package/src/lint/rules/enforce-barrel-ref.ts +75 -0
  202. package/src/lint/rules/evl001-non-literal-expression.test.ts +158 -0
  203. package/src/lint/rules/evl001-non-literal-expression.ts +149 -0
  204. package/src/lint/rules/evl002-control-flow-resource.test.ts +110 -0
  205. package/src/lint/rules/evl002-control-flow-resource.ts +61 -0
  206. package/src/lint/rules/evl003-dynamic-property-access.test.ts +63 -0
  207. package/src/lint/rules/evl003-dynamic-property-access.ts +41 -0
  208. package/src/lint/rules/evl004-spread-non-const.test.ts +130 -0
  209. package/src/lint/rules/evl004-spread-non-const.ts +111 -0
  210. package/src/lint/rules/evl005-resource-block-body.test.ts +59 -0
  211. package/src/lint/rules/evl005-resource-block-body.ts +49 -0
  212. package/src/lint/rules/evl006-barrel-usage.test.ts +63 -0
  213. package/src/lint/rules/evl006-barrel-usage.ts +95 -0
  214. package/src/lint/rules/evl007-invalid-siblings.test.ts +87 -0
  215. package/src/lint/rules/evl007-invalid-siblings.ts +139 -0
  216. package/src/lint/rules/evl008-unresolvable-barrel-ref.test.ts +118 -0
  217. package/src/lint/rules/evl008-unresolvable-barrel-ref.ts +140 -0
  218. package/src/lint/rules/evl009-composite-no-constant.test.ts +162 -0
  219. package/src/lint/rules/evl009-composite-no-constant.ts +171 -0
  220. package/src/lint/rules/evl010-composite-no-transform.test.ts +121 -0
  221. package/src/lint/rules/evl010-composite-no-transform.ts +69 -0
  222. package/src/lint/rules/export-required.test.ts +213 -0
  223. package/src/lint/rules/export-required.ts +158 -0
  224. package/src/lint/rules/file-declarable-limit.test.ts +148 -0
  225. package/src/lint/rules/file-declarable-limit.ts +96 -0
  226. package/src/lint/rules/flat-declarations.test.ts +210 -0
  227. package/src/lint/rules/flat-declarations.ts +70 -0
  228. package/src/lint/rules/index.ts +99 -0
  229. package/src/lint/rules/no-cyclic-declarable-ref.test.ts +135 -0
  230. package/src/lint/rules/no-cyclic-declarable-ref.ts +178 -0
  231. package/src/lint/rules/no-redundant-type-import.test.ts +129 -0
  232. package/src/lint/rules/no-redundant-type-import.ts +85 -0
  233. package/src/lint/rules/no-redundant-value-cast.test.ts +51 -0
  234. package/src/lint/rules/no-redundant-value-cast.ts +46 -0
  235. package/src/lint/rules/no-string-ref.test.ts +100 -0
  236. package/src/lint/rules/no-string-ref.ts +66 -0
  237. package/src/lint/rules/no-unused-declarable-import.test.ts +74 -0
  238. package/src/lint/rules/no-unused-declarable-import.ts +103 -0
  239. package/src/lint/rules/no-unused-declarable.test.ts +134 -0
  240. package/src/lint/rules/no-unused-declarable.ts +118 -0
  241. package/src/lint/rules/prefer-namespace-import.test.ts +102 -0
  242. package/src/lint/rules/prefer-namespace-import.ts +63 -0
  243. package/src/lint/rules/single-concern-file.test.ts +156 -0
  244. package/src/lint/rules/single-concern-file.ts +98 -0
  245. package/src/lint/rules/stale-barrel-types.ts +60 -0
  246. package/src/lint/selectors.test.ts +113 -0
  247. package/src/lint/selectors.ts +188 -0
  248. package/src/lsp/lexicon-providers.ts +191 -0
  249. package/src/lsp/types.ts +79 -0
  250. package/src/mcp/types.ts +22 -0
  251. package/src/project/scan.test.ts +178 -0
  252. package/src/project/scan.ts +182 -0
  253. package/src/project/sync.test.ts +87 -0
  254. package/src/project/sync.ts +46 -0
  255. package/src/project-validation.test.ts +64 -0
  256. package/src/project-validation.ts +79 -0
  257. package/src/pseudo-parameter.test.ts +39 -0
  258. package/src/pseudo-parameter.ts +47 -0
  259. package/src/runtime.ts +68 -0
  260. package/src/serializer-walker.test.ts +124 -0
  261. package/src/serializer-walker.ts +83 -0
  262. package/src/serializer.ts +42 -0
  263. package/src/sort.test.ts +290 -0
  264. package/src/sort.ts +58 -0
  265. package/src/stack-output.ts +82 -0
  266. package/src/types.test.ts +307 -0
  267. package/src/types.ts +46 -0
  268. package/src/utils.test.ts +195 -0
  269. package/src/utils.ts +46 -0
  270. package/src/validation.test.ts +308 -0
  271. package/src/validation.ts +50 -0
@@ -0,0 +1,402 @@
1
+ import { createInterface } from "readline";
2
+ import { resolve } from "node:path";
3
+ import { buildTool, handleBuild } from "./tools/build";
4
+ import { lintTool, handleLint } from "./tools/lint";
5
+ import { importTool, handleImport } from "./tools/import";
6
+ import { getContext } from "./resources/context";
7
+ import type { LexiconPlugin } from "../../lexicon";
8
+ import type { McpToolContribution, McpResourceContribution } from "../../mcp/types";
9
+
10
+ /**
11
+ * MCP message types
12
+ */
13
+ interface McpRequest {
14
+ jsonrpc: "2.0";
15
+ id: string | number;
16
+ method: string;
17
+ params?: Record<string, unknown>;
18
+ }
19
+
20
+ interface McpResponse {
21
+ jsonrpc: "2.0";
22
+ id: string | number;
23
+ result?: unknown;
24
+ error?: {
25
+ code: number;
26
+ message: string;
27
+ data?: unknown;
28
+ };
29
+ }
30
+
31
+ interface McpNotification {
32
+ jsonrpc: "2.0";
33
+ method: string;
34
+ params?: Record<string, unknown>;
35
+ }
36
+
37
+ /**
38
+ * Tool definition for MCP
39
+ */
40
+ interface ToolDefinition {
41
+ name: string;
42
+ description: string;
43
+ inputSchema: {
44
+ type: "object";
45
+ properties: Record<string, unknown>;
46
+ required?: string[];
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Resource definition for MCP
52
+ */
53
+ interface ResourceDefinition {
54
+ uri: string;
55
+ name: string;
56
+ description: string;
57
+ mimeType?: string;
58
+ }
59
+
60
+ /**
61
+ * MCP Server implementation
62
+ */
63
+ export class McpServer {
64
+ private tools: Map<string, ToolDefinition> = new Map();
65
+ private toolHandlers: Map<string, (params: Record<string, unknown>) => Promise<unknown>> = new Map();
66
+ private pluginResources: Map<string, { definition: ResourceDefinition; handler: () => Promise<string> }> = new Map();
67
+
68
+ constructor(plugins?: LexiconPlugin[]) {
69
+ // Register core tools
70
+ this.registerTool(buildTool, handleBuild);
71
+ this.registerTool(lintTool, handleLint);
72
+ this.registerTool(importTool, handleImport);
73
+
74
+ // Register plugin contributions
75
+ if (plugins) {
76
+ for (const plugin of plugins) {
77
+ this.registerPluginTools(plugin);
78
+ this.registerPluginResources(plugin);
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Register tools contributed by a plugin, namespaced as `lexicon:toolName`
85
+ */
86
+ private registerPluginTools(plugin: LexiconPlugin): void {
87
+ const tools = plugin.mcpTools?.() ?? [];
88
+ for (const tool of tools) {
89
+ const namespacedName = `${plugin.name}:${tool.name}`;
90
+ this.registerTool(
91
+ {
92
+ name: namespacedName,
93
+ description: tool.description,
94
+ inputSchema: tool.inputSchema,
95
+ },
96
+ tool.handler,
97
+ );
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Register resources contributed by a plugin, namespaced as `chant://lexicon/uri`
103
+ */
104
+ private registerPluginResources(plugin: LexiconPlugin): void {
105
+ const resources = plugin.mcpResources?.() ?? [];
106
+ for (const resource of resources) {
107
+ const namespacedUri = `chant://${plugin.name}/${resource.uri}`;
108
+ this.pluginResources.set(namespacedUri, {
109
+ definition: {
110
+ uri: namespacedUri,
111
+ name: resource.name,
112
+ description: resource.description,
113
+ mimeType: resource.mimeType,
114
+ },
115
+ handler: resource.handler,
116
+ });
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Register a tool with its handler
122
+ */
123
+ private registerTool(
124
+ definition: ToolDefinition,
125
+ handler: (params: Record<string, unknown>) => Promise<unknown>
126
+ ): void {
127
+ this.tools.set(definition.name, definition);
128
+ this.toolHandlers.set(definition.name, handler);
129
+ }
130
+
131
+ /**
132
+ * Handle incoming MCP request
133
+ */
134
+ async handleRequest(request: McpRequest): Promise<McpResponse> {
135
+ try {
136
+ const result = await this.dispatch(request.method, request.params ?? {});
137
+ return {
138
+ jsonrpc: "2.0",
139
+ id: request.id,
140
+ result,
141
+ };
142
+ } catch (error) {
143
+ return {
144
+ jsonrpc: "2.0",
145
+ id: request.id,
146
+ error: {
147
+ code: -32603,
148
+ message: error instanceof Error ? error.message : String(error),
149
+ },
150
+ };
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Dispatch request to appropriate handler
156
+ */
157
+ private async dispatch(method: string, params: Record<string, unknown>): Promise<unknown> {
158
+ switch (method) {
159
+ case "initialize":
160
+ return this.handleInitialize(params);
161
+
162
+ case "tools/list":
163
+ return this.handleToolsList();
164
+
165
+ case "tools/call":
166
+ return this.handleToolsCall(params);
167
+
168
+ case "resources/list":
169
+ return this.handleResourcesList();
170
+
171
+ case "resources/read":
172
+ return this.handleResourcesRead(params);
173
+
174
+ default:
175
+ throw new Error(`Unknown method: ${method}`);
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Handle initialize request
181
+ */
182
+ private handleInitialize(params: Record<string, unknown>): unknown {
183
+ return {
184
+ protocolVersion: "2024-11-05",
185
+ capabilities: {
186
+ tools: {},
187
+ resources: {},
188
+ },
189
+ serverInfo: {
190
+ name: "chant",
191
+ version: "0.1.0",
192
+ },
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Handle tools/list request
198
+ */
199
+ private handleToolsList(): unknown {
200
+ return {
201
+ tools: Array.from(this.tools.values()),
202
+ };
203
+ }
204
+
205
+ /**
206
+ * Handle tools/call request
207
+ */
208
+ private async handleToolsCall(params: Record<string, unknown>): Promise<unknown> {
209
+ const name = params.name as string;
210
+ const toolParams = (params.arguments ?? {}) as Record<string, unknown>;
211
+
212
+ const handler = this.toolHandlers.get(name);
213
+ if (!handler) {
214
+ return {
215
+ content: [
216
+ {
217
+ type: "text",
218
+ text: `Error: Unknown tool: ${name}`,
219
+ },
220
+ ],
221
+ isError: true,
222
+ };
223
+ }
224
+
225
+ try {
226
+ const result = await handler(toolParams);
227
+ return {
228
+ content: [
229
+ {
230
+ type: "text",
231
+ text: typeof result === "string" ? result : JSON.stringify(result, null, 2),
232
+ },
233
+ ],
234
+ };
235
+ } catch (error) {
236
+ return {
237
+ content: [
238
+ {
239
+ type: "text",
240
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
241
+ },
242
+ ],
243
+ isError: true,
244
+ };
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Handle resources/list request — merges core + plugin resources
250
+ */
251
+ private handleResourcesList(): unknown {
252
+ const resources: ResourceDefinition[] = [
253
+ {
254
+ uri: "chant://context",
255
+ name: "chant Context",
256
+ description: "Lexicon-specific instructions and patterns for chant development",
257
+ mimeType: "text/markdown",
258
+ },
259
+ {
260
+ uri: "chant://examples/list",
261
+ name: "Examples List",
262
+ description: "List of available chant examples",
263
+ mimeType: "application/json",
264
+ },
265
+ ];
266
+
267
+ // Merge plugin resources
268
+ for (const { definition } of this.pluginResources.values()) {
269
+ resources.push(definition);
270
+ }
271
+
272
+ return { resources };
273
+ }
274
+
275
+ /**
276
+ * Collect example resources from plugins whose URI contains "examples/"
277
+ */
278
+ private collectExamples(): Array<{ name: string; description: string }> {
279
+ const examples: Array<{ name: string; description: string }> = [];
280
+ for (const [uri, { definition }] of this.pluginResources.entries()) {
281
+ if (uri.includes("/examples/")) {
282
+ const name = uri.replace(/^chant:\/\/[^/]+\/examples\//, "");
283
+ examples.push({ name, description: definition.description });
284
+ }
285
+ }
286
+ return examples;
287
+ }
288
+
289
+ /**
290
+ * Handle resources/read request — checks plugin resources after core
291
+ */
292
+ private async handleResourcesRead(params: Record<string, unknown>): Promise<unknown> {
293
+ const uri = params.uri as string;
294
+
295
+ if (uri === "chant://context") {
296
+ return {
297
+ contents: [
298
+ {
299
+ uri,
300
+ mimeType: "text/markdown",
301
+ text: getContext(),
302
+ },
303
+ ],
304
+ };
305
+ }
306
+
307
+ if (uri === "chant://examples/list") {
308
+ return {
309
+ contents: [
310
+ {
311
+ uri,
312
+ mimeType: "application/json",
313
+ text: JSON.stringify(this.collectExamples()),
314
+ },
315
+ ],
316
+ };
317
+ }
318
+
319
+ if (uri.startsWith("chant://examples/")) {
320
+ // Look up example in plugin resources
321
+ const name = uri.replace("chant://examples/", "");
322
+ for (const [pluginUri, pluginResource] of this.pluginResources.entries()) {
323
+ if (pluginUri.endsWith(`/examples/${name}`)) {
324
+ const text = await pluginResource.handler();
325
+ return {
326
+ contents: [
327
+ {
328
+ uri,
329
+ mimeType: pluginResource.definition.mimeType ?? "text/typescript",
330
+ text,
331
+ },
332
+ ],
333
+ };
334
+ }
335
+ }
336
+ throw new Error(`Example not found: ${name}`);
337
+ }
338
+
339
+ // Check plugin resources
340
+ const pluginResource = this.pluginResources.get(uri);
341
+ if (pluginResource) {
342
+ const text = await pluginResource.handler();
343
+ return {
344
+ contents: [
345
+ {
346
+ uri,
347
+ mimeType: pluginResource.definition.mimeType ?? "text/plain",
348
+ text,
349
+ },
350
+ ],
351
+ };
352
+ }
353
+
354
+ throw new Error(`Unknown resource: ${uri}`);
355
+ }
356
+
357
+ /**
358
+ * Start the MCP server on stdio
359
+ */
360
+ async start(): Promise<void> {
361
+ const rl = createInterface({
362
+ input: process.stdin,
363
+ output: process.stdout,
364
+ terminal: false,
365
+ });
366
+
367
+ rl.on("line", async (line) => {
368
+ try {
369
+ const request = JSON.parse(line) as McpRequest;
370
+ const response = await this.handleRequest(request);
371
+ console.log(JSON.stringify(response));
372
+ } catch (error) {
373
+ const errorResponse: McpResponse = {
374
+ jsonrpc: "2.0",
375
+ id: 0,
376
+ error: {
377
+ code: -32700,
378
+ message: "Parse error",
379
+ },
380
+ };
381
+ console.log(JSON.stringify(errorResponse));
382
+ }
383
+ });
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Start MCP server, loading plugins from the project
389
+ */
390
+ export async function startMcpServer(): Promise<void> {
391
+ let plugins: LexiconPlugin[] = [];
392
+ try {
393
+ const { resolveProjectLexicons, loadPlugins } = await import("../plugins");
394
+ const lexiconNames = await resolveProjectLexicons(resolve("."));
395
+ plugins = await loadPlugins(lexiconNames);
396
+ } catch {
397
+ // Start without plugins if resolution fails
398
+ }
399
+
400
+ const server = new McpServer(plugins);
401
+ server.start();
402
+ }
@@ -0,0 +1,117 @@
1
+ import { build } from "../../../build";
2
+ import { resolve } from "path";
3
+
4
+ // Default lexicon serializer for MCP builds (fallback when no lexicon is detected)
5
+ const defaultLexicon = {
6
+ name: "default",
7
+ rulePrefix: "DF",
8
+ serialize(entities: Map<string, unknown>): string {
9
+ const output: Record<string, unknown> = {};
10
+ for (const [name, entity] of entities) {
11
+ output[name] = entity;
12
+ }
13
+ return JSON.stringify(output, null, 2);
14
+ },
15
+ };
16
+
17
+ /**
18
+ * Build tool definition for MCP
19
+ */
20
+ export const buildTool = {
21
+ name: "build",
22
+ description: "Build chant infrastructure code and generate output for the target lexicon",
23
+ inputSchema: {
24
+ type: "object" as const,
25
+ properties: {
26
+ path: {
27
+ type: "string",
28
+ description: "Path to the infrastructure directory or file to build",
29
+ },
30
+ output: {
31
+ type: "string",
32
+ description: "Output file path (optional, returns in response if not specified)",
33
+ },
34
+ format: {
35
+ type: "string",
36
+ enum: ["json", "yaml"],
37
+ description: "Output format (default: json)",
38
+ },
39
+ },
40
+ required: ["path"],
41
+ },
42
+ };
43
+
44
+ /**
45
+ * Handle build tool invocation
46
+ */
47
+ export async function handleBuild(params: Record<string, unknown>): Promise<unknown> {
48
+ const path = params.path as string;
49
+ const format = (params.format as "json" | "yaml") ?? "json";
50
+
51
+ const infraPath = resolve(path);
52
+ const result = await build(infraPath, [defaultLexicon]);
53
+
54
+ if (result.errors.length > 0) {
55
+ throw new Error(result.errors.map((e) => e.message).join("; "));
56
+ }
57
+
58
+ // Combine all lexicon outputs
59
+ const combined: Record<string, unknown> = {};
60
+ for (const [lexiconName, lexiconOutput] of result.outputs) {
61
+ combined[lexiconName] = JSON.parse(lexiconOutput);
62
+ }
63
+
64
+ let output = JSON.stringify(combined, null, 2);
65
+ if (format === "yaml") {
66
+ // Basic YAML conversion
67
+ output = jsonToYaml(JSON.parse(output));
68
+ }
69
+
70
+ return {
71
+ success: true,
72
+ resourceCount: result.entities.size,
73
+ output,
74
+ format,
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Simple JSON to YAML converter
80
+ */
81
+ function jsonToYaml(obj: unknown, indent = 0): string {
82
+ const spaces = " ".repeat(indent);
83
+
84
+ if (obj === null) return "null";
85
+ if (obj === undefined) return "~";
86
+ if (typeof obj === "boolean") return obj ? "true" : "false";
87
+ if (typeof obj === "number") return String(obj);
88
+ if (typeof obj === "string") {
89
+ if (obj.includes("\n") || obj.includes(":") || obj.includes("#")) {
90
+ return `"${obj.replace(/"/g, '\\"')}"`;
91
+ }
92
+ return obj;
93
+ }
94
+
95
+ if (Array.isArray(obj)) {
96
+ if (obj.length === 0) return "[]";
97
+ return obj
98
+ .map((item) => `${spaces}- ${jsonToYaml(item, indent + 1).trimStart()}`)
99
+ .join("\n");
100
+ }
101
+
102
+ if (typeof obj === "object") {
103
+ const entries = Object.entries(obj);
104
+ if (entries.length === 0) return "{}";
105
+ return entries
106
+ .map(([key, value]) => {
107
+ const yamlValue = jsonToYaml(value, indent + 1);
108
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
109
+ return `${spaces}${key}:\n${yamlValue}`;
110
+ }
111
+ return `${spaces}${key}: ${yamlValue.trimStart()}`;
112
+ })
113
+ .join("\n");
114
+ }
115
+
116
+ return String(obj);
117
+ }
@@ -0,0 +1,48 @@
1
+ import { importCommand } from "../../commands/import";
2
+
3
+ /**
4
+ * Import tool definition for MCP
5
+ */
6
+ export const importTool = {
7
+ name: "import",
8
+ description: "Import external templates and convert them to chant TypeScript",
9
+ inputSchema: {
10
+ type: "object" as const,
11
+ properties: {
12
+ source: {
13
+ type: "string",
14
+ description: "Path to the template file to import",
15
+ },
16
+ output: {
17
+ type: "string",
18
+ description: "Output directory (default: ./infra/)",
19
+ },
20
+ },
21
+ required: ["source"],
22
+ },
23
+ };
24
+
25
+ /**
26
+ * Handle import tool invocation
27
+ */
28
+ export async function handleImport(params: Record<string, unknown>): Promise<unknown> {
29
+ const source = params.source as string;
30
+ const output = params.output as string | undefined;
31
+
32
+ const result = await importCommand({
33
+ templatePath: source,
34
+ output,
35
+ force: true,
36
+ });
37
+
38
+ if (!result.success) {
39
+ throw new Error(result.error ?? "Import failed");
40
+ }
41
+
42
+ return {
43
+ success: true,
44
+ lexicon: result.lexicon,
45
+ generatedFiles: result.generatedFiles,
46
+ warnings: result.warnings,
47
+ };
48
+ }
@@ -0,0 +1,45 @@
1
+ import { lintCommand } from "../../commands/lint";
2
+
3
+ /**
4
+ * Lint tool definition for MCP
5
+ */
6
+ export const lintTool = {
7
+ name: "lint",
8
+ description: "Lint chant infrastructure code and report issues",
9
+ inputSchema: {
10
+ type: "object" as const,
11
+ properties: {
12
+ path: {
13
+ type: "string",
14
+ description: "Path to the infrastructure directory or file to lint",
15
+ },
16
+ fix: {
17
+ type: "boolean",
18
+ description: "Automatically fix issues where possible",
19
+ },
20
+ },
21
+ required: ["path"],
22
+ },
23
+ };
24
+
25
+ /**
26
+ * Handle lint tool invocation
27
+ */
28
+ export async function handleLint(params: Record<string, unknown>): Promise<unknown> {
29
+ const path = params.path as string;
30
+ const fix = (params.fix as boolean) ?? false;
31
+
32
+ const result = await lintCommand({
33
+ path,
34
+ fix,
35
+ format: "json",
36
+ });
37
+
38
+ return {
39
+ success: result.success,
40
+ errorCount: result.errorCount,
41
+ warningCount: result.warningCount,
42
+ diagnostics: result.diagnostics,
43
+ output: result.output,
44
+ };
45
+ }
@@ -0,0 +1,31 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { loadPlugin, loadPlugins } from "./plugins";
3
+ import { isLexiconPlugin } from "../lexicon";
4
+
5
+ describe("loadPlugin", () => {
6
+ test("loads aws plugin with full LexiconPlugin interface", async () => {
7
+ const plugin = await loadPlugin("aws");
8
+ expect(isLexiconPlugin(plugin)).toBe(true);
9
+ expect(plugin.name).toBe("aws");
10
+ expect(plugin.serializer.name).toBe("aws");
11
+ expect(typeof plugin.lintRules).toBe("function");
12
+ expect(typeof plugin.detectTemplate).toBe("function");
13
+ });
14
+
15
+ test("throws for unknown lexicon package", async () => {
16
+ await expect(loadPlugin("nonexistent")).rejects.toThrow();
17
+ });
18
+ });
19
+
20
+ describe("loadPlugins", () => {
21
+ test("loads multiple plugins", async () => {
22
+ const plugins = await loadPlugins(["aws"]);
23
+ expect(plugins).toHaveLength(1);
24
+ expect(plugins[0].name).toBe("aws");
25
+ });
26
+
27
+ test("returns empty array for no serializers", async () => {
28
+ const plugins = await loadPlugins([]);
29
+ expect(plugins).toHaveLength(0);
30
+ });
31
+ });