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