@decantr/mcp-server 1.0.0-beta.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 +96 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +510 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# @decantr/mcp-server
|
|
2
|
+
|
|
3
|
+
Design intelligence for AI-generated UI. Make Claude, Cursor, and Windsurf generate better code.
|
|
4
|
+
|
|
5
|
+
- **Structured design context** -- gives your AI assistant patterns, layouts, and component specs instead of letting it guess
|
|
6
|
+
- **Drift detection** -- catches when generated code deviates from your design intent
|
|
7
|
+
- **Zero config** -- run with `npx`, no API keys or accounts required
|
|
8
|
+
|
|
9
|
+
## Quick Setup
|
|
10
|
+
|
|
11
|
+
### Claude Desktop
|
|
12
|
+
|
|
13
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"mcpServers": {
|
|
18
|
+
"decantr": {
|
|
19
|
+
"command": "npx",
|
|
20
|
+
"args": ["@decantr/mcp-server"]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Restart Claude Desktop. The Decantr tools will appear automatically.
|
|
27
|
+
|
|
28
|
+
### Cursor
|
|
29
|
+
|
|
30
|
+
Create `.cursor/mcp.json` in your project root:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"decantr": {
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["@decantr/mcp-server"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Restart Cursor. The tools are available in Agent mode.
|
|
44
|
+
|
|
45
|
+
### Windsurf
|
|
46
|
+
|
|
47
|
+
Add to your Windsurf MCP config (`~/.windsurf/mcp.json`):
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"decantr": {
|
|
53
|
+
"command": "npx",
|
|
54
|
+
"args": ["@decantr/mcp-server"]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Tools
|
|
61
|
+
|
|
62
|
+
| Tool | Description | Example Input |
|
|
63
|
+
|------|-------------|---------------|
|
|
64
|
+
| `decantr_create_essence` | Generate an Essence spec skeleton from a project description | `{ "description": "SaaS dashboard with analytics and billing", "framework": "react" }` |
|
|
65
|
+
| `decantr_read_essence` | Read the current `decantr.essence.json` from the working directory | `{}` or `{ "path": "./custom.essence.json" }` |
|
|
66
|
+
| `decantr_validate` | Validate an Essence file against the schema and guard rules | `{ "path": "./decantr.essence.json" }` |
|
|
67
|
+
| `decantr_search_registry` | Search the community registry for patterns, archetypes, recipes, and styles | `{ "query": "kanban", "type": "pattern" }` |
|
|
68
|
+
| `decantr_resolve_pattern` | Get full pattern details: layout spec, components, presets, code examples | `{ "id": "data-table", "preset": "product" }` |
|
|
69
|
+
| `decantr_resolve_archetype` | Get archetype details: default pages, layouts, features, suggested theme | `{ "id": "saas-dashboard" }` |
|
|
70
|
+
| `decantr_resolve_recipe` | Get recipe decoration rules: shell styles, spatial hints, visual effects | `{ "id": "auradecantism" }` |
|
|
71
|
+
| `decantr_resolve_blueprint` | Get a full app composition with page structure and personality traits | `{ "id": "ecommerce" }` |
|
|
72
|
+
| `decantr_suggest_patterns` | Given a page description, get ranked pattern suggestions | `{ "description": "dashboard with metrics and charts" }` |
|
|
73
|
+
| `decantr_check_drift` | Check if generated code violates the design intent in the Essence spec | `{ "page_id": "overview", "components_used": ["Card", "LineChart"], "theme_used": "auradecantism" }` |
|
|
74
|
+
|
|
75
|
+
## How It Works
|
|
76
|
+
|
|
77
|
+
An Essence spec (`decantr.essence.json`) captures your design intent -- archetype, theme, page structure, patterns, and guard rules -- in a single declarative file. The MCP server exposes this spec and the Decantr registry to your AI assistant, giving it concrete layout specs, component lists, and decoration rules instead of relying on the model's generic training data. The result is generated code that follows a coherent design system, and drift detection that catches deviations before they ship.
|
|
78
|
+
|
|
79
|
+
## Example Workflow
|
|
80
|
+
|
|
81
|
+
**Prompt:** "Build me a SaaS dashboard with user analytics, a data table of recent signups, and a settings page."
|
|
82
|
+
|
|
83
|
+
The AI assistant calls these tools behind the scenes:
|
|
84
|
+
|
|
85
|
+
1. `decantr_create_essence` -- generates a spec skeleton matched to the `saas-dashboard` archetype
|
|
86
|
+
2. `decantr_resolve_archetype` -- pulls default pages, layouts, and features for a SaaS dashboard
|
|
87
|
+
3. `decantr_suggest_patterns` -- recommends `kpi-grid`, `chart-grid`, `data-table`, and `form-sections` for the described pages
|
|
88
|
+
4. `decantr_resolve_pattern` -- fetches layout specs and component lists for each pattern
|
|
89
|
+
5. `decantr_resolve_recipe` -- loads decoration rules (spacing, borders, effects) for the theme
|
|
90
|
+
6. `decantr_check_drift` -- validates the generated code against the Essence spec before presenting it
|
|
91
|
+
|
|
92
|
+
The AI now generates code with the right layout structure, correct components, and consistent styling -- not a generic guess.
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import {
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
CallToolRequestSchema
|
|
9
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
|
|
11
|
+
// src/tools.ts
|
|
12
|
+
import { readFile } from "fs/promises";
|
|
13
|
+
import { join as join2 } from "path";
|
|
14
|
+
import { validateEssence, evaluateGuard } from "@decantr/essence-spec";
|
|
15
|
+
import { createRegistryClient, resolvePatternPreset } from "@decantr/registry";
|
|
16
|
+
|
|
17
|
+
// src/helpers.ts
|
|
18
|
+
import { createResolver } from "@decantr/registry";
|
|
19
|
+
import { join } from "path";
|
|
20
|
+
var MAX_INPUT_LENGTH = 1e3;
|
|
21
|
+
function validateStringArg(args, field) {
|
|
22
|
+
const val = args[field];
|
|
23
|
+
if (!val || typeof val !== "string") {
|
|
24
|
+
return `Required parameter "${field}" must be a non-empty string.`;
|
|
25
|
+
}
|
|
26
|
+
if (val.length > MAX_INPUT_LENGTH) {
|
|
27
|
+
return `Parameter "${field}" exceeds maximum length of ${MAX_INPUT_LENGTH} characters.`;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
var _resolver = null;
|
|
32
|
+
function getResolver() {
|
|
33
|
+
if (!_resolver) {
|
|
34
|
+
const envRoot = process.env.DECANTR_CONTENT_ROOT;
|
|
35
|
+
const bundledRoot = join(import.meta.dirname, "..", "..", "..", "content");
|
|
36
|
+
const npmRoot = join(process.cwd(), "node_modules", "@decantr", "content");
|
|
37
|
+
_resolver = createResolver({
|
|
38
|
+
contentRoot: envRoot || bundledRoot,
|
|
39
|
+
overridePaths: envRoot ? [] : [npmRoot]
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return _resolver;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/tools.ts
|
|
46
|
+
var READ_ONLY = {
|
|
47
|
+
readOnlyHint: true,
|
|
48
|
+
destructiveHint: false,
|
|
49
|
+
idempotentHint: true,
|
|
50
|
+
openWorldHint: false
|
|
51
|
+
};
|
|
52
|
+
var TOOLS = [
|
|
53
|
+
{
|
|
54
|
+
name: "decantr_read_essence",
|
|
55
|
+
title: "Read Essence",
|
|
56
|
+
description: "Read and return the current decantr.essence.json file from the working directory.",
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
path: { type: "string", description: "Optional path to essence file. Defaults to ./decantr.essence.json." }
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
annotations: READ_ONLY
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "decantr_validate",
|
|
67
|
+
title: "Validate Essence",
|
|
68
|
+
description: "Validate a decantr.essence.json file against the schema and guard rules. Returns errors and warnings.",
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: "object",
|
|
71
|
+
properties: {
|
|
72
|
+
path: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." }
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
annotations: READ_ONLY
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "decantr_search_registry",
|
|
79
|
+
title: "Search Registry",
|
|
80
|
+
description: "Search the Decantr community content registry for patterns, archetypes, recipes, and styles.",
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
query: { type: "string", description: 'Search query (e.g. "kanban", "neon", "dashboard")' },
|
|
85
|
+
type: { type: "string", description: "Filter by type: pattern, archetype, recipe, style" }
|
|
86
|
+
},
|
|
87
|
+
required: ["query"]
|
|
88
|
+
},
|
|
89
|
+
annotations: READ_ONLY
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "decantr_resolve_pattern",
|
|
93
|
+
title: "Resolve Pattern",
|
|
94
|
+
description: "Get full pattern details including layout spec, components, presets, and code examples.",
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: {
|
|
98
|
+
id: { type: "string", description: 'Pattern ID (e.g. "hero", "data-table", "kpi-grid")' },
|
|
99
|
+
preset: { type: "string", description: 'Optional preset name (e.g. "product", "content")' }
|
|
100
|
+
},
|
|
101
|
+
required: ["id"]
|
|
102
|
+
},
|
|
103
|
+
annotations: READ_ONLY
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "decantr_resolve_archetype",
|
|
107
|
+
title: "Resolve Archetype",
|
|
108
|
+
description: "Get archetype details including default pages, layouts, features, and suggested theme.",
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: "object",
|
|
111
|
+
properties: {
|
|
112
|
+
id: { type: "string", description: 'Archetype ID (e.g. "saas-dashboard", "ecommerce")' }
|
|
113
|
+
},
|
|
114
|
+
required: ["id"]
|
|
115
|
+
},
|
|
116
|
+
annotations: READ_ONLY
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "decantr_resolve_recipe",
|
|
120
|
+
title: "Resolve Recipe",
|
|
121
|
+
description: "Get recipe decoration rules including shell styles, spatial hints, visual effects, and pattern preferences.",
|
|
122
|
+
inputSchema: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: {
|
|
125
|
+
id: { type: "string", description: 'Recipe ID (e.g. "auradecantism")' }
|
|
126
|
+
},
|
|
127
|
+
required: ["id"]
|
|
128
|
+
},
|
|
129
|
+
annotations: READ_ONLY
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "decantr_resolve_blueprint",
|
|
133
|
+
title: "Resolve Blueprint",
|
|
134
|
+
description: "Get a blueprint (app composition) with its archetype list, suggested theme, personality traits, and full page structure.",
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: {
|
|
138
|
+
id: { type: "string", description: 'Blueprint ID (e.g. "saas-dashboard", "ecommerce", "portfolio")' }
|
|
139
|
+
},
|
|
140
|
+
required: ["id"]
|
|
141
|
+
},
|
|
142
|
+
annotations: READ_ONLY
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "decantr_suggest_patterns",
|
|
146
|
+
title: "Suggest Patterns",
|
|
147
|
+
description: "Given a page description, suggest appropriate patterns from the registry. Returns ranked pattern matches with layout specs and component lists.",
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: "object",
|
|
150
|
+
properties: {
|
|
151
|
+
description: { type: "string", description: 'Description of the page or section (e.g. "dashboard with metrics and charts", "settings form with toggles")' }
|
|
152
|
+
},
|
|
153
|
+
required: ["description"]
|
|
154
|
+
},
|
|
155
|
+
annotations: READ_ONLY
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "decantr_check_drift",
|
|
159
|
+
title: "Check Drift",
|
|
160
|
+
description: "Check if code changes violate the design intent captured in the Essence spec. Returns guard rule violations with severity and fix suggestions.",
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties: {
|
|
164
|
+
path: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." },
|
|
165
|
+
page_id: { type: "string", description: 'Page ID being modified (e.g. "overview", "settings")' },
|
|
166
|
+
components_used: {
|
|
167
|
+
type: "array",
|
|
168
|
+
items: { type: "string" },
|
|
169
|
+
description: "List of component names used in the generated code"
|
|
170
|
+
},
|
|
171
|
+
theme_used: { type: "string", description: "Theme/style name used in the generated code" }
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
annotations: READ_ONLY
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "decantr_create_essence",
|
|
178
|
+
title: "Create Essence",
|
|
179
|
+
description: "Generate a valid Essence spec skeleton from a project description. Returns a structured essence.json template based on the closest matching archetype and blueprint.",
|
|
180
|
+
inputSchema: {
|
|
181
|
+
type: "object",
|
|
182
|
+
properties: {
|
|
183
|
+
description: { type: "string", description: 'Natural language project description (e.g. "SaaS dashboard with analytics, user management, and billing")' },
|
|
184
|
+
framework: { type: "string", description: 'Target framework (e.g. "react", "vue", "svelte"). Defaults to "react".' }
|
|
185
|
+
},
|
|
186
|
+
required: ["description"]
|
|
187
|
+
},
|
|
188
|
+
annotations: READ_ONLY
|
|
189
|
+
}
|
|
190
|
+
];
|
|
191
|
+
async function handleTool(name, args) {
|
|
192
|
+
switch (name) {
|
|
193
|
+
case "decantr_read_essence": {
|
|
194
|
+
const essencePath = args.path || join2(process.cwd(), "decantr.essence.json");
|
|
195
|
+
try {
|
|
196
|
+
const raw = await readFile(essencePath, "utf-8");
|
|
197
|
+
return JSON.parse(raw);
|
|
198
|
+
} catch (e) {
|
|
199
|
+
return { error: `Could not read essence file: ${e.message}` };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
case "decantr_validate": {
|
|
203
|
+
const essencePath = args.path || join2(process.cwd(), "decantr.essence.json");
|
|
204
|
+
let essence;
|
|
205
|
+
try {
|
|
206
|
+
essence = JSON.parse(await readFile(essencePath, "utf-8"));
|
|
207
|
+
} catch (e) {
|
|
208
|
+
return { valid: false, errors: [`Could not read: ${e.message}`], guardViolations: [] };
|
|
209
|
+
}
|
|
210
|
+
const result = validateEssence(essence);
|
|
211
|
+
let guardViolations = [];
|
|
212
|
+
if (result.valid && typeof essence === "object" && essence !== null) {
|
|
213
|
+
try {
|
|
214
|
+
guardViolations = evaluateGuard(essence, {});
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return { ...result, guardViolations };
|
|
219
|
+
}
|
|
220
|
+
case "decantr_search_registry": {
|
|
221
|
+
const err = validateStringArg(args, "query");
|
|
222
|
+
if (err) return { error: err };
|
|
223
|
+
try {
|
|
224
|
+
const client = createRegistryClient();
|
|
225
|
+
const results = await client.search(args.query, args.type);
|
|
226
|
+
return {
|
|
227
|
+
total: results.length,
|
|
228
|
+
results: results.map((r) => ({
|
|
229
|
+
type: r.type,
|
|
230
|
+
id: r.id,
|
|
231
|
+
name: r.name,
|
|
232
|
+
description: r.description,
|
|
233
|
+
install: `decantr registry add ${r.type}/${r.id}`
|
|
234
|
+
}))
|
|
235
|
+
};
|
|
236
|
+
} catch (e) {
|
|
237
|
+
return { error: `Search failed: ${e.message}` };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
case "decantr_resolve_pattern": {
|
|
241
|
+
const err = validateStringArg(args, "id");
|
|
242
|
+
if (err) return { error: err };
|
|
243
|
+
const resolver = getResolver();
|
|
244
|
+
const resolved = await resolver.resolve("pattern", args.id);
|
|
245
|
+
if (!resolved) {
|
|
246
|
+
return { found: false, message: `Pattern "${args.id}" not found.` };
|
|
247
|
+
}
|
|
248
|
+
const result = { found: true, ...resolved.item };
|
|
249
|
+
if (args.preset && typeof args.preset === "string") {
|
|
250
|
+
const preset = resolvePatternPreset(resolved.item, args.preset);
|
|
251
|
+
if (preset) result.resolvedPreset = preset;
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
case "decantr_resolve_archetype": {
|
|
256
|
+
const err = validateStringArg(args, "id");
|
|
257
|
+
if (err) return { error: err };
|
|
258
|
+
const resolver = getResolver();
|
|
259
|
+
const resolved = await resolver.resolve("archetype", args.id);
|
|
260
|
+
if (!resolved) {
|
|
261
|
+
return { found: false, message: `Archetype "${args.id}" not found.` };
|
|
262
|
+
}
|
|
263
|
+
return { found: true, ...resolved.item };
|
|
264
|
+
}
|
|
265
|
+
case "decantr_resolve_recipe": {
|
|
266
|
+
const err = validateStringArg(args, "id");
|
|
267
|
+
if (err) return { error: err };
|
|
268
|
+
const resolver = getResolver();
|
|
269
|
+
const resolved = await resolver.resolve("recipe", args.id);
|
|
270
|
+
if (!resolved) {
|
|
271
|
+
return { found: false, message: `Recipe "${args.id}" not found.` };
|
|
272
|
+
}
|
|
273
|
+
return { found: true, ...resolved.item };
|
|
274
|
+
}
|
|
275
|
+
case "decantr_resolve_blueprint": {
|
|
276
|
+
const err = validateStringArg(args, "id");
|
|
277
|
+
if (err) return { error: err };
|
|
278
|
+
const resolver = getResolver();
|
|
279
|
+
const resolved = await resolver.resolve("blueprint", args.id);
|
|
280
|
+
if (!resolved) {
|
|
281
|
+
return { found: false, message: `Blueprint "${args.id}" not found.` };
|
|
282
|
+
}
|
|
283
|
+
return { found: true, ...resolved.item };
|
|
284
|
+
}
|
|
285
|
+
case "decantr_suggest_patterns": {
|
|
286
|
+
const err = validateStringArg(args, "description");
|
|
287
|
+
if (err) return { error: err };
|
|
288
|
+
const desc = args.description.toLowerCase();
|
|
289
|
+
const resolver = getResolver();
|
|
290
|
+
const patternIds = [
|
|
291
|
+
"hero",
|
|
292
|
+
"kpi-grid",
|
|
293
|
+
"data-table",
|
|
294
|
+
"card-grid",
|
|
295
|
+
"chart-grid",
|
|
296
|
+
"filter-bar",
|
|
297
|
+
"form-sections",
|
|
298
|
+
"detail-header",
|
|
299
|
+
"activity-feed",
|
|
300
|
+
"cta-section"
|
|
301
|
+
];
|
|
302
|
+
const suggestions = [];
|
|
303
|
+
for (const id of patternIds) {
|
|
304
|
+
const resolved = await resolver.resolve("pattern", id);
|
|
305
|
+
if (!resolved) continue;
|
|
306
|
+
const p = resolved.item;
|
|
307
|
+
const searchable = [
|
|
308
|
+
p.name || "",
|
|
309
|
+
p.description || "",
|
|
310
|
+
...p.components || [],
|
|
311
|
+
...p.tags || []
|
|
312
|
+
].join(" ").toLowerCase();
|
|
313
|
+
let score = 0;
|
|
314
|
+
const words = desc.split(/\s+/);
|
|
315
|
+
for (const word of words) {
|
|
316
|
+
if (word.length < 3) continue;
|
|
317
|
+
if (searchable.includes(word)) score += 10;
|
|
318
|
+
}
|
|
319
|
+
if (desc.includes("dashboard") && ["kpi-grid", "chart-grid", "data-table", "filter-bar"].includes(id)) score += 20;
|
|
320
|
+
if (desc.includes("metric") && id === "kpi-grid") score += 15;
|
|
321
|
+
if (desc.includes("chart") && id === "chart-grid") score += 15;
|
|
322
|
+
if (desc.includes("table") && id === "data-table") score += 15;
|
|
323
|
+
if (desc.includes("form") && id === "form-sections") score += 15;
|
|
324
|
+
if (desc.includes("setting") && id === "form-sections") score += 15;
|
|
325
|
+
if (desc.includes("landing") && ["hero", "cta-section", "card-grid"].includes(id)) score += 20;
|
|
326
|
+
if (desc.includes("hero") && id === "hero") score += 20;
|
|
327
|
+
if (desc.includes("ecommerce") && ["card-grid", "filter-bar", "detail-header"].includes(id)) score += 15;
|
|
328
|
+
if (desc.includes("product") && id === "card-grid") score += 15;
|
|
329
|
+
if (desc.includes("feed") && id === "activity-feed") score += 15;
|
|
330
|
+
if (desc.includes("filter") && id === "filter-bar") score += 15;
|
|
331
|
+
if (desc.includes("search") && id === "filter-bar") score += 10;
|
|
332
|
+
if (score > 0) {
|
|
333
|
+
const preset = p.presets && typeof p.presets === "object" ? Object.values(p.presets)[0] : null;
|
|
334
|
+
suggestions.push({
|
|
335
|
+
id,
|
|
336
|
+
score,
|
|
337
|
+
name: p.name || id,
|
|
338
|
+
description: p.description || "",
|
|
339
|
+
components: p.components || [],
|
|
340
|
+
layout: preset?.layout ? preset.layout.layout || "grid" : "grid"
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
suggestions.sort((a, b) => b.score - a.score);
|
|
345
|
+
return {
|
|
346
|
+
query: args.description,
|
|
347
|
+
suggestions: suggestions.slice(0, 5),
|
|
348
|
+
total: suggestions.length
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
case "decantr_check_drift": {
|
|
352
|
+
const essencePath = args.path || join2(process.cwd(), "decantr.essence.json");
|
|
353
|
+
let essence;
|
|
354
|
+
try {
|
|
355
|
+
essence = JSON.parse(await readFile(essencePath, "utf-8"));
|
|
356
|
+
} catch (e) {
|
|
357
|
+
return { error: `Could not read essence: ${e.message}` };
|
|
358
|
+
}
|
|
359
|
+
const validation = validateEssence(essence);
|
|
360
|
+
if (!validation.valid) {
|
|
361
|
+
return { drifted: true, reason: "invalid_essence", errors: validation.errors };
|
|
362
|
+
}
|
|
363
|
+
const violations = [];
|
|
364
|
+
if (args.theme_used && typeof args.theme_used === "string") {
|
|
365
|
+
const expectedTheme = essence.theme;
|
|
366
|
+
if (expectedTheme?.style && args.theme_used !== expectedTheme.style) {
|
|
367
|
+
violations.push({
|
|
368
|
+
rule: "theme-match",
|
|
369
|
+
severity: "critical",
|
|
370
|
+
message: `Theme drift: code uses "${args.theme_used}" but Essence specifies "${expectedTheme.style}". Do not switch themes.`
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (args.page_id && typeof args.page_id === "string") {
|
|
375
|
+
const structure = essence.structure;
|
|
376
|
+
if (structure && !structure.find((p) => p.id === args.page_id)) {
|
|
377
|
+
violations.push({
|
|
378
|
+
rule: "page-exists",
|
|
379
|
+
severity: "critical",
|
|
380
|
+
message: `Page "${args.page_id}" not found in Essence structure. Add it to the Essence before generating code for it.`
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
try {
|
|
385
|
+
const guardViolations = evaluateGuard(essence, {
|
|
386
|
+
pageId: args.page_id
|
|
387
|
+
});
|
|
388
|
+
for (const gv of guardViolations) {
|
|
389
|
+
violations.push({
|
|
390
|
+
rule: gv.rule || "guard",
|
|
391
|
+
severity: gv.severity || "warning",
|
|
392
|
+
message: gv.message || "Guard violation"
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
drifted: violations.length > 0,
|
|
399
|
+
violations,
|
|
400
|
+
checkedAgainst: essencePath
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
case "decantr_create_essence": {
|
|
404
|
+
const err = validateStringArg(args, "description");
|
|
405
|
+
if (err) return { error: err };
|
|
406
|
+
const desc = args.description.toLowerCase();
|
|
407
|
+
const framework = args.framework || "react";
|
|
408
|
+
const archetypeScores = [];
|
|
409
|
+
const archetypeIds = [
|
|
410
|
+
"saas-dashboard",
|
|
411
|
+
"ecommerce",
|
|
412
|
+
"portfolio",
|
|
413
|
+
"content-site",
|
|
414
|
+
"financial-dashboard",
|
|
415
|
+
"cloud-platform",
|
|
416
|
+
"gaming-platform",
|
|
417
|
+
"ecommerce-admin",
|
|
418
|
+
"workbench"
|
|
419
|
+
];
|
|
420
|
+
const resolver = getResolver();
|
|
421
|
+
for (const id of archetypeIds) {
|
|
422
|
+
let score = 0;
|
|
423
|
+
if (desc.includes("dashboard") && id.includes("dashboard")) score += 20;
|
|
424
|
+
if (desc.includes("saas") && id.includes("saas")) score += 20;
|
|
425
|
+
if (desc.includes("ecommerce") && id.includes("ecommerce")) score += 20;
|
|
426
|
+
if (desc.includes("shop") && id.includes("ecommerce")) score += 15;
|
|
427
|
+
if (desc.includes("portfolio") && id.includes("portfolio")) score += 20;
|
|
428
|
+
if (desc.includes("blog") && id.includes("content")) score += 15;
|
|
429
|
+
if (desc.includes("content") && id.includes("content")) score += 15;
|
|
430
|
+
if (desc.includes("finance") && id.includes("financial")) score += 20;
|
|
431
|
+
if (desc.includes("cloud") && id.includes("cloud")) score += 15;
|
|
432
|
+
if (desc.includes("game") && id.includes("gaming")) score += 15;
|
|
433
|
+
if (desc.includes("admin") && id.includes("admin")) score += 15;
|
|
434
|
+
if (desc.includes("analytics") && id.includes("dashboard")) score += 10;
|
|
435
|
+
if (desc.includes("tool") && id === "workbench") score += 10;
|
|
436
|
+
if (score > 0) archetypeScores.push({ id, score });
|
|
437
|
+
}
|
|
438
|
+
archetypeScores.sort((a, b) => b.score - a.score);
|
|
439
|
+
const bestMatch = archetypeScores[0]?.id || "saas-dashboard";
|
|
440
|
+
const archetypeResult = await resolver.resolve("archetype", bestMatch);
|
|
441
|
+
const archetype = archetypeResult?.item;
|
|
442
|
+
const pages = archetype?.pages;
|
|
443
|
+
const structure = (pages || [{ id: "home", shell: "full-bleed", default_layout: ["hero"] }]).map((p) => ({
|
|
444
|
+
id: p.id,
|
|
445
|
+
shell: p.shell || "sidebar-main",
|
|
446
|
+
layout: p.default_layout || []
|
|
447
|
+
}));
|
|
448
|
+
const essence = {
|
|
449
|
+
version: "2.0.0",
|
|
450
|
+
archetype: bestMatch,
|
|
451
|
+
theme: {
|
|
452
|
+
style: "auradecantism",
|
|
453
|
+
mode: "dark",
|
|
454
|
+
recipe: "auradecantism",
|
|
455
|
+
shape: "rounded"
|
|
456
|
+
},
|
|
457
|
+
personality: ["professional"],
|
|
458
|
+
platform: { type: "spa", routing: "hash" },
|
|
459
|
+
structure,
|
|
460
|
+
features: archetype?.features || [],
|
|
461
|
+
guard: { enforce_style: true, enforce_recipe: true, mode: "strict" },
|
|
462
|
+
density: { level: "comfortable", content_gap: "_gap4" },
|
|
463
|
+
target: framework,
|
|
464
|
+
_generated: {
|
|
465
|
+
matched_archetype: bestMatch,
|
|
466
|
+
confidence: archetypeScores[0]?.score || 0,
|
|
467
|
+
alternatives: archetypeScores.slice(1, 4).map((a) => a.id),
|
|
468
|
+
description: args.description
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
return {
|
|
472
|
+
essence,
|
|
473
|
+
archetype: bestMatch,
|
|
474
|
+
instructions: `Save this as decantr.essence.json in your project root. Review the structure (pages, patterns) and adjust to match your needs. The guard rules will validate your code against this spec.`
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
default:
|
|
478
|
+
return { error: `Unknown tool: ${name}` };
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/index.ts
|
|
483
|
+
var VERSION = "0.1.0";
|
|
484
|
+
var server = new Server(
|
|
485
|
+
{ name: "decantr", version: VERSION },
|
|
486
|
+
{ capabilities: { tools: {} } }
|
|
487
|
+
);
|
|
488
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
489
|
+
return { tools: TOOLS };
|
|
490
|
+
});
|
|
491
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
492
|
+
const { name, arguments: args } = request.params;
|
|
493
|
+
try {
|
|
494
|
+
const result = await handleTool(name, args ?? {});
|
|
495
|
+
const response = {
|
|
496
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
497
|
+
};
|
|
498
|
+
if (result && typeof result === "object" && "error" in result) {
|
|
499
|
+
response.isError = true;
|
|
500
|
+
}
|
|
501
|
+
return response;
|
|
502
|
+
} catch (err) {
|
|
503
|
+
return {
|
|
504
|
+
content: [{ type: "text", text: JSON.stringify({ error: err.message }) }],
|
|
505
|
+
isError: true
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
var transport = new StdioServerTransport();
|
|
510
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@decantr/mcp-server",
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "MCP server for Decantr — exposes design intelligence tools to AI coding assistants",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/decantr-ai/decantr.git",
|
|
9
|
+
"directory": "packages/mcp-server"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://decantr.ai",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"bin": {
|
|
14
|
+
"decantr-mcp": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"files": ["dist"],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
29
|
+
"@decantr/essence-spec": "workspace:*",
|
|
30
|
+
"@decantr/registry": "workspace:*"
|
|
31
|
+
}
|
|
32
|
+
}
|