@decantr/mcp-server 2.0.0 → 2.1.0
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 +8 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-MUNBODQK.js → chunk-FEXPLJKB.js} +486 -0
- package/dist/index.js +1 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ Design intelligence for AI-generated UI. Make Claude, Cursor, and Windsurf gener
|
|
|
13
13
|

|
|
14
14
|
|
|
15
15
|
- **Structured design context** -- gives your AI assistant patterns, layouts, and component specs instead of letting it guess
|
|
16
|
+
- **Evidence-backed repair loops** -- gives AI agents Project Health, Evidence Bundles, workspace health, and scoped repair prompts without uploading source
|
|
16
17
|
- **Drift detection** -- catches when generated code deviates from your design intent
|
|
17
18
|
- **Zero config** -- run with `npx`, no API keys or accounts required
|
|
18
19
|
|
|
@@ -138,6 +139,10 @@ The server exposes Decantr registry, context, benchmark, and verification tools.
|
|
|
138
139
|
| `decantr_compile_execution_packs` | Compile a hosted execution-pack bundle from a local or inline essence document | `{ "path": "./decantr.essence.json", "namespace": "@official" }` |
|
|
139
140
|
| `decantr_audit_project` | Run the schema-backed Decantr project audit against essence and compiled packs, with hosted fallback when local pack artifacts are missing | `{ "namespace": "@official" }` |
|
|
140
141
|
| `decantr_critique` | Critique a file against the compiled review contract, with hosted fallback when local review packs are missing | `{ "file_path": "./src/pages/Overview.tsx", "namespace": "@official" }` |
|
|
142
|
+
| `decantr_get_evidence_bundle` | Generate the local privacy-redacted Evidence Bundle for a project | `{ "project_path": "apps/web" }` |
|
|
143
|
+
| `decantr_workspace_health` | Discover Decantr projects and return aggregate workspace health | `{ "workspace_root": ".", "max_projects": 100 }` |
|
|
144
|
+
| `decantr_get_repair_prompt` | Return the scoped repair prompt for a health finding | `{ "finding_id": "assertion-contract-context-pack-manifest" }` |
|
|
145
|
+
| `decantr_run_health_loop` | Run health, evidence, and next repair prompt in one local agent loop | `{ "project_path": "apps/web" }` |
|
|
141
146
|
| `decantr_get_showcase_benchmarks` | Read the audited showcase corpus manifest, shortlist, or verification report | `{ "view": "verification" }` |
|
|
142
147
|
|
|
143
148
|
For the broader product surface and support policy, see the root Decantr docs and package support matrix.
|
|
@@ -169,8 +174,10 @@ The AI assistant calls these tools behind the scenes:
|
|
|
169
174
|
7. `decantr_check_drift` -- validates the generated code against the Essence spec before presenting it
|
|
170
175
|
8. `decantr_critique` -- critiques a specific file, falling back to the hosted verifier when the local review pack is missing
|
|
171
176
|
9. `decantr_audit_project` -- runs the stronger project-level audit once the implementation is in place
|
|
177
|
+
10. `decantr_get_evidence_bundle` -- returns the local evidence bundle for the AI repair loop
|
|
178
|
+
11. `decantr_get_repair_prompt` -- gives the assistant exact finding evidence, constraints to preserve, and commands to rerun
|
|
172
179
|
|
|
173
|
-
The AI now generates code with the right layout structure, correct components, and consistent styling
|
|
180
|
+
The AI now generates code with the right layout structure, correct components, and consistent styling, then gets a scoped evidence-backed repair loop instead of a generic guess.
|
|
174
181
|
|
|
175
182
|
## License
|
|
176
183
|
|
package/dist/bin.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-FEXPLJKB.js";
|
|
@@ -469,6 +469,326 @@ var WRITE_TOOL = {
|
|
|
469
469
|
idempotentHint: false,
|
|
470
470
|
openWorldHint: false
|
|
471
471
|
};
|
|
472
|
+
var MCP_PROJECT_HEALTH_SCHEMA_URL = "https://decantr.ai/schemas/project-health-report.v1.json";
|
|
473
|
+
var MCP_WORKSPACE_HEALTH_SCHEMA_URL = "https://decantr.ai/schemas/workspace-health-report.v1.json";
|
|
474
|
+
var MCP_WORKSPACE_IGNORES = /* @__PURE__ */ new Set([
|
|
475
|
+
".git",
|
|
476
|
+
".next",
|
|
477
|
+
".turbo",
|
|
478
|
+
".vercel",
|
|
479
|
+
"coverage",
|
|
480
|
+
"dist",
|
|
481
|
+
"node_modules",
|
|
482
|
+
"playwright-report"
|
|
483
|
+
]);
|
|
484
|
+
function mcpSlug(value) {
|
|
485
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
486
|
+
}
|
|
487
|
+
function mcpStatusFromCounts(counts) {
|
|
488
|
+
if (counts.errorCount > 0) return "error";
|
|
489
|
+
if (counts.warnCount > 0) return "warning";
|
|
490
|
+
return "healthy";
|
|
491
|
+
}
|
|
492
|
+
function mcpScoreFromCounts(counts) {
|
|
493
|
+
return Math.max(0, Math.min(100, 100 - counts.errorCount * 15 - counts.warnCount * 5 - counts.infoCount));
|
|
494
|
+
}
|
|
495
|
+
function mcpCommandsForFinding(source) {
|
|
496
|
+
switch (source) {
|
|
497
|
+
case "assertion":
|
|
498
|
+
return ["decantr refresh", "decantr health --evidence"];
|
|
499
|
+
case "brownfield":
|
|
500
|
+
return ["decantr analyze", "decantr init --existing --merge-proposal", "decantr health"];
|
|
501
|
+
case "browser":
|
|
502
|
+
return ["decantr health --browser", "decantr health --evidence"];
|
|
503
|
+
case "check":
|
|
504
|
+
return ["decantr check", "decantr health"];
|
|
505
|
+
case "design-token":
|
|
506
|
+
return ["decantr export --to figma-tokens", "decantr health --evidence"];
|
|
507
|
+
case "interaction":
|
|
508
|
+
return ["decantr check --strict", "decantr health"];
|
|
509
|
+
case "pack":
|
|
510
|
+
return ["decantr refresh", "decantr registry get-pack review --write-context", "decantr health"];
|
|
511
|
+
case "runtime":
|
|
512
|
+
return ["npm run build", "decantr health"];
|
|
513
|
+
default:
|
|
514
|
+
return ["decantr audit", "decantr health"];
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function mcpSourceFromFinding(finding) {
|
|
518
|
+
const category = finding.category.toLowerCase();
|
|
519
|
+
const id = finding.id.toLowerCase();
|
|
520
|
+
const rule = finding.rule?.toLowerCase() ?? "";
|
|
521
|
+
if (category.includes("runtime") || category.includes("document") || category.includes("performance")) {
|
|
522
|
+
return "runtime";
|
|
523
|
+
}
|
|
524
|
+
if (category.includes("pack") || category.includes("review contract")) {
|
|
525
|
+
return "pack";
|
|
526
|
+
}
|
|
527
|
+
if (category.includes("interaction") || id.includes("interaction") || rule.includes("interaction")) {
|
|
528
|
+
return "interaction";
|
|
529
|
+
}
|
|
530
|
+
return "audit";
|
|
531
|
+
}
|
|
532
|
+
function mcpBuildRepairPrompt(input) {
|
|
533
|
+
return [
|
|
534
|
+
"You are fixing one Decantr Project Health finding in this local workspace.",
|
|
535
|
+
"",
|
|
536
|
+
"Read `DECANTR.md`, `decantr.essence.json`, and `.decantr/context/scaffold-pack.md` if they exist. For route or page work, read the matching page/section packs before editing.",
|
|
537
|
+
"",
|
|
538
|
+
`Finding: ${input.id}`,
|
|
539
|
+
`Source: ${input.source}`,
|
|
540
|
+
`Severity: ${input.severity}`,
|
|
541
|
+
`Category: ${input.category}`,
|
|
542
|
+
`Message: ${input.message}`,
|
|
543
|
+
input.evidence.length > 0 ? `Evidence:
|
|
544
|
+
${input.evidence.map((entry) => `- ${entry}`).join("\n")}` : null,
|
|
545
|
+
input.suggestedFix ? `Suggested fix: ${input.suggestedFix}` : null,
|
|
546
|
+
"",
|
|
547
|
+
"Make the smallest coherent code or contract change that resolves this finding. Preserve the existing framework, routing, styling system, and Decantr workflow mode unless the finding explicitly requires a contract update.",
|
|
548
|
+
"Do not rewrite unrelated routes, replace the styling system, remove existing product behavior, or regenerate Decantr artifacts unless the finding is about stale or missing generated context.",
|
|
549
|
+
"",
|
|
550
|
+
`After the fix, run:
|
|
551
|
+
${input.commands.map((command) => `- ${command}`).join("\n")}`
|
|
552
|
+
].filter((line) => Boolean(line)).join("\n");
|
|
553
|
+
}
|
|
554
|
+
function mcpHealthFinding(input) {
|
|
555
|
+
const id = `${input.source}-${mcpSlug(input.baseId || input.rule || `${input.category}-${input.message}`)}`;
|
|
556
|
+
const commands = mcpCommandsForFinding(input.source);
|
|
557
|
+
return {
|
|
558
|
+
id,
|
|
559
|
+
source: input.source,
|
|
560
|
+
category: input.category,
|
|
561
|
+
severity: input.severity,
|
|
562
|
+
message: input.message,
|
|
563
|
+
evidence: input.evidence ?? [],
|
|
564
|
+
target: input.target,
|
|
565
|
+
file: input.file,
|
|
566
|
+
rule: input.rule,
|
|
567
|
+
suggestedFix: input.suggestedFix,
|
|
568
|
+
remediation: {
|
|
569
|
+
summary: input.suggestedFix || `Resolve ${input.category.toLowerCase()} finding.`,
|
|
570
|
+
commands,
|
|
571
|
+
prompt: mcpBuildRepairPrompt({
|
|
572
|
+
id,
|
|
573
|
+
source: input.source,
|
|
574
|
+
category: input.category,
|
|
575
|
+
severity: input.severity,
|
|
576
|
+
message: input.message,
|
|
577
|
+
evidence: input.evidence ?? [],
|
|
578
|
+
suggestedFix: input.suggestedFix,
|
|
579
|
+
commands
|
|
580
|
+
})
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function mcpCollectDeclaredRoutes(essence) {
|
|
585
|
+
if (!essence || !isV42(essence)) return [];
|
|
586
|
+
return Object.keys(essence.blueprint.routes ?? {}).sort();
|
|
587
|
+
}
|
|
588
|
+
function mcpReportFromAudit(projectRoot, audit, assertions) {
|
|
589
|
+
const findings = [];
|
|
590
|
+
const seen = /* @__PURE__ */ new Set();
|
|
591
|
+
const pushUnique = (finding) => {
|
|
592
|
+
const key = `${finding.rule ?? finding.id}|${finding.message}`;
|
|
593
|
+
if (seen.has(key)) return;
|
|
594
|
+
seen.add(key);
|
|
595
|
+
findings.push(finding);
|
|
596
|
+
};
|
|
597
|
+
for (const finding of audit.findings) {
|
|
598
|
+
pushUnique(
|
|
599
|
+
mcpHealthFinding({
|
|
600
|
+
source: mcpSourceFromFinding(finding),
|
|
601
|
+
category: finding.category,
|
|
602
|
+
severity: finding.severity,
|
|
603
|
+
message: finding.message,
|
|
604
|
+
evidence: finding.evidence,
|
|
605
|
+
target: finding.target,
|
|
606
|
+
file: finding.file,
|
|
607
|
+
rule: finding.rule,
|
|
608
|
+
suggestedFix: finding.suggestedFix,
|
|
609
|
+
baseId: finding.id
|
|
610
|
+
})
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
for (const assertion of assertions) {
|
|
614
|
+
if (assertion.status !== "failed") continue;
|
|
615
|
+
pushUnique(
|
|
616
|
+
mcpHealthFinding({
|
|
617
|
+
source: "assertion",
|
|
618
|
+
category: `Contract ${assertion.category}`,
|
|
619
|
+
severity: assertion.severity,
|
|
620
|
+
message: assertion.message,
|
|
621
|
+
evidence: assertion.evidence,
|
|
622
|
+
target: assertion.target,
|
|
623
|
+
rule: assertion.rule,
|
|
624
|
+
suggestedFix: assertion.suggestedFix,
|
|
625
|
+
baseId: assertion.id
|
|
626
|
+
})
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
if (!audit.valid && findings.every((finding) => finding.severity !== "error")) {
|
|
630
|
+
pushUnique(
|
|
631
|
+
mcpHealthFinding({
|
|
632
|
+
source: "audit",
|
|
633
|
+
category: "Project Contract",
|
|
634
|
+
severity: "error",
|
|
635
|
+
message: "Project audit is not valid.",
|
|
636
|
+
evidence: ["The verifier returned valid=false."],
|
|
637
|
+
rule: "project-audit-invalid",
|
|
638
|
+
suggestedFix: "Resolve blocking audit findings and rerun `decantr health`."
|
|
639
|
+
})
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
const counts = {
|
|
643
|
+
errorCount: findings.filter((finding) => finding.severity === "error").length,
|
|
644
|
+
warnCount: findings.filter((finding) => finding.severity === "warn").length,
|
|
645
|
+
infoCount: findings.filter((finding) => finding.severity === "info").length
|
|
646
|
+
};
|
|
647
|
+
const manifest = audit.packManifest;
|
|
648
|
+
return {
|
|
649
|
+
$schema: MCP_PROJECT_HEALTH_SCHEMA_URL,
|
|
650
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
651
|
+
projectRoot,
|
|
652
|
+
status: mcpStatusFromCounts(counts),
|
|
653
|
+
score: mcpScoreFromCounts(counts),
|
|
654
|
+
summary: {
|
|
655
|
+
...counts,
|
|
656
|
+
findingCount: findings.length,
|
|
657
|
+
workflowMode: null,
|
|
658
|
+
adoptionMode: null,
|
|
659
|
+
essenceVersion: audit.summary.essenceVersion,
|
|
660
|
+
pageCount: audit.summary.pageCount,
|
|
661
|
+
runtimeAuditChecked: audit.summary.runtimeAuditChecked,
|
|
662
|
+
runtimePassed: audit.summary.runtimePassed,
|
|
663
|
+
packManifestPresent: audit.summary.packManifestPresent,
|
|
664
|
+
reviewPackPresent: audit.summary.reviewPackPresent
|
|
665
|
+
},
|
|
666
|
+
routes: {
|
|
667
|
+
declared: mcpCollectDeclaredRoutes(audit.essence),
|
|
668
|
+
runtimeChecked: audit.runtimeAudit.routeHintsChecked,
|
|
669
|
+
runtimeMatched: audit.runtimeAudit.routeHintsMatched,
|
|
670
|
+
runtimeCoverageOk: audit.summary.runtimeAuditChecked ? audit.runtimeAudit.routeHintsCoverageOk : null,
|
|
671
|
+
issues: findings.filter(
|
|
672
|
+
(finding) => finding.category.toLowerCase().includes("route") || finding.rule?.toLowerCase().includes("route") || finding.id.toLowerCase().includes("route")
|
|
673
|
+
).map((finding) => finding.message)
|
|
674
|
+
},
|
|
675
|
+
packs: {
|
|
676
|
+
manifestPresent: Boolean(manifest),
|
|
677
|
+
reviewPackPresent: Boolean(manifest?.review ?? audit.reviewPack),
|
|
678
|
+
scaffoldPackPresent: Boolean(manifest?.scaffold),
|
|
679
|
+
sectionPackCount: manifest?.sections.length ?? 0,
|
|
680
|
+
pagePackCount: manifest?.pages.length ?? 0,
|
|
681
|
+
mutationPackCount: manifest?.mutations?.length ?? 0,
|
|
682
|
+
generatedAt: typeof manifest?.generatedAt === "string" ? manifest.generatedAt : null
|
|
683
|
+
},
|
|
684
|
+
ci: {
|
|
685
|
+
recommendedCommand: "decantr health --ci --fail-on error",
|
|
686
|
+
failOn: "error"
|
|
687
|
+
},
|
|
688
|
+
findings
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
function resolveMcpProjectRoot(value) {
|
|
692
|
+
if (value == null) return process.cwd();
|
|
693
|
+
if (typeof value !== "string") {
|
|
694
|
+
throw new Error("project_path must be a string when provided.");
|
|
695
|
+
}
|
|
696
|
+
return resolveWorkspacePath(value);
|
|
697
|
+
}
|
|
698
|
+
async function getMcpHealthState(projectRoot) {
|
|
699
|
+
const { auditProject, createContractAssertions, createEvidenceBundle } = await import("@decantr/verifier");
|
|
700
|
+
const audit = await auditProject(projectRoot);
|
|
701
|
+
const assertions = createContractAssertions(projectRoot, audit);
|
|
702
|
+
const report = mcpReportFromAudit(projectRoot, audit, assertions);
|
|
703
|
+
const evidence = createEvidenceBundle({
|
|
704
|
+
projectRoot,
|
|
705
|
+
audit,
|
|
706
|
+
assertions,
|
|
707
|
+
report,
|
|
708
|
+
workspaceConfigPath: existsSync(join2(projectRoot, ".decantr", "workspace.json")) ? join2(projectRoot, ".decantr", "workspace.json") : null
|
|
709
|
+
});
|
|
710
|
+
return { audit, assertions, report, evidence };
|
|
711
|
+
}
|
|
712
|
+
function discoverMcpWorkspaceProjects(root, maxProjects = 500) {
|
|
713
|
+
const projects = [];
|
|
714
|
+
function walk(dir, depth) {
|
|
715
|
+
if (projects.length >= maxProjects || depth > 6) return;
|
|
716
|
+
if (existsSync(join2(dir, "decantr.essence.json"))) {
|
|
717
|
+
const path = relative2(root, dir).replace(/\\/g, "/") || ".";
|
|
718
|
+
projects.push({
|
|
719
|
+
id: path.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "project",
|
|
720
|
+
path,
|
|
721
|
+
absolutePath: dir
|
|
722
|
+
});
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
726
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
727
|
+
if (MCP_WORKSPACE_IGNORES.has(entry.name)) continue;
|
|
728
|
+
walk(join2(dir, entry.name), depth + 1);
|
|
729
|
+
if (projects.length >= maxProjects) return;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
walk(root, 0);
|
|
733
|
+
return projects.sort((a, b) => a.path.localeCompare(b.path));
|
|
734
|
+
}
|
|
735
|
+
async function getMcpWorkspaceHealth(args) {
|
|
736
|
+
const root = args.workspace_root == null ? process.cwd() : resolveMcpProjectRoot(args.workspace_root);
|
|
737
|
+
const maxProjects = typeof args.max_projects === "number" && Number.isFinite(args.max_projects) ? Math.max(1, Math.floor(args.max_projects)) : 500;
|
|
738
|
+
const discovered = discoverMcpWorkspaceProjects(root, maxProjects);
|
|
739
|
+
const projects = [];
|
|
740
|
+
for (const project of discovered) {
|
|
741
|
+
const startedAt = Date.now();
|
|
742
|
+
try {
|
|
743
|
+
const state = await getMcpHealthState(project.absolutePath);
|
|
744
|
+
projects.push({
|
|
745
|
+
id: project.id,
|
|
746
|
+
path: project.path,
|
|
747
|
+
status: state.report.status,
|
|
748
|
+
score: state.report.score,
|
|
749
|
+
errorCount: state.report.summary.errorCount,
|
|
750
|
+
warnCount: state.report.summary.warnCount,
|
|
751
|
+
infoCount: state.report.summary.infoCount,
|
|
752
|
+
findingCount: state.report.summary.findingCount,
|
|
753
|
+
durationMs: Date.now() - startedAt,
|
|
754
|
+
changed: false,
|
|
755
|
+
source: "auto",
|
|
756
|
+
error: null
|
|
757
|
+
});
|
|
758
|
+
} catch (error) {
|
|
759
|
+
projects.push({
|
|
760
|
+
id: project.id,
|
|
761
|
+
path: project.path,
|
|
762
|
+
status: "failed",
|
|
763
|
+
score: 0,
|
|
764
|
+
errorCount: 1,
|
|
765
|
+
warnCount: 0,
|
|
766
|
+
infoCount: 0,
|
|
767
|
+
findingCount: 1,
|
|
768
|
+
durationMs: Date.now() - startedAt,
|
|
769
|
+
changed: false,
|
|
770
|
+
source: "auto",
|
|
771
|
+
error: error.message
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
$schema: MCP_WORKSPACE_HEALTH_SCHEMA_URL,
|
|
777
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
778
|
+
workspaceRoot: "<workspace>",
|
|
779
|
+
changedOnly: false,
|
|
780
|
+
since: null,
|
|
781
|
+
summary: {
|
|
782
|
+
projectCount: discovered.length,
|
|
783
|
+
checkedCount: projects.length,
|
|
784
|
+
healthyCount: projects.filter((project) => project.status === "healthy").length,
|
|
785
|
+
warningCount: projects.filter((project) => project.status === "warning").length,
|
|
786
|
+
errorCount: projects.filter((project) => project.status === "error").length,
|
|
787
|
+
failedCount: projects.filter((project) => project.status === "failed").length
|
|
788
|
+
},
|
|
789
|
+
projects
|
|
790
|
+
};
|
|
791
|
+
}
|
|
472
792
|
var TOOLS = [
|
|
473
793
|
// 1. decantr_read_essence — local read
|
|
474
794
|
{
|
|
@@ -940,6 +1260,82 @@ var TOOLS = [
|
|
|
940
1260
|
required: ["file_path"]
|
|
941
1261
|
},
|
|
942
1262
|
annotations: READ_ONLY_NETWORK
|
|
1263
|
+
},
|
|
1264
|
+
// 22. decantr_get_evidence_bundle — local reliability artifact
|
|
1265
|
+
{
|
|
1266
|
+
name: "decantr_get_evidence_bundle",
|
|
1267
|
+
title: "Get Evidence Bundle",
|
|
1268
|
+
description: "Generate a local Evidence Bundle for the current Decantr project. The bundle redacts source, prompts, secrets, absolute paths, repo names, and screenshots by default.",
|
|
1269
|
+
inputSchema: {
|
|
1270
|
+
type: "object",
|
|
1271
|
+
properties: {
|
|
1272
|
+
project_path: {
|
|
1273
|
+
type: "string",
|
|
1274
|
+
description: "Optional relative project path inside the active workspace. Defaults to the current working directory."
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
},
|
|
1278
|
+
annotations: READ_ONLY
|
|
1279
|
+
},
|
|
1280
|
+
// 23. decantr_workspace_health — local workspace reliability scan
|
|
1281
|
+
{
|
|
1282
|
+
name: "decantr_workspace_health",
|
|
1283
|
+
title: "Workspace Health",
|
|
1284
|
+
description: "Discover Decantr projects in the active workspace and return deterministic aggregate health for monorepos with many Decantr apps.",
|
|
1285
|
+
inputSchema: {
|
|
1286
|
+
type: "object",
|
|
1287
|
+
properties: {
|
|
1288
|
+
workspace_root: {
|
|
1289
|
+
type: "string",
|
|
1290
|
+
description: "Optional relative workspace root inside the active workspace. Defaults to the current working directory."
|
|
1291
|
+
},
|
|
1292
|
+
max_projects: {
|
|
1293
|
+
type: "number",
|
|
1294
|
+
description: "Optional cap on discovered projects. Defaults to 500."
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
},
|
|
1298
|
+
annotations: READ_ONLY
|
|
1299
|
+
},
|
|
1300
|
+
// 24. decantr_get_repair_prompt — local AI repair loop
|
|
1301
|
+
{
|
|
1302
|
+
name: "decantr_get_repair_prompt",
|
|
1303
|
+
title: "Get Repair Prompt",
|
|
1304
|
+
description: "Return an AI-ready repair prompt for a Project Health finding, including exact finding evidence, preserved constraints, do-not-change guidance, and rerun commands.",
|
|
1305
|
+
inputSchema: {
|
|
1306
|
+
type: "object",
|
|
1307
|
+
properties: {
|
|
1308
|
+
project_path: {
|
|
1309
|
+
type: "string",
|
|
1310
|
+
description: "Optional relative project path inside the active workspace. Defaults to the current working directory."
|
|
1311
|
+
},
|
|
1312
|
+
finding_id: {
|
|
1313
|
+
type: "string",
|
|
1314
|
+
description: "Optional finding id. Defaults to the first error or warning, then the first finding."
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
},
|
|
1318
|
+
annotations: READ_ONLY
|
|
1319
|
+
},
|
|
1320
|
+
// 25. decantr_run_health_loop — local evidence + repair loop
|
|
1321
|
+
{
|
|
1322
|
+
name: "decantr_run_health_loop",
|
|
1323
|
+
title: "Run Health Loop",
|
|
1324
|
+
description: "Run Project Health, produce evidence, and return the next repair prompt for AI agents without uploading project source.",
|
|
1325
|
+
inputSchema: {
|
|
1326
|
+
type: "object",
|
|
1327
|
+
properties: {
|
|
1328
|
+
project_path: {
|
|
1329
|
+
type: "string",
|
|
1330
|
+
description: "Optional relative project path inside the active workspace. Defaults to the current working directory."
|
|
1331
|
+
},
|
|
1332
|
+
finding_id: {
|
|
1333
|
+
type: "string",
|
|
1334
|
+
description: "Optional finding id to target. Defaults to the first error or warning, then the first finding."
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
},
|
|
1338
|
+
annotations: READ_ONLY
|
|
943
1339
|
}
|
|
944
1340
|
];
|
|
945
1341
|
async function handleTool(name, args) {
|
|
@@ -2102,6 +2498,96 @@ async function handleTool(name, args) {
|
|
|
2102
2498
|
}
|
|
2103
2499
|
return auditProject(projectRoot);
|
|
2104
2500
|
}
|
|
2501
|
+
case "decantr_get_evidence_bundle": {
|
|
2502
|
+
try {
|
|
2503
|
+
const projectRoot = resolveMcpProjectRoot(args.project_path);
|
|
2504
|
+
const state = await getMcpHealthState(projectRoot);
|
|
2505
|
+
return state.evidence;
|
|
2506
|
+
} catch (error) {
|
|
2507
|
+
return { error: error.message };
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
case "decantr_workspace_health": {
|
|
2511
|
+
if (args.workspace_root != null && typeof args.workspace_root !== "string") {
|
|
2512
|
+
return { error: "Invalid workspace_root. Must be a string when provided." };
|
|
2513
|
+
}
|
|
2514
|
+
if (args.max_projects != null && (typeof args.max_projects !== "number" || !Number.isFinite(args.max_projects))) {
|
|
2515
|
+
return { error: "Invalid max_projects. Must be a finite number when provided." };
|
|
2516
|
+
}
|
|
2517
|
+
try {
|
|
2518
|
+
return await getMcpWorkspaceHealth(args);
|
|
2519
|
+
} catch (error) {
|
|
2520
|
+
return { error: error.message };
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
case "decantr_get_repair_prompt": {
|
|
2524
|
+
if (args.finding_id != null && typeof args.finding_id !== "string") {
|
|
2525
|
+
return { error: "Invalid finding_id. Must be a string when provided." };
|
|
2526
|
+
}
|
|
2527
|
+
try {
|
|
2528
|
+
const projectRoot = resolveMcpProjectRoot(args.project_path);
|
|
2529
|
+
const state = await getMcpHealthState(projectRoot);
|
|
2530
|
+
const finding = (typeof args.finding_id === "string" ? state.report.findings.find((entry) => entry.id === args.finding_id) : void 0) ?? state.report.findings.find((entry) => entry.severity === "error") ?? state.report.findings.find((entry) => entry.severity === "warn") ?? state.report.findings[0] ?? null;
|
|
2531
|
+
if (!finding) {
|
|
2532
|
+
return {
|
|
2533
|
+
project: state.evidence.project,
|
|
2534
|
+
health: state.evidence.health,
|
|
2535
|
+
prompt: null,
|
|
2536
|
+
message: "No Project Health findings require repair.",
|
|
2537
|
+
commands: ["decantr health --evidence"]
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
return {
|
|
2541
|
+
project: state.evidence.project,
|
|
2542
|
+
health: state.evidence.health,
|
|
2543
|
+
finding: {
|
|
2544
|
+
id: finding.id,
|
|
2545
|
+
source: finding.source,
|
|
2546
|
+
severity: finding.severity,
|
|
2547
|
+
category: finding.category,
|
|
2548
|
+
message: finding.message
|
|
2549
|
+
},
|
|
2550
|
+
prompt: finding.remediation.prompt,
|
|
2551
|
+
commands: finding.remediation.commands
|
|
2552
|
+
};
|
|
2553
|
+
} catch (error) {
|
|
2554
|
+
return { error: error.message };
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
case "decantr_run_health_loop": {
|
|
2558
|
+
if (args.finding_id != null && typeof args.finding_id !== "string") {
|
|
2559
|
+
return { error: "Invalid finding_id. Must be a string when provided." };
|
|
2560
|
+
}
|
|
2561
|
+
try {
|
|
2562
|
+
const projectRoot = resolveMcpProjectRoot(args.project_path);
|
|
2563
|
+
const state = await getMcpHealthState(projectRoot);
|
|
2564
|
+
const finding = (typeof args.finding_id === "string" ? state.report.findings.find((entry) => entry.id === args.finding_id) : void 0) ?? state.report.findings.find((entry) => entry.severity === "error") ?? state.report.findings.find((entry) => entry.severity === "warn") ?? state.report.findings[0] ?? null;
|
|
2565
|
+
return {
|
|
2566
|
+
project: state.evidence.project,
|
|
2567
|
+
health: state.evidence.health,
|
|
2568
|
+
report: state.report,
|
|
2569
|
+
evidence: state.evidence,
|
|
2570
|
+
repair: finding === null ? {
|
|
2571
|
+
finding: null,
|
|
2572
|
+
prompt: null,
|
|
2573
|
+
commands: ["decantr health --evidence"],
|
|
2574
|
+
message: "No Project Health findings require repair."
|
|
2575
|
+
} : {
|
|
2576
|
+
finding: {
|
|
2577
|
+
id: finding.id,
|
|
2578
|
+
source: finding.source,
|
|
2579
|
+
severity: finding.severity,
|
|
2580
|
+
category: finding.category,
|
|
2581
|
+
message: finding.message
|
|
2582
|
+
},
|
|
2583
|
+
prompt: finding.remediation.prompt,
|
|
2584
|
+
commands: finding.remediation.commands
|
|
2585
|
+
}
|
|
2586
|
+
};
|
|
2587
|
+
} catch (error) {
|
|
2588
|
+
return { error: error.message };
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2105
2591
|
default:
|
|
2106
2592
|
return { error: `Unknown tool: ${name}` };
|
|
2107
2593
|
}
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-FEXPLJKB.js";
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decantr/mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
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": [
|
|
7
|
+
"decantr",
|
|
8
|
+
"decantr-ai",
|
|
7
9
|
"mcp",
|
|
8
10
|
"mcp-server",
|
|
9
11
|
"model-context-protocol",
|
|
@@ -15,6 +17,7 @@
|
|
|
15
17
|
"windsurf",
|
|
16
18
|
"design-system",
|
|
17
19
|
"design-intelligence",
|
|
20
|
+
"project-health",
|
|
18
21
|
"ui-generation",
|
|
19
22
|
"drift-detection"
|
|
20
23
|
],
|
|
@@ -46,9 +49,9 @@
|
|
|
46
49
|
},
|
|
47
50
|
"dependencies": {
|
|
48
51
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
49
|
-
"@decantr/
|
|
50
|
-
"@decantr/verifier": "2.
|
|
51
|
-
"@decantr/
|
|
52
|
+
"@decantr/essence-spec": "2.0.1",
|
|
53
|
+
"@decantr/verifier": "2.1.0",
|
|
54
|
+
"@decantr/registry": "2.0.0"
|
|
52
55
|
},
|
|
53
56
|
"scripts": {
|
|
54
57
|
"build": "tsup",
|