@atomixstudio/mcp 0.1.1 → 1.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 +72 -288
- package/dist/index.d.ts +1 -201
- package/dist/index.js +1288 -2740
- package/dist/index.js.map +1 -1
- package/package.json +31 -32
- package/data/component-tokens-snapshot.json +0 -659
- package/data/tenants/default.json +0 -73
- package/scripts/sync-component-tokens.cjs +0 -974
- package/scripts/sync-component-tokens.js +0 -678
- package/src/ai-rules-generator.ts +0 -1144
- package/src/component-tokens.ts +0 -702
- package/src/index.ts +0 -1155
- package/src/tenant-store.ts +0 -436
- package/src/tokens.ts +0 -208
- package/src/user-tokens.ts +0 -268
- package/src/utils.ts +0 -465
- package/tests/stress-test.cjs +0 -907
- package/tsconfig.json +0 -21
- package/tsup.config.ts +0 -16
package/src/index.ts
DELETED
|
@@ -1,1155 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Atomix MCP Server
|
|
3
|
-
*
|
|
4
|
-
* Exposes design tokens to AI tools via the Model Context Protocol.
|
|
5
|
-
*
|
|
6
|
-
* Tools:
|
|
7
|
-
* - getToken: Get a specific token by path
|
|
8
|
-
* - listTokens: List all tokens in a category
|
|
9
|
-
* - getComponentTokens: Get tokens for a specific component
|
|
10
|
-
* - validateUsage: Check if a value follows the design system
|
|
11
|
-
* - generateCursorRules: Generate .cursorrules for a project
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
15
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16
|
-
import {
|
|
17
|
-
CallToolRequestSchema,
|
|
18
|
-
ListToolsRequestSchema,
|
|
19
|
-
ListResourcesRequestSchema,
|
|
20
|
-
ReadResourceRequestSchema,
|
|
21
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
22
|
-
|
|
23
|
-
import { loadPrimitives } from "./tokens.js";
|
|
24
|
-
import {
|
|
25
|
-
getTokenByPath,
|
|
26
|
-
listTokensInCategory,
|
|
27
|
-
flattenTokens,
|
|
28
|
-
getTokenCategories,
|
|
29
|
-
getCssVariableName,
|
|
30
|
-
getTokenMetadata,
|
|
31
|
-
isSemanticToken,
|
|
32
|
-
type TokenTier,
|
|
33
|
-
} from "./utils.js";
|
|
34
|
-
import { COMPONENT_TOKENS } from "./component-tokens.js";
|
|
35
|
-
import {
|
|
36
|
-
parseCLIArgs,
|
|
37
|
-
isUserDSMode,
|
|
38
|
-
fetchUserDesignSystem,
|
|
39
|
-
transformUserTokens,
|
|
40
|
-
} from "./user-tokens.js";
|
|
41
|
-
import {
|
|
42
|
-
generateCursorRules,
|
|
43
|
-
generateRulesForTool,
|
|
44
|
-
generateRulesForAllTools,
|
|
45
|
-
getSupportedAITools,
|
|
46
|
-
generateToolSetupGuide,
|
|
47
|
-
generateMCPConfig,
|
|
48
|
-
generateAllMCPConfigs,
|
|
49
|
-
getSetupInstructions,
|
|
50
|
-
AI_TOOLS,
|
|
51
|
-
type AIToolId,
|
|
52
|
-
type MCPConfigToolId,
|
|
53
|
-
} from "./ai-rules-generator.js";
|
|
54
|
-
|
|
55
|
-
// ============================================
|
|
56
|
-
// HELPERS
|
|
57
|
-
// ============================================
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Format a timestamp as a human-readable "time ago" string
|
|
61
|
-
*/
|
|
62
|
-
function formatTimeAgo(timestamp: number): string {
|
|
63
|
-
const now = Date.now();
|
|
64
|
-
const diffMs = now - timestamp;
|
|
65
|
-
const diffSec = Math.floor(diffMs / 1000);
|
|
66
|
-
const diffMin = Math.floor(diffSec / 60);
|
|
67
|
-
const diffHour = Math.floor(diffMin / 60);
|
|
68
|
-
const diffDay = Math.floor(diffHour / 24);
|
|
69
|
-
|
|
70
|
-
if (diffSec < 60) return "just now";
|
|
71
|
-
if (diffMin < 60) return `${diffMin} minute${diffMin > 1 ? "s" : ""} ago`;
|
|
72
|
-
if (diffHour < 24) return `${diffHour} hour${diffHour > 1 ? "s" : ""} ago`;
|
|
73
|
-
if (diffDay < 7) return `${diffDay} day${diffDay > 1 ? "s" : ""} ago`;
|
|
74
|
-
|
|
75
|
-
// For older dates, show the date
|
|
76
|
-
const date = new Date(timestamp);
|
|
77
|
-
return date.toLocaleDateString("en-US", {
|
|
78
|
-
month: "short",
|
|
79
|
-
day: "numeric",
|
|
80
|
-
year: date.getFullYear() !== new Date().getFullYear() ? "numeric" : undefined
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ============================================
|
|
85
|
-
// SERVER SETUP
|
|
86
|
-
// ============================================
|
|
87
|
-
|
|
88
|
-
const server = new Server(
|
|
89
|
-
{
|
|
90
|
-
name: "atomix-mcp",
|
|
91
|
-
version: "0.1.0",
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
capabilities: {
|
|
95
|
-
tools: {},
|
|
96
|
-
resources: {},
|
|
97
|
-
},
|
|
98
|
-
}
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
// ============================================
|
|
102
|
-
// TOOL DEFINITIONS
|
|
103
|
-
// ============================================
|
|
104
|
-
|
|
105
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
106
|
-
return {
|
|
107
|
-
tools: [
|
|
108
|
-
{
|
|
109
|
-
name: "getToken",
|
|
110
|
-
description: "Get a specific design token by its path. Returns value, CSS variable, and tier metadata. IMPORTANT: Tokens are tiered - 'primitive' tokens are read-only reference values, 'semantic' tokens are the primary API for styling.",
|
|
111
|
-
inputSchema: {
|
|
112
|
-
type: "object",
|
|
113
|
-
properties: {
|
|
114
|
-
path: {
|
|
115
|
-
type: "string",
|
|
116
|
-
description: "Token path in dot notation. Prefer semantic paths (colors.modes.light.bgPage) over primitive paths (colors.scales.green.500)",
|
|
117
|
-
},
|
|
118
|
-
tenantId: {
|
|
119
|
-
type: "string",
|
|
120
|
-
description: "Tenant ID for multi-tenant support (optional, defaults to 'default')",
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
required: ["path"],
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
name: "listTokens",
|
|
128
|
-
description: "List tokens in a category. Use 'tier' filter to get only semantic tokens (recommended) or primitives (reference only). Semantic tokens are the primary API; primitives are read-only reference values.",
|
|
129
|
-
inputSchema: {
|
|
130
|
-
type: "object",
|
|
131
|
-
properties: {
|
|
132
|
-
category: {
|
|
133
|
-
type: "string",
|
|
134
|
-
enum: ["colors", "typography", "spacing", "sizing", "shadows", "radius", "motion", "zIndex", "borders"],
|
|
135
|
-
description: "Token category to list",
|
|
136
|
-
},
|
|
137
|
-
subcategory: {
|
|
138
|
-
type: "string",
|
|
139
|
-
description: "Optional subcategory, e.g., 'modes.light' for semantic colors, 'scale' for primitive spacing",
|
|
140
|
-
},
|
|
141
|
-
tier: {
|
|
142
|
-
type: "string",
|
|
143
|
-
enum: ["semantic", "primitive", "all"],
|
|
144
|
-
description: "Filter by token tier. 'semantic' (recommended) returns purpose-driven tokens for styling. 'primitive' returns raw reference values. Default: 'all'",
|
|
145
|
-
},
|
|
146
|
-
tenantId: {
|
|
147
|
-
type: "string",
|
|
148
|
-
description: "Tenant ID for multi-tenant support (optional, defaults to 'default')",
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
required: ["category"],
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
name: "getComponentTokens",
|
|
156
|
-
description: "Get all design tokens used by a specific component (Button, Card, Dialog, etc.)",
|
|
157
|
-
inputSchema: {
|
|
158
|
-
type: "object",
|
|
159
|
-
properties: {
|
|
160
|
-
component: {
|
|
161
|
-
type: "string",
|
|
162
|
-
enum: ["button", "card", "dialog", "input", "select", "heading", "checkbox", "radio", "toggle", "selectionControls"],
|
|
163
|
-
description: "Component name",
|
|
164
|
-
},
|
|
165
|
-
variant: {
|
|
166
|
-
type: "string",
|
|
167
|
-
description: "Optional variant name (e.g., 'primary', 'outline', 'ghost')",
|
|
168
|
-
},
|
|
169
|
-
size: {
|
|
170
|
-
type: "string",
|
|
171
|
-
description: "Optional size name (e.g., 'sm', 'md', 'lg')",
|
|
172
|
-
},
|
|
173
|
-
tenantId: {
|
|
174
|
-
type: "string",
|
|
175
|
-
description: "Tenant ID for multi-tenant support (optional, defaults to 'default')",
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
required: ["component"],
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
name: "validateUsage",
|
|
183
|
-
description: "Check if a CSS value follows the Atomix design system. Detects arbitrary values that should use tokens.",
|
|
184
|
-
inputSchema: {
|
|
185
|
-
type: "object",
|
|
186
|
-
properties: {
|
|
187
|
-
value: {
|
|
188
|
-
type: "string",
|
|
189
|
-
description: "CSS value to validate, e.g., '#ff0000', '16px', 'rgb(0,112,97)'",
|
|
190
|
-
},
|
|
191
|
-
context: {
|
|
192
|
-
type: "string",
|
|
193
|
-
enum: ["color", "spacing", "radius", "shadow", "typography", "any"],
|
|
194
|
-
description: "Context of the value (helps find the right token)",
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
required: ["value"],
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
name: "generateCursorRules",
|
|
202
|
-
description: "[DEPRECATED] Use 'getAIToolRules' instead. Generate .cursorrules content for a project.",
|
|
203
|
-
inputSchema: {
|
|
204
|
-
type: "object",
|
|
205
|
-
properties: {
|
|
206
|
-
projectName: {
|
|
207
|
-
type: "string",
|
|
208
|
-
description: "Name of the project",
|
|
209
|
-
},
|
|
210
|
-
strict: {
|
|
211
|
-
type: "boolean",
|
|
212
|
-
description: "Whether to enforce strict token usage (no arbitrary values)",
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
required: [],
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
name: "getAIToolRules",
|
|
220
|
-
description: "Generate design system rules for AI coding tools. Supports Cursor, GitHub Copilot, Windsurf, Cline, Continue, Zed, and more.",
|
|
221
|
-
inputSchema: {
|
|
222
|
-
type: "object",
|
|
223
|
-
properties: {
|
|
224
|
-
tool: {
|
|
225
|
-
type: "string",
|
|
226
|
-
enum: ["cursor", "copilot", "windsurf", "cline", "continue", "zed", "generic", "all"],
|
|
227
|
-
description: "AI tool to generate rules for. Use 'all' to generate for all supported tools.",
|
|
228
|
-
},
|
|
229
|
-
projectName: {
|
|
230
|
-
type: "string",
|
|
231
|
-
description: "Name of the project (default: 'My Project')",
|
|
232
|
-
},
|
|
233
|
-
strict: {
|
|
234
|
-
type: "boolean",
|
|
235
|
-
description: "Enforce strict token usage - no arbitrary values allowed (default: true)",
|
|
236
|
-
},
|
|
237
|
-
},
|
|
238
|
-
required: ["tool"],
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
name: "listAITools",
|
|
243
|
-
description: "List all supported AI coding tools and their rules file conventions.",
|
|
244
|
-
inputSchema: {
|
|
245
|
-
type: "object",
|
|
246
|
-
properties: {},
|
|
247
|
-
required: [],
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
name: "getAIToolSetupGuide",
|
|
252
|
-
description: "Get a complete setup guide for integrating Atomix with AI coding tools.",
|
|
253
|
-
inputSchema: {
|
|
254
|
-
type: "object",
|
|
255
|
-
properties: {
|
|
256
|
-
projectName: {
|
|
257
|
-
type: "string",
|
|
258
|
-
description: "Name of the project",
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
required: [],
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
name: "exportMCPConfig",
|
|
266
|
-
description: "Generate MCP configuration file for AI tools. Creates ready-to-use config for Cursor (.cursor/mcp.json), Claude Desktop, Windsurf, Continue, or VS Code.",
|
|
267
|
-
inputSchema: {
|
|
268
|
-
type: "object",
|
|
269
|
-
properties: {
|
|
270
|
-
tool: {
|
|
271
|
-
type: "string",
|
|
272
|
-
enum: ["cursor", "claude-desktop", "windsurf", "continue", "vscode", "all"],
|
|
273
|
-
description: "AI tool to generate MCP config for. Use 'all' to generate for all supported tools.",
|
|
274
|
-
},
|
|
275
|
-
tenantId: {
|
|
276
|
-
type: "string",
|
|
277
|
-
description: "Tenant ID for multi-tenant setups (optional, defaults to 'default')",
|
|
278
|
-
},
|
|
279
|
-
projectName: {
|
|
280
|
-
type: "string",
|
|
281
|
-
description: "Project name for identification (optional)",
|
|
282
|
-
},
|
|
283
|
-
useNpx: {
|
|
284
|
-
type: "boolean",
|
|
285
|
-
description: "Use npx to run the server (default: true). Set to false for local development.",
|
|
286
|
-
},
|
|
287
|
-
},
|
|
288
|
-
required: ["tool"],
|
|
289
|
-
},
|
|
290
|
-
},
|
|
291
|
-
{
|
|
292
|
-
name: "getSetupInstructions",
|
|
293
|
-
description: "Get detailed step-by-step setup instructions for a specific AI tool. Includes MCP configuration, rules file setup, and verification steps.",
|
|
294
|
-
inputSchema: {
|
|
295
|
-
type: "object",
|
|
296
|
-
properties: {
|
|
297
|
-
tool: {
|
|
298
|
-
type: "string",
|
|
299
|
-
enum: ["cursor", "copilot", "windsurf", "cline", "continue", "zed", "claude-desktop", "generic"],
|
|
300
|
-
description: "AI tool to get setup instructions for.",
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
required: ["tool"],
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
name: "searchTokens",
|
|
308
|
-
description: "Search for tokens by name or value. Results include tier metadata - prefer using 'semantic' tier tokens in code, use 'primitive' tier only for reference/validation.",
|
|
309
|
-
inputSchema: {
|
|
310
|
-
type: "object",
|
|
311
|
-
properties: {
|
|
312
|
-
query: {
|
|
313
|
-
type: "string",
|
|
314
|
-
description: "Search query (matches token paths or values)",
|
|
315
|
-
},
|
|
316
|
-
tier: {
|
|
317
|
-
type: "string",
|
|
318
|
-
enum: ["semantic", "primitive", "all"],
|
|
319
|
-
description: "Filter results by tier. 'semantic' recommended for styling. Default: 'all'",
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
required: ["query"],
|
|
323
|
-
},
|
|
324
|
-
},
|
|
325
|
-
],
|
|
326
|
-
};
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
// ============================================
|
|
330
|
-
// TOOL HANDLERS
|
|
331
|
-
// ============================================
|
|
332
|
-
|
|
333
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
334
|
-
const { name, arguments: args } = request.params;
|
|
335
|
-
|
|
336
|
-
switch (name) {
|
|
337
|
-
case "getToken": {
|
|
338
|
-
const path = args?.path as string;
|
|
339
|
-
const value = getTokenByPath(primitives, path);
|
|
340
|
-
|
|
341
|
-
if (value === undefined) {
|
|
342
|
-
return {
|
|
343
|
-
content: [
|
|
344
|
-
{
|
|
345
|
-
type: "text",
|
|
346
|
-
text: JSON.stringify({
|
|
347
|
-
error: `Token not found: ${path}`,
|
|
348
|
-
suggestion: `Use listTokens to see available tokens. Available categories: ${getTokenCategories().join(", ")}`,
|
|
349
|
-
}, null, 2),
|
|
350
|
-
},
|
|
351
|
-
],
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const cssVar = getCssVariableName(path);
|
|
356
|
-
const metadata = getTokenMetadata(path);
|
|
357
|
-
|
|
358
|
-
return {
|
|
359
|
-
content: [
|
|
360
|
-
{
|
|
361
|
-
type: "text",
|
|
362
|
-
text: JSON.stringify({
|
|
363
|
-
path,
|
|
364
|
-
value,
|
|
365
|
-
cssVariable: cssVar,
|
|
366
|
-
// Token tier classification
|
|
367
|
-
tier: metadata.tier,
|
|
368
|
-
mutable: metadata.mutable,
|
|
369
|
-
editVia: metadata.editVia || null,
|
|
370
|
-
guidance: metadata.guidance,
|
|
371
|
-
// Usage examples
|
|
372
|
-
usage: metadata.tier === "semantic" ? {
|
|
373
|
-
css: `var(${cssVar})`,
|
|
374
|
-
tailwind: getTailwindClass(path, value),
|
|
375
|
-
recommendation: "Use this token in your components.",
|
|
376
|
-
} : {
|
|
377
|
-
css: `var(${cssVar})`,
|
|
378
|
-
tailwind: getTailwindClass(path, value),
|
|
379
|
-
recommendation: "This is a primitive (read-only reference). Consider using a semantic token instead.",
|
|
380
|
-
semanticAlternatives: getSuggestedSemanticTokens(path),
|
|
381
|
-
},
|
|
382
|
-
}, null, 2),
|
|
383
|
-
},
|
|
384
|
-
],
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
case "listTokens": {
|
|
389
|
-
const category = args?.category as string;
|
|
390
|
-
const subcategory = args?.subcategory as string | undefined;
|
|
391
|
-
const tierFilter = (args?.tier as TokenTier | "all") || "all";
|
|
392
|
-
|
|
393
|
-
const rawTokens = listTokensInCategory(primitives, category, subcategory);
|
|
394
|
-
|
|
395
|
-
// Apply tier filter and add metadata
|
|
396
|
-
const tokensWithMetadata: Record<string, {
|
|
397
|
-
value: unknown;
|
|
398
|
-
tier: TokenTier;
|
|
399
|
-
mutable: boolean;
|
|
400
|
-
guidance: string;
|
|
401
|
-
}> = {};
|
|
402
|
-
|
|
403
|
-
for (const [tokenPath, value] of Object.entries(rawTokens)) {
|
|
404
|
-
const fullPath = subcategory
|
|
405
|
-
? `${category}.${subcategory}.${tokenPath}`
|
|
406
|
-
: `${category}.${tokenPath}`;
|
|
407
|
-
const metadata = getTokenMetadata(fullPath);
|
|
408
|
-
|
|
409
|
-
// Apply tier filter
|
|
410
|
-
if (tierFilter !== "all" && metadata.tier !== tierFilter) {
|
|
411
|
-
continue;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
tokensWithMetadata[tokenPath] = {
|
|
415
|
-
value,
|
|
416
|
-
tier: metadata.tier,
|
|
417
|
-
mutable: metadata.mutable,
|
|
418
|
-
guidance: metadata.guidance,
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
return {
|
|
423
|
-
content: [
|
|
424
|
-
{
|
|
425
|
-
type: "text",
|
|
426
|
-
text: JSON.stringify({
|
|
427
|
-
category,
|
|
428
|
-
subcategory: subcategory || null,
|
|
429
|
-
tierFilter,
|
|
430
|
-
count: Object.keys(tokensWithMetadata).length,
|
|
431
|
-
note: tierFilter === "all"
|
|
432
|
-
? "Results include both primitive (read-only) and semantic (usable) tokens. Use tier='semantic' for styling recommendations."
|
|
433
|
-
: tierFilter === "semantic"
|
|
434
|
-
? "Showing semantic tokens - these are the recommended tokens for styling."
|
|
435
|
-
: "Showing primitive tokens - use these for reference/validation only, not direct styling.",
|
|
436
|
-
tokens: tokensWithMetadata,
|
|
437
|
-
}, null, 2),
|
|
438
|
-
},
|
|
439
|
-
],
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
case "getComponentTokens": {
|
|
444
|
-
const component = args?.component as string;
|
|
445
|
-
const variant = args?.variant as string | undefined;
|
|
446
|
-
const size = args?.size as string | undefined;
|
|
447
|
-
|
|
448
|
-
const componentKey = component.toLowerCase();
|
|
449
|
-
const componentData = COMPONENT_TOKENS[componentKey];
|
|
450
|
-
|
|
451
|
-
if (!componentData) {
|
|
452
|
-
return {
|
|
453
|
-
content: [
|
|
454
|
-
{
|
|
455
|
-
type: "text",
|
|
456
|
-
text: JSON.stringify({
|
|
457
|
-
error: `Component not found: ${component}`,
|
|
458
|
-
available: Object.keys(COMPONENT_TOKENS),
|
|
459
|
-
}, null, 2),
|
|
460
|
-
},
|
|
461
|
-
],
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
let result = { ...componentData };
|
|
466
|
-
|
|
467
|
-
// Filter by variant if specified
|
|
468
|
-
if (variant && result.variants) {
|
|
469
|
-
result.variants = { [variant]: result.variants[variant] };
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Filter by size if specified
|
|
473
|
-
if (size && result.sizes) {
|
|
474
|
-
result.sizes = { [size]: result.sizes[size] };
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
return {
|
|
478
|
-
content: [
|
|
479
|
-
{
|
|
480
|
-
type: "text",
|
|
481
|
-
text: JSON.stringify(result, null, 2),
|
|
482
|
-
},
|
|
483
|
-
],
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
case "validateUsage": {
|
|
488
|
-
const value = args?.value as string;
|
|
489
|
-
const context = (args?.context as string) || "any";
|
|
490
|
-
|
|
491
|
-
const validation = validateValue(value, context);
|
|
492
|
-
|
|
493
|
-
return {
|
|
494
|
-
content: [
|
|
495
|
-
{
|
|
496
|
-
type: "text",
|
|
497
|
-
text: JSON.stringify(validation, null, 2),
|
|
498
|
-
},
|
|
499
|
-
],
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
case "generateCursorRules": {
|
|
504
|
-
// DEPRECATED: Forward to getAIToolRules
|
|
505
|
-
const projectName = (args?.projectName as string) || "My Project";
|
|
506
|
-
const strict = (args?.strict as boolean) ?? true;
|
|
507
|
-
|
|
508
|
-
const rules = generateCursorRules(projectName, strict);
|
|
509
|
-
|
|
510
|
-
return {
|
|
511
|
-
content: [
|
|
512
|
-
{
|
|
513
|
-
type: "text",
|
|
514
|
-
text: `<!-- DEPRECATED: Use getAIToolRules instead -->\n${rules}`,
|
|
515
|
-
},
|
|
516
|
-
],
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
case "getAIToolRules": {
|
|
521
|
-
const tool = args?.tool as string;
|
|
522
|
-
const projectName = (args?.projectName as string) || "My Project";
|
|
523
|
-
const strict = (args?.strict as boolean) ?? true;
|
|
524
|
-
|
|
525
|
-
if (tool === "all") {
|
|
526
|
-
// Generate for all tools
|
|
527
|
-
const allRules = generateRulesForAllTools(projectName, strict);
|
|
528
|
-
|
|
529
|
-
return {
|
|
530
|
-
content: [
|
|
531
|
-
{
|
|
532
|
-
type: "text",
|
|
533
|
-
text: JSON.stringify({
|
|
534
|
-
message: `Generated rules for ${allRules.length} AI tools`,
|
|
535
|
-
tools: allRules.map(r => ({
|
|
536
|
-
tool: r.tool.name,
|
|
537
|
-
filename: r.filename,
|
|
538
|
-
path: r.path,
|
|
539
|
-
description: r.tool.description,
|
|
540
|
-
})),
|
|
541
|
-
files: allRules.map(r => ({
|
|
542
|
-
path: r.path,
|
|
543
|
-
content: r.content,
|
|
544
|
-
})),
|
|
545
|
-
}, null, 2),
|
|
546
|
-
},
|
|
547
|
-
],
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// Generate for specific tool
|
|
552
|
-
try {
|
|
553
|
-
const result = generateRulesForTool(tool as AIToolId, projectName, strict);
|
|
554
|
-
|
|
555
|
-
return {
|
|
556
|
-
content: [
|
|
557
|
-
{
|
|
558
|
-
type: "text",
|
|
559
|
-
text: JSON.stringify({
|
|
560
|
-
tool: result.tool.name,
|
|
561
|
-
filename: result.filename,
|
|
562
|
-
path: result.path,
|
|
563
|
-
description: result.tool.description,
|
|
564
|
-
content: result.content,
|
|
565
|
-
}, null, 2),
|
|
566
|
-
},
|
|
567
|
-
],
|
|
568
|
-
};
|
|
569
|
-
} catch (error) {
|
|
570
|
-
return {
|
|
571
|
-
content: [
|
|
572
|
-
{
|
|
573
|
-
type: "text",
|
|
574
|
-
text: JSON.stringify({
|
|
575
|
-
error: String(error),
|
|
576
|
-
availableTools: Object.keys(AI_TOOLS),
|
|
577
|
-
}, null, 2),
|
|
578
|
-
},
|
|
579
|
-
],
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
case "listAITools": {
|
|
585
|
-
const tools = getSupportedAITools();
|
|
586
|
-
|
|
587
|
-
return {
|
|
588
|
-
content: [
|
|
589
|
-
{
|
|
590
|
-
type: "text",
|
|
591
|
-
text: JSON.stringify({
|
|
592
|
-
count: tools.length,
|
|
593
|
-
tools: tools.map(t => ({
|
|
594
|
-
id: t.id,
|
|
595
|
-
name: t.name,
|
|
596
|
-
rulesFile: t.rulesPath,
|
|
597
|
-
description: t.description,
|
|
598
|
-
})),
|
|
599
|
-
note: "Use getAIToolRules({ tool: 'toolId' }) to generate rules for a specific tool.",
|
|
600
|
-
}, null, 2),
|
|
601
|
-
},
|
|
602
|
-
],
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
case "getAIToolSetupGuide": {
|
|
607
|
-
const projectName = (args?.projectName as string) || "My Project";
|
|
608
|
-
const guide = generateToolSetupGuide(projectName);
|
|
609
|
-
|
|
610
|
-
return {
|
|
611
|
-
content: [
|
|
612
|
-
{
|
|
613
|
-
type: "text",
|
|
614
|
-
text: guide,
|
|
615
|
-
},
|
|
616
|
-
],
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
case "exportMCPConfig": {
|
|
621
|
-
const tool = args?.tool as string;
|
|
622
|
-
const tenantId = (args?.tenantId as string) || "default";
|
|
623
|
-
const projectName = (args?.projectName as string) || "my-project";
|
|
624
|
-
const useNpx = (args?.useNpx as boolean) ?? true;
|
|
625
|
-
|
|
626
|
-
const options = { tenantId, projectName, useNpx };
|
|
627
|
-
|
|
628
|
-
if (tool === "all") {
|
|
629
|
-
// Generate for all tools
|
|
630
|
-
const allConfigs = generateAllMCPConfigs(options);
|
|
631
|
-
|
|
632
|
-
return {
|
|
633
|
-
content: [
|
|
634
|
-
{
|
|
635
|
-
type: "text",
|
|
636
|
-
text: JSON.stringify({
|
|
637
|
-
message: `Generated MCP configs for ${allConfigs.length} AI tools`,
|
|
638
|
-
configs: allConfigs.map(c => ({
|
|
639
|
-
tool: c.tool,
|
|
640
|
-
path: c.path,
|
|
641
|
-
filename: c.filename,
|
|
642
|
-
})),
|
|
643
|
-
files: allConfigs.map(c => ({
|
|
644
|
-
tool: c.tool,
|
|
645
|
-
path: c.path,
|
|
646
|
-
content: c.content,
|
|
647
|
-
instructions: c.instructions,
|
|
648
|
-
})),
|
|
649
|
-
}, null, 2),
|
|
650
|
-
},
|
|
651
|
-
],
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// Generate for specific tool
|
|
656
|
-
try {
|
|
657
|
-
const config = generateMCPConfig(tool as MCPConfigToolId, options);
|
|
658
|
-
|
|
659
|
-
return {
|
|
660
|
-
content: [
|
|
661
|
-
{
|
|
662
|
-
type: "text",
|
|
663
|
-
text: JSON.stringify({
|
|
664
|
-
tool: config.tool,
|
|
665
|
-
path: config.path,
|
|
666
|
-
filename: config.filename,
|
|
667
|
-
content: config.content,
|
|
668
|
-
instructions: config.instructions,
|
|
669
|
-
}, null, 2),
|
|
670
|
-
},
|
|
671
|
-
],
|
|
672
|
-
};
|
|
673
|
-
} catch (error) {
|
|
674
|
-
return {
|
|
675
|
-
content: [
|
|
676
|
-
{
|
|
677
|
-
type: "text",
|
|
678
|
-
text: JSON.stringify({
|
|
679
|
-
error: String(error),
|
|
680
|
-
availableTools: ["cursor", "claude-desktop", "windsurf", "continue", "vscode", "all"],
|
|
681
|
-
}, null, 2),
|
|
682
|
-
},
|
|
683
|
-
],
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
case "getSetupInstructions": {
|
|
689
|
-
const toolId = args?.tool as AIToolId;
|
|
690
|
-
|
|
691
|
-
try {
|
|
692
|
-
const instructions = getSetupInstructions(toolId);
|
|
693
|
-
|
|
694
|
-
return {
|
|
695
|
-
content: [
|
|
696
|
-
{
|
|
697
|
-
type: "text",
|
|
698
|
-
text: instructions,
|
|
699
|
-
},
|
|
700
|
-
],
|
|
701
|
-
};
|
|
702
|
-
} catch (error) {
|
|
703
|
-
return {
|
|
704
|
-
content: [
|
|
705
|
-
{
|
|
706
|
-
type: "text",
|
|
707
|
-
text: JSON.stringify({
|
|
708
|
-
error: String(error),
|
|
709
|
-
availableTools: Object.keys(AI_TOOLS),
|
|
710
|
-
}, null, 2),
|
|
711
|
-
},
|
|
712
|
-
],
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
case "searchTokens": {
|
|
718
|
-
const query = (args?.query as string).toLowerCase();
|
|
719
|
-
const tierFilter = (args?.tier as TokenTier | "all") || "all";
|
|
720
|
-
const allTokens = flattenTokens(primitives);
|
|
721
|
-
|
|
722
|
-
const matches = Object.entries(allTokens)
|
|
723
|
-
.filter(([path, value]) => {
|
|
724
|
-
const pathLower = path.toLowerCase();
|
|
725
|
-
const valueStr = String(value).toLowerCase();
|
|
726
|
-
const matchesQuery = pathLower.includes(query) || valueStr.includes(query);
|
|
727
|
-
|
|
728
|
-
if (!matchesQuery) return false;
|
|
729
|
-
|
|
730
|
-
// Apply tier filter
|
|
731
|
-
if (tierFilter !== "all") {
|
|
732
|
-
const metadata = getTokenMetadata(path);
|
|
733
|
-
if (metadata.tier !== tierFilter) return false;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
return true;
|
|
737
|
-
})
|
|
738
|
-
.slice(0, 50) // Limit results
|
|
739
|
-
.map(([path, value]) => {
|
|
740
|
-
const metadata = getTokenMetadata(path);
|
|
741
|
-
return {
|
|
742
|
-
path,
|
|
743
|
-
value,
|
|
744
|
-
cssVariable: getCssVariableName(path),
|
|
745
|
-
tier: metadata.tier,
|
|
746
|
-
mutable: metadata.mutable,
|
|
747
|
-
guidance: metadata.guidance,
|
|
748
|
-
};
|
|
749
|
-
});
|
|
750
|
-
|
|
751
|
-
// Sort semantic tokens first
|
|
752
|
-
matches.sort((a, b) => {
|
|
753
|
-
if (a.tier === "semantic" && b.tier !== "semantic") return -1;
|
|
754
|
-
if (a.tier !== "semantic" && b.tier === "semantic") return 1;
|
|
755
|
-
return 0;
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
return {
|
|
759
|
-
content: [
|
|
760
|
-
{
|
|
761
|
-
type: "text",
|
|
762
|
-
text: JSON.stringify({
|
|
763
|
-
query,
|
|
764
|
-
tierFilter,
|
|
765
|
-
count: matches.length,
|
|
766
|
-
note: "Semantic tokens are listed first. Use semantic tokens for styling; primitives are for reference only.",
|
|
767
|
-
matches,
|
|
768
|
-
}, null, 2),
|
|
769
|
-
},
|
|
770
|
-
],
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
default:
|
|
775
|
-
return {
|
|
776
|
-
content: [
|
|
777
|
-
{
|
|
778
|
-
type: "text",
|
|
779
|
-
text: JSON.stringify({ error: `Unknown tool: ${name}` }),
|
|
780
|
-
},
|
|
781
|
-
],
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
});
|
|
785
|
-
|
|
786
|
-
// ============================================
|
|
787
|
-
// RESOURCE DEFINITIONS
|
|
788
|
-
// ============================================
|
|
789
|
-
|
|
790
|
-
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
791
|
-
return {
|
|
792
|
-
resources: [
|
|
793
|
-
{
|
|
794
|
-
uri: "atomix://tokens/all",
|
|
795
|
-
name: "All Atomix Tokens",
|
|
796
|
-
description: "Complete design token reference",
|
|
797
|
-
mimeType: "application/json",
|
|
798
|
-
},
|
|
799
|
-
{
|
|
800
|
-
uri: "atomix://tokens/colors",
|
|
801
|
-
name: "Color Tokens",
|
|
802
|
-
description: "All color tokens (static, scales, modes)",
|
|
803
|
-
mimeType: "application/json",
|
|
804
|
-
},
|
|
805
|
-
{
|
|
806
|
-
uri: "atomix://tokens/typography",
|
|
807
|
-
name: "Typography Tokens",
|
|
808
|
-
description: "Font families, sizes, weights, line heights",
|
|
809
|
-
mimeType: "application/json",
|
|
810
|
-
},
|
|
811
|
-
{
|
|
812
|
-
uri: "atomix://tokens/spacing",
|
|
813
|
-
name: "Spacing Tokens",
|
|
814
|
-
description: "Spacing scale, insets, gaps",
|
|
815
|
-
mimeType: "application/json",
|
|
816
|
-
},
|
|
817
|
-
{
|
|
818
|
-
uri: "atomix://components",
|
|
819
|
-
name: "Component Tokens",
|
|
820
|
-
description: "Token mappings for all components",
|
|
821
|
-
mimeType: "application/json",
|
|
822
|
-
},
|
|
823
|
-
],
|
|
824
|
-
};
|
|
825
|
-
});
|
|
826
|
-
|
|
827
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
828
|
-
const uri = request.params.uri;
|
|
829
|
-
|
|
830
|
-
switch (uri) {
|
|
831
|
-
case "atomix://tokens/all":
|
|
832
|
-
return {
|
|
833
|
-
contents: [
|
|
834
|
-
{
|
|
835
|
-
uri,
|
|
836
|
-
mimeType: "application/json",
|
|
837
|
-
text: JSON.stringify(primitives, null, 2),
|
|
838
|
-
},
|
|
839
|
-
],
|
|
840
|
-
};
|
|
841
|
-
|
|
842
|
-
case "atomix://tokens/colors":
|
|
843
|
-
return {
|
|
844
|
-
contents: [
|
|
845
|
-
{
|
|
846
|
-
uri,
|
|
847
|
-
mimeType: "application/json",
|
|
848
|
-
text: JSON.stringify(primitives.colors, null, 2),
|
|
849
|
-
},
|
|
850
|
-
],
|
|
851
|
-
};
|
|
852
|
-
|
|
853
|
-
case "atomix://tokens/typography":
|
|
854
|
-
return {
|
|
855
|
-
contents: [
|
|
856
|
-
{
|
|
857
|
-
uri,
|
|
858
|
-
mimeType: "application/json",
|
|
859
|
-
text: JSON.stringify(primitives.typography, null, 2),
|
|
860
|
-
},
|
|
861
|
-
],
|
|
862
|
-
};
|
|
863
|
-
|
|
864
|
-
case "atomix://tokens/spacing":
|
|
865
|
-
return {
|
|
866
|
-
contents: [
|
|
867
|
-
{
|
|
868
|
-
uri,
|
|
869
|
-
mimeType: "application/json",
|
|
870
|
-
text: JSON.stringify(primitives.spacing, null, 2),
|
|
871
|
-
},
|
|
872
|
-
],
|
|
873
|
-
};
|
|
874
|
-
|
|
875
|
-
case "atomix://components":
|
|
876
|
-
return {
|
|
877
|
-
contents: [
|
|
878
|
-
{
|
|
879
|
-
uri,
|
|
880
|
-
mimeType: "application/json",
|
|
881
|
-
text: JSON.stringify(COMPONENT_TOKENS, null, 2),
|
|
882
|
-
},
|
|
883
|
-
],
|
|
884
|
-
};
|
|
885
|
-
|
|
886
|
-
default:
|
|
887
|
-
throw new Error(`Unknown resource: ${uri}`);
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
// ============================================
|
|
892
|
-
// HELPER FUNCTIONS
|
|
893
|
-
// ============================================
|
|
894
|
-
|
|
895
|
-
function getTailwindClass(path: string, value: unknown): string | null {
|
|
896
|
-
// Map common token paths to Tailwind classes
|
|
897
|
-
if (path.startsWith("colors.static.brand.primary")) return "bg-brand text-brand";
|
|
898
|
-
// Spacing: flat structure (spacing.xs, spacing.md, etc.)
|
|
899
|
-
if (path.startsWith("spacing.") && !path.includes(".inset")) {
|
|
900
|
-
const size = path.split(".").pop();
|
|
901
|
-
return `p-${size} m-${size} gap-${size}`;
|
|
902
|
-
}
|
|
903
|
-
// Radius: flat structure (radius.xs, radius.md, etc.)
|
|
904
|
-
if (path.startsWith("radius.")) {
|
|
905
|
-
const size = path.split(".").pop();
|
|
906
|
-
return `rounded-${size}`;
|
|
907
|
-
}
|
|
908
|
-
return null;
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
/**
|
|
912
|
-
* Suggest semantic token alternatives for a primitive token path
|
|
913
|
-
*/
|
|
914
|
-
function getSuggestedSemanticTokens(primitivePath: string): string[] {
|
|
915
|
-
const suggestions: string[] = [];
|
|
916
|
-
|
|
917
|
-
// Color scale → Mode colors
|
|
918
|
-
if (primitivePath.startsWith("colors.scales.")) {
|
|
919
|
-
suggestions.push(
|
|
920
|
-
"colors.modes.light.bgPage (page background)",
|
|
921
|
-
"colors.modes.light.bgSurface (card/panel background)",
|
|
922
|
-
"colors.modes.light.textPrimary (main text)",
|
|
923
|
-
"colors.modes.light.borderPrimary (borders)",
|
|
924
|
-
"colors.modes.light.actionPrimary (interactive elements)"
|
|
925
|
-
);
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
// Spacing → Use flat spacing tokens (spacing.xs, spacing.md, etc.)
|
|
929
|
-
if (primitivePath.startsWith("spacing.")) {
|
|
930
|
-
suggestions.push(
|
|
931
|
-
"Use spacing tokens (spacing.xs, spacing.sm, spacing.md, spacing.lg, etc.)",
|
|
932
|
-
"These resolve to CSS variables automatically"
|
|
933
|
-
);
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// Typography fontSize → TypeSets
|
|
937
|
-
if (primitivePath.startsWith("typography.fontSize.")) {
|
|
938
|
-
suggestions.push(
|
|
939
|
-
"typography.typeSets.title-lg (large titles)",
|
|
940
|
-
"typography.typeSets.title-md (medium titles)",
|
|
941
|
-
"typography.typeSets.text-normal-regular (body text)",
|
|
942
|
-
"typography.typeSets.label-md (form labels)"
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
// Radius → Use flat radius tokens (radius.sm, radius.md, etc.)
|
|
947
|
-
if (primitivePath.startsWith("radius.")) {
|
|
948
|
-
suggestions.push(
|
|
949
|
-
"Use radius tokens (radius.sm, radius.md, radius.lg, radius.full, etc.)",
|
|
950
|
-
"Describe semantic usage in AI guidance (e.g., 'Buttons use @radius.md')"
|
|
951
|
-
);
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
// Shadow elevation → Use elevation keys
|
|
955
|
-
if (primitivePath.startsWith("shadows.elevation.")) {
|
|
956
|
-
suggestions.push(
|
|
957
|
-
"Use elevation keys (none, sm, md, lg, xl) in component defaults",
|
|
958
|
-
"These resolve to CSS variables automatically"
|
|
959
|
-
);
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
return suggestions;
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
function validateValue(value: string, context: string): {
|
|
966
|
-
valid: boolean;
|
|
967
|
-
issue?: string;
|
|
968
|
-
suggestion?: string;
|
|
969
|
-
matchingTokens?: Array<{ path: string; value: unknown; cssVariable: string }>;
|
|
970
|
-
} {
|
|
971
|
-
// Check for hex colors
|
|
972
|
-
if (/^#[0-9A-Fa-f]{3,8}$/.test(value)) {
|
|
973
|
-
const allTokens = flattenTokens(primitives);
|
|
974
|
-
const matches = Object.entries(allTokens)
|
|
975
|
-
.filter(([path, v]) => {
|
|
976
|
-
if (typeof v !== "string") return false;
|
|
977
|
-
return v.toLowerCase() === value.toLowerCase();
|
|
978
|
-
})
|
|
979
|
-
.map(([path, v]) => ({
|
|
980
|
-
path,
|
|
981
|
-
value: v,
|
|
982
|
-
cssVariable: getCssVariableName(path),
|
|
983
|
-
}));
|
|
984
|
-
|
|
985
|
-
if (matches.length > 0) {
|
|
986
|
-
return {
|
|
987
|
-
valid: false,
|
|
988
|
-
issue: "Hardcoded hex color detected",
|
|
989
|
-
suggestion: `Use token instead: ${matches[0].cssVariable}`,
|
|
990
|
-
matchingTokens: matches,
|
|
991
|
-
};
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
return {
|
|
995
|
-
valid: false,
|
|
996
|
-
issue: "Hardcoded hex color with no matching token",
|
|
997
|
-
suggestion: "Check colors.static, colors.scales, or colors.modes for appropriate tokens",
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
// Check for rgb/rgba/hsl colors
|
|
1002
|
-
if (/^(rgb|rgba|hsl|hsla)\(/.test(value)) {
|
|
1003
|
-
return {
|
|
1004
|
-
valid: false,
|
|
1005
|
-
issue: "Hardcoded color function detected",
|
|
1006
|
-
suggestion: "Use CSS variable: var(--atomix-colors-*) or primitives.colors.*",
|
|
1007
|
-
};
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
// Check for pixel values in spacing context
|
|
1011
|
-
if (context === "spacing" && /^\d+px$/.test(value)) {
|
|
1012
|
-
return {
|
|
1013
|
-
valid: false,
|
|
1014
|
-
issue: "Hardcoded pixel value for spacing",
|
|
1015
|
-
suggestion: "Use spacing.* tokens (e.g., @spacing.md, @spacing.lg)",
|
|
1016
|
-
};
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
// Check for pixel values in any context
|
|
1020
|
-
if (/^\d+px$/.test(value)) {
|
|
1021
|
-
return {
|
|
1022
|
-
valid: false,
|
|
1023
|
-
issue: "Hardcoded pixel value detected",
|
|
1024
|
-
suggestion: "Consider using design tokens from spacing, sizing, or typography",
|
|
1025
|
-
};
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
// Check for Tailwind arbitrary values
|
|
1029
|
-
if (/\[.*\]/.test(value)) {
|
|
1030
|
-
return {
|
|
1031
|
-
valid: false,
|
|
1032
|
-
issue: "Tailwind arbitrary value detected",
|
|
1033
|
-
suggestion: "Replace with token-based class or CSS variable",
|
|
1034
|
-
};
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
return { valid: true };
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
// ============================================
|
|
1041
|
-
// START SERVER
|
|
1042
|
-
// ============================================
|
|
1043
|
-
|
|
1044
|
-
// Global primitives reference (loaded on startup)
|
|
1045
|
-
let primitives: Record<string, unknown> = {};
|
|
1046
|
-
|
|
1047
|
-
// Global governance reference (for user DS mode)
|
|
1048
|
-
let userGovernance: { rules: string[]; categories?: Record<string, string[]> } | undefined;
|
|
1049
|
-
|
|
1050
|
-
// Global DS mode flag
|
|
1051
|
-
let isUserMode = false;
|
|
1052
|
-
let userDsId: string | undefined;
|
|
1053
|
-
|
|
1054
|
-
async function main() {
|
|
1055
|
-
// Parse CLI arguments
|
|
1056
|
-
const cliArgs = parseCLIArgs();
|
|
1057
|
-
|
|
1058
|
-
if (isUserDSMode(cliArgs)) {
|
|
1059
|
-
// User DS mode: fetch tokens from Atomix API
|
|
1060
|
-
console.error(`[atomix-mcp] User DS mode: loading design system ${cliArgs.dsId}`);
|
|
1061
|
-
isUserMode = true;
|
|
1062
|
-
userDsId = cliArgs.dsId;
|
|
1063
|
-
|
|
1064
|
-
const result = await fetchUserDesignSystem({
|
|
1065
|
-
dsId: cliArgs.dsId!,
|
|
1066
|
-
apiKey: cliArgs.apiKey,
|
|
1067
|
-
baseUrl: cliArgs.baseUrl,
|
|
1068
|
-
});
|
|
1069
|
-
|
|
1070
|
-
if (!result.success || !result.tokens) {
|
|
1071
|
-
console.error(`[atomix-mcp] Failed to load user DS: ${result.error}`);
|
|
1072
|
-
console.error("[atomix-mcp] Falling back to Atomix internal tokens");
|
|
1073
|
-
|
|
1074
|
-
// Fallback to internal tokens
|
|
1075
|
-
primitives = await loadPrimitives();
|
|
1076
|
-
} else {
|
|
1077
|
-
// Transform user tokens to MCP-compatible format
|
|
1078
|
-
primitives = transformUserTokens(result.tokens);
|
|
1079
|
-
userGovernance = result.governance;
|
|
1080
|
-
|
|
1081
|
-
// Display startup summary
|
|
1082
|
-
const meta = result.meta;
|
|
1083
|
-
const dsName = meta?.name || cliArgs.dsId;
|
|
1084
|
-
const tokenCategories = Object.keys(primitives);
|
|
1085
|
-
const rulesCount = userGovernance?.rules?.length || 0;
|
|
1086
|
-
|
|
1087
|
-
console.error("");
|
|
1088
|
-
console.error(`[atomix-mcp] ========================================`);
|
|
1089
|
-
console.error(`[atomix-mcp] Design System: ${dsName}`);
|
|
1090
|
-
console.error(`[atomix-mcp] ----------------------------------------`);
|
|
1091
|
-
|
|
1092
|
-
// Show publish info
|
|
1093
|
-
if (meta?.publishedAt) {
|
|
1094
|
-
const publishedDate = new Date(meta.publishedAt);
|
|
1095
|
-
const timeAgo = formatTimeAgo(meta.publishedAt);
|
|
1096
|
-
console.error(`[atomix-mcp] Published: ${timeAgo}`);
|
|
1097
|
-
console.error(`[atomix-mcp] (${publishedDate.toLocaleString()})`);
|
|
1098
|
-
}
|
|
1099
|
-
if (meta?.version) {
|
|
1100
|
-
console.error(`[atomix-mcp] Version: ${meta.version}`);
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
console.error(`[atomix-mcp] ----------------------------------------`);
|
|
1104
|
-
console.error(`[atomix-mcp] Tokens: ${tokenCategories.length} categories`);
|
|
1105
|
-
console.error(`[atomix-mcp] ${tokenCategories.join(", ")}`);
|
|
1106
|
-
console.error(`[atomix-mcp] Rules: ${rulesCount} governance rules`);
|
|
1107
|
-
console.error(`[atomix-mcp] ========================================`);
|
|
1108
|
-
console.error("");
|
|
1109
|
-
}
|
|
1110
|
-
} else {
|
|
1111
|
-
// Standard mode: load Atomix internal primitives
|
|
1112
|
-
console.error("[atomix-mcp] Loading Atomix internal primitives...");
|
|
1113
|
-
primitives = await loadPrimitives();
|
|
1114
|
-
console.error(`[atomix-mcp] Loaded ${Object.keys(primitives).length} token categories`);
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
const transport = new StdioServerTransport();
|
|
1118
|
-
await server.connect(transport);
|
|
1119
|
-
|
|
1120
|
-
console.error("[atomix-mcp] Atomix MCP server started");
|
|
1121
|
-
if (isUserMode) {
|
|
1122
|
-
console.error(`[atomix-mcp] Serving design system: ${userDsId}`);
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
main().catch((error) => {
|
|
1127
|
-
console.error("[atomix-mcp] Fatal error:", error);
|
|
1128
|
-
process.exit(1);
|
|
1129
|
-
});
|
|
1130
|
-
|
|
1131
|
-
// ============================================
|
|
1132
|
-
// EXPORTS (for programmatic use)
|
|
1133
|
-
// ============================================
|
|
1134
|
-
|
|
1135
|
-
export {
|
|
1136
|
-
// AI rules generator functions
|
|
1137
|
-
generateCursorRules,
|
|
1138
|
-
generateRulesForTool,
|
|
1139
|
-
generateRulesForAllTools,
|
|
1140
|
-
getSupportedAITools,
|
|
1141
|
-
generateToolSetupGuide,
|
|
1142
|
-
generateMCPConfig,
|
|
1143
|
-
generateAllMCPConfigs,
|
|
1144
|
-
getSetupInstructions,
|
|
1145
|
-
AI_TOOLS,
|
|
1146
|
-
// User tokens functions
|
|
1147
|
-
parseCLIArgs,
|
|
1148
|
-
isUserDSMode,
|
|
1149
|
-
fetchUserDesignSystem,
|
|
1150
|
-
transformUserTokens,
|
|
1151
|
-
// Types
|
|
1152
|
-
type AIToolId,
|
|
1153
|
-
type MCPConfigToolId,
|
|
1154
|
-
};
|
|
1155
|
-
|