@mindfiredigital/ignix-lite-mcp 1.0.0 → 1.2.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 (96) hide show
  1. package/.turbo/turbo-build.log +17 -14
  2. package/CHANGELOG.md +7 -0
  3. package/dist/manifests/accordion.json +61 -0
  4. package/dist/manifests/alert.json +69 -0
  5. package/dist/manifests/avatar.json +75 -0
  6. package/dist/manifests/badge.json +74 -0
  7. package/dist/manifests/breadcrumb.json +87 -0
  8. package/dist/manifests/button.json +85 -0
  9. package/dist/manifests/card.json +91 -0
  10. package/dist/manifests/checkbox.json +122 -0
  11. package/dist/manifests/codeblock.json +63 -0
  12. package/dist/manifests/combobox.json +33 -0
  13. package/dist/manifests/dialog.json +64 -0
  14. package/dist/manifests/divider.json +47 -0
  15. package/dist/manifests/dropdown.json +105 -0
  16. package/dist/manifests/form.json +81 -0
  17. package/dist/manifests/grid.json +143 -0
  18. package/dist/manifests/input.json +99 -0
  19. package/dist/manifests/meter.json +103 -0
  20. package/dist/manifests/navigation.json +70 -0
  21. package/dist/manifests/progress.json +88 -0
  22. package/dist/manifests/radio.json +121 -0
  23. package/dist/manifests/select.json +109 -0
  24. package/dist/manifests/skeleton.json +101 -0
  25. package/dist/manifests/tab.json +88 -0
  26. package/dist/manifests/table.json +92 -0
  27. package/dist/manifests/textarea.json +117 -0
  28. package/dist/manifests/toast.json +157 -0
  29. package/dist/manifests/tooltip.json +115 -0
  30. package/dist/server.d.ts +1 -2
  31. package/dist/server.js +2102 -1
  32. package/dist/server.js.map +1 -1
  33. package/dist/utils/check-api.d.ts +2 -0
  34. package/dist/utils/check-api.js +11 -0
  35. package/dist/utils/check-api.js.map +1 -0
  36. package/dist/vector-index.json +14015 -0
  37. package/package.json +25 -11
  38. package/src/context/api-context.ts +14 -0
  39. package/src/global.d.ts +15 -0
  40. package/src/manifests/accordion.json +61 -0
  41. package/src/manifests/alert.json +69 -0
  42. package/src/manifests/avatar.json +75 -0
  43. package/src/manifests/badge.json +74 -0
  44. package/src/manifests/breadcrumb.json +87 -0
  45. package/src/manifests/button.json +85 -0
  46. package/src/manifests/card.json +91 -0
  47. package/src/manifests/checkbox.json +122 -0
  48. package/src/manifests/codeblock.json +63 -0
  49. package/src/manifests/combobox.json +33 -0
  50. package/src/manifests/dialog.json +64 -0
  51. package/src/manifests/divider.json +47 -0
  52. package/src/manifests/dropdown.json +105 -0
  53. package/src/manifests/form.json +81 -0
  54. package/src/manifests/grid.json +143 -0
  55. package/src/manifests/index.ts +45 -0
  56. package/src/manifests/input.json +99 -0
  57. package/src/manifests/meter.json +103 -0
  58. package/src/manifests/navigation.json +70 -0
  59. package/src/manifests/progress.json +88 -0
  60. package/src/manifests/radio.json +121 -0
  61. package/src/manifests/select.json +109 -0
  62. package/src/manifests/skeleton.json +101 -0
  63. package/src/manifests/tab.json +88 -0
  64. package/src/manifests/table.json +92 -0
  65. package/src/manifests/textarea.json +117 -0
  66. package/src/manifests/toast.json +157 -0
  67. package/src/manifests/tooltip.json +115 -0
  68. package/src/server.ts +200 -2
  69. package/src/tools/build-index.ts +55 -0
  70. package/src/tools/check-a11y.ts +106 -0
  71. package/src/tools/embedder.ts +18 -0
  72. package/src/tools/generate-theme.ts +42 -0
  73. package/src/tools/get-emmet.ts +64 -0
  74. package/src/tools/get-manifests.ts +55 -0
  75. package/src/tools/intent-engine.ts +197 -0
  76. package/src/tools/list-components.ts +20 -0
  77. package/src/tools/search-index.ts +66 -0
  78. package/src/tools/theme-palette.ts +65 -0
  79. package/src/tools/theme-tokens.ts +176 -0
  80. package/src/tools/validator.ts +367 -0
  81. package/src/types.ts +63 -0
  82. package/src/utils/a11y-rules.ts +873 -0
  83. package/src/utils/a11y-types.ts +15 -0
  84. package/src/utils/check-api.ts +13 -0
  85. package/src/utils/cosine.ts +15 -0
  86. package/src/utils/emmet-helpers.ts +171 -0
  87. package/src/utils/intent-helpers.ts +66 -0
  88. package/src/utils/intent-parser.ts +186 -0
  89. package/src/utils/tokenizer.ts +7 -0
  90. package/tsconfig.json +9 -2
  91. package/tsup.config.ts +6 -3
  92. package/LICENSE +0 -21
  93. package/dist/server.cjs +0 -2
  94. package/dist/server.cjs.map +0 -1
  95. package/dist/server.d.cts +0 -3
  96. package/test/basic.test.ts +0 -8
