@decantr/mcp-server 1.0.0-beta.11 → 1.0.0-beta.12

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/LICENSE ADDED
@@ -0,0 +1,21 @@
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.
package/dist/bin.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-ARK7P76H.js";
2
+ import "./chunk-YOFQYTEC.js";
@@ -1091,6 +1091,20 @@ var TOOLS = [
1091
1091
  required: ["component"]
1092
1092
  },
1093
1093
  annotations: READ_ONLY
1094
+ },
1095
+ // 15. decantr_critique — local read
1096
+ {
1097
+ name: "decantr_critique",
1098
+ title: "Design Critique",
1099
+ description: "Evaluate generated code against the essence spec for visual quality. Returns a scorecard covering treatment usage, decorator coverage, personality alignment, motion, accessibility, and responsiveness.",
1100
+ inputSchema: {
1101
+ type: "object",
1102
+ properties: {
1103
+ file_path: { type: "string", description: "Path to the component file to critique" }
1104
+ },
1105
+ required: ["file_path"]
1106
+ },
1107
+ annotations: READ_ONLY
1094
1108
  }
1095
1109
  ];
1096
1110
  async function handleTool(name, args) {
@@ -1198,14 +1212,14 @@ async function handleTool(name, args) {
1198
1212
  try {
1199
1213
  const blueprint = await apiClient.getBlueprint(namespace, args.id);
1200
1214
  let topology = null;
1201
- const composeEntries = blueprint.compose || blueprint.data?.compose;
1215
+ const composeEntries = blueprint.compose;
1202
1216
  if (composeEntries && Array.isArray(composeEntries) && composeEntries.length > 0) {
1203
1217
  const zoneInputs = [];
1204
1218
  const archetypePromises = composeEntries.map(async (entry) => {
1205
1219
  const arcId = typeof entry === "string" ? entry : entry.archetype;
1206
1220
  try {
1207
1221
  const arch = await apiClient.getContent("archetypes", namespace, arcId);
1208
- const archData = arch.data || arch;
1222
+ const archData = arch;
1209
1223
  const explicitRole = typeof entry === "object" ? entry.role : void 0;
1210
1224
  zoneInputs.push({
1211
1225
  archetypeId: arcId,
@@ -1252,50 +1266,72 @@ async function handleTool(name, args) {
1252
1266
  namespace: "@official",
1253
1267
  limit: 100
1254
1268
  });
1255
- const suggestions = [];
1269
+ const prelimScores = [];
1256
1270
  for (const p of patternsResponse.items) {
1257
- const searchable = [
1258
- p.name || "",
1259
- p.description || "",
1260
- ...p.components || [],
1261
- ...p.tags || []
1262
- ].join(" ").toLowerCase();
1271
+ const slug = p.slug || "";
1272
+ const name2 = p.name || slug;
1273
+ const description = p.description || "";
1274
+ const searchable = [name2, description].join(" ").toLowerCase();
1263
1275
  let score = 0;
1264
1276
  const words = desc.split(/\s+/);
1265
1277
  for (const word of words) {
1266
1278
  if (word.length < 3) continue;
1267
1279
  if (searchable.includes(word)) score += 10;
1268
1280
  }
1269
- if (desc.includes("dashboard") && ["kpi-grid", "chart-grid", "data-table", "filter-bar"].includes(p.id)) score += 20;
1270
- if (desc.includes("metric") && p.id === "kpi-grid") score += 15;
1271
- if (desc.includes("chart") && p.id === "chart-grid") score += 15;
1272
- if (desc.includes("table") && p.id === "data-table") score += 15;
1273
- if (desc.includes("form") && p.id === "form-sections") score += 15;
1274
- if (desc.includes("setting") && p.id === "form-sections") score += 15;
1275
- if (desc.includes("landing") && ["hero", "cta-section", "card-grid"].includes(p.id)) score += 20;
1276
- if (desc.includes("hero") && p.id === "hero") score += 20;
1277
- if (desc.includes("ecommerce") && ["card-grid", "filter-bar", "detail-header"].includes(p.id)) score += 15;
1278
- if (desc.includes("product") && p.id === "card-grid") score += 15;
1279
- if (desc.includes("feed") && p.id === "activity-feed") score += 15;
1280
- if (desc.includes("filter") && p.id === "filter-bar") score += 15;
1281
- if (desc.includes("search") && p.id === "filter-bar") score += 10;
1281
+ if (desc.includes("dashboard") && ["kpi-grid", "chart-grid", "data-table", "filter-bar"].includes(slug)) score += 20;
1282
+ if (desc.includes("metric") && slug === "kpi-grid") score += 15;
1283
+ if (desc.includes("chart") && slug === "chart-grid") score += 15;
1284
+ if (desc.includes("table") && slug === "data-table") score += 15;
1285
+ if (desc.includes("form") && slug === "form-sections") score += 15;
1286
+ if (desc.includes("setting") && slug === "form-sections") score += 15;
1287
+ if (desc.includes("landing") && ["hero", "cta-section", "card-grid"].includes(slug)) score += 20;
1288
+ if (desc.includes("hero") && slug === "hero") score += 20;
1289
+ if (desc.includes("ecommerce") && ["card-grid", "filter-bar", "detail-header"].includes(slug)) score += 15;
1290
+ if (desc.includes("product") && slug === "card-grid") score += 15;
1291
+ if (desc.includes("feed") && slug === "activity-feed") score += 15;
1292
+ if (desc.includes("filter") && slug === "filter-bar") score += 15;
1293
+ if (desc.includes("search") && slug === "filter-bar") score += 10;
1282
1294
  if (score > 0) {
1283
- const preset = p.presets ? Object.values(p.presets)[0] : null;
1284
- suggestions.push({
1285
- id: p.id,
1286
- score,
1287
- name: p.name || p.id,
1288
- description: p.description || "",
1289
- components: p.components || [],
1290
- layout: preset?.layout ? preset.layout.layout : "grid"
1291
- });
1295
+ prelimScores.push({ slug, score, name: name2, description });
1296
+ }
1297
+ }
1298
+ prelimScores.sort((a, b) => b.score - a.score);
1299
+ const top10 = prelimScores.slice(0, 10);
1300
+ const suggestions = [];
1301
+ for (const candidate of top10) {
1302
+ let fullPattern = null;
1303
+ try {
1304
+ const fetched = await apiClient.getPattern("@official", candidate.slug);
1305
+ fullPattern = fetched;
1306
+ } catch {
1292
1307
  }
1308
+ let score = candidate.score;
1309
+ if (fullPattern) {
1310
+ const fullSearchable = [
1311
+ ...fullPattern.components || [],
1312
+ ...fullPattern.tags || []
1313
+ ].join(" ").toLowerCase();
1314
+ const words = desc.split(/\s+/);
1315
+ for (const word of words) {
1316
+ if (word.length < 3) continue;
1317
+ if (fullSearchable.includes(word)) score += 10;
1318
+ }
1319
+ }
1320
+ const preset = fullPattern?.presets ? Object.values(fullPattern.presets)[0] : null;
1321
+ suggestions.push({
1322
+ id: candidate.slug,
1323
+ score,
1324
+ name: fullPattern?.name || candidate.name,
1325
+ description: fullPattern?.description || candidate.description,
1326
+ components: fullPattern?.components || [],
1327
+ layout: preset?.layout ? preset.layout.layout : "grid"
1328
+ });
1293
1329
  }
1294
1330
  suggestions.sort((a, b) => b.score - a.score);
1295
1331
  return {
1296
1332
  query: args.description,
1297
1333
  suggestions: suggestions.slice(0, 5),
1298
- total: suggestions.length
1334
+ total: prelimScores.length
1299
1335
  };
1300
1336
  } catch (e) {
1301
1337
  return { error: `Could not fetch patterns: ${e.message}` };
@@ -1746,6 +1782,11 @@ async function handleTool(name, args) {
1746
1782
  }
1747
1783
  return { content: lines.join("\n") };
1748
1784
  }
1785
+ case "decantr_critique": {
1786
+ const { critiqueFile } = await import("./critique-DKY4NB2O.js");
1787
+ const result = await critiqueFile(args.file_path, process.cwd());
1788
+ return result;
1789
+ }
1749
1790
  default:
1750
1791
  return { error: `Unknown tool: ${name}` };
1751
1792
  }
@@ -0,0 +1,74 @@
1
+ // src/critique.ts
2
+ import { readFile } from "fs/promises";
3
+ import { join } from "path";
4
+ async function critiqueFile(filePath, projectRoot) {
5
+ const code = await readFile(filePath, "utf-8");
6
+ const codeLower = code.toLowerCase();
7
+ let treatments = "";
8
+ try {
9
+ treatments = await readFile(join(projectRoot, "src", "styles", "treatments.css"), "utf-8");
10
+ } catch {
11
+ }
12
+ const scores = [];
13
+ const treatmentClasses = ["d-interactive", "d-surface", "d-data", "d-control", "d-section", "d-annotation", "d-label"];
14
+ const usedTreatments = treatmentClasses.filter((t) => code.includes(t));
15
+ scores.push({
16
+ category: "Treatment Usage",
17
+ score: Math.min(5, Math.max(1, Math.round(usedTreatments.length / treatmentClasses.length * 5))),
18
+ details: `${usedTreatments.length}/${treatmentClasses.length} base treatments used: ${usedTreatments.join(", ") || "none"}`,
19
+ suggestions: treatmentClasses.filter((t) => !code.includes(t)).map((t) => `Consider using \`${t}\` where appropriate`)
20
+ });
21
+ const decoratorMatches = treatments.match(/\.([\w-]+)\s*\{/g) || [];
22
+ const decoratorNames = decoratorMatches.map((m) => m.slice(1).replace(/\s*\{$/, "")).filter((n) => !n.startsWith("d-") && !["neon-glow", "neon-glow-hover", "neon-text-glow", "neon-border-glow", "mono-data", "status-ring", "entrance-fade"].includes(n));
23
+ const usedDecorators = decoratorNames.filter((d) => code.includes(d));
24
+ const decTarget = Math.min(decoratorNames.length, 5);
25
+ scores.push({
26
+ category: "Decorator Usage",
27
+ score: decTarget > 0 ? Math.min(5, Math.max(1, Math.round(usedDecorators.length / decTarget * 5))) : 3,
28
+ details: `${usedDecorators.length}/${decoratorNames.length} theme decorators used`,
29
+ suggestions: decoratorNames.filter((d) => !code.includes(d)).slice(0, 3).map((d) => `Theme decorator \`${d}\` available but unused`)
30
+ });
31
+ const personalityUtils = ["neon-glow", "neon-text-glow", "neon-border-glow", "mono-data", "status-ring", "entrance-fade"];
32
+ const availableUtils = personalityUtils.filter((u) => treatments.includes(u));
33
+ const usedUtils = availableUtils.filter((u) => code.includes(u));
34
+ const utilTarget = Math.min(availableUtils.length, 3);
35
+ scores.push({
36
+ category: "Personality Alignment",
37
+ score: utilTarget > 0 ? Math.min(5, Math.max(1, Math.round(usedUtils.length / utilTarget * 5))) : 3,
38
+ details: `${usedUtils.length}/${availableUtils.length} personality utilities used`,
39
+ suggestions: availableUtils.filter((u) => !code.includes(u)).map((u) => `Personality utility \`${u}\` defined but not used`)
40
+ });
41
+ const hasTransition = codeLower.includes("transition") || codeLower.includes("animate") || codeLower.includes("keyframe") || codeLower.includes("framer");
42
+ const hasHover = codeLower.includes(":hover") || codeLower.includes("onmouseenter") || codeLower.includes("hover:");
43
+ scores.push({
44
+ category: "Motion & Interaction",
45
+ score: Math.min(5, (hasTransition ? 3 : 1) + (hasHover ? 1 : 0)),
46
+ details: `Transitions: ${hasTransition ? "yes" : "no"}, Hover states: ${hasHover ? "yes" : "no"}`,
47
+ suggestions: hasTransition ? [] : ["Add transition effects for interactive elements"]
48
+ });
49
+ const hasAria = codeLower.includes("aria-") || codeLower.includes("role=");
50
+ const hasFocus = codeLower.includes("focus-visible") || codeLower.includes("focusvisible");
51
+ const hasKeyboard = codeLower.includes("onkeydown") || codeLower.includes("onkeyup");
52
+ scores.push({
53
+ category: "Accessibility",
54
+ score: Math.min(5, 1 + (hasAria ? 2 : 0) + (hasFocus ? 1 : 0) + (hasKeyboard ? 1 : 0)),
55
+ details: `ARIA: ${hasAria}, Focus: ${hasFocus}, Keyboard: ${hasKeyboard}`,
56
+ suggestions: [
57
+ ...!hasAria ? ["Add ARIA attributes to interactive regions"] : [],
58
+ ...!hasKeyboard ? ["Add keyboard event handlers"] : []
59
+ ]
60
+ });
61
+ const hasMedia = codeLower.includes("@media") || codeLower.includes("usemediaquery") || codeLower.includes("breakpoint");
62
+ const hasResponsive = codeLower.includes("sm:") || codeLower.includes("md:") || codeLower.includes("lg:") || codeLower.includes("_sm_") || codeLower.includes("_md_");
63
+ scores.push({
64
+ category: "Responsive Design",
65
+ score: Math.min(5, (hasMedia ? 3 : 1) + (hasResponsive ? 2 : 0)),
66
+ details: `Media queries: ${hasMedia}, Responsive classes: ${hasResponsive}`,
67
+ suggestions: !hasMedia && !hasResponsive ? ["Add responsive breakpoint handling"] : []
68
+ });
69
+ const overall = Math.round(scores.reduce((s, c) => s + c.score, 0) / scores.length * 10) / 10;
70
+ return { file: filePath, overall, scores };
71
+ }
72
+ export {
73
+ critiqueFile
74
+ };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import "./chunk-ARK7P76H.js";
1
+ import "./chunk-YOFQYTEC.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decantr/mcp-server",
3
- "version": "1.0.0-beta.11",
3
+ "version": "1.0.0-beta.12",
4
4
  "description": "MCP server for Decantr — exposes design intelligence tools to AI coding assistants",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -15,18 +15,20 @@
15
15
  },
16
16
  "main": "dist/index.js",
17
17
  "types": "dist/index.d.ts",
18
- "files": ["dist"],
18
+ "files": [
19
+ "dist"
20
+ ],
19
21
  "publishConfig": {
20
22
  "access": "public"
21
23
  },
24
+ "dependencies": {
25
+ "@modelcontextprotocol/sdk": "^1.0.0",
26
+ "@decantr/essence-spec": "1.0.0-beta.11",
27
+ "@decantr/registry": "1.0.0-beta.11"
28
+ },
22
29
  "scripts": {
23
30
  "build": "tsup",
24
31
  "test": "vitest run",
25
32
  "test:watch": "vitest"
26
- },
27
- "dependencies": {
28
- "@modelcontextprotocol/sdk": "^1.0.0",
29
- "@decantr/essence-spec": "workspace:*",
30
- "@decantr/registry": "workspace:*"
31
33
  }
32
- }
34
+ }