@neuctra/ui-mcp 0.1.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.
@@ -0,0 +1,37 @@
1
+ {
2
+ "system": "Tailwind CSS v4, semantic CSS-variable tokens mapped via @theme, toggled by a .dark class (not prefers-color-scheme).",
3
+ "rules": [
4
+ "Never use hardcoded Tailwind palette colors (text-blue-500, bg-red-500, border-gray-200, etc.) or inline hex/rgb colors in generated UI.",
5
+ "Always use the semantic token classes below (bg-primary, text-foreground, border-border, ...) so generated UI automatically follows the consumer's theme in both light and dark mode.",
6
+ "Every base token except background/foreground/border/input/ring has a matching '-foreground' token for text/icons placed on top of it (e.g. bg-primary + text-primary-foreground, bg-destructive + text-destructive-foreground).",
7
+ "Page background: bg-background, text-foreground. Card/panel surfaces: bg-card (or bg-muted/bg-accent for secondary surfaces). Popovers/dropdowns/tooltips: bg-popover.",
8
+ "Status colors: success (positive/confirmation), warning (caution), info (neutral notice), destructive (danger/errors) — use these instead of ad-hoc green/yellow/blue/red.",
9
+ "Dark mode requires no extra work in generated code: the same class names resolve to the .dark palette automatically because the consumer's CSS defines both blocks. Do not write dark: variants for color — only use dark: for things the token system doesn't cover (rare)."
10
+ ],
11
+ "tokens": [
12
+ { "token": "primary", "className": "bg-primary / text-primary / border-primary", "pairsWith": "primary-foreground", "light": "#00c214", "dark": "#00c214", "usage": "Brand color: primary buttons, active states, links, focus accents." },
13
+ { "token": "primary-foreground", "className": "text-primary-foreground", "light": "#ffffff", "dark": "#ffffff", "usage": "Text/icons on top of bg-primary." },
14
+ { "token": "background", "className": "bg-background", "light": "#ffffff", "dark": "#09090b", "usage": "Page/app background." },
15
+ { "token": "foreground", "className": "text-foreground", "light": "#09090b", "dark": "#fafafa", "usage": "Default body text color." },
16
+ { "token": "muted", "className": "bg-muted", "pairsWith": "muted-foreground", "light": "#f4f4f5", "dark": "#27272a", "usage": "Subtle/secondary surfaces (skeletons, disabled fills, code blocks)." },
17
+ { "token": "muted-foreground", "className": "text-muted-foreground", "light": "#71717a", "dark": "#a1a1aa", "usage": "Secondary/helper text, placeholders, disabled text." },
18
+ { "token": "accent", "className": "bg-accent", "pairsWith": "accent-foreground", "light": "#e4e4e7", "dark": "#18181b", "usage": "Hover/active background for interactive rows and menu items." },
19
+ { "token": "accent-foreground", "className": "text-accent-foreground", "light": "#09090b", "dark": "#fafafa", "usage": "Text on bg-accent." },
20
+ { "token": "border", "className": "border-border", "light": "#e4e4e7", "dark": "#27272a", "usage": "Default border color for inputs, cards, dividers." },
21
+ { "token": "input", "className": "bg-input / border-input", "light": "#f4f4f5", "dark": "#18181b", "usage": "Form control backgrounds." },
22
+ { "token": "ring", "className": "ring-ring / focus-visible:ring-ring", "light": "#a1a1aa", "dark": "#3f3f46", "usage": "Focus ring color — always pair interactive elements with focus-visible:ring-2 focus-visible:ring-ring." },
23
+ { "token": "card", "className": "bg-card", "pairsWith": "card-foreground", "light": "#ffffff", "dark": "#09090b", "usage": "Card / panel surfaces, distinct from the page background." },
24
+ { "token": "card-foreground", "className": "text-card-foreground", "light": "#09090b", "dark": "#fafafa", "usage": "Text inside cards." },
25
+ { "token": "popover", "className": "bg-popover", "pairsWith": "popover-foreground", "light": "#ffffff", "dark": "#18181b", "usage": "Dropdowns, popovers, tooltips, menus." },
26
+ { "token": "popover-foreground", "className": "text-popover-foreground", "light": "#09090b", "dark": "#fafafa", "usage": "Text inside popovers/dropdowns." },
27
+ { "token": "destructive", "className": "bg-destructive / text-destructive / border-destructive", "pairsWith": "destructive-foreground", "light": "#d40000", "dark": "#e7000b", "usage": "Errors, delete/danger actions, invalid form states." },
28
+ { "token": "destructive-foreground", "className": "text-destructive-foreground", "light": "#fafafa", "dark": "#fafafa", "usage": "Text on bg-destructive." },
29
+ { "token": "success", "className": "bg-success / text-success / border-success", "pairsWith": "success-foreground", "light": "#16a34a", "dark": "#22c55e", "usage": "Confirmation, online status, valid form states, positive metrics." },
30
+ { "token": "success-foreground", "className": "text-success-foreground", "light": "#fafafa", "dark": "#052e16", "usage": "Text on bg-success." },
31
+ { "token": "warning", "className": "bg-warning / text-warning / border-warning", "pairsWith": "warning-foreground", "light": "#d97706", "dark": "#f59e0b", "usage": "Caution states, pending status, non-blocking alerts." },
32
+ { "token": "warning-foreground", "className": "text-warning-foreground", "light": "#fafafa", "dark": "#451a03", "usage": "Text on bg-warning." },
33
+ { "token": "info", "className": "bg-info / text-info / border-info", "pairsWith": "info-foreground", "light": "#2563eb", "dark": "#3b82f6", "usage": "Neutral notices, informational banners/badges." },
34
+ { "token": "info-foreground", "className": "text-info-foreground", "light": "#fafafa", "dark": "#eff6ff", "usage": "Text on bg-info." }
35
+ ],
36
+ "note": "Values shown are the CLI-generated defaults (npx @neuctra/ui-cli init). A consumer project may have redefined these hex values in its own CSS — the token NAMES and pairing rules are the stable contract; always prefer the class names over the hex values."
37
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@neuctra/ui-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server exposing the @neuctra/ui component registry (props, defaults, examples, theme tokens) so AI coding tools generate accurate Neuctra UI code.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Your Name <tahaasifaqwe@gmail.com>",
8
+ "homepage": "https://github.com/Taha-Asif-313/neuctra-ui#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/Taha-Asif-313/neuctra-ui.git"
12
+ },
13
+ "keywords": [
14
+ "mcp",
15
+ "model-context-protocol",
16
+ "neuctra",
17
+ "ui",
18
+ "react",
19
+ "component-registry"
20
+ ],
21
+ "bin": {
22
+ "neuctra-ui-mcp": "./bin/cli.mjs"
23
+ },
24
+ "files": [
25
+ "bin",
26
+ "src",
27
+ "data"
28
+ ],
29
+ "scripts": {
30
+ "sync-registry": "node scripts/sync-registry.mjs",
31
+ "start": "node bin/cli.mjs",
32
+ "prepublishOnly": "npm run sync-registry"
33
+ },
34
+ "dependencies": {
35
+ "@modelcontextprotocol/sdk": "^1.30.0",
36
+ "zod": "^3.25.0"
37
+ }
38
+ }
package/src/server.mjs ADDED
@@ -0,0 +1,145 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { z } from "zod";
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const DATA_DIR = path.resolve(__dirname, "..", "data");
9
+
10
+ const registry = JSON.parse(
11
+ fs.readFileSync(path.join(DATA_DIR, "components.json"), "utf8"),
12
+ );
13
+ const theme = JSON.parse(fs.readFileSync(path.join(DATA_DIR, "theme.json"), "utf8"));
14
+
15
+ const componentsByName = new Map(registry.components.map((c) => [c.name, c]));
16
+
17
+ function summarize(component) {
18
+ return {
19
+ name: component.name,
20
+ category: component.category,
21
+ description: component.description,
22
+ propCount: component.props.length,
23
+ };
24
+ }
25
+
26
+ function matchesQuery(component, query) {
27
+ const q = query.toLowerCase();
28
+ if (component.name.toLowerCase().includes(q)) return true;
29
+ if (component.category.toLowerCase().includes(q)) return true;
30
+ if (component.description.toLowerCase().includes(q)) return true;
31
+ return component.props.some((p) => p.name.toLowerCase().includes(q));
32
+ }
33
+
34
+ export function createServer() {
35
+ const server = new McpServer({
36
+ name: "neuctra-ui",
37
+ version: "0.1.0",
38
+ });
39
+
40
+ server.registerTool(
41
+ "list_components",
42
+ {
43
+ title: "List Neuctra UI components",
44
+ description:
45
+ "List every component exported by @neuctra/ui, optionally filtered by category. Returns compact summaries — use get_component for full prop details.",
46
+ inputSchema: {
47
+ category: z
48
+ .enum([
49
+ "layout",
50
+ "typography",
51
+ "form",
52
+ "actions",
53
+ "data-display",
54
+ "feedback",
55
+ "overlay",
56
+ "navigation",
57
+ ])
58
+ .optional()
59
+ .describe("Restrict results to one category."),
60
+ },
61
+ },
62
+ async ({ category }) => {
63
+ const list = registry.components
64
+ .filter((c) => !category || c.category === category)
65
+ .map(summarize);
66
+ return {
67
+ content: [{ type: "text", text: JSON.stringify(list, null, 2) }],
68
+ };
69
+ },
70
+ );
71
+
72
+ server.registerTool(
73
+ "get_component",
74
+ {
75
+ title: "Get a Neuctra UI component's full spec",
76
+ description:
77
+ "Get the full spec for one @neuctra/ui component: every prop with its type, whether it's required, its default value, description, plus a real usage example. Always call this before writing code that uses a component you haven't used yet in this conversation — do not guess prop names.",
78
+ inputSchema: {
79
+ name: z
80
+ .string()
81
+ .describe('Exact component name, e.g. "Input", "Select", "Modal", "CardHeader".'),
82
+ },
83
+ },
84
+ async ({ name }) => {
85
+ const component = componentsByName.get(name);
86
+ if (!component) {
87
+ const suggestions = registry.components
88
+ .map((c) => c.name)
89
+ .filter((n) => n.toLowerCase().includes(name.toLowerCase()))
90
+ .slice(0, 8);
91
+ return {
92
+ isError: true,
93
+ content: [
94
+ {
95
+ type: "text",
96
+ text: `No component named "${name}". ${
97
+ suggestions.length
98
+ ? `Did you mean: ${suggestions.join(", ")}?`
99
+ : "Call list_components to see everything available."
100
+ }`,
101
+ },
102
+ ],
103
+ };
104
+ }
105
+ return {
106
+ content: [{ type: "text", text: JSON.stringify(component, null, 2) }],
107
+ };
108
+ },
109
+ );
110
+
111
+ server.registerTool(
112
+ "search_components",
113
+ {
114
+ title: "Search Neuctra UI components",
115
+ description:
116
+ "Search components by keyword against name, category, description, and prop names. Use this when you know what you need (e.g. \"date picker\", \"loading state\", \"icon button\") but not the exact component name.",
117
+ inputSchema: {
118
+ query: z.string().describe("Keyword or short phrase to search for."),
119
+ },
120
+ },
121
+ async ({ query }) => {
122
+ const results = registry.components.filter((c) => matchesQuery(c, query)).map(summarize);
123
+ return {
124
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
125
+ };
126
+ },
127
+ );
128
+
129
+ server.registerTool(
130
+ "get_theme",
131
+ {
132
+ title: "Get Neuctra UI theming rules and tokens",
133
+ description:
134
+ "Get the semantic color token system (bg-primary, text-foreground, etc.) and the styling rules generated UI must follow. Call this before generating any UI so colors come from theme tokens instead of hardcoded Tailwind palette classes or hex values — hardcoded colors will not respect the consumer's light/dark theme.",
135
+ inputSchema: {},
136
+ },
137
+ async () => {
138
+ return {
139
+ content: [{ type: "text", text: JSON.stringify(theme, null, 2) }],
140
+ };
141
+ },
142
+ );
143
+
144
+ return server;
145
+ }