@@ -0,0 +1,367 @@
1
+ import { parse } from 'node-html-parser'
2
+ import { manifests } from '../manifests/index.js'
3
+ import type { MCPResponse } from '../types.js'
4
+
5
+ type ErrorType =
6
+ | 'UNKNOWN_ATTRIBUTE'
7
+ | 'INVALID_VALUE'
8
+ | 'FORBIDDEN_CLASS'
9
+ | 'MISSING_REQUIRED'
10
+ | 'WRONG_ELEMENT'
11
+ | 'PROP_EXPLOSION'
12
+ | 'JS_ON_CSS_COMPONENT'
13
+ | 'MISSING_SLOT'
14
+
15
+ type ValidationError = {
16
+ element: string
17
+ prop: string
18
+ type: ErrorType
19
+ message: string
20
+ suggestion?: string
21
+ valid_values?: string[]
22
+ fix: string
23
+ confidence: number
24
+ }
25
+
26
+ export function validate(html: string): MCPResponse {
27
+ const root = parse(html)
28
+
29
+ const errors: ValidationError[] = []
30
+
31
+ const elements = root.querySelectorAll('*')
32
+
33
+ // SRS wrappers only
34
+ const allowedWrappers = [
35
+ 'label',
36
+ 'span',
37
+ 'p',
38
+ 'img',
39
+ 'small',
40
+ 'h1',
41
+ 'h2',
42
+ 'h3',
43
+ 'a',
44
+ 'button',
45
+ 'ul',
46
+ 'li',
47
+ 'thead',
48
+ 'tbody',
49
+ 'tr',
50
+ 'td',
51
+ 'th',
52
+ 'summary'
53
+ ]
54
+
55
+ const nativeAttributes = new Set([
56
+ 'id',
57
+ 'role',
58
+ 'slot',
59
+ 'tabindex',
60
+
61
+ 'aria-label',
62
+ 'aria-live',
63
+ 'aria-hidden',
64
+ 'aria-expanded',
65
+ 'aria-selected',
66
+ 'aria-current',
67
+ 'aria-sort',
68
+ 'aria-invalid',
69
+ 'aria-describedby',
70
+ 'aria-labelledby',
71
+ 'aria-haspopup',
72
+ 'aria-busy',
73
+
74
+ 'disabled',
75
+ 'required',
76
+ 'checked',
77
+ 'multiple',
78
+ 'readonly',
79
+
80
+ 'type',
81
+ 'value',
82
+ 'placeholder',
83
+ 'name',
84
+
85
+ 'min',
86
+ 'max',
87
+ 'low',
88
+ 'high',
89
+ 'optimum',
90
+
91
+ 'rows',
92
+
93
+ 'open',
94
+ 'hidden',
95
+
96
+ 'href',
97
+
98
+ 'content',
99
+
100
+ 'is',
101
+
102
+ 'src',
103
+ 'alt',
104
+
105
+ 'data-intent',
106
+ 'data-position',
107
+ 'data-variant',
108
+ 'data-shape',
109
+ 'data-sortable',
110
+ 'data-open',
111
+ 'data-lines',
112
+
113
+ 'data-grid',
114
+ 'data-gap',
115
+ 'data-align',
116
+ 'data-justify',
117
+ 'data-col',
118
+ 'data-row',
119
+ 'data-dense'
120
+ ])
121
+
122
+ for (const el of elements) {
123
+ const tag = el.tagName.toLowerCase()
124
+
125
+ const attrs = el.attributes
126
+
127
+ // ix-* support
128
+ let manifestKey = tag.startsWith('ix-') ? tag.slice(3) : tag
129
+
130
+ // built in table
131
+ if (tag === 'table' && attrs.is === 'ix-table') {
132
+ manifestKey = 'table'
133
+ }
134
+
135
+ // navigation
136
+ if (tag === 'nav') {
137
+ manifestKey = 'navigation'
138
+ }
139
+
140
+ // card
141
+ if (tag === 'article' && el.querySelector('[slot]')) {
142
+ manifestKey = 'card'
143
+ }
144
+
145
+ let manifest = manifests[manifestKey]
146
+
147
+ // badge support
148
+ if (!manifest) {
149
+ if (tag === 'mark') {
150
+ manifest = manifests.badge
151
+ }
152
+
153
+ if (tag === 'span' && attrs.role === 'status') {
154
+ manifest = manifests.badge
155
+ }
156
+ }
157
+
158
+ // invalid component
159
+ if (!manifest && !allowedWrappers.includes(tag)) {
160
+ errors.push({
161
+ element: tag,
162
+ prop: '',
163
+ type: 'WRONG_ELEMENT',
164
+ message: `<${tag}> is not a valid ignix-lite component`,
165
+ fix: `<button>Fix me</button>`,
166
+ confidence: 0.7
167
+ })
168
+
169
+ continue
170
+ }
171
+
172
+ // wrappers skip
173
+ if (!manifest) continue
174
+
175
+ const elementName = tag.startsWith('ix-') ? tag : manifest.element || tag
176
+
177
+ // required wrapper
178
+ if (manifest.required_wrapper) {
179
+ const parent = el.parentNode as unknown as {
180
+ tagName?: string
181
+ }
182
+
183
+ if (
184
+ !parent ||
185
+ parent.tagName?.toLowerCase() !== manifest.required_wrapper
186
+ ) {
187
+ errors.push({
188
+ element: tag,
189
+ prop: 'wrapper',
190
+ type: 'MISSING_REQUIRED',
191
+ message: `<${tag}> must be inside <${manifest.required_wrapper}>`,
192
+ fix: `<${manifest.required_wrapper}><${elementName}></${elementName}></${manifest.required_wrapper}>`,
193
+ confidence: 0.95
194
+ })
195
+ }
196
+ }
197
+
198
+ // badge rule
199
+ if (
200
+ tag === 'span' &&
201
+ manifest.component === 'badge' &&
202
+ attrs.role !== 'status'
203
+ ) {
204
+ errors.push({
205
+ element: tag,
206
+
207
+ prop: 'role',
208
+ type: 'INVALID_VALUE',
209
+ message: 'span badge must have role=status',
210
+ fix: `<span role="status">${el.innerText}</span>`,
211
+ confidence: 0.9
212
+ })
213
+ }
214
+
215
+ // class forbidden
216
+ if ('class' in attrs) {
217
+ errors.push({
218
+ element: tag,
219
+ prop: 'class',
220
+ type: 'FORBIDDEN_CLASS',
221
+ message: 'class attribute not allowed',
222
+ fix: `<${elementName}>${el.innerText}</${elementName}>`,
223
+ confidence: 0.99
224
+ })
225
+ }
226
+
227
+ // prop explosion
228
+ if (Object.keys(attrs).length > 4) {
229
+ errors.push({
230
+ element: tag,
231
+ prop: 'multiple',
232
+ type: 'PROP_EXPLOSION',
233
+ message: 'Too many props',
234
+ fix: `<${elementName}></${elementName}>`,
235
+ confidence: 0.85
236
+ })
237
+ }
238
+
239
+ // js handlers
240
+ for (const attr of Object.keys(attrs)) {
241
+ if (attr.startsWith('on')) {
242
+ errors.push({
243
+ element: tag,
244
+ prop: attr,
245
+ type: 'JS_ON_CSS_COMPONENT',
246
+ message: 'JS handlers forbidden',
247
+ fix: `<${elementName}></${elementName}>`,
248
+ confidence: 0.95
249
+ })
250
+ }
251
+ }
252
+
253
+ // attribute validation
254
+ for (const attr of Object.keys(attrs)) {
255
+ if (manifest.forbidden_props?.includes(attr)) {
256
+ errors.push({
257
+ element: tag,
258
+ prop: attr,
259
+ type: 'UNKNOWN_ATTRIBUTE',
260
+ message: `'${attr}' forbidden`,
261
+ fix: `<${elementName}></${elementName}>`,
262
+ confidence: 0.98
263
+ })
264
+
265
+ continue
266
+ }
267
+
268
+ if (!manifest.props?.[attr] && !nativeAttributes.has(attr)) {
269
+ errors.push({
270
+ element: tag,
271
+ prop: attr,
272
+ type: 'UNKNOWN_ATTRIBUTE',
273
+ message: `'${attr}' invalid`,
274
+ fix: `<${elementName}></${elementName}>`,
275
+ confidence: 0.95
276
+ })
277
+ }
278
+ }
279
+
280
+ // enum validation
281
+ for (const attr of Object.keys(attrs)) {
282
+ const def = manifest.props?.[attr]
283
+
284
+ if (def?.values) {
285
+ const value = attrs[attr]
286
+
287
+ if (!def.values.includes(value)) {
288
+ errors.push({
289
+ element: tag,
290
+ prop: attr,
291
+ type: 'INVALID_VALUE',
292
+ message: `'${value}' invalid`,
293
+ valid_values: def.values,
294
+ fix: `<${elementName} ${attr}="${def.values[0]}"></${elementName}>`,
295
+ confidence: 0.97
296
+ })
297
+ }
298
+ }
299
+ }
300
+
301
+ // required props
302
+ for (const req of manifest.required_props || []) {
303
+ if (!(req in attrs)) {
304
+ errors.push({
305
+ element: tag,
306
+ prop: req,
307
+ type: 'MISSING_REQUIRED',
308
+ message: `Missing ${req}`,
309
+ fix: `<${elementName} ${req}=""></${elementName}>`,
310
+ confidence: 0.9
311
+ })
312
+ }
313
+ }
314
+
315
+ // slot validation
316
+ const slots = manifest.slots ?? {}
317
+
318
+ for (const [slotName, slotDef] of Object.entries(slots)) {
319
+ const child = el.querySelector(`[slot="${slotName}"]`)
320
+
321
+ if (slotDef.required && !child) {
322
+ errors.push({
323
+ element: tag,
324
+ prop: slotName,
325
+ type: 'MISSING_SLOT',
326
+ message: `Missing slot ${slotName}`,
327
+ fix: `<${elementName}><span slot="${slotName}"></span></${elementName}>`,
328
+ confidence: 0.95
329
+ })
330
+ }
331
+
332
+ if (child && slotDef.element) {
333
+ const childTag = child.tagName.toLowerCase()
334
+
335
+ if (!slotDef.element.includes(childTag)) {
336
+ errors.push({
337
+ element: childTag,
338
+ prop: slotName,
339
+ type: 'INVALID_VALUE',
340
+ message: `${childTag} invalid for slot ${slotName}`,
341
+ valid_values: slotDef.element,
342
+ fix: `<${slotDef.element[0]} slot="${slotName}"></${slotDef.element[0]}>`,
343
+ confidence: 0.95
344
+ })
345
+ }
346
+ }
347
+ }
348
+ }
349
+
350
+ const valid = errors.length === 0
351
+ const score = valid ? 100 : Math.max(0, 100 - errors.length * 10)
352
+
353
+ return {
354
+ content: [
355
+ {
356
+ type: 'text',
357
+
358
+ text: JSON.stringify({
359
+ valid,
360
+ score,
361
+ errors,
362
+ tokens_used: 50
363
+ })
364
+ }
365
+ ]
366
+ }
367
+ }
package/src/types.ts ADDED
@@ -0,0 +1,63 @@
1
+ export type ToolName =
2
+ | 'list_components'
3
+ | 'get_manifest'
4
+ | 'get_emmet'
5
+ | 'validate'
6
+
7
+ export type ToolRequest = {
8
+ params: {
9
+ name: ToolName
10
+
11
+ arguments?: unknown
12
+ }
13
+ }
14
+
15
+ export type MCPResponse = {
16
+ content: {
17
+ type: 'text'
18
+ text: string
19
+
20
+ }[]
21
+ }
22
+
23
+ export type ManifestProp = {
24
+ type: string
25
+ values?: string[]
26
+ default?: string | boolean
27
+ native?: boolean
28
+ agent_hint?: string
29
+ }
30
+
31
+ export type ManifestSlot = {
32
+ required: boolean
33
+ element: string[]
34
+ agent_hint?: string
35
+ }
36
+
37
+ export type ManifestExample = {
38
+ label: string
39
+ emmet: string
40
+ html: string
41
+ }
42
+
43
+ export type Manifest = {
44
+ component: string
45
+ version: string
46
+ description: string
47
+ element: string
48
+ emmet: string
49
+ tokens: number
50
+ props: Record<string, ManifestProp>
51
+ slots?: Record<string, ManifestSlot>
52
+ states?: string[]
53
+ forbidden_props?: string[]
54
+ required_props?: string[]
55
+ required_slots?: string[]
56
+ required_wrapper?: string
57
+ methods?: string[]
58
+ do?: string[]
59
+ dont?: string[]
60
+ examples?: ManifestExample[]
61
+ extends?: string
62
+
63
+ }