@csszyx/mcp-server 0.9.9

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/dist/index.mjs ADDED
@@ -0,0 +1,785 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
8
+ import fs from 'node:fs';
9
+ import { SPECIAL_VARIANTS, KNOWN_VARIANTS, PROPERTY_MAP, transform, SUGGESTION_MAP, BOOLEAN_SHORTHANDS } from '@csszyx/compiler';
10
+ import { z } from 'zod';
11
+ import { migrateSource, classNameToSzObject } from '@csszyx/cli';
12
+ import { parseThemeBlocks, hasTokens } from '@csszyx/unplugin';
13
+
14
+ function listPrompts() {
15
+ return [
16
+ {
17
+ name: "migrate_component",
18
+ description: "Migrate a Tailwind CSS component to use csszyx sz props. Paste your component code and get a transformed version with sz syntax.",
19
+ arguments: [
20
+ {
21
+ name: "code",
22
+ description: "The JSX/TSX component code to migrate from className to sz props",
23
+ required: true
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ name: "create_component",
29
+ description: "Create a UI component using csszyx sz props. Describe the component you want and get production-ready code with proper sz syntax.",
30
+ arguments: [
31
+ {
32
+ name: "description",
33
+ description: 'Description of the component to create (e.g., "a responsive card with hover effects")',
34
+ required: true
35
+ }
36
+ ]
37
+ }
38
+ ];
39
+ }
40
+ function getPrompt(name, args) {
41
+ switch (name) {
42
+ case "migrate_component":
43
+ return {
44
+ messages: [
45
+ {
46
+ role: "user",
47
+ content: {
48
+ type: "text",
49
+ text: `I want to migrate this Tailwind CSS component to use csszyx sz props.
50
+
51
+ Here is the component code:
52
+
53
+ \`\`\`tsx
54
+ ${args.code ?? "// paste your component here"}
55
+ \`\`\`
56
+
57
+ Please:
58
+ 1. Use the csszyx_migrate tool to transform the className attributes to sz props
59
+ 2. Review the result for any unrecognized classes
60
+ 3. Explain any changes and suggest improvements
61
+ 4. If there are dynamic className patterns (clsx, ternary, template literals), explain how sz handles them
62
+
63
+ Key csszyx rules:
64
+ - Numbers are bare: { p: 4 } \u2192 p-4
65
+ - Colors are strings: { bg: 'blue-500' } \u2192 bg-blue-500
66
+ - Color + opacity is an object: { bg: { color: 'blue-500', op: 50 } } \u2192 bg-blue-500/50
67
+ - Variants nest: { hover: { bg: 'blue-700' } } \u2192 hover:bg-blue-700
68
+ - Boolean sugar: { flex: true } \u2192 flex
69
+ - Fractions stay native: { w: '1/2' } \u2192 w-1/2
70
+ - Arbitrary values: { p: '5px' } \u2192 p-[5px]
71
+ - CSS variables: { p: '--my-var' } \u2192 p-(--my-var)
72
+ - Scope variants: { group: { hover: { bg: 'blue-700' } } } \u2192 group-hover:bg-blue-700 (also peer)
73
+ - Conditional/feature variants: { has: { checked: { ... } } } \u2192 has-[:checked]:\u2026, { supports: { 'display:grid': { ... } } } \u2192 supports-[display:grid]:\u2026 (also not/data/aria)`
74
+ }
75
+ }
76
+ ]
77
+ };
78
+ case "create_component":
79
+ return {
80
+ messages: [
81
+ {
82
+ role: "user",
83
+ content: {
84
+ type: "text",
85
+ text: `I want to create a React component using csszyx sz props.
86
+
87
+ Component description: ${args.description ?? "a responsive card component"}
88
+
89
+ Please:
90
+ 1. Generate a complete React component using sz props (NOT className/Tailwind)
91
+ 2. Use the csszyx_lookup tool to verify prop names for any CSS properties
92
+ 3. Include responsive breakpoints using nested variants: { md: { ... } }
93
+ 4. Include hover/focus states using nested variants: { hover: { ... } }
94
+ 5. Include dark mode support: { dark: { ... } }
95
+ 6. Use the csszyx_expand tool to verify the generated sz objects
96
+
97
+ Key csszyx syntax:
98
+ - <div sz={{ p: 4, bg: 'blue-500', hover: { bg: 'blue-700' } }} />
99
+ - <div sz={{ flex: true, items: 'center', gap: 4 }} />
100
+ - <div sz={{ md: { gridCols: 3 }, gridCols: 1, gap: 6 }} />
101
+ - <div sz={{ dark: { bg: 'gray-900', color: 'white' } }} />
102
+ - <div sz={{ bg: { color: 'blue-500', op: 50 }, w: '1/2' }} /> (color opacity + fraction)
103
+ - <div sz={{ group: { hover: { bg: 'blue-700' } } }} /> (group/peer scope variants)`
104
+ }
105
+ }
106
+ ]
107
+ };
108
+ default:
109
+ throw new Error(`Unknown prompt: ${name}`);
110
+ }
111
+ }
112
+
113
+ const packageRoot = path.resolve(fileURLToPath(import.meta.url), "../../..");
114
+ const LLMS_FULL_PATH = path.join(packageRoot, "llms-full.txt");
115
+ const SETUP_GUIDE = `# csszyx setup
116
+
117
+ Fastest path: run \`csszyx init\` (it does everything below). Manual steps:
118
+
119
+ ## 1. Install
120
+ \`\`\`bash
121
+ pnpm add csszyx @csszyx/runtime
122
+ pnpm add -D @csszyx/types @csszyx/cli tailwindcss
123
+ \`\`\`
124
+ \`@csszyx/runtime\` and \`@csszyx/types\` MUST be DIRECT dependencies: the
125
+ transform injects a bare \`import { _szMerge } from '@csszyx/runtime'\`, and
126
+ \`@csszyx/types\` provides the \`sz\` JSX types. Strict package managers (pnpm) do
127
+ not resolve them transitively.
128
+
129
+ ## 2. Bundler config
130
+ ### Vite
131
+ \`\`\`ts
132
+ import csszyx from 'csszyx/vite';
133
+ import tailwindcss from '@tailwindcss/vite';
134
+ export default defineConfig({ plugins: [...csszyx(), tailwindcss(), react()] });
135
+ // Order matters: csszyx BEFORE tailwindcss BEFORE react.
136
+ \`\`\`
137
+ ### Next.js \u2014 webpack (full parity, recommended)
138
+ \`\`\`ts
139
+ // next.config.ts
140
+ export default {
141
+ webpack: (config) => {
142
+ config.plugins.push(require('@csszyx/unplugin/webpack').default());
143
+ return config;
144
+ },
145
+ };
146
+ // Run: next dev --webpack / next build --webpack
147
+ \`\`\`
148
+ ### Next.js \u2014 Turbopack (experimental; no production class/CSS-var mangling)
149
+ \`\`\`js
150
+ // next.config.mjs
151
+ import { csszyxTurbopack } from '@csszyx/unplugin/next';
152
+ export default {
153
+ turbopack: csszyxTurbopack({}, { safelistOutputFile: '.csszyx/next-loader-classes.html' }),
154
+ };
155
+ \`\`\`
156
+ - Do NOT set an \`as\` field on the loader rule \u2014 the helper omits it; an \`as\`
157
+ makes the loader output self-match into \`./X.tsx.tsx\` (Module not found).
158
+ - Keep the safelist fresh: \`csszyx next watch\` (dev) and \`csszyx next prebuild\`
159
+ before \`next build --turbopack\`. Point Tailwind \`@source\` at the safelist file.
160
+
161
+ ## 3. Tailwind v4 entry
162
+ \`\`\`css
163
+ /* src/index.css */
164
+ @import "tailwindcss";
165
+ \`\`\`
166
+ Import it from your app entry.
167
+
168
+ ## 4. TypeScript \u2014 enable the \`sz\` prop types
169
+ Create \`csszyx-env.d.ts\` at the project root (kept in tsconfig \`include\`):
170
+ \`\`\`ts
171
+ /// <reference types="@csszyx/types/jsx" />
172
+ \`\`\`
173
+ Without it: \`Property 'sz' does not exist on type 'DetailedHTMLProps<...>'\`.
174
+
175
+ ## Usage
176
+ \`\`\`tsx
177
+ <div sz={{ p: 4, bg: 'blue-500', hover: { bg: 'blue-700' } }} />
178
+ \`\`\`
179
+ `;
180
+ function listResources() {
181
+ return [
182
+ {
183
+ uri: "csszyx://reference",
184
+ name: "CSSzyx Full Reference",
185
+ description: "Complete sz prop API reference \u2014 all property mappings, variant syntax, and examples",
186
+ mimeType: "text/plain"
187
+ },
188
+ {
189
+ uri: "csszyx://property-map",
190
+ name: "CSSzyx Property Map",
191
+ description: "JSON mapping of sz keys to Tailwind CSS utility prefixes (PROPERTY_MAP)",
192
+ mimeType: "application/json"
193
+ },
194
+ {
195
+ uri: "csszyx://variants",
196
+ name: "CSSzyx Variant List",
197
+ description: "JSON with `standard` variants (hover, focus, dark, sm, md, \u2026) and `parametric` scope variants (group, peer, has, not, data, aria, supports) that take a nested target",
198
+ mimeType: "application/json"
199
+ },
200
+ {
201
+ uri: "csszyx://setup",
202
+ name: "CSSzyx Setup Guide",
203
+ description: "How to install and wire csszyx into a project (Vite / Next.js webpack / Next.js Turbopack) \u2014 direct @csszyx/runtime + @csszyx/types deps, the sz JSX types (csszyx-env.d.ts), and the Turbopack no-`as` rule",
204
+ mimeType: "text/markdown"
205
+ }
206
+ ];
207
+ }
208
+ function readResource(uri) {
209
+ switch (uri) {
210
+ case "csszyx://reference": {
211
+ let content = "CSSzyx Full Reference \u2014 llms-full.txt not found. Use csszyx_lookup tool instead.";
212
+ try {
213
+ content = fs.readFileSync(LLMS_FULL_PATH, "utf-8");
214
+ } catch {
215
+ }
216
+ return {
217
+ contents: [{ uri, mimeType: "text/plain", text: content }]
218
+ };
219
+ }
220
+ case "csszyx://property-map":
221
+ return {
222
+ contents: [
223
+ {
224
+ uri,
225
+ mimeType: "application/json",
226
+ text: JSON.stringify(PROPERTY_MAP, null, 2)
227
+ }
228
+ ]
229
+ };
230
+ case "csszyx://variants":
231
+ return {
232
+ contents: [
233
+ {
234
+ uri,
235
+ mimeType: "application/json",
236
+ text: JSON.stringify(
237
+ {
238
+ standard: [...KNOWN_VARIANTS].sort(),
239
+ parametric: [...SPECIAL_VARIANTS].sort()
240
+ },
241
+ null,
242
+ 2
243
+ )
244
+ }
245
+ ]
246
+ };
247
+ case "csszyx://setup":
248
+ return {
249
+ contents: [{ uri, mimeType: "text/markdown", text: SETUP_GUIDE }]
250
+ };
251
+ default:
252
+ throw new Error(`Unknown resource URI: ${uri}`);
253
+ }
254
+ }
255
+
256
+ const batchSchema = z.object({
257
+ items: z.array(z.record(z.any())).describe(
258
+ "Array of sz prop objects to expand. Example: [{ p: 4 }, { m: 2, bg: 'red-500' }]"
259
+ )
260
+ });
261
+ function handleBatch(input) {
262
+ const results = input.items.map((sz, index) => {
263
+ try {
264
+ const result = transform(sz);
265
+ return {
266
+ index,
267
+ className: result.className,
268
+ attributes: Object.keys(result.attributes).length > 0 ? result.attributes : void 0
269
+ };
270
+ } catch (err) {
271
+ return {
272
+ index,
273
+ error: err instanceof Error ? err.message : String(err)
274
+ };
275
+ }
276
+ });
277
+ return {
278
+ content: [
279
+ {
280
+ type: "text",
281
+ text: JSON.stringify(
282
+ {
283
+ results,
284
+ totalItems: input.items.length,
285
+ successful: results.filter((r) => !("error" in r)).length
286
+ },
287
+ null,
288
+ 2
289
+ )
290
+ }
291
+ ]
292
+ };
293
+ }
294
+
295
+ const expandSchema = z.object({
296
+ sz: z.record(z.any()).describe(
297
+ "The sz prop object to expand. Example: { p: 4, bg: 'blue-500', hover: { bg: 'blue-700' } }"
298
+ )
299
+ });
300
+ function handleExpand(input) {
301
+ const result = transform(input.sz);
302
+ return {
303
+ content: [
304
+ {
305
+ type: "text",
306
+ text: JSON.stringify(
307
+ {
308
+ className: result.className,
309
+ attributes: result.attributes,
310
+ classCount: result.className.split(/\s+/).filter(Boolean).length
311
+ },
312
+ null,
313
+ 2
314
+ )
315
+ }
316
+ ]
317
+ };
318
+ }
319
+
320
+ const SEARCH_INDEX = /* @__PURE__ */ new Map();
321
+ function addSearch(term, szKey) {
322
+ const lower = term.toLowerCase();
323
+ const existing = SEARCH_INDEX.get(lower) ?? [];
324
+ if (!existing.includes(szKey)) {
325
+ existing.push(szKey);
326
+ SEARCH_INDEX.set(lower, existing);
327
+ }
328
+ }
329
+ function toKebab(camel) {
330
+ return camel.replace(/([A-Z])/g, "-$1").toLowerCase();
331
+ }
332
+ for (const szKey of Object.keys(PROPERTY_MAP)) {
333
+ addSearch(szKey, szKey);
334
+ addSearch(toKebab(szKey), szKey);
335
+ }
336
+ for (const [cssAlias, szTarget] of Object.entries(SUGGESTION_MAP)) {
337
+ const primaryKey = szTarget.split(/[\s/(]/)[0];
338
+ if (primaryKey && (PROPERTY_MAP[primaryKey] !== void 0 || BOOLEAN_SHORTHANDS.has(primaryKey))) {
339
+ addSearch(cssAlias, primaryKey);
340
+ addSearch(toKebab(cssAlias), primaryKey);
341
+ }
342
+ }
343
+ for (const key of BOOLEAN_SHORTHANDS) {
344
+ addSearch(key, key);
345
+ addSearch(toKebab(key), key);
346
+ }
347
+ const EXAMPLES = {
348
+ bg: ["{ bg: 'blue-500' }", "{ bg: { color: 'blue-500', op: 50 } }"],
349
+ p: ["{ p: 4 }", "{ p: '5px' }"],
350
+ m: ["{ m: 4 }", "{ m: 'auto' }"],
351
+ px: ["{ px: 4 }"],
352
+ py: ["{ py: 2 }"],
353
+ mx: ["{ mx: 'auto' }"],
354
+ my: ["{ my: 4 }"],
355
+ w: ["{ w: 64 }", "{ w: 'full' }"],
356
+ h: ["{ h: 16 }", "{ h: 'screen' }"],
357
+ text: ["{ text: 'lg' }", "{ text: '2xl' }"],
358
+ color: ["{ color: 'white' }", "{ color: 'slate-500' }"],
359
+ fontWeight: ["{ fontWeight: 'bold' }", "{ fontWeight: 700 }"],
360
+ fontFamily: ["{ fontFamily: 'sans' }", "{ fontFamily: 'mono' }"],
361
+ flex: ["{ flex: true }", "{ flex: 'auto' }", "{ flex: 1 }"],
362
+ flexDir: ["{ flexDir: 'col' }", "{ flexDir: 'row' }"],
363
+ grid: ["{ grid: true }"],
364
+ gridCols: ["{ gridCols: 3 }", "{ gridCols: 'none' }"],
365
+ gap: ["{ gap: 4 }"],
366
+ items: ["{ items: 'center' }", "{ items: 'start' }"],
367
+ justify: ["{ justify: 'center' }", "{ justify: 'between' }"],
368
+ rounded: ["{ rounded: 'md' }", "{ rounded: 'full' }"],
369
+ shadow: ["{ shadow: 'md' }"],
370
+ border: ["{ border: true }", "{ border: 2 }"],
371
+ opacity: ["{ opacity: 50 }"],
372
+ z: ["{ z: 10 }", "{ z: 'auto' }"],
373
+ overflow: ["{ overflow: 'hidden' }"],
374
+ cursor: ["{ cursor: 'pointer' }"],
375
+ transition: ["{ transition: 'all' }", "{ transition: 'colors' }"],
376
+ duration: ["{ duration: 300 }"],
377
+ scale: ["{ scale: 150 }", "{ scale: 75 }"],
378
+ rotate: ["{ rotate: 45 }"],
379
+ leading: ["{ leading: 'tight' }", "{ leading: 7 }"],
380
+ tracking: ["{ tracking: 'wide' }"],
381
+ textAlign: ["{ textAlign: 'center' }"],
382
+ aspect: ["{ aspect: 'video' }", "{ aspect: 'square' }"],
383
+ blur: ["{ blur: true }", "{ blur: 'sm' }"],
384
+ grow: ["{ grow: true }"],
385
+ shrink: ["{ shrink: true }"]
386
+ };
387
+ const lookupSchema = z.object({
388
+ query: z.string().describe(
389
+ 'CSS property name, sz key, or search term. Examples: "background-color", "bg", "padding", "flex-direction"'
390
+ )
391
+ });
392
+ function handleLookup(input) {
393
+ const query = input.query.trim().toLowerCase();
394
+ const seen = /* @__PURE__ */ new Set();
395
+ const results = [];
396
+ function pushResult(szKey) {
397
+ if (seen.has(szKey)) {
398
+ return;
399
+ }
400
+ seen.add(szKey);
401
+ results.push({
402
+ szKey,
403
+ tailwindPrefix: PROPERTY_MAP[szKey] ?? szKey,
404
+ examples: (EXAMPLES[szKey] ?? []).slice(0, 2)
405
+ });
406
+ }
407
+ const exact = SEARCH_INDEX.get(query);
408
+ if (exact) {
409
+ exact.forEach(pushResult);
410
+ }
411
+ if (results.length === 0) {
412
+ for (const [term, szKeys] of SEARCH_INDEX.entries()) {
413
+ if (results.length >= 5) {
414
+ break;
415
+ }
416
+ if (term.includes(query)) {
417
+ szKeys.forEach(pushResult);
418
+ }
419
+ }
420
+ }
421
+ return {
422
+ content: [
423
+ {
424
+ type: "text",
425
+ text: results.length > 0 ? JSON.stringify(
426
+ { query: input.query, results: results.slice(0, 8) },
427
+ null,
428
+ 2
429
+ ) : JSON.stringify(
430
+ {
431
+ query: input.query,
432
+ results: [],
433
+ message: `No mapping found for "${input.query}". Try a CSS property name (e.g. "padding") or sz key (e.g. "p").`
434
+ },
435
+ null,
436
+ 2
437
+ )
438
+ }
439
+ ]
440
+ };
441
+ }
442
+
443
+ const migrateSchema = z.object({
444
+ code: z.string().describe(
445
+ `JSX/TSX code snippet containing className attributes to transform. Example: '<div className="p-4 bg-blue-500" />'`
446
+ ),
447
+ customMap: z.record(z.unknown()).optional().describe(
448
+ 'Parsed .csszyx-todo.json map. Values: sz object, Tailwind string, "sz:keep", "sz:remove", "sz:todo", null, or false.'
449
+ ),
450
+ injectTodos: z.boolean().optional().describe(
451
+ "If true, inserts {/* @sz-todo: ... */} comments above elements with unrecognized classes."
452
+ )
453
+ });
454
+ function handleMigrate(input) {
455
+ const result = migrateSource(input.code, "snippet.tsx", {
456
+ injectTodos: input.injectTodos,
457
+ customMap: input.customMap
458
+ });
459
+ return {
460
+ content: [
461
+ {
462
+ type: "text",
463
+ text: JSON.stringify(
464
+ {
465
+ code: result.code,
466
+ changed: result.changed,
467
+ stats: {
468
+ classNamesTransformed: result.stats.classNamesTransformed,
469
+ classNamesSkipped: result.stats.classNamesSkipped,
470
+ unrecognized: result.stats.classesUnrecognized.length > 0 ? result.stats.classesUnrecognized : void 0
471
+ },
472
+ warnings: result.warnings.length > 0 ? result.warnings : void 0,
473
+ potentiallyUnusedImports: result.potentiallyUnusedImports.length > 0 ? result.potentiallyUnusedImports : void 0
474
+ },
475
+ null,
476
+ 2
477
+ )
478
+ }
479
+ ]
480
+ };
481
+ }
482
+
483
+ const reverseSchema = z.object({
484
+ classes: z.string().describe(
485
+ 'Tailwind CSS class string to convert. Example: "p-4 bg-blue-500 hover:text-white"'
486
+ )
487
+ });
488
+ function handleReverse(input) {
489
+ const { szObject, unrecognized } = classNameToSzObject(input.classes.trim());
490
+ return {
491
+ content: [
492
+ {
493
+ type: "text",
494
+ text: JSON.stringify(
495
+ {
496
+ szObject,
497
+ unrecognized: unrecognized.length > 0 ? unrecognized : void 0,
498
+ totalRecognized: Object.keys(szObject).length,
499
+ totalUnrecognized: unrecognized.length
500
+ },
501
+ null,
502
+ 2
503
+ )
504
+ }
505
+ ]
506
+ };
507
+ }
508
+
509
+ const themeSchema = z.object({
510
+ css: z.string().describe(
511
+ 'CSS file content containing @theme blocks. Example: "@theme { --color-brand: #ff6b35; --font-display: \\"Cal Sans\\"; }"'
512
+ )
513
+ });
514
+ function handleTheme(input) {
515
+ const theme = parseThemeBlocks(input.css);
516
+ const totalVars = theme.colors.length + theme.spacings.length + theme.fonts.length + theme.radii.length + theme.shadows.length;
517
+ return {
518
+ content: [
519
+ {
520
+ type: "text",
521
+ text: JSON.stringify(
522
+ {
523
+ theme,
524
+ totalVariables: totalVars,
525
+ usage: hasTokens(theme) ? "Use these custom values in sz props. Example: { bg: 'brand' } for --color-brand-500" : "No @theme block found in the CSS content. Make sure your CSS contains @theme { --color-*: ...; }"
526
+ },
527
+ null,
528
+ 2
529
+ )
530
+ }
531
+ ]
532
+ };
533
+ }
534
+
535
+ const validateSchema = z.object({
536
+ sz: z.record(z.any()).describe('The sz prop object to validate. Example: { padding: 4, bg: "blue-500" }')
537
+ });
538
+ function handleValidate(input) {
539
+ const errors = [];
540
+ const warnings = [];
541
+ for (const key of Object.keys(input.sz)) {
542
+ if (SUGGESTION_MAP[key]) {
543
+ errors.push({
544
+ key,
545
+ message: `Unknown prop '${key}'. This is a CSS property name, not an sz key.`,
546
+ suggestion: `Use '${SUGGESTION_MAP[key]}' instead. Example: { ${SUGGESTION_MAP[key].split(/[\s/(]/)[0]}: ${JSON.stringify(input.sz[key])} }`
547
+ });
548
+ continue;
549
+ }
550
+ const isProperty = key in PROPERTY_MAP;
551
+ const isBoolean = BOOLEAN_SHORTHANDS.has(key);
552
+ const isVariant = KNOWN_VARIANTS.has(key);
553
+ const isSpecial = ["css", "@container", "*"].includes(key) || key.startsWith("@") || key.startsWith("[");
554
+ if (!isProperty && !isBoolean && !isVariant && !isSpecial) {
555
+ errors.push({
556
+ key,
557
+ message: `Unknown prop '${key}'. Not a valid sz key, variant, or special prop.`
558
+ });
559
+ }
560
+ }
561
+ let transformResult = null;
562
+ let transformError = null;
563
+ try {
564
+ transformResult = transform(input.sz);
565
+ } catch (err) {
566
+ transformError = err instanceof Error ? err.message : String(err);
567
+ }
568
+ return {
569
+ content: [
570
+ {
571
+ type: "text",
572
+ text: JSON.stringify(
573
+ {
574
+ valid: errors.length === 0 && !transformError,
575
+ errors: errors.length > 0 ? errors : void 0,
576
+ warnings: warnings.length > 0 ? warnings : void 0,
577
+ transformResult: transformResult ? {
578
+ className: transformResult.className,
579
+ classCount: transformResult.className.split(/\s+/).filter(Boolean).length
580
+ } : void 0,
581
+ transformError: transformError ?? void 0
582
+ },
583
+ null,
584
+ 2
585
+ )
586
+ }
587
+ ]
588
+ };
589
+ }
590
+
591
+ const _require = createRequire(import.meta.url);
592
+ const packageJson = _require(
593
+ path.resolve(fileURLToPath(import.meta.url), "../../package.json")
594
+ );
595
+ const VERSION = packageJson.version;
596
+ const server = new Server(
597
+ {
598
+ name: "csszyx-mcp-server",
599
+ version: VERSION
600
+ },
601
+ {
602
+ capabilities: {
603
+ tools: {},
604
+ resources: {},
605
+ prompts: {}
606
+ }
607
+ }
608
+ );
609
+ const TOOLS = [
610
+ {
611
+ name: "csszyx_expand",
612
+ description: "Expand ONE csszyx sz object into a Tailwind CSS className string. Use this for a single component or quick lookup. For multiple objects at once, use csszyx_batch instead.",
613
+ inputSchema: {
614
+ type: "object",
615
+ properties: {
616
+ sz: {
617
+ type: "object",
618
+ description: "The sz prop object. Example: { p: 4, bg: 'blue-500', hover: { bg: 'blue-700' } }"
619
+ }
620
+ },
621
+ required: ["sz"]
622
+ }
623
+ },
624
+ {
625
+ name: "csszyx_reverse",
626
+ description: "Convert a Tailwind CSS class string into a csszyx sz object. Input: class string. Output: structured sz object + unrecognized classes.",
627
+ inputSchema: {
628
+ type: "object",
629
+ properties: {
630
+ classes: {
631
+ type: "string",
632
+ description: 'Tailwind CSS class string. Example: "p-4 bg-blue-500 hover:text-white"'
633
+ }
634
+ },
635
+ required: ["classes"]
636
+ }
637
+ },
638
+ {
639
+ name: "csszyx_validate",
640
+ description: "Validate a csszyx sz object for correctness. Detects unknown props, wrong types, deprecated CSS property names, and suggests fixes.",
641
+ inputSchema: {
642
+ type: "object",
643
+ properties: {
644
+ sz: {
645
+ type: "object",
646
+ description: 'The sz prop object to validate. Example: { padding: 4, bg: "blue-500" }'
647
+ }
648
+ },
649
+ required: ["sz"]
650
+ }
651
+ },
652
+ {
653
+ name: "csszyx_lookup",
654
+ description: "Look up how a CSS property maps to csszyx sz syntax. Search by CSS property name, sz key, or keyword. Returns sz key, Tailwind prefix, and usage examples.",
655
+ inputSchema: {
656
+ type: "object",
657
+ properties: {
658
+ query: {
659
+ type: "string",
660
+ description: 'CSS property name, sz key, or search term. Examples: "background-color", "bg", "padding", "flex-direction"'
661
+ }
662
+ },
663
+ required: ["query"]
664
+ }
665
+ },
666
+ {
667
+ name: "csszyx_migrate",
668
+ description: "Transform JSX/TSX code from Tailwind className attributes to csszyx sz props. Handles static strings, clsx/cn calls, ternary expressions, and template literals. Optionally pass a customMap (parsed .csszyx-todo.json) to resolve custom class names, and injectTodos to annotate unrecognized classes.",
669
+ inputSchema: {
670
+ type: "object",
671
+ properties: {
672
+ code: {
673
+ type: "string",
674
+ description: `JSX/TSX code snippet. Example: '<div className="p-4 bg-blue-500" />'`
675
+ },
676
+ customMap: {
677
+ type: "object",
678
+ description: 'Parsed .csszyx-todo.json map. Values per entry: sz object | Tailwind string | "sz:keep" | "sz:remove" | "sz:todo" | null | false.'
679
+ },
680
+ injectTodos: {
681
+ type: "boolean",
682
+ description: "If true, inserts {/* @sz-todo: ... */} comments above elements with unrecognized classes."
683
+ }
684
+ },
685
+ required: ["code"]
686
+ }
687
+ },
688
+ {
689
+ name: "csszyx_batch",
690
+ description: "Expand MULTIPLE sz objects into Tailwind class strings in one call. Use when you need to process an array of components. For a single object, use csszyx_expand instead.",
691
+ inputSchema: {
692
+ type: "object",
693
+ properties: {
694
+ items: {
695
+ type: "array",
696
+ items: { type: "object" },
697
+ description: "Array of sz objects. Example: [{ p: 4 }, { m: 2, bg: 'red-500' }]"
698
+ }
699
+ },
700
+ required: ["items"]
701
+ }
702
+ },
703
+ {
704
+ name: "csszyx_theme",
705
+ description: "Extract @theme CSS custom properties from Tailwind CSS file content. Categorizes variables by type: colors, fonts, spacing, breakpoints.",
706
+ inputSchema: {
707
+ type: "object",
708
+ properties: {
709
+ css: {
710
+ type: "string",
711
+ description: "CSS file content containing @theme blocks"
712
+ }
713
+ },
714
+ required: ["css"]
715
+ }
716
+ }
717
+ ];
718
+ const TOOL_HANDLERS = {
719
+ csszyx_expand: { schema: expandSchema, handler: handleExpand },
720
+ csszyx_reverse: { schema: reverseSchema, handler: handleReverse },
721
+ csszyx_validate: { schema: validateSchema, handler: handleValidate },
722
+ csszyx_lookup: { schema: lookupSchema, handler: handleLookup },
723
+ csszyx_migrate: { schema: migrateSchema, handler: handleMigrate },
724
+ csszyx_batch: { schema: batchSchema, handler: handleBatch },
725
+ csszyx_theme: { schema: themeSchema, handler: handleTheme }
726
+ };
727
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
728
+ return { tools: TOOLS };
729
+ });
730
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
731
+ const { name, arguments: args } = request.params;
732
+ const entry = TOOL_HANDLERS[name];
733
+ if (!entry) {
734
+ throw new Error(
735
+ `Unknown tool: ${name}. Available: ${Object.keys(TOOL_HANDLERS).join(", ")}`
736
+ );
737
+ }
738
+ try {
739
+ const validated = entry.schema.parse(args);
740
+ return entry.handler(validated);
741
+ } catch (error) {
742
+ const message = error instanceof Error ? error.message : String(error);
743
+ return {
744
+ content: [{ type: "text", text: `Error in ${name}: ${message}` }],
745
+ isError: true
746
+ };
747
+ }
748
+ });
749
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
750
+ return { resources: listResources() };
751
+ });
752
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
753
+ try {
754
+ return readResource(request.params.uri);
755
+ } catch (error) {
756
+ const message = error instanceof Error ? error.message : String(error);
757
+ throw new Error(`Resource error: ${message}`);
758
+ }
759
+ });
760
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
761
+ return { prompts: listPrompts() };
762
+ });
763
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
764
+ try {
765
+ return getPrompt(
766
+ request.params.name,
767
+ request.params.arguments ?? {}
768
+ );
769
+ } catch (error) {
770
+ const message = error instanceof Error ? error.message : String(error);
771
+ throw new Error(`Prompt error: ${message}`);
772
+ }
773
+ });
774
+ async function main() {
775
+ const transport = new StdioServerTransport();
776
+ await server.connect(transport);
777
+ console.error(`csszyx MCP Server v${VERSION} running on stdio`);
778
+ console.error(
779
+ ` Tools: ${TOOLS.length} | Resources: ${listResources().length} | Prompts: ${listPrompts().length}`
780
+ );
781
+ }
782
+ main().catch((error) => {
783
+ console.error("Server error:", error);
784
+ process.exit(1);
785
+ });