@decantr/mcp-server 1.0.3 → 1.0.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/README.md +1 -3
- package/dist/bin.js +1 -1
- package/dist/{chunk-WN4I3BY6.js → chunk-A4ZCCVQR.js} +275 -87
- package/dist/index.js +1 -1
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -10,9 +10,7 @@ Release channel: `stable`
|
|
|
10
10
|
|
|
11
11
|
Design intelligence for AI-generated UI. Make Claude, Cursor, and Windsurf generate better code.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
<!-- e.g. [](https://asciinema.org/a/XXXXXX) -->
|
|
15
|
-
|
|
13
|
+

|
|
16
14
|
|
|
17
15
|
- **Structured design context** -- gives your AI assistant patterns, layouts, and component specs instead of letting it guess
|
|
18
16
|
- **Drift detection** -- catches when generated code deviates from your design intent
|
package/dist/bin.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-A4ZCCVQR.js";
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import {
|
|
5
|
-
ListToolsRequestSchema,
|
|
6
|
-
CallToolRequestSchema
|
|
7
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
8
5
|
|
|
9
6
|
// src/tools.ts
|
|
10
7
|
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
11
8
|
import { readFile as readFile2 } from "fs/promises";
|
|
12
|
-
import { basename
|
|
13
|
-
import {
|
|
9
|
+
import { basename as basename2, dirname as dirname2, join as join2, relative as relative2 } from "path";
|
|
10
|
+
import { evaluateGuard, isV3 as isV32, validateEssence } from "@decantr/essence-spec";
|
|
14
11
|
import { isContentIntelligenceSource, resolvePatternPreset } from "@decantr/registry";
|
|
15
12
|
|
|
16
13
|
// src/helpers.ts
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
14
|
+
import { realpathSync } from "fs";
|
|
15
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
16
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "path";
|
|
20
17
|
import { isV3, migrateV2ToV3 } from "@decantr/essence-spec";
|
|
18
|
+
import { RegistryAPIClient } from "@decantr/registry";
|
|
21
19
|
var MAX_INPUT_LENGTH = 1e3;
|
|
22
20
|
function validateStringArg(args, field) {
|
|
23
21
|
const val = args[field];
|
|
@@ -60,8 +58,32 @@ function getPublicAPIClient() {
|
|
|
60
58
|
}
|
|
61
59
|
return _publicApiClient;
|
|
62
60
|
}
|
|
61
|
+
function resolveWorkspacePath(inputPath, workspaceRoot = process.cwd()) {
|
|
62
|
+
const rawRoot = resolve(workspaceRoot);
|
|
63
|
+
const resolvedPath = isAbsolute(inputPath) ? resolve(inputPath) : resolve(rawRoot, inputPath);
|
|
64
|
+
const root = canonicalizeForContainment(rawRoot);
|
|
65
|
+
const candidate = canonicalizeForContainment(resolvedPath);
|
|
66
|
+
const relativePath = relative(root, candidate);
|
|
67
|
+
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
68
|
+
throw new Error(`Path escapes the active workspace root: ${inputPath}`);
|
|
69
|
+
}
|
|
70
|
+
return candidate;
|
|
71
|
+
}
|
|
72
|
+
function canonicalizeForContainment(path) {
|
|
73
|
+
const resolvedPath = resolve(path);
|
|
74
|
+
try {
|
|
75
|
+
return realpathSync.native(resolvedPath);
|
|
76
|
+
} catch {
|
|
77
|
+
const parent = dirname(resolvedPath);
|
|
78
|
+
try {
|
|
79
|
+
return join(realpathSync.native(parent), basename(resolvedPath));
|
|
80
|
+
} catch {
|
|
81
|
+
return resolvedPath;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
63
85
|
async function readEssenceFile(essencePath) {
|
|
64
|
-
const resolvedPath = essencePath
|
|
86
|
+
const resolvedPath = essencePath ? resolveWorkspacePath(essencePath) : join(process.cwd(), "decantr.essence.json");
|
|
65
87
|
const raw = await readFile(resolvedPath, "utf-8");
|
|
66
88
|
const essence = JSON.parse(raw);
|
|
67
89
|
return { essence, raw, path: resolvedPath };
|
|
@@ -79,7 +101,7 @@ async function mutateEssenceFile(essencePath, mutate) {
|
|
|
79
101
|
return { essence: updated, path };
|
|
80
102
|
}
|
|
81
103
|
async function readDriftLog(projectRoot) {
|
|
82
|
-
const root = projectRoot
|
|
104
|
+
const root = projectRoot ? resolveWorkspacePath(projectRoot) : process.cwd();
|
|
83
105
|
const logPath = join(root, ".decantr", "drift-log.json");
|
|
84
106
|
try {
|
|
85
107
|
const raw = await readFile(logPath, "utf-8");
|
|
@@ -89,7 +111,7 @@ async function readDriftLog(projectRoot) {
|
|
|
89
111
|
}
|
|
90
112
|
}
|
|
91
113
|
async function writeDriftLog(entries, projectRoot) {
|
|
92
|
-
const root = projectRoot
|
|
114
|
+
const root = projectRoot ? resolveWorkspacePath(projectRoot) : process.cwd();
|
|
93
115
|
const logPath = join(root, ".decantr", "drift-log.json");
|
|
94
116
|
await mkdir(dirname(logPath), { recursive: true });
|
|
95
117
|
await writeFile(logPath, JSON.stringify(entries, null, 2) + "\n", "utf-8");
|
|
@@ -155,17 +177,18 @@ async function getHostedExecutionPackManifestPayload(args) {
|
|
|
155
177
|
);
|
|
156
178
|
}
|
|
157
179
|
async function getHostedFileCritiquePayload(args) {
|
|
158
|
-
const client =
|
|
180
|
+
const client = getAPIClient();
|
|
159
181
|
const filePath = args.file_path;
|
|
160
|
-
const resolvedFilePath =
|
|
182
|
+
const resolvedFilePath = resolveWorkspacePath(filePath);
|
|
183
|
+
const snapshotFilePath = relative2(process.cwd(), resolvedFilePath).replace(/\\/g, "/") || basename2(resolvedFilePath);
|
|
161
184
|
const code = await readFile2(resolvedFilePath, "utf-8");
|
|
162
185
|
const { essence } = await readEssenceFile(args.path);
|
|
163
|
-
const treatmentsPath = typeof args.treatments_path === "string" ?
|
|
186
|
+
const treatmentsPath = typeof args.treatments_path === "string" ? resolveWorkspacePath(args.treatments_path) : join2(process.cwd(), "src", "styles", "treatments.css");
|
|
164
187
|
const treatmentsCss = existsSync(treatmentsPath) ? readFileSync(treatmentsPath, "utf-8") : void 0;
|
|
165
188
|
return client.critiqueFile(
|
|
166
189
|
{
|
|
167
190
|
essence,
|
|
168
|
-
filePath,
|
|
191
|
+
filePath: snapshotFilePath,
|
|
169
192
|
code,
|
|
170
193
|
treatmentsCss
|
|
171
194
|
},
|
|
@@ -183,7 +206,7 @@ function extractHostedAssetPaths(indexHtml) {
|
|
|
183
206
|
return [...assetPaths];
|
|
184
207
|
}
|
|
185
208
|
async function captureHostedDistSnapshot(projectRoot, distPathArg) {
|
|
186
|
-
const distRoot = distPathArg ?
|
|
209
|
+
const distRoot = distPathArg ? resolveWorkspacePath(distPathArg, projectRoot) : join2(projectRoot, "dist");
|
|
187
210
|
const indexPath = join2(distRoot, "index.html");
|
|
188
211
|
if (!existsSync(indexPath)) {
|
|
189
212
|
return void 0;
|
|
@@ -191,9 +214,10 @@ async function captureHostedDistSnapshot(projectRoot, distPathArg) {
|
|
|
191
214
|
const indexHtml = readFileSync(indexPath, "utf-8");
|
|
192
215
|
const assets = {};
|
|
193
216
|
for (const assetPath of extractHostedAssetPaths(indexHtml)) {
|
|
194
|
-
const
|
|
217
|
+
const snapshotAssetPath = assetPath.replace(/^[/\\]+/, "");
|
|
218
|
+
const assetFilePath = resolveWorkspacePath(snapshotAssetPath, distRoot);
|
|
195
219
|
if (existsSync(assetFilePath)) {
|
|
196
|
-
assets[
|
|
220
|
+
assets[snapshotAssetPath] = readFileSync(assetFilePath, "utf-8");
|
|
197
221
|
}
|
|
198
222
|
}
|
|
199
223
|
return {
|
|
@@ -209,13 +233,20 @@ async function captureHostedSourceSnapshot(projectRoot, sourcesPathArg) {
|
|
|
209
233
|
if (!sourcesPathArg) {
|
|
210
234
|
return void 0;
|
|
211
235
|
}
|
|
212
|
-
const sourcesRoot =
|
|
236
|
+
const sourcesRoot = resolveWorkspacePath(sourcesPathArg, projectRoot);
|
|
213
237
|
if (!existsSync(sourcesRoot)) {
|
|
214
238
|
return void 0;
|
|
215
239
|
}
|
|
216
240
|
const files = {};
|
|
217
|
-
const ignoredDirNames = /* @__PURE__ */ new Set([
|
|
218
|
-
|
|
241
|
+
const ignoredDirNames = /* @__PURE__ */ new Set([
|
|
242
|
+
"node_modules",
|
|
243
|
+
".git",
|
|
244
|
+
".decantr",
|
|
245
|
+
"dist",
|
|
246
|
+
"build",
|
|
247
|
+
"coverage"
|
|
248
|
+
]);
|
|
249
|
+
const rootPrefix = basename2(sourcesRoot);
|
|
219
250
|
const walk = (absoluteDir, relativeDir) => {
|
|
220
251
|
for (const entry of readdirSync(absoluteDir, { withFileTypes: true })) {
|
|
221
252
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
@@ -234,10 +265,13 @@ async function captureHostedSourceSnapshot(projectRoot, sourcesPathArg) {
|
|
|
234
265
|
return Object.keys(files).length > 0 ? { files } : void 0;
|
|
235
266
|
}
|
|
236
267
|
async function getHostedProjectAuditPayload(args) {
|
|
237
|
-
const client =
|
|
268
|
+
const client = getAPIClient();
|
|
238
269
|
const { essence } = await readEssenceFile(args.path);
|
|
239
270
|
const dist = await captureHostedDistSnapshot(process.cwd(), args.dist_path);
|
|
240
|
-
const sources = await captureHostedSourceSnapshot(
|
|
271
|
+
const sources = await captureHostedSourceSnapshot(
|
|
272
|
+
process.cwd(),
|
|
273
|
+
args.sources_path
|
|
274
|
+
);
|
|
241
275
|
return client.auditProject(
|
|
242
276
|
{
|
|
243
277
|
essence,
|
|
@@ -315,6 +349,9 @@ async function loadHostedProjectAuditFallback(args) {
|
|
|
315
349
|
function hasExecutionPackPayload(payload) {
|
|
316
350
|
return payload.markdown !== null || payload.json !== null;
|
|
317
351
|
}
|
|
352
|
+
function allowsHostedUpload(args) {
|
|
353
|
+
return args.allow_hosted_upload === true;
|
|
354
|
+
}
|
|
318
355
|
function toHostedExecutionPackPayload(pack) {
|
|
319
356
|
return {
|
|
320
357
|
markdown: pack && typeof pack.renderedMarkdown === "string" ? pack.renderedMarkdown : null,
|
|
@@ -390,7 +427,12 @@ function deriveTransitions(zones) {
|
|
|
390
427
|
const hasGateway = roles.has("gateway");
|
|
391
428
|
const hasPublic = roles.has("public");
|
|
392
429
|
if (hasPublic && hasGateway) {
|
|
393
|
-
transitions.push({
|
|
430
|
+
transitions.push({
|
|
431
|
+
from: "public",
|
|
432
|
+
to: "gateway",
|
|
433
|
+
type: "conversion",
|
|
434
|
+
trigger: gatewayTrigger
|
|
435
|
+
});
|
|
394
436
|
}
|
|
395
437
|
if (hasPublic && hasApp && !hasGateway) {
|
|
396
438
|
transitions.push({ from: "public", to: "app", type: "conversion", trigger: "navigation" });
|
|
@@ -431,8 +473,15 @@ var TOOLS = [
|
|
|
431
473
|
inputSchema: {
|
|
432
474
|
type: "object",
|
|
433
475
|
properties: {
|
|
434
|
-
path: {
|
|
435
|
-
|
|
476
|
+
path: {
|
|
477
|
+
type: "string",
|
|
478
|
+
description: "Optional path to essence file. Defaults to ./decantr.essence.json."
|
|
479
|
+
},
|
|
480
|
+
layer: {
|
|
481
|
+
type: "string",
|
|
482
|
+
enum: ["dna", "blueprint", "full"],
|
|
483
|
+
description: "For v3 essences: return only the specified layer. Defaults to full."
|
|
484
|
+
}
|
|
436
485
|
}
|
|
437
486
|
},
|
|
438
487
|
annotations: READ_ONLY
|
|
@@ -445,7 +494,10 @@ var TOOLS = [
|
|
|
445
494
|
inputSchema: {
|
|
446
495
|
type: "object",
|
|
447
496
|
properties: {
|
|
448
|
-
path: {
|
|
497
|
+
path: {
|
|
498
|
+
type: "string",
|
|
499
|
+
description: "Path to essence file. Defaults to ./decantr.essence.json."
|
|
500
|
+
}
|
|
449
501
|
}
|
|
450
502
|
},
|
|
451
503
|
annotations: READ_ONLY
|
|
@@ -462,7 +514,10 @@ var TOOLS = [
|
|
|
462
514
|
type: { type: "string", description: "Filter by type: pattern, archetype, theme, shell" },
|
|
463
515
|
sort: { type: "string", description: "Optional sort: recommended, recent, or name." },
|
|
464
516
|
recommended: { type: "boolean", description: "When true, only return recommended items." },
|
|
465
|
-
source: {
|
|
517
|
+
source: {
|
|
518
|
+
type: "string",
|
|
519
|
+
description: "Optional intelligence source filter: authored, benchmark, or hybrid."
|
|
520
|
+
}
|
|
466
521
|
},
|
|
467
522
|
required: ["query"]
|
|
468
523
|
},
|
|
@@ -507,7 +562,10 @@ var TOOLS = [
|
|
|
507
562
|
inputSchema: {
|
|
508
563
|
type: "object",
|
|
509
564
|
properties: {
|
|
510
|
-
id: {
|
|
565
|
+
id: {
|
|
566
|
+
type: "string",
|
|
567
|
+
description: 'Blueprint ID (e.g. "saas-dashboard", "ecommerce", "portfolio")'
|
|
568
|
+
},
|
|
511
569
|
namespace: { type: "string", description: 'Namespace (default: "@official")' }
|
|
512
570
|
},
|
|
513
571
|
required: ["id"]
|
|
@@ -522,7 +580,10 @@ var TOOLS = [
|
|
|
522
580
|
inputSchema: {
|
|
523
581
|
type: "object",
|
|
524
582
|
properties: {
|
|
525
|
-
description: {
|
|
583
|
+
description: {
|
|
584
|
+
type: "string",
|
|
585
|
+
description: 'Description of the page or section (e.g. "dashboard with metrics and charts", "settings form with toggles")'
|
|
586
|
+
}
|
|
526
587
|
},
|
|
527
588
|
required: ["description"]
|
|
528
589
|
},
|
|
@@ -536,8 +597,14 @@ var TOOLS = [
|
|
|
536
597
|
inputSchema: {
|
|
537
598
|
type: "object",
|
|
538
599
|
properties: {
|
|
539
|
-
path: {
|
|
540
|
-
|
|
600
|
+
path: {
|
|
601
|
+
type: "string",
|
|
602
|
+
description: "Path to essence file. Defaults to ./decantr.essence.json."
|
|
603
|
+
},
|
|
604
|
+
page_id: {
|
|
605
|
+
type: "string",
|
|
606
|
+
description: 'Page ID being modified (e.g. "overview", "settings")'
|
|
607
|
+
},
|
|
541
608
|
components_used: {
|
|
542
609
|
type: "array",
|
|
543
610
|
items: { type: "string" },
|
|
@@ -556,8 +623,14 @@ var TOOLS = [
|
|
|
556
623
|
inputSchema: {
|
|
557
624
|
type: "object",
|
|
558
625
|
properties: {
|
|
559
|
-
description: {
|
|
560
|
-
|
|
626
|
+
description: {
|
|
627
|
+
type: "string",
|
|
628
|
+
description: 'Natural language project description (e.g. "SaaS dashboard with analytics, user management, and billing")'
|
|
629
|
+
},
|
|
630
|
+
framework: {
|
|
631
|
+
type: "string",
|
|
632
|
+
description: 'Target framework (e.g. "react", "vue", "svelte"). Defaults to "react".'
|
|
633
|
+
}
|
|
561
634
|
},
|
|
562
635
|
required: ["description"]
|
|
563
636
|
},
|
|
@@ -590,8 +663,14 @@ var TOOLS = [
|
|
|
590
663
|
description: "How to resolve: accept updates the essence, accept_scoped limits to a page, reject is a no-op, defer logs for later."
|
|
591
664
|
},
|
|
592
665
|
scope: { type: "string", description: "For accept_scoped: the page or section scope." },
|
|
593
|
-
path: {
|
|
594
|
-
|
|
666
|
+
path: {
|
|
667
|
+
type: "string",
|
|
668
|
+
description: "Path to essence file. Defaults to ./decantr.essence.json."
|
|
669
|
+
},
|
|
670
|
+
confirm_dna: {
|
|
671
|
+
type: "boolean",
|
|
672
|
+
description: "Required to be true when accepting DNA-layer violations."
|
|
673
|
+
}
|
|
595
674
|
},
|
|
596
675
|
required: ["violations", "resolution"]
|
|
597
676
|
},
|
|
@@ -607,14 +686,25 @@ var TOOLS = [
|
|
|
607
686
|
properties: {
|
|
608
687
|
operation: {
|
|
609
688
|
type: "string",
|
|
610
|
-
enum: [
|
|
689
|
+
enum: [
|
|
690
|
+
"add_page",
|
|
691
|
+
"remove_page",
|
|
692
|
+
"update_page_layout",
|
|
693
|
+
"update_dna",
|
|
694
|
+
"update_blueprint",
|
|
695
|
+
"add_feature",
|
|
696
|
+
"remove_feature"
|
|
697
|
+
],
|
|
611
698
|
description: "The mutation operation to perform."
|
|
612
699
|
},
|
|
613
700
|
payload: {
|
|
614
701
|
type: "object",
|
|
615
702
|
description: "Operation-specific payload. See tool docs for each operation."
|
|
616
703
|
},
|
|
617
|
-
path: {
|
|
704
|
+
path: {
|
|
705
|
+
type: "string",
|
|
706
|
+
description: "Path to essence file. Defaults to ./decantr.essence.json."
|
|
707
|
+
}
|
|
618
708
|
},
|
|
619
709
|
required: ["operation", "payload"]
|
|
620
710
|
},
|
|
@@ -648,7 +738,10 @@ var TOOLS = [
|
|
|
648
738
|
inputSchema: {
|
|
649
739
|
type: "object",
|
|
650
740
|
properties: {
|
|
651
|
-
section_id: {
|
|
741
|
+
section_id: {
|
|
742
|
+
type: "string",
|
|
743
|
+
description: 'Section ID (archetype ID, e.g., "ai-chatbot", "auth-full", "settings-full")'
|
|
744
|
+
},
|
|
652
745
|
path: {
|
|
653
746
|
type: "string",
|
|
654
747
|
description: "Optional path to an essence file when using hosted fallback compilation. Defaults to ./decantr.essence.json."
|
|
@@ -670,7 +763,10 @@ var TOOLS = [
|
|
|
670
763
|
inputSchema: {
|
|
671
764
|
type: "object",
|
|
672
765
|
properties: {
|
|
673
|
-
page_id: {
|
|
766
|
+
page_id: {
|
|
767
|
+
type: "string",
|
|
768
|
+
description: 'Page ID (for example "overview", "settings", or "home").'
|
|
769
|
+
},
|
|
674
770
|
path: {
|
|
675
771
|
type: "string",
|
|
676
772
|
description: "Optional path to an essence file when using hosted fallback compilation. Defaults to ./decantr.essence.json."
|
|
@@ -779,14 +875,30 @@ var TOOLS = [
|
|
|
779
875
|
{
|
|
780
876
|
name: "decantr_audit_project",
|
|
781
877
|
title: "Audit Project",
|
|
782
|
-
description: "Audit the current project against the essence contract, guard rules, and compiled execution packs.
|
|
878
|
+
description: "Audit the current project against the essence contract, guard rules, and compiled execution packs. Can fall back to the hosted verifier when local compiled pack artifacts are missing only when allow_hosted_upload is true. Returns a schema-backed project audit report.",
|
|
783
879
|
inputSchema: {
|
|
784
880
|
type: "object",
|
|
785
881
|
properties: {
|
|
786
|
-
path: {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
882
|
+
path: {
|
|
883
|
+
type: "string",
|
|
884
|
+
description: "Optional path to the essence file for hosted fallback. Defaults to ./decantr.essence.json."
|
|
885
|
+
},
|
|
886
|
+
namespace: {
|
|
887
|
+
type: "string",
|
|
888
|
+
description: 'Optional preferred public namespace for hosted fallback. Defaults to "@official".'
|
|
889
|
+
},
|
|
890
|
+
dist_path: {
|
|
891
|
+
type: "string",
|
|
892
|
+
description: "Optional path to a local dist directory to snapshot for hosted runtime verification. Defaults to ./dist."
|
|
893
|
+
},
|
|
894
|
+
sources_path: {
|
|
895
|
+
type: "string",
|
|
896
|
+
description: "Optional path to a local source directory to snapshot for hosted source-level verification. For example `src` or `app`."
|
|
897
|
+
},
|
|
898
|
+
allow_hosted_upload: {
|
|
899
|
+
type: "boolean",
|
|
900
|
+
description: "Explicitly opt in to uploading local dist/source snapshots to the hosted verifier when local packs are missing. Defaults to false."
|
|
901
|
+
}
|
|
790
902
|
}
|
|
791
903
|
},
|
|
792
904
|
annotations: READ_ONLY_NETWORK
|
|
@@ -795,14 +907,30 @@ var TOOLS = [
|
|
|
795
907
|
{
|
|
796
908
|
name: "decantr_critique",
|
|
797
909
|
title: "Design Critique",
|
|
798
|
-
description: "Critique a file against the compiled review contract and Decantr verification heuristics.
|
|
910
|
+
description: "Critique a file against the compiled review contract and Decantr verification heuristics. Can fall back to the hosted verifier when local review packs are missing only when allow_hosted_upload is true. Returns a schema-backed file critique report with scores, findings, and focus areas.",
|
|
799
911
|
inputSchema: {
|
|
800
912
|
type: "object",
|
|
801
913
|
properties: {
|
|
802
|
-
file_path: {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
914
|
+
file_path: {
|
|
915
|
+
type: "string",
|
|
916
|
+
description: "Path to the component file to critique"
|
|
917
|
+
},
|
|
918
|
+
path: {
|
|
919
|
+
type: "string",
|
|
920
|
+
description: "Optional path to the essence file when using hosted fallback. Defaults to ./decantr.essence.json."
|
|
921
|
+
},
|
|
922
|
+
namespace: {
|
|
923
|
+
type: "string",
|
|
924
|
+
description: 'Optional preferred public namespace for hosted fallback. Defaults to "@official".'
|
|
925
|
+
},
|
|
926
|
+
treatments_path: {
|
|
927
|
+
type: "string",
|
|
928
|
+
description: "Optional path to treatments.css when using hosted fallback. Defaults to ./src/styles/treatments.css."
|
|
929
|
+
},
|
|
930
|
+
allow_hosted_upload: {
|
|
931
|
+
type: "boolean",
|
|
932
|
+
description: "Explicitly opt in to uploading the target source file to the hosted verifier when local review packs are missing. Defaults to false."
|
|
933
|
+
}
|
|
806
934
|
},
|
|
807
935
|
required: ["file_path"]
|
|
808
936
|
},
|
|
@@ -833,7 +961,11 @@ async function handleTool(name, args) {
|
|
|
833
961
|
try {
|
|
834
962
|
essence = JSON.parse(await readFile2(essencePath, "utf-8"));
|
|
835
963
|
} catch (e) {
|
|
836
|
-
return {
|
|
964
|
+
return {
|
|
965
|
+
valid: false,
|
|
966
|
+
errors: [`Could not read: ${e.message}`],
|
|
967
|
+
guardViolations: []
|
|
968
|
+
};
|
|
837
969
|
}
|
|
838
970
|
const result = validateEssence(essence);
|
|
839
971
|
let guardViolations = [];
|
|
@@ -986,15 +1118,18 @@ async function handleTool(name, args) {
|
|
|
986
1118
|
if (word.length < 3) continue;
|
|
987
1119
|
if (searchable.includes(word)) score += 10;
|
|
988
1120
|
}
|
|
989
|
-
if (desc.includes("dashboard") && ["kpi-grid", "chart-grid", "data-table", "filter-bar"].includes(slug))
|
|
1121
|
+
if (desc.includes("dashboard") && ["kpi-grid", "chart-grid", "data-table", "filter-bar"].includes(slug))
|
|
1122
|
+
score += 20;
|
|
990
1123
|
if (desc.includes("metric") && slug === "kpi-grid") score += 15;
|
|
991
1124
|
if (desc.includes("chart") && slug === "chart-grid") score += 15;
|
|
992
1125
|
if (desc.includes("table") && slug === "data-table") score += 15;
|
|
993
1126
|
if (desc.includes("form") && slug === "form-sections") score += 15;
|
|
994
1127
|
if (desc.includes("setting") && slug === "form-sections") score += 15;
|
|
995
|
-
if (desc.includes("landing") && ["hero", "cta-section", "card-grid"].includes(slug))
|
|
1128
|
+
if (desc.includes("landing") && ["hero", "cta-section", "card-grid"].includes(slug))
|
|
1129
|
+
score += 20;
|
|
996
1130
|
if (desc.includes("hero") && slug === "hero") score += 20;
|
|
997
|
-
if (desc.includes("ecommerce") && ["card-grid", "filter-bar", "detail-header"].includes(slug))
|
|
1131
|
+
if (desc.includes("ecommerce") && ["card-grid", "filter-bar", "detail-header"].includes(slug))
|
|
1132
|
+
score += 15;
|
|
998
1133
|
if (desc.includes("product") && slug === "card-grid") score += 15;
|
|
999
1134
|
if (desc.includes("feed") && slug === "activity-feed") score += 15;
|
|
1000
1135
|
if (desc.includes("filter") && slug === "filter-bar") score += 15;
|
|
@@ -1015,10 +1150,7 @@ async function handleTool(name, args) {
|
|
|
1015
1150
|
}
|
|
1016
1151
|
let score = candidate.score;
|
|
1017
1152
|
if (fullPattern) {
|
|
1018
|
-
const fullSearchable = [
|
|
1019
|
-
...fullPattern.components || [],
|
|
1020
|
-
...fullPattern.tags || []
|
|
1021
|
-
].join(" ").toLowerCase();
|
|
1153
|
+
const fullSearchable = [...fullPattern.components || [], ...fullPattern.tags || []].join(" ").toLowerCase();
|
|
1022
1154
|
const words = desc.split(/\s+/);
|
|
1023
1155
|
for (const word of words) {
|
|
1024
1156
|
if (word.length < 3) continue;
|
|
@@ -1272,7 +1404,7 @@ async function handleTool(name, args) {
|
|
|
1272
1404
|
meta: {
|
|
1273
1405
|
archetype: bestMatch,
|
|
1274
1406
|
target: framework,
|
|
1275
|
-
platform: { type: "spa", routing: "
|
|
1407
|
+
platform: { type: "spa", routing: "history" },
|
|
1276
1408
|
guard: { mode: "strict", dna_enforcement: "error", blueprint_enforcement: "warn" }
|
|
1277
1409
|
}
|
|
1278
1410
|
};
|
|
@@ -1296,18 +1428,24 @@ async function handleTool(name, args) {
|
|
|
1296
1428
|
return { error: 'Required parameter "violations" must be a non-empty array.' };
|
|
1297
1429
|
}
|
|
1298
1430
|
if (!resolution || !["accept", "accept_scoped", "reject", "defer"].includes(resolution)) {
|
|
1299
|
-
return {
|
|
1431
|
+
return {
|
|
1432
|
+
error: 'Required parameter "resolution" must be one of: accept, accept_scoped, reject, defer.'
|
|
1433
|
+
};
|
|
1300
1434
|
}
|
|
1301
1435
|
const hasDnaViolation = violations.some((v) => {
|
|
1302
1436
|
const rule = v.rule;
|
|
1303
|
-
return ["theme", "style", "density", "theme-mode", "accessibility", "theme-match"].includes(
|
|
1437
|
+
return ["theme", "style", "density", "theme-mode", "accessibility", "theme-match"].includes(
|
|
1438
|
+
rule
|
|
1439
|
+
);
|
|
1304
1440
|
});
|
|
1305
1441
|
if (hasDnaViolation && resolution !== "reject" && resolution !== "defer" && !args.confirm_dna) {
|
|
1306
1442
|
return {
|
|
1307
1443
|
error: "DNA-layer violations detected. Set confirm_dna: true to accept changes to design axioms (theme, density, accessibility, etc.).",
|
|
1308
1444
|
requires_confirmation: true,
|
|
1309
1445
|
dna_rules_affected: violations.filter(
|
|
1310
|
-
(v) => ["theme", "style", "density", "theme-mode", "accessibility", "theme-match"].includes(
|
|
1446
|
+
(v) => ["theme", "style", "density", "theme-mode", "accessibility", "theme-match"].includes(
|
|
1447
|
+
v.rule
|
|
1448
|
+
)
|
|
1311
1449
|
).map((v) => v.rule)
|
|
1312
1450
|
};
|
|
1313
1451
|
}
|
|
@@ -1364,9 +1502,19 @@ async function handleTool(name, args) {
|
|
|
1364
1502
|
if (!payload || typeof payload !== "object") {
|
|
1365
1503
|
return { error: 'Required parameter "payload" must be an object.' };
|
|
1366
1504
|
}
|
|
1367
|
-
const validOps = [
|
|
1505
|
+
const validOps = [
|
|
1506
|
+
"add_page",
|
|
1507
|
+
"remove_page",
|
|
1508
|
+
"update_page_layout",
|
|
1509
|
+
"update_dna",
|
|
1510
|
+
"update_blueprint",
|
|
1511
|
+
"add_feature",
|
|
1512
|
+
"remove_feature"
|
|
1513
|
+
];
|
|
1368
1514
|
if (!validOps.includes(operation)) {
|
|
1369
|
-
return {
|
|
1515
|
+
return {
|
|
1516
|
+
error: `Invalid operation "${operation}". Must be one of: ${validOps.join(", ")}`
|
|
1517
|
+
};
|
|
1370
1518
|
}
|
|
1371
1519
|
try {
|
|
1372
1520
|
const { essence, path } = await mutateEssenceFile(args.path, (v3) => {
|
|
@@ -1413,9 +1561,18 @@ async function handleTool(name, args) {
|
|
|
1413
1561
|
execution_pack: scaffoldPayload2,
|
|
1414
1562
|
review_pack: reviewPayload2,
|
|
1415
1563
|
pack_manifest: scaffoldSelected.manifest,
|
|
1416
|
-
available_sections: scaffoldSelected.manifest.sections.map((section) => ({
|
|
1417
|
-
|
|
1418
|
-
|
|
1564
|
+
available_sections: scaffoldSelected.manifest.sections.map((section) => ({
|
|
1565
|
+
id: section.id,
|
|
1566
|
+
page_ids: section.pageIds
|
|
1567
|
+
})),
|
|
1568
|
+
available_pages: scaffoldSelected.manifest.pages.map((page) => ({
|
|
1569
|
+
id: page.id,
|
|
1570
|
+
section_id: page.sectionId
|
|
1571
|
+
})),
|
|
1572
|
+
available_mutations: (scaffoldSelected.manifest.mutations ?? []).map((mutation) => ({
|
|
1573
|
+
id: mutation.id,
|
|
1574
|
+
mutation_type: mutation.mutationType
|
|
1575
|
+
})),
|
|
1419
1576
|
note: "Using hosted selected execution packs because local scaffold context artifacts were not found; scaffold pack markdown is being reused as readable scaffold context."
|
|
1420
1577
|
};
|
|
1421
1578
|
}
|
|
@@ -1435,9 +1592,18 @@ async function handleTool(name, args) {
|
|
|
1435
1592
|
execution_pack: scaffoldPayload,
|
|
1436
1593
|
review_pack: reviewPayload,
|
|
1437
1594
|
pack_manifest: hosted.bundle.manifest,
|
|
1438
|
-
available_sections: hosted.bundle.manifest.sections.map((section) => ({
|
|
1439
|
-
|
|
1440
|
-
|
|
1595
|
+
available_sections: hosted.bundle.manifest.sections.map((section) => ({
|
|
1596
|
+
id: section.id,
|
|
1597
|
+
page_ids: section.pageIds
|
|
1598
|
+
})),
|
|
1599
|
+
available_pages: hosted.bundle.manifest.pages.map((page) => ({
|
|
1600
|
+
id: page.id,
|
|
1601
|
+
section_id: page.sectionId
|
|
1602
|
+
})),
|
|
1603
|
+
available_mutations: (hosted.bundle.manifest.mutations ?? []).map((mutation) => ({
|
|
1604
|
+
id: mutation.id,
|
|
1605
|
+
mutation_type: mutation.mutationType
|
|
1606
|
+
})),
|
|
1441
1607
|
note: "Using hosted compiled execution packs because local scaffold context artifacts were not found; scaffold pack markdown is being reused as readable scaffold context."
|
|
1442
1608
|
};
|
|
1443
1609
|
}
|
|
@@ -1464,7 +1630,10 @@ async function handleTool(name, args) {
|
|
|
1464
1630
|
pack_manifest: manifest,
|
|
1465
1631
|
available_sections: manifest?.sections.map((section) => ({ id: section.id, page_ids: section.pageIds })) ?? [],
|
|
1466
1632
|
available_pages: manifest?.pages.map((page) => ({ id: page.id, section_id: page.sectionId })) ?? [],
|
|
1467
|
-
available_mutations: manifest?.mutations?.map((mutation) => ({
|
|
1633
|
+
available_mutations: manifest?.mutations?.map((mutation) => ({
|
|
1634
|
+
id: mutation.id,
|
|
1635
|
+
mutation_type: mutation.mutationType
|
|
1636
|
+
})) ?? []
|
|
1468
1637
|
};
|
|
1469
1638
|
}
|
|
1470
1639
|
case "decantr_get_section_context": {
|
|
@@ -1486,7 +1655,11 @@ async function handleTool(name, args) {
|
|
|
1486
1655
|
if (!section) {
|
|
1487
1656
|
return {
|
|
1488
1657
|
error: `Section "${sectionId}" not found.`,
|
|
1489
|
-
available_sections: sections.map((s) => ({
|
|
1658
|
+
available_sections: sections.map((s) => ({
|
|
1659
|
+
id: s.id,
|
|
1660
|
+
role: s.role,
|
|
1661
|
+
pages: s.pages.length
|
|
1662
|
+
}))
|
|
1490
1663
|
};
|
|
1491
1664
|
}
|
|
1492
1665
|
const packBasePath = join2(process.cwd(), ".decantr", "context", `section-${sectionId}-pack`);
|
|
@@ -1608,7 +1781,10 @@ async function handleTool(name, args) {
|
|
|
1608
1781
|
if (!pageEntry) {
|
|
1609
1782
|
return {
|
|
1610
1783
|
error: `Page "${pageId}" not found in execution pack manifest.`,
|
|
1611
|
-
available_pages: manifest.pages.map((page) => ({
|
|
1784
|
+
available_pages: manifest.pages.map((page) => ({
|
|
1785
|
+
id: page.id,
|
|
1786
|
+
section_id: page.sectionId
|
|
1787
|
+
})),
|
|
1612
1788
|
hosted_fallback_error: manifestSource === "hosted_fallback" ? void 0 : hostedFallbackError
|
|
1613
1789
|
};
|
|
1614
1790
|
}
|
|
@@ -1865,14 +2041,19 @@ async function handleTool(name, args) {
|
|
|
1865
2041
|
if (args.treatments_path != null && typeof args.treatments_path !== "string") {
|
|
1866
2042
|
return { error: "Invalid treatments_path. Must be a string when provided." };
|
|
1867
2043
|
}
|
|
2044
|
+
if (args.allow_hosted_upload != null && typeof args.allow_hosted_upload !== "boolean") {
|
|
2045
|
+
return { error: "Invalid allow_hosted_upload. Must be a boolean when provided." };
|
|
2046
|
+
}
|
|
1868
2047
|
const { critiqueFile } = await import("./critique-VEROHUF4.js");
|
|
1869
2048
|
const localReviewPackPath = join2(process.cwd(), ".decantr", "context", "review-pack.json");
|
|
1870
2049
|
if (existsSync(localReviewPackPath)) {
|
|
1871
2050
|
return critiqueFile(args.file_path, process.cwd());
|
|
1872
2051
|
}
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
2052
|
+
if (allowsHostedUpload(args)) {
|
|
2053
|
+
const hosted = await loadHostedFileCritiqueFallback(args);
|
|
2054
|
+
if (hosted.report) {
|
|
2055
|
+
return hosted.report;
|
|
2056
|
+
}
|
|
1876
2057
|
}
|
|
1877
2058
|
return critiqueFile(args.file_path, process.cwd());
|
|
1878
2059
|
}
|
|
@@ -1889,16 +2070,25 @@ async function handleTool(name, args) {
|
|
|
1889
2070
|
if (args.sources_path != null && typeof args.sources_path !== "string") {
|
|
1890
2071
|
return { error: "Invalid sources_path. Must be a string when provided." };
|
|
1891
2072
|
}
|
|
2073
|
+
if (args.allow_hosted_upload != null && typeof args.allow_hosted_upload !== "boolean") {
|
|
2074
|
+
return { error: "Invalid allow_hosted_upload. Must be a boolean when provided." };
|
|
2075
|
+
}
|
|
1892
2076
|
const { auditProject } = await import("@decantr/verifier");
|
|
1893
2077
|
const projectRoot = process.cwd();
|
|
1894
|
-
const hasReviewPack = existsSync(
|
|
1895
|
-
|
|
2078
|
+
const hasReviewPack = existsSync(
|
|
2079
|
+
join2(projectRoot, ".decantr", "context", "review-pack.json")
|
|
2080
|
+
);
|
|
2081
|
+
const hasPackManifest = existsSync(
|
|
2082
|
+
join2(projectRoot, ".decantr", "context", "pack-manifest.json")
|
|
2083
|
+
);
|
|
1896
2084
|
if (hasReviewPack && hasPackManifest) {
|
|
1897
2085
|
return auditProject(projectRoot);
|
|
1898
2086
|
}
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
2087
|
+
if (allowsHostedUpload(args)) {
|
|
2088
|
+
const hosted = await loadHostedProjectAuditFallback(args);
|
|
2089
|
+
if (hosted.report) {
|
|
2090
|
+
return hosted.report;
|
|
2091
|
+
}
|
|
1902
2092
|
}
|
|
1903
2093
|
return auditProject(projectRoot);
|
|
1904
2094
|
}
|
|
@@ -1966,7 +2156,8 @@ function applyEssenceUpdate(essence, operation, payload) {
|
|
|
1966
2156
|
const id = payload.id;
|
|
1967
2157
|
const layout = payload.layout;
|
|
1968
2158
|
if (!id) throw new Error('Payload must include "id" for update_page_layout.');
|
|
1969
|
-
if (!layout || !Array.isArray(layout))
|
|
2159
|
+
if (!layout || !Array.isArray(layout))
|
|
2160
|
+
throw new Error('Payload must include "layout" array for update_page_layout.');
|
|
1970
2161
|
const page = essence.blueprint.pages.find((p) => p.id === id);
|
|
1971
2162
|
if (!page) throw new Error(`Page "${id}" not found.`);
|
|
1972
2163
|
page.layout = layout;
|
|
@@ -2034,10 +2225,7 @@ function describeUpdate(operation, payload) {
|
|
|
2034
2225
|
|
|
2035
2226
|
// src/index.ts
|
|
2036
2227
|
var VERSION = "0.2.0";
|
|
2037
|
-
var server = new Server(
|
|
2038
|
-
{ name: "decantr", version: VERSION },
|
|
2039
|
-
{ capabilities: { tools: {} } }
|
|
2040
|
-
);
|
|
2228
|
+
var server = new Server({ name: "decantr", version: VERSION }, { capabilities: { tools: {} } });
|
|
2041
2229
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2042
2230
|
return { tools: TOOLS };
|
|
2043
2231
|
});
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-A4ZCCVQR.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decantr/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"mcpName": "io.github.decantr-ai/mcp-server",
|
|
5
5
|
"description": "MCP server for Decantr — exposes design intelligence, packs, and verification to AI coding assistants",
|
|
6
6
|
"keywords": [
|
|
@@ -38,13 +38,18 @@
|
|
|
38
38
|
"files": [
|
|
39
39
|
"dist"
|
|
40
40
|
],
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20"
|
|
43
|
+
},
|
|
41
44
|
"publishConfig": {
|
|
42
45
|
"access": "public"
|
|
43
46
|
},
|
|
44
47
|
"scripts": {
|
|
45
48
|
"build": "tsup",
|
|
46
49
|
"test": "vitest run",
|
|
47
|
-
"test:watch": "vitest"
|
|
50
|
+
"test:watch": "vitest",
|
|
51
|
+
"prepublishOnly": "pnpm build",
|
|
52
|
+
"preversion": "pnpm build && pnpm test"
|
|
48
53
|
},
|
|
49
54
|
"dependencies": {
|
|
50
55
|
"@decantr/essence-spec": "workspace:*",
|