@ng-solid/mcp 22.0.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 (47) hide show
  1. package/GUIDE.md +292 -0
  2. package/README.md +159 -0
  3. package/data/design-system.json +24204 -0
  4. package/data/icons.json +1 -0
  5. package/data/skills/ns-solid-components/SKILL.md +140 -0
  6. package/data/skills/ns-solid-forms/SKILL.md +183 -0
  7. package/data/skills/ns-solid-icons/SKILL.md +95 -0
  8. package/data/skills/ns-solid-install/SKILL.md +120 -0
  9. package/data/skills/ns-solid-style-contract/SKILL.md +84 -0
  10. package/data/skills/ns-solid-theming/SKILL.md +123 -0
  11. package/index.d.ts +3 -0
  12. package/index.js +38 -0
  13. package/lib/data.d.ts +26 -0
  14. package/lib/data.js +51 -0
  15. package/lib/install-skills.d.ts +1 -0
  16. package/lib/install-skills.js +159 -0
  17. package/lib/preset-validator.d.ts +44 -0
  18. package/lib/preset-validator.js +172 -0
  19. package/lib/prompts.d.ts +2 -0
  20. package/lib/prompts.js +173 -0
  21. package/lib/resources.d.ts +2 -0
  22. package/lib/resources.js +328 -0
  23. package/lib/server.d.ts +3 -0
  24. package/lib/server.js +31 -0
  25. package/lib/style-contract.d.ts +37 -0
  26. package/lib/style-contract.js +167 -0
  27. package/lib/template-parser.d.ts +29 -0
  28. package/lib/template-parser.js +230 -0
  29. package/lib/tools.components.d.ts +44 -0
  30. package/lib/tools.components.js +836 -0
  31. package/lib/tools.d.ts +9 -0
  32. package/lib/tools.guides.d.ts +15 -0
  33. package/lib/tools.guides.js +103 -0
  34. package/lib/tools.icons.d.ts +7 -0
  35. package/lib/tools.icons.js +106 -0
  36. package/lib/tools.js +18 -0
  37. package/lib/tools.tokens.d.ts +7 -0
  38. package/lib/tools.tokens.js +278 -0
  39. package/lib/types.d.ts +349 -0
  40. package/lib/types.js +11 -0
  41. package/package.json +52 -0
  42. package/skills/ns-solid-components/SKILL.md +77 -0
  43. package/skills/ns-solid-forms/SKILL.md +166 -0
  44. package/skills/ns-solid-icons/SKILL.md +95 -0
  45. package/skills/ns-solid-install/SKILL.md +119 -0
  46. package/skills/ns-solid-style-contract/SKILL.md +84 -0
  47. package/skills/ns-solid-theming/SKILL.md +123 -0
