@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.
- package/README.md +365 -0
- package/package.json +22 -0
- package/src/attrref.test.ts +148 -0
- package/src/attrref.ts +50 -0
- package/src/barrel.test.ts +157 -0
- package/src/barrel.ts +101 -0
- package/src/bench.test.ts +227 -0
- package/src/build.test.ts +437 -0
- package/src/build.ts +425 -0
- package/src/builder.test.ts +312 -0
- package/src/builder.ts +56 -0
- package/src/child-project.ts +44 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/README.md +26 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +16 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +8 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +7 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/examples/getting-started/.gitkeep +0 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/justfile +26 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +29 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +8 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +74 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/naming.ts +33 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +45 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/coverage.ts +11 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/generated/.gitkeep +0 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/generator.ts +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/parser.ts +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/index.ts +9 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/index.ts +1 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/sample.ts +18 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/completions.ts +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/hover.ts +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +110 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/serializer.ts +24 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/fetch.ts +21 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/parse.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate-cli.ts +4 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate.ts +24 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/tsconfig.json +10 -0
- package/src/cli/commands/__fixtures__/sample-rule.ts +11 -0
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +222 -0
- package/src/cli/commands/build.test.ts +149 -0
- package/src/cli/commands/build.ts +344 -0
- package/src/cli/commands/diff.test.ts +148 -0
- package/src/cli/commands/diff.ts +221 -0
- package/src/cli/commands/doctor.test.ts +239 -0
- package/src/cli/commands/doctor.ts +224 -0
- package/src/cli/commands/import.test.ts +379 -0
- package/src/cli/commands/import.ts +335 -0
- package/src/cli/commands/init-lexicon.test.ts +297 -0
- package/src/cli/commands/init-lexicon.ts +993 -0
- package/src/cli/commands/init.test.ts +317 -0
- package/src/cli/commands/init.ts +505 -0
- package/src/cli/commands/licenses.ts +165 -0
- package/src/cli/commands/lint.test.ts +332 -0
- package/src/cli/commands/lint.ts +408 -0
- package/src/cli/commands/list.test.ts +100 -0
- package/src/cli/commands/list.ts +108 -0
- package/src/cli/commands/update.test.ts +38 -0
- package/src/cli/commands/update.ts +207 -0
- package/src/cli/conflict-check.test.ts +255 -0
- package/src/cli/conflict-check.ts +89 -0
- package/src/cli/debug.ts +8 -0
- package/src/cli/format.test.ts +140 -0
- package/src/cli/format.ts +133 -0
- package/src/cli/handlers/build.ts +58 -0
- package/src/cli/handlers/dev.ts +38 -0
- package/src/cli/handlers/init.ts +46 -0
- package/src/cli/handlers/lint.ts +36 -0
- package/src/cli/handlers/misc.ts +57 -0
- package/src/cli/handlers/serve.ts +26 -0
- package/src/cli/index.ts +3 -0
- package/src/cli/lsp/capabilities.ts +46 -0
- package/src/cli/lsp/diagnostics.ts +52 -0
- package/src/cli/lsp/server.test.ts +618 -0
- package/src/cli/lsp/server.ts +393 -0
- package/src/cli/main.test.ts +257 -0
- package/src/cli/main.ts +224 -0
- package/src/cli/mcp/resources/context.ts +59 -0
- package/src/cli/mcp/server.test.ts +747 -0
- package/src/cli/mcp/server.ts +402 -0
- package/src/cli/mcp/tools/build.ts +117 -0
- package/src/cli/mcp/tools/import.ts +48 -0
- package/src/cli/mcp/tools/lint.ts +45 -0
- package/src/cli/plugins.test.ts +31 -0
- package/src/cli/plugins.ts +94 -0
- package/src/cli/registry.ts +73 -0
- package/src/cli/reporters/stylish.test.ts +282 -0
- package/src/cli/reporters/stylish.ts +186 -0
- package/src/cli/watch.test.ts +81 -0
- package/src/cli/watch.ts +101 -0
- package/src/codegen/case.test.ts +30 -0
- package/src/codegen/case.ts +11 -0
- package/src/codegen/coverage.ts +167 -0
- package/src/codegen/docs.ts +634 -0
- package/src/codegen/fetch.test.ts +119 -0
- package/src/codegen/fetch.ts +261 -0
- package/src/codegen/generate-registry.test.ts +118 -0
- package/src/codegen/generate-registry.ts +107 -0
- package/src/codegen/generate-runtime-index.test.ts +81 -0
- package/src/codegen/generate-runtime-index.ts +99 -0
- package/src/codegen/generate-typescript.test.ts +146 -0
- package/src/codegen/generate-typescript.ts +161 -0
- package/src/codegen/generate.ts +206 -0
- package/src/codegen/json-patch.test.ts +113 -0
- package/src/codegen/json-patch.ts +151 -0
- package/src/codegen/json-schema.test.ts +196 -0
- package/src/codegen/json-schema.ts +209 -0
- package/src/codegen/naming.ts +201 -0
- package/src/codegen/package.ts +161 -0
- package/src/codegen/rollback.test.ts +92 -0
- package/src/codegen/rollback.ts +115 -0
- package/src/codegen/topo-sort.test.ts +69 -0
- package/src/codegen/topo-sort.ts +46 -0
- package/src/codegen/typecheck.test.ts +37 -0
- package/src/codegen/typecheck.ts +74 -0
- package/src/codegen/validate.test.ts +86 -0
- package/src/codegen/validate.ts +143 -0
- package/src/composite.test.ts +426 -0
- package/src/composite.ts +243 -0
- package/src/config.test.ts +91 -0
- package/src/config.ts +87 -0
- package/src/declarable.test.ts +160 -0
- package/src/declarable.ts +47 -0
- package/src/detectLexicon.test.ts +236 -0
- package/src/detectLexicon.ts +37 -0
- package/src/discovery/cache.test.ts +78 -0
- package/src/discovery/cache.ts +86 -0
- package/src/discovery/collect.test.ts +269 -0
- package/src/discovery/collect.ts +51 -0
- package/src/discovery/cycles.test.ts +238 -0
- package/src/discovery/cycles.ts +107 -0
- package/src/discovery/files.test.ts +154 -0
- package/src/discovery/files.ts +61 -0
- package/src/discovery/graph.test.ts +476 -0
- package/src/discovery/graph.ts +150 -0
- package/src/discovery/import.test.ts +199 -0
- package/src/discovery/import.ts +20 -0
- package/src/discovery/index.test.ts +272 -0
- package/src/discovery/index.ts +132 -0
- package/src/discovery/resolve.test.ts +267 -0
- package/src/discovery/resolve.ts +54 -0
- package/src/errors.test.ts +138 -0
- package/src/errors.ts +86 -0
- package/src/import/base-parser.test.ts +67 -0
- package/src/import/base-parser.ts +48 -0
- package/src/import/generator.ts +21 -0
- package/src/import/ir-utils.test.ts +103 -0
- package/src/import/ir-utils.ts +87 -0
- package/src/import/parser.ts +41 -0
- package/src/index.ts +60 -0
- package/src/intrinsic-interpolation.test.ts +91 -0
- package/src/intrinsic-interpolation.ts +89 -0
- package/src/intrinsic.test.ts +69 -0
- package/src/intrinsic.ts +43 -0
- package/src/lexicon-integrity.test.ts +94 -0
- package/src/lexicon-integrity.ts +69 -0
- package/src/lexicon-manifest.test.ts +101 -0
- package/src/lexicon-manifest.ts +71 -0
- package/src/lexicon-output.test.ts +182 -0
- package/src/lexicon-output.ts +82 -0
- package/src/lexicon-schema.test.ts +239 -0
- package/src/lexicon-schema.ts +144 -0
- package/src/lexicon.ts +212 -0
- package/src/lint/config-overrides.test.ts +254 -0
- package/src/lint/config.test.ts +644 -0
- package/src/lint/config.ts +375 -0
- package/src/lint/declarative.test.ts +256 -0
- package/src/lint/declarative.ts +187 -0
- package/src/lint/engine.test.ts +465 -0
- package/src/lint/engine.ts +172 -0
- package/src/lint/named-checks.test.ts +37 -0
- package/src/lint/named-checks.ts +33 -0
- package/src/lint/parser.test.ts +129 -0
- package/src/lint/parser.ts +42 -0
- package/src/lint/post-synth.test.ts +113 -0
- package/src/lint/post-synth.ts +76 -0
- package/src/lint/presets/relaxed.json +19 -0
- package/src/lint/presets/strict.json +19 -0
- package/src/lint/rule-loader.test.ts +67 -0
- package/src/lint/rule-loader.ts +67 -0
- package/src/lint/rule-options.test.ts +141 -0
- package/src/lint/rule.test.ts +196 -0
- package/src/lint/rule.ts +98 -0
- package/src/lint/rules/barrel-import-style.test.ts +80 -0
- package/src/lint/rules/barrel-import-style.ts +59 -0
- package/src/lint/rules/composite-scope.ts +55 -0
- package/src/lint/rules/cor017-composite-name-match.test.ts +107 -0
- package/src/lint/rules/cor017-composite-name-match.ts +108 -0
- package/src/lint/rules/cor018-composite-prefer-lexicon-type.test.ts +172 -0
- package/src/lint/rules/cor018-composite-prefer-lexicon-type.ts +167 -0
- package/src/lint/rules/declarable-naming-convention.test.ts +69 -0
- package/src/lint/rules/declarable-naming-convention.ts +70 -0
- package/src/lint/rules/enforce-barrel-import.test.ts +169 -0
- package/src/lint/rules/enforce-barrel-import.ts +81 -0
- package/src/lint/rules/enforce-barrel-ref.test.ts +114 -0
- package/src/lint/rules/enforce-barrel-ref.ts +75 -0
- package/src/lint/rules/evl001-non-literal-expression.test.ts +158 -0
- package/src/lint/rules/evl001-non-literal-expression.ts +149 -0
- package/src/lint/rules/evl002-control-flow-resource.test.ts +110 -0
- package/src/lint/rules/evl002-control-flow-resource.ts +61 -0
- package/src/lint/rules/evl003-dynamic-property-access.test.ts +63 -0
- package/src/lint/rules/evl003-dynamic-property-access.ts +41 -0
- package/src/lint/rules/evl004-spread-non-const.test.ts +130 -0
- package/src/lint/rules/evl004-spread-non-const.ts +111 -0
- package/src/lint/rules/evl005-resource-block-body.test.ts +59 -0
- package/src/lint/rules/evl005-resource-block-body.ts +49 -0
- package/src/lint/rules/evl006-barrel-usage.test.ts +63 -0
- package/src/lint/rules/evl006-barrel-usage.ts +95 -0
- package/src/lint/rules/evl007-invalid-siblings.test.ts +87 -0
- package/src/lint/rules/evl007-invalid-siblings.ts +139 -0
- package/src/lint/rules/evl008-unresolvable-barrel-ref.test.ts +118 -0
- package/src/lint/rules/evl008-unresolvable-barrel-ref.ts +140 -0
- package/src/lint/rules/evl009-composite-no-constant.test.ts +162 -0
- package/src/lint/rules/evl009-composite-no-constant.ts +171 -0
- package/src/lint/rules/evl010-composite-no-transform.test.ts +121 -0
- package/src/lint/rules/evl010-composite-no-transform.ts +69 -0
- package/src/lint/rules/export-required.test.ts +213 -0
- package/src/lint/rules/export-required.ts +158 -0
- package/src/lint/rules/file-declarable-limit.test.ts +148 -0
- package/src/lint/rules/file-declarable-limit.ts +96 -0
- package/src/lint/rules/flat-declarations.test.ts +210 -0
- package/src/lint/rules/flat-declarations.ts +70 -0
- package/src/lint/rules/index.ts +99 -0
- package/src/lint/rules/no-cyclic-declarable-ref.test.ts +135 -0
- package/src/lint/rules/no-cyclic-declarable-ref.ts +178 -0
- package/src/lint/rules/no-redundant-type-import.test.ts +129 -0
- package/src/lint/rules/no-redundant-type-import.ts +85 -0
- package/src/lint/rules/no-redundant-value-cast.test.ts +51 -0
- package/src/lint/rules/no-redundant-value-cast.ts +46 -0
- package/src/lint/rules/no-string-ref.test.ts +100 -0
- package/src/lint/rules/no-string-ref.ts +66 -0
- package/src/lint/rules/no-unused-declarable-import.test.ts +74 -0
- package/src/lint/rules/no-unused-declarable-import.ts +103 -0
- package/src/lint/rules/no-unused-declarable.test.ts +134 -0
- package/src/lint/rules/no-unused-declarable.ts +118 -0
- package/src/lint/rules/prefer-namespace-import.test.ts +102 -0
- package/src/lint/rules/prefer-namespace-import.ts +63 -0
- package/src/lint/rules/single-concern-file.test.ts +156 -0
- package/src/lint/rules/single-concern-file.ts +98 -0
- package/src/lint/rules/stale-barrel-types.ts +60 -0
- package/src/lint/selectors.test.ts +113 -0
- package/src/lint/selectors.ts +188 -0
- package/src/lsp/lexicon-providers.ts +191 -0
- package/src/lsp/types.ts +79 -0
- package/src/mcp/types.ts +22 -0
- package/src/project/scan.test.ts +178 -0
- package/src/project/scan.ts +182 -0
- package/src/project/sync.test.ts +87 -0
- package/src/project/sync.ts +46 -0
- package/src/project-validation.test.ts +64 -0
- package/src/project-validation.ts +79 -0
- package/src/pseudo-parameter.test.ts +39 -0
- package/src/pseudo-parameter.ts +47 -0
- package/src/runtime.ts +68 -0
- package/src/serializer-walker.test.ts +124 -0
- package/src/serializer-walker.ts +83 -0
- package/src/serializer.ts +42 -0
- package/src/sort.test.ts +290 -0
- package/src/sort.ts +58 -0
- package/src/stack-output.ts +82 -0
- package/src/types.test.ts +307 -0
- package/src/types.ts +46 -0
- package/src/utils.test.ts +195 -0
- package/src/utils.ts +46 -0
- package/src/validation.test.ts +308 -0
- 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
|
+
});
|