@decantr/mcp-server 1.0.0-beta.3 → 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 -510
- package/package.json +9 -11
- package/LICENSE +0 -21
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,510 +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 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);
|
|
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,24 +11,22 @@
|
|
|
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",
|
|
18
|
-
"files": [
|
|
19
|
-
"dist"
|
|
20
|
-
],
|
|
18
|
+
"files": ["dist"],
|
|
21
19
|
"publishConfig": {
|
|
22
20
|
"access": "public"
|
|
23
21
|
},
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
26
|
-
"@decantr/essence-spec": "1.0.0-beta.3",
|
|
27
|
-
"@decantr/registry": "1.0.0-beta.3"
|
|
28
|
-
},
|
|
29
22
|
"scripts": {
|
|
30
23
|
"build": "tsup",
|
|
31
24
|
"test": "vitest run",
|
|
32
25
|
"test:watch": "vitest"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
29
|
+
"@decantr/essence-spec": "workspace:*",
|
|
30
|
+
"@decantr/registry": "workspace:*"
|
|
33
31
|
}
|
|
34
|
-
}
|
|
32
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Decantr AI
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|