package/lib/tools.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ /**
3
+ * Registers the full ng-solid MCP tool catalog (~32 tools), grouped by concern:
4
+ * - discovery, API, forms, services/providers, style validation -> tools.components.ts
5
+ * - the Arvisfy token pipeline + theme-preset validation -> tools.tokens.ts
6
+ * - the Lucide icon catalog -> tools.icons.ts
7
+ * - guides + server metadata -> tools.guides.ts
8
+ */
9
+ export declare function registerTools(server: McpServer): void;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Guides and server metadata. `list_guides`/`get_guide` read `snapshot.guides`, generated by
3
+ * Fase 5's `extract-guides.mjs` from each skill's `SKILL.md`. That array is empty
4
+ * until Fase 5 lands -- both tools degrade to an explicit "no guides yet" note rather than
5
+ * erroring, since an empty catalog is a valid (if temporary) snapshot state.
6
+ */
7
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ import type { GuideDoc } from './types.js';
9
+ export declare function resolveGuide(guides: GuideDoc[], input: string): {
10
+ guide: GuideDoc | null;
11
+ error?: string;
12
+ suggestions?: string[];
13
+ };
14
+ export declare function renderGuideMarkdown(guide: GuideDoc): string;
15
+ export declare function registerGuideTools(server: McpServer): void;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Guides and server metadata. `list_guides`/`get_guide` read `snapshot.guides`, generated by
3
+ * Fase 5's `extract-guides.mjs` from each skill's `SKILL.md`. That array is empty
4
+ * until Fase 5 lands -- both tools degrade to an explicit "no guides yet" note rather than
5
+ * erroring, since an empty catalog is a valid (if temporary) snapshot state.
6
+ */
7
+ import { z } from 'zod';
8
+ import { jsonResult, loadIcons, loadSnapshot } from './data.js';
9
+ import { jsonError, levenshtein } from './tools.components.js';
10
+ export function resolveGuide(guides, input) {
11
+ const lower = input.trim().toLowerCase();
12
+ const exact = guides.find((g) => g.name.toLowerCase() === lower);
13
+ if (exact)
14
+ return { guide: exact };
15
+ const partial = guides.find((g) => g.name.toLowerCase().includes(lower));
16
+ if (partial)
17
+ return { guide: partial };
18
+ const scored = guides
19
+ .map((g) => ({ g, score: levenshtein(lower, g.name.toLowerCase()) }))
20
+ .sort((a, b) => a.score - b.score)
21
+ .slice(0, 5)
22
+ .map(({ g }) => g.name);
23
+ return {
24
+ guide: null,
25
+ error: `No guide named "${input}".`,
26
+ suggestions: scored
27
+ };
28
+ }
29
+ export function renderGuideMarkdown(guide) {
30
+ const lines = [
31
+ `# ${guide.title}`,
32
+ '',
33
+ guide.description,
34
+ ...guide.sections.flatMap((s) => ['', `## ${s.title}`, '', s.content])
35
+ ];
36
+ return lines.join('\n');
37
+ }
38
+ export function registerGuideTools(server) {
39
+ server.registerTool('list_guides', {
40
+ title: 'List guides',
41
+ description: 'Lists the ng-solid usage guides (install, theming, forms, icons, style contract, components) generated from the distributable skills.',
42
+ inputSchema: {}
43
+ }, async () => {
44
+ const snapshot = loadSnapshot();
45
+ if (snapshot.guides.length === 0) {
46
+ return jsonResult({
47
+ count: 0,
48
+ guides: [],
49
+ note: 'No guides in this snapshot yet -- Fase 5 populates snapshot.guides from packages/mcp/skills/*/SKILL.md.'
50
+ });
51
+ }
52
+ return jsonResult({
53
+ count: snapshot.guides.length,
54
+ guides: snapshot.guides.map((g) => ({
55
+ name: g.name,
56
+ title: g.title,
57
+ description: g.description
58
+ }))
59
+ });
60
+ });
61
+ server.registerTool('get_guide', {
62
+ title: 'Get a guide',
63
+ description: 'Returns the full Markdown body of one usage guide by name.',
64
+ inputSchema: {
65
+ guide: z.string().describe('Guide name, e.g. "ns-solid-forms".')
66
+ }
67
+ }, async ({ guide }) => {
68
+ const snapshot = loadSnapshot();
69
+ if (snapshot.guides.length === 0) {
70
+ return jsonError({
71
+ error: 'No guides in this snapshot yet -- Fase 5 populates snapshot.guides.'
72
+ });
73
+ }
74
+ const resolution = resolveGuide(snapshot.guides, guide);
75
+ if (!resolution.guide)
76
+ return jsonError({
77
+ error: resolution.error,
78
+ suggestions: resolution.suggestions
79
+ });
80
+ return jsonResult({
81
+ name: resolution.guide.name,
82
+ title: resolution.guide.title,
83
+ markdown: renderGuideMarkdown(resolution.guide)
84
+ });
85
+ });
86
+ server.registerTool('get_version_info', {
87
+ title: 'Get ng-solid version info',
88
+ description: 'Returns the snapshot version and the design-system/icons package versions it was generated from.',
89
+ inputSchema: {}
90
+ }, async () => {
91
+ const snapshot = loadSnapshot();
92
+ const icons = loadIcons();
93
+ return jsonResult({
94
+ schemaVersion: snapshot.schemaVersion,
95
+ snapshotVersion: snapshot.version,
96
+ generatedAt: snapshot.generatedAt,
97
+ packages: snapshot.packages,
98
+ iconsVersion: icons.version,
99
+ stats: snapshot.stats
100
+ });
101
+ });
102
+ }
103
+ //# sourceMappingURL=tools.guides.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Icon-catalog tools. The catalog is `name` (kebab-case, matches Lucide upstream) <-> `symbol`
3
+ * (camelCase, the export `provideIcons([...])` actually needs) plus search keywords, with SVG
4
+ * bodies deliberately stripped from the snapshot (see Fase 3 -- 468 KB -> ~90 KB).
5
+ */
6
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ export declare function registerIconTools(server: McpServer): void;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Icon-catalog tools. The catalog is `name` (kebab-case, matches Lucide upstream) <-> `symbol`
3
+ * (camelCase, the export `provideIcons([...])` actually needs) plus search keywords, with SVG
4
+ * bodies deliberately stripped from the snapshot (see Fase 3 -- 468 KB -> ~90 KB).
5
+ */
6
+ import { z } from 'zod';
7
+ import { jsonResult, loadIcons } from './data.js';
8
+ import { jsonError, levenshtein } from './tools.components.js';
9
+ function resolveIcon(icons, input) {
10
+ const lower = input.trim().toLowerCase();
11
+ const byName = icons.find((i) => i.name.toLowerCase() === lower);
12
+ if (byName)
13
+ return { icon: byName };
14
+ const bySymbol = icons.find((i) => i.symbol?.toLowerCase() === lower);
15
+ if (bySymbol)
16
+ return { icon: bySymbol };
17
+ const scored = icons
18
+ .map((i) => ({
19
+ i,
20
+ score: Math.min(levenshtein(lower, i.name.toLowerCase()), levenshtein(lower, (i.symbol ?? '').toLowerCase()))
21
+ }))
22
+ .sort((a, b) => a.score - b.score)
23
+ .slice(0, 5)
24
+ .map(({ i }) => i.name);
25
+ return {
26
+ icon: null,
27
+ error: `No icon named or symbolized "${input}".`,
28
+ suggestions: scored
29
+ };
30
+ }
31
+ export function registerIconTools(server) {
32
+ server.registerTool('search_icons', {
33
+ title: 'Search the icon catalog',
34
+ description: 'Searches the 1790-icon Lucide catalog by name or keyword. Each result carries the camelCase `symbol` needed by `provideIcons([...])` alongside the kebab-case `name`.',
35
+ inputSchema: {
36
+ query: z.string(),
37
+ limit: z.number().int().positive().max(100).optional()
38
+ }
39
+ }, async ({ query, limit }) => {
40
+ const icons = loadIcons();
41
+ const q = query.trim().toLowerCase();
42
+ const matches = icons.icons
43
+ .filter((i) => i.name.includes(q) || i.keywords.some((k) => k.includes(q)))
44
+ .slice(0, limit ?? 20);
45
+ return jsonResult({ query, count: matches.length, icons: matches });
46
+ });
47
+ server.registerTool('get_icon', {
48
+ title: 'Get one icon',
49
+ description: 'Resolves a single icon by its kebab-case name or camelCase symbol and returns both forms plus a ready-to-use `<ns-icon>` snippet.',
50
+ inputSchema: { icon: z.string() }
51
+ }, async ({ icon }) => {
52
+ const icons = loadIcons();
53
+ const resolution = resolveIcon(icons.icons, icon);
54
+ if (!resolution.icon)
55
+ return jsonError({
56
+ error: resolution.error,
57
+ suggestions: resolution.suggestions
58
+ });
59
+ const found = resolution.icon;
60
+ return jsonResult({
61
+ ...found,
62
+ usage: {
63
+ provide: found.symbol ? `provideIcons([${found.symbol}])` : null,
64
+ template: `<ns-icon name="${found.name}"></ns-icon>`
65
+ }
66
+ });
67
+ });
68
+ server.registerTool('get_icons_setup', {
69
+ title: 'Get icon setup boilerplate',
70
+ description: "Returns the ng-solid icon component's own API plus the `provideIcons`/`provideIconsPatch` boilerplate. Pass `icons` (names or symbols) to get a ready import/provide snippet for exactly those icons instead of the generic template.",
71
+ inputSchema: {
72
+ icons: z
73
+ .array(z.string())
74
+ .optional()
75
+ .describe('Icon names or symbols to include in the generated snippet.')
76
+ }
77
+ }, async ({ icons: requested }) => {
78
+ const icons = loadIcons();
79
+ if (!requested || requested.length === 0) {
80
+ return jsonResult({
81
+ component: icons.component,
82
+ setup: {
83
+ importStatement: "import { provideIcons } from '@ng-solid/icons';",
84
+ example: 'provideIcons([aArrowDown, calendar]) // pass EnvironmentProviders to `appConfig.providers`',
85
+ perRoute: 'providers: [provideIconsPatch([add])] // scoped to a lazily-loaded Route instead of bootstrap'
86
+ }
87
+ });
88
+ }
89
+ const resolved = requested.map((name) => resolveIcon(icons.icons, name));
90
+ const missing = resolved.filter((r) => !r.icon);
91
+ if (missing.length > 0) {
92
+ return jsonError({
93
+ error: `Unknown icon(s): ${requested.filter((_, idx) => !resolved[idx].icon).join(', ')}`,
94
+ suggestions: missing.flatMap((m) => m.suggestions ?? [])
95
+ });
96
+ }
97
+ const symbols = resolved
98
+ .map((r) => r.icon?.symbol)
99
+ .filter((s) => Boolean(s));
100
+ return jsonResult({
101
+ importStatement: `import { ${symbols.join(', ')} } from '@ng-solid/icons';`,
102
+ provideStatement: `provideIcons([${symbols.join(', ')}])`
103
+ });
104
+ });
105
+ }
106
+ //# sourceMappingURL=tools.icons.js.map
package/lib/tools.js ADDED
@@ -0,0 +1,18 @@
1
+ import { registerComponentTools } from './tools.components.js';
2
+ import { registerGuideTools } from './tools.guides.js';
3
+ import { registerIconTools } from './tools.icons.js';
4
+ import { registerTokenTools } from './tools.tokens.js';
5
+ /**
6
+ * Registers the full ng-solid MCP tool catalog (~32 tools), grouped by concern:
7
+ * - discovery, API, forms, services/providers, style validation -> tools.components.ts
8
+ * - the Arvisfy token pipeline + theme-preset validation -> tools.tokens.ts
9
+ * - the Lucide icon catalog -> tools.icons.ts
10
+ * - guides + server metadata -> tools.guides.ts
11
+ */
12
+ export function registerTools(server) {
13
+ registerComponentTools(server);
14
+ registerTokenTools(server);
15
+ registerIconTools(server);
16
+ registerGuideTools(server);
17
+ }
18
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Theme/token tools: the Arvisfy 3-tier token pipeline (primitive -> semantic -> component),
3
+ * drift detection against component SCSS, and a pure-JS `validateNsPreset` port for
4
+ * `validate_theme_preset`.
5
+ */
6
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ export declare function registerTokenTools(server: McpServer): void;
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Theme/token tools: the Arvisfy 3-tier token pipeline (primitive -> semantic -> component),
3
+ * drift detection against component SCSS, and a pure-JS `validateNsPreset` port for
4
+ * `validate_theme_preset`.
5
+ */
6
+ import { z } from 'zod';
7
+ import { jsonResult, loadSnapshot } from './data.js';
8
+ import { importTrio, jsonError, resolveComponent } from './tools.components.js';
9
+ import { validatePreset } from './preset-validator.js';
10
+ function findPrimitive(theme, path) {
11
+ return theme.tiers.primitive.find((t) => t.path === path);
12
+ }
13
+ function findSemantic(theme, path) {
14
+ return (theme.tiers.semantic.common.find((t) => t.path === path) ??
15
+ theme.tiers.semantic.light.find((t) => t.path === path) ??
16
+ theme.tiers.semantic.dark.find((t) => t.path === path));
17
+ }
18
+ function findComponentToken(theme, cssVariable) {
19
+ return theme.tiers.component.find((t) => t.cssVariable === cssVariable);
20
+ }
21
+ /** Walks a token reference chain (component -> semantic -> primitive) down to its literal. */
22
+ function buildChain(theme, cssVariable) {
23
+ const componentToken = theme.tiers.component.find((t) => t.cssVariable === cssVariable);
24
+ if (componentToken) {
25
+ const chain = [
26
+ {
27
+ tier: 'component',
28
+ path: componentToken.path,
29
+ cssVariable: componentToken.cssVariable,
30
+ reference: componentToken.reference
31
+ }
32
+ ];
33
+ const semanticPath = /^\{semantic\.(.+)\}$/.exec(componentToken.reference)?.[1];
34
+ const semanticToken = semanticPath
35
+ ? findSemantic(theme, semanticPath)
36
+ : undefined;
37
+ if (semanticToken) {
38
+ chain.push({
39
+ tier: 'semantic',
40
+ path: semanticToken.path,
41
+ cssVariable: semanticToken.cssVariable,
42
+ reference: semanticToken.reference
43
+ });
44
+ const primitivePath = /^\{primitive\.(.+)\}$/.exec(semanticToken.reference)?.[1];
45
+ const primitiveToken = primitivePath
46
+ ? findPrimitive(theme, primitivePath)
47
+ : undefined;
48
+ if (primitiveToken) {
49
+ chain.push({
50
+ tier: 'primitive',
51
+ path: primitiveToken.path,
52
+ cssVariable: primitiveToken.cssVariable,
53
+ value: primitiveToken.value
54
+ });
55
+ }
56
+ }
57
+ chain.push({
58
+ tier: 'component',
59
+ path: `${componentToken.path} (resolved)`,
60
+ cssVariable: componentToken.cssVariable,
61
+ value: `light: ${componentToken.resolved.light ?? 'undefined'} / dark: ${componentToken.resolved.dark ?? 'undefined'}`
62
+ });
63
+ return chain;
64
+ }
65
+ const semanticEntry = theme.tiers.semantic.common.find((t) => t.cssVariable === cssVariable) ??
66
+ theme.tiers.semantic.light.find((t) => t.cssVariable === cssVariable) ??
67
+ theme.tiers.semantic.dark.find((t) => t.cssVariable === cssVariable);
68
+ if (semanticEntry) {
69
+ const chain = [
70
+ {
71
+ tier: 'semantic',
72
+ path: semanticEntry.path,
73
+ cssVariable: semanticEntry.cssVariable,
74
+ reference: semanticEntry.reference
75
+ }
76
+ ];
77
+ const primitivePath = /^\{primitive\.(.+)\}$/.exec(semanticEntry.reference)?.[1];
78
+ const primitiveToken = primitivePath
79
+ ? findPrimitive(theme, primitivePath)
80
+ : undefined;
81
+ if (primitiveToken) {
82
+ chain.push({
83
+ tier: 'primitive',
84
+ path: primitiveToken.path,
85
+ cssVariable: primitiveToken.cssVariable,
86
+ value: primitiveToken.value
87
+ });
88
+ }
89
+ return chain;
90
+ }
91
+ const primitiveEntry = theme.tiers.primitive.find((t) => t.cssVariable === cssVariable);
92
+ if (primitiveEntry) {
93
+ return [
94
+ {
95
+ tier: 'primitive',
96
+ path: primitiveEntry.path,
97
+ cssVariable: primitiveEntry.cssVariable,
98
+ value: primitiveEntry.value
99
+ }
100
+ ];
101
+ }
102
+ return null;
103
+ }
104
+ export function registerTokenTools(server) {
105
+ server.registerTool('get_component_tokens', {
106
+ title: "Get a component's style tokens",
107
+ description: 'Returns every `--ns-component-*` CSS custom property a component reads, resolved to its literal value for both light and dark color schemes -- lets an agent reason about contrast/theming without running Angular.',
108
+ inputSchema: { component: z.string() }
109
+ }, async ({ component }) => {
110
+ const snapshot = loadSnapshot();
111
+ const resolution = resolveComponent(snapshot, component);
112
+ if (!resolution.component)
113
+ return jsonError({
114
+ error: resolution.error,
115
+ suggestions: resolution.suggestions
116
+ });
117
+ const doc = resolution.component;
118
+ if (!snapshot.theme)
119
+ return jsonError({ error: 'theme is missing from the snapshot.' });
120
+ const tokens = doc.tokens.map((cssVariable) => {
121
+ const entry = findComponentToken(snapshot.theme, cssVariable);
122
+ return (entry ?? {
123
+ cssVariable,
124
+ path: null,
125
+ reference: null,
126
+ resolved: { light: null, dark: null }
127
+ });
128
+ });
129
+ return jsonResult({
130
+ ...importTrio(doc),
131
+ tokenCount: tokens.length,
132
+ tokens
133
+ });
134
+ });
135
+ server.registerTool('list_theme_tokens', {
136
+ title: 'List theme tokens for a tier',
137
+ description: 'Lists flattened tokens for one tier of the Arvisfy 3-tier preset: `primitive` (raw values), `semantic` (references primitives, optionally filtered by color scheme), or `component` (references semantics, resolved for both schemes -- optionally filtered by group, e.g. "button").',
138
+ inputSchema: {
139
+ tier: z.enum(['primitive', 'semantic', 'component']),
140
+ colorScheme: z
141
+ .enum(['common', 'light', 'dark'])
142
+ .optional()
143
+ .describe('Only applies to tier: "semantic".'),
144
+ group: z
145
+ .string()
146
+ .optional()
147
+ .describe('Only applies to tier: "component" -- filters to tokens whose path starts with this group, e.g. "button".')
148
+ }
149
+ }, async ({ tier, colorScheme, group }) => {
150
+ const snapshot = loadSnapshot();
151
+ if (!snapshot.theme)
152
+ return jsonError({ error: 'theme is missing from the snapshot.' });
153
+ const theme = snapshot.theme;
154
+ if (tier === 'primitive') {
155
+ return jsonResult({
156
+ tier,
157
+ count: theme.tiers.primitive.length,
158
+ tokens: theme.tiers.primitive
159
+ });
160
+ }
161
+ if (tier === 'semantic') {
162
+ const scope = colorScheme ?? 'common';
163
+ const tokens = theme.tiers.semantic[scope];
164
+ return jsonResult({
165
+ tier,
166
+ colorScheme: scope,
167
+ count: tokens.length,
168
+ tokens
169
+ });
170
+ }
171
+ const tokens = group
172
+ ? theme.tiers.component.filter((t) => t.path === group || t.path.startsWith(`${group}.`))
173
+ : theme.tiers.component;
174
+ return jsonResult({
175
+ tier,
176
+ group: group ?? null,
177
+ count: tokens.length,
178
+ tokens,
179
+ componentGroups: theme.componentGroups
180
+ });
181
+ });
182
+ server.registerTool('resolve_token', {
183
+ title: 'Resolve a token to its literal value',
184
+ description: 'Walks a `--ns-*` CSS custom property down its full reference chain (component -> semantic -> primitive) to its literal CSS value, at any tier you start from.',
185
+ inputSchema: {
186
+ cssVariable: z
187
+ .string()
188
+ .describe('e.g. "--ns-component-button-background".')
189
+ }
190
+ }, async ({ cssVariable }) => {
191
+ const snapshot = loadSnapshot();
192
+ if (!snapshot.theme)
193
+ return jsonError({ error: 'theme is missing from the snapshot.' });
194
+ const chain = buildChain(snapshot.theme, cssVariable);
195
+ if (!chain) {
196
+ return jsonError({
197
+ error: `No token found for "${cssVariable}". CSS variables in this preset follow --ns-<tier>-<dot.path> (dots -> dashes).`
198
+ });
199
+ }
200
+ return jsonResult({ cssVariable, chain });
201
+ });
202
+ server.registerTool('get_token_scope', {
203
+ title: "Classify a CSS variable's scope",
204
+ description: 'Classifies a `--ns-*` CSS custom property by tier (primitive/semantic/component) and, for component tokens, its owning group -- helps decide whether a token is safe to override in a preset.',
205
+ inputSchema: { cssVariable: z.string() }
206
+ }, async ({ cssVariable }) => {
207
+ const snapshot = loadSnapshot();
208
+ if (!snapshot.theme)
209
+ return jsonError({ error: 'theme is missing from the snapshot.' });
210
+ const theme = snapshot.theme;
211
+ const component = theme.tiers.component.find((t) => t.cssVariable === cssVariable);
212
+ if (component) {
213
+ return jsonResult({
214
+ cssVariable,
215
+ tier: 'component',
216
+ group: component.path.split('.')[0],
217
+ path: component.path
218
+ });
219
+ }
220
+ const semantic = theme.tiers.semantic.common.find((t) => t.cssVariable === cssVariable) ??
221
+ theme.tiers.semantic.light.find((t) => t.cssVariable === cssVariable) ??
222
+ theme.tiers.semantic.dark.find((t) => t.cssVariable === cssVariable);
223
+ if (semantic) {
224
+ return jsonResult({
225
+ cssVariable,
226
+ tier: 'semantic',
227
+ path: semantic.path
228
+ });
229
+ }
230
+ const primitive = theme.tiers.primitive.find((t) => t.cssVariable === cssVariable);
231
+ if (primitive) {
232
+ return jsonResult({
233
+ cssVariable,
234
+ tier: 'primitive',
235
+ path: primitive.path
236
+ });
237
+ }
238
+ return jsonError({
239
+ error: `"${cssVariable}" is not defined by the Arvisfy preset.`
240
+ });
241
+ });
242
+ server.registerTool('validate_theme_preset', {
243
+ title: 'Validate a theme preset',
244
+ description: 'Angular-free port of `validateNsPreset`: validates a preset (or a `defineNsPreset` override tree) against the same rules the design-system enforces at load time -- preset name shape, safe primitive values, primitive/semantic reference resolution, and the "must resolve in both color schemes" rule. `mode: "overrides"` validates a partial override tree (only checks tiers actually present); `mode: "full"` (default) expects the complete 3-tier shape.',
245
+ inputSchema: {
246
+ preset: z
247
+ .string()
248
+ .describe('JSON-encoded preset object: { name, primitive, semantic: { common, colorScheme: { light, dark } }, components }. For mode "overrides", any subset of these keys.'),
249
+ mode: z.enum(['full', 'overrides']).optional()
250
+ }
251
+ }, async ({ preset, mode }) => {
252
+ const snapshot = loadSnapshot();
253
+ if (!snapshot.theme)
254
+ return jsonError({ error: 'theme is missing from the snapshot.' });
255
+ let parsed;
256
+ try {
257
+ parsed = JSON.parse(preset);
258
+ }
259
+ catch (err) {
260
+ return jsonError({
261
+ error: `preset is not valid JSON: ${err.message}`
262
+ });
263
+ }
264
+ const result = validatePreset(parsed, snapshot.theme.rules, mode ?? 'full');
265
+ return jsonResult(result);
266
+ });
267
+ server.registerTool('get_token_drift', {
268
+ title: 'Get token drift',
269
+ description: 'Cross-references the `--ns-component-*` variables referenced in component SCSS against the ones the Arvisfy preset actually defines. `undefinedInPreset` (SCSS reads a token the preset never sets) is a real bug -- the design-system would render a broken value. `unusedInScss` (the preset defines a token nothing reads) is dead-weight, not necessarily a bug.',
270
+ inputSchema: {}
271
+ }, async () => {
272
+ const snapshot = loadSnapshot();
273
+ if (!snapshot.theme)
274
+ return jsonError({ error: 'theme is missing from the snapshot.' });
275
+ return jsonResult(snapshot.theme.drift);
276
+ });
277
+ }
278
+ //# sourceMappingURL=tools.tokens.js.map