@decantr/mcp-server 1.0.4 → 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/dist/bin.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-UNVKE2YC.js";
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, join as join2, dirname as dirname2, isAbsolute, resolve } from "path";
13
- import { validateEssence, evaluateGuard, isV3 as isV32 } from "@decantr/essence-spec";
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 { readFile, writeFile, mkdir } from "fs/promises";
18
- import { join, dirname } from "path";
19
- import { RegistryAPIClient } from "@decantr/registry";
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 || join(process.cwd(), "decantr.essence.json");
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 || process.cwd();
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 || process.cwd();
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 = getPublicAPIClient();
180
+ const client = getAPIClient();
159
181
  const filePath = args.file_path;
160
- const resolvedFilePath = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath);
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" ? isAbsolute(args.treatments_path) ? args.treatments_path : resolve(process.cwd(), args.treatments_path) : join2(process.cwd(), "src", "styles", "treatments.css");
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 ? isAbsolute(distPathArg) ? distPathArg : resolve(projectRoot, distPathArg) : join2(projectRoot, "dist");
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 assetFilePath = join2(distRoot, assetPath.replace(/^[/\\]+/, ""));
217
+ const snapshotAssetPath = assetPath.replace(/^[/\\]+/, "");
218
+ const assetFilePath = resolveWorkspacePath(snapshotAssetPath, distRoot);
195
219
  if (existsSync(assetFilePath)) {
196
- assets[assetPath] = readFileSync(assetFilePath, "utf-8");
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 = isAbsolute(sourcesPathArg) ? sourcesPathArg : resolve(projectRoot, sourcesPathArg);
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(["node_modules", ".git", ".decantr", "dist", "build", "coverage"]);
218
- const rootPrefix = basename(sourcesRoot);
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 = getPublicAPIClient();
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(process.cwd(), args.sources_path);
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({ from: "public", to: "gateway", type: "conversion", trigger: gatewayTrigger });
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: { type: "string", description: "Optional path to essence file. Defaults to ./decantr.essence.json." },
435
- layer: { type: "string", enum: ["dna", "blueprint", "full"], description: "For v3 essences: return only the specified layer. Defaults to full." }
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: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." }
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: { type: "string", description: "Optional intelligence source filter: authored, benchmark, or hybrid." }
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: { type: "string", description: 'Blueprint ID (e.g. "saas-dashboard", "ecommerce", "portfolio")' },
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: { type: "string", description: 'Description of the page or section (e.g. "dashboard with metrics and charts", "settings form with toggles")' }
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: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." },
540
- page_id: { type: "string", description: 'Page ID being modified (e.g. "overview", "settings")' },
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: { type: "string", description: 'Natural language project description (e.g. "SaaS dashboard with analytics, user management, and billing")' },
560
- framework: { type: "string", description: 'Target framework (e.g. "react", "vue", "svelte"). Defaults to "react".' }
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: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." },
594
- confirm_dna: { type: "boolean", description: "Required to be true when accepting DNA-layer violations." }
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: ["add_page", "remove_page", "update_page_layout", "update_dna", "update_blueprint", "add_feature", "remove_feature"],
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: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." }
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: { type: "string", description: 'Section ID (archetype ID, e.g., "ai-chatbot", "auth-full", "settings-full")' },
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: { type: "string", description: 'Page ID (for example "overview", "settings", or "home").' },
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. Falls back to the hosted verifier when local compiled pack artifacts are missing. Returns a schema-backed project audit report.",
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: { type: "string", description: "Optional path to the essence file for hosted fallback. Defaults to ./decantr.essence.json." },
787
- namespace: { type: "string", description: 'Optional preferred public namespace for hosted fallback. Defaults to "@official".' },
788
- dist_path: { type: "string", description: "Optional path to a local dist directory to snapshot for hosted runtime verification. Defaults to ./dist." },
789
- sources_path: { type: "string", description: "Optional path to a local source directory to snapshot for hosted source-level verification. For example `src` or `app`." }
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. Falls back to the hosted verifier when local review packs are missing. Returns a schema-backed file critique report with scores, findings, and focus areas.",
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: { type: "string", description: "Path to the component file to critique" },
803
- path: { type: "string", description: "Optional path to the essence file when using hosted fallback. Defaults to ./decantr.essence.json." },
804
- namespace: { type: "string", description: 'Optional preferred public namespace for hosted fallback. Defaults to "@official".' },
805
- treatments_path: { type: "string", description: "Optional path to treatments.css when using hosted fallback. Defaults to ./src/styles/treatments.css." }
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 { valid: false, errors: [`Could not read: ${e.message}`], guardViolations: [] };
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)) score += 20;
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)) score += 20;
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)) score += 15;
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;
@@ -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 { error: 'Required parameter "resolution" must be one of: accept, accept_scoped, reject, defer.' };
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(rule);
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(v.rule)
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 = ["add_page", "remove_page", "update_page_layout", "update_dna", "update_blueprint", "add_feature", "remove_feature"];
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 { error: `Invalid operation "${operation}". Must be one of: ${validOps.join(", ")}` };
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) => ({ id: section.id, page_ids: section.pageIds })),
1417
- available_pages: scaffoldSelected.manifest.pages.map((page) => ({ id: page.id, section_id: page.sectionId })),
1418
- available_mutations: (scaffoldSelected.manifest.mutations ?? []).map((mutation) => ({ id: mutation.id, mutation_type: mutation.mutationType })),
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) => ({ id: section.id, page_ids: section.pageIds })),
1439
- available_pages: hosted.bundle.manifest.pages.map((page) => ({ id: page.id, section_id: page.sectionId })),
1440
- available_mutations: (hosted.bundle.manifest.mutations ?? []).map((mutation) => ({ id: mutation.id, mutation_type: mutation.mutationType })),
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) => ({ id: mutation.id, mutation_type: mutation.mutationType })) ?? []
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) => ({ id: s.id, role: s.role, pages: s.pages.length }))
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) => ({ id: page.id, section_id: page.sectionId })),
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
- const hosted = await loadHostedFileCritiqueFallback(args);
1874
- if (hosted.report) {
1875
- return hosted.report;
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(join2(projectRoot, ".decantr", "context", "review-pack.json"));
1895
- const hasPackManifest = existsSync(join2(projectRoot, ".decantr", "context", "pack-manifest.json"));
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
- const hosted = await loadHostedProjectAuditFallback(args);
1900
- if (hosted.report) {
1901
- return hosted.report;
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)) throw new Error('Payload must include "layout" array for update_page_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-UNVKE2YC.js";
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.4",
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,18 +38,23 @@
38
38
  "files": [
39
39
  "dist"
40
40
  ],
41
+ "engines": {
42
+ "node": ">=20"
43
+ },
41
44
  "publishConfig": {
42
45
  "access": "public"
43
46
  },
44
- "dependencies": {
45
- "@modelcontextprotocol/sdk": "^1.29.0",
46
- "@decantr/essence-spec": "1.0.3",
47
- "@decantr/registry": "1.0.2",
48
- "@decantr/verifier": "1.0.2"
49
- },
50
47
  "scripts": {
51
48
  "build": "tsup",
52
49
  "test": "vitest run",
53
- "test:watch": "vitest"
50
+ "test:watch": "vitest",
51
+ "prepublishOnly": "pnpm build",
52
+ "preversion": "pnpm build && pnpm test"
53
+ },
54
+ "dependencies": {
55
+ "@decantr/essence-spec": "workspace:*",
56
+ "@decantr/registry": "workspace:*",
57
+ "@decantr/verifier": "workspace:*",
58
+ "@modelcontextprotocol/sdk": "^1.29.0"
54
59
  }
55
- }
60
+ }
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.