@decantr/mcp-server 2.1.0 → 2.3.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 +10 -8
- package/dist/bin.js +1 -1
- package/dist/{chunk-FEXPLJKB.js → chunk-GIVGJE76.js} +316 -70
- package/dist/index.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Design intelligence for AI-generated UI. Make Claude, Cursor, and Windsurf gener
|
|
|
12
12
|
|
|
13
13
|

|
|
14
14
|
|
|
15
|
-
- **Structured design context** -- gives your AI assistant patterns, layouts,
|
|
15
|
+
- **Structured design context** -- gives your AI assistant patterns, layouts, component specs, Brownfield local law, and task-time context instead of letting it guess
|
|
16
16
|
- **Evidence-backed repair loops** -- gives AI agents Project Health, Evidence Bundles, workspace health, and scoped repair prompts without uploading source
|
|
17
17
|
- **Drift detection** -- catches when generated code deviates from your design intent
|
|
18
18
|
- **Zero config** -- run with `npx`, no API keys or accounts required
|
|
@@ -133,9 +133,10 @@ The server exposes Decantr registry, context, benchmark, and verification tools.
|
|
|
133
133
|
| `decantr_resolve_pattern` | Get full pattern details: layout spec, components, presets, code examples | `{ "id": "data-table", "preset": "product" }` |
|
|
134
134
|
| `decantr_resolve_archetype` | Get archetype details: default pages, layouts, features, suggested theme | `{ "id": "saas-dashboard" }` |
|
|
135
135
|
| `decantr_resolve_blueprint` | Get a full app composition with page structure and personality traits | `{ "id": "ecommerce" }` |
|
|
136
|
-
| `decantr_suggest_patterns` | Given a page description, get ranked pattern suggestions | `{ "description": "
|
|
136
|
+
| `decantr_suggest_patterns` | Given a page description plus optional route/source excerpt, get ranked pattern suggestions | `{ "description": "recipe feed with avatars and infinite scroll", "route": "/feed" }` |
|
|
137
137
|
| `decantr_check_drift` | Check if generated code violates the design intent in the Essence spec | `{ "page_id": "overview", "components_used": ["Card", "LineChart"], "theme_used": "auradecantism" }` |
|
|
138
138
|
| `decantr_get_execution_pack` | Read compiled scaffold, section, page, review, or mutation execution packs, with hosted fallback when local context is missing | `{ "pack_type": "page", "id": "overview", "format": "json" }` |
|
|
139
|
+
| `decantr_prepare_task_context` | Resolve compact route/task context, local law, evidence, and changed-file impact before editing a Brownfield or Essence route | `{ "route": "/feed", "task": "improve recipe card loading" }` |
|
|
139
140
|
| `decantr_compile_execution_packs` | Compile a hosted execution-pack bundle from a local or inline essence document | `{ "path": "./decantr.essence.json", "namespace": "@official" }` |
|
|
140
141
|
| `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" }` |
|
|
141
142
|
| `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" }` |
|
|
@@ -170,12 +171,13 @@ The AI assistant calls these tools behind the scenes:
|
|
|
170
171
|
3. `decantr_suggest_patterns` -- recommends `kpi-grid`, `chart-grid`, `data-table`, and `form-sections` for the described pages
|
|
171
172
|
4. `decantr_resolve_pattern` -- fetches layout specs and component lists for each pattern
|
|
172
173
|
5. `decantr_get_execution_pack` -- loads the compiled scaffold/page/review packs as the task contract, falling back to hosted compilation when local pack artifacts are missing
|
|
173
|
-
6. `
|
|
174
|
-
7. `
|
|
175
|
-
8. `
|
|
176
|
-
9. `
|
|
177
|
-
10. `
|
|
178
|
-
11. `
|
|
174
|
+
6. `decantr_prepare_task_context` -- resolves route-local Brownfield context, accepted local law, changed-file impact, visual evidence, and theme inventory before editing an existing app
|
|
175
|
+
7. `decantr_compile_execution_packs` -- compiles the hosted pack bundle when the task needs a fresh remote contract from the essence document
|
|
176
|
+
8. `decantr_check_drift` -- validates the generated code against the Essence spec before presenting it
|
|
177
|
+
9. `decantr_critique` -- critiques a specific file, falling back to the hosted verifier when the local review pack is missing
|
|
178
|
+
10. `decantr_audit_project` -- runs the stronger project-level audit once the implementation is in place
|
|
179
|
+
11. `decantr_get_evidence_bundle` -- returns the local evidence bundle for the AI repair loop
|
|
180
|
+
12. `decantr_get_repair_prompt` -- gives the assistant exact finding evidence, constraints to preserve, and commands to rerun
|
|
179
181
|
|
|
180
182
|
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.
|
|
181
183
|
|
package/dist/bin.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-GIVGJE76.js";
|
|
@@ -4,11 +4,17 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
|
|
6
6
|
// src/tools.ts
|
|
7
|
+
import { execFileSync } from "child_process";
|
|
7
8
|
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
8
9
|
import { readFile as readFile2 } from "fs/promises";
|
|
9
10
|
import { basename as basename2, dirname as dirname2, join as join2, relative as relative2 } from "path";
|
|
10
11
|
import { evaluateGuard, isV4 as isV42, validateEssence } from "@decantr/essence-spec";
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
isContentIntelligenceSource,
|
|
14
|
+
patternToDiscoveryCandidate,
|
|
15
|
+
rankPatternCandidates,
|
|
16
|
+
resolvePatternPreset
|
|
17
|
+
} from "@decantr/registry";
|
|
12
18
|
|
|
13
19
|
// src/helpers.ts
|
|
14
20
|
import { realpathSync } from "fs";
|
|
@@ -124,6 +130,102 @@ async function writeDriftLog(entries, projectRoot) {
|
|
|
124
130
|
}
|
|
125
131
|
|
|
126
132
|
// src/tools.ts
|
|
133
|
+
function readJsonIfExists(path) {
|
|
134
|
+
if (!existsSync(path)) return null;
|
|
135
|
+
try {
|
|
136
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function changedFilesForTask(projectRoot) {
|
|
142
|
+
const changed = /* @__PURE__ */ new Set();
|
|
143
|
+
try {
|
|
144
|
+
for (const args of [
|
|
145
|
+
["diff", "--name-only"],
|
|
146
|
+
["diff", "--name-only", "--cached"]
|
|
147
|
+
]) {
|
|
148
|
+
const output = execFileSync("git", args, {
|
|
149
|
+
cwd: projectRoot,
|
|
150
|
+
encoding: "utf-8",
|
|
151
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
152
|
+
});
|
|
153
|
+
for (const entry of output.split(/\r?\n/)) {
|
|
154
|
+
const file = entry.trim();
|
|
155
|
+
if (file) changed.add(file);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
}
|
|
160
|
+
return [...changed].sort();
|
|
161
|
+
}
|
|
162
|
+
function impactedRoutesForFiles(projectRoot, files) {
|
|
163
|
+
const analysis = readJsonIfExists(
|
|
164
|
+
join2(projectRoot, ".decantr", "analysis.json")
|
|
165
|
+
);
|
|
166
|
+
const routeEntries = analysis?.routes?.routes ?? [];
|
|
167
|
+
const impacted = /* @__PURE__ */ new Set();
|
|
168
|
+
for (const file of files) {
|
|
169
|
+
for (const route of routeEntries) {
|
|
170
|
+
if (route.file && (file === route.file || file.endsWith(route.file))) {
|
|
171
|
+
if (route.path) impacted.add(route.path);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return [...impacted].sort();
|
|
176
|
+
}
|
|
177
|
+
function localLawSummary(projectRoot) {
|
|
178
|
+
const patterns = readJsonIfExists(join2(projectRoot, ".decantr", "local-patterns.json"));
|
|
179
|
+
const rules = readJsonIfExists(join2(projectRoot, ".decantr", "rules.json"));
|
|
180
|
+
return {
|
|
181
|
+
patterns_path: patterns ? ".decantr/local-patterns.json" : null,
|
|
182
|
+
rules_path: rules ? ".decantr/rules.json" : null,
|
|
183
|
+
patterns: patterns?.patterns?.map((pattern) => ({
|
|
184
|
+
id: pattern.id ?? "unknown",
|
|
185
|
+
role: pattern.role ?? null,
|
|
186
|
+
component_paths: pattern.componentPaths ?? []
|
|
187
|
+
})) ?? [],
|
|
188
|
+
rules: rules?.rules?.map((rule) => ({
|
|
189
|
+
id: rule.id ?? "unknown",
|
|
190
|
+
enabled: rule.enabled ?? false,
|
|
191
|
+
severity: rule.severity ?? "warn",
|
|
192
|
+
description: rule.description ?? null
|
|
193
|
+
})) ?? []
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function extractPatternIdsFromLayoutItem(item, ids) {
|
|
197
|
+
if (typeof item === "string") {
|
|
198
|
+
ids.add(item);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (!item || typeof item !== "object") return;
|
|
202
|
+
const record = item;
|
|
203
|
+
if (typeof record.pattern === "string") ids.add(record.pattern);
|
|
204
|
+
if (Array.isArray(record.cols)) {
|
|
205
|
+
for (const col of record.cols) extractPatternIdsFromLayoutItem(col, ids);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function extractPagePatternIds(page) {
|
|
209
|
+
if (!page) return [];
|
|
210
|
+
const ids = /* @__PURE__ */ new Set();
|
|
211
|
+
for (const item of page.layout ?? []) extractPatternIdsFromLayoutItem(item, ids);
|
|
212
|
+
return [...ids].sort();
|
|
213
|
+
}
|
|
214
|
+
function summarizePackJson(pack) {
|
|
215
|
+
if (!pack || typeof pack !== "object") {
|
|
216
|
+
return { directives: [], patterns: [], visualTarget: null, sharedComponents: [] };
|
|
217
|
+
}
|
|
218
|
+
const record = pack;
|
|
219
|
+
const data = record.data && typeof record.data === "object" ? record.data : record;
|
|
220
|
+
const patterns = Array.isArray(data.patterns) ? data.patterns : [];
|
|
221
|
+
const directives = Array.isArray(data.directives) ? data.directives : [];
|
|
222
|
+
const sharedComponents = Array.isArray(data.sharedComponents) ? data.sharedComponents : Array.isArray(data.shared_components) ? data.shared_components : [];
|
|
223
|
+
const visualTarget = typeof data.visualTarget === "string" ? data.visualTarget : typeof data.visual_target === "string" ? data.visual_target : null;
|
|
224
|
+
return { directives, patterns, visualTarget, sharedComponents };
|
|
225
|
+
}
|
|
226
|
+
function routeSlug(route) {
|
|
227
|
+
return route.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "root";
|
|
228
|
+
}
|
|
127
229
|
async function getShowcaseBenchmarkPayload(view) {
|
|
128
230
|
const client = getPublicAPIClient();
|
|
129
231
|
if (view === "manifest") {
|
|
@@ -490,7 +592,10 @@ function mcpStatusFromCounts(counts) {
|
|
|
490
592
|
return "healthy";
|
|
491
593
|
}
|
|
492
594
|
function mcpScoreFromCounts(counts) {
|
|
493
|
-
return Math.max(
|
|
595
|
+
return Math.max(
|
|
596
|
+
0,
|
|
597
|
+
Math.min(100, 100 - counts.errorCount * 15 - counts.warnCount * 5 - counts.infoCount)
|
|
598
|
+
);
|
|
494
599
|
}
|
|
495
600
|
function mcpCommandsForFinding(source) {
|
|
496
601
|
switch (source) {
|
|
@@ -507,7 +612,11 @@ function mcpCommandsForFinding(source) {
|
|
|
507
612
|
case "interaction":
|
|
508
613
|
return ["decantr check --strict", "decantr health"];
|
|
509
614
|
case "pack":
|
|
510
|
-
return [
|
|
615
|
+
return [
|
|
616
|
+
"decantr refresh",
|
|
617
|
+
"decantr registry get-pack review --write-context",
|
|
618
|
+
"decantr health"
|
|
619
|
+
];
|
|
511
620
|
case "runtime":
|
|
512
621
|
return ["npm run build", "decantr health"];
|
|
513
622
|
default:
|
|
@@ -908,6 +1017,14 @@ var TOOLS = [
|
|
|
908
1017
|
description: {
|
|
909
1018
|
type: "string",
|
|
910
1019
|
description: 'Description of the page or section (e.g. "dashboard with metrics and charts", "settings form with toggles")'
|
|
1020
|
+
},
|
|
1021
|
+
route: {
|
|
1022
|
+
type: "string",
|
|
1023
|
+
description: 'Optional route context, for example "/feed" or "/settings".'
|
|
1024
|
+
},
|
|
1025
|
+
source_code: {
|
|
1026
|
+
type: "string",
|
|
1027
|
+
description: "Optional local source excerpt to rank against actual code evidence."
|
|
911
1028
|
}
|
|
912
1029
|
},
|
|
913
1030
|
required: ["description"]
|
|
@@ -1105,7 +1222,31 @@ var TOOLS = [
|
|
|
1105
1222
|
},
|
|
1106
1223
|
annotations: READ_ONLY
|
|
1107
1224
|
},
|
|
1108
|
-
// 16.
|
|
1225
|
+
// 16. decantr_prepare_task_context — local read
|
|
1226
|
+
{
|
|
1227
|
+
name: "decantr_prepare_task_context",
|
|
1228
|
+
title: "Prepare Task Context",
|
|
1229
|
+
description: "Resolve compact Brownfield/Essence task-time context for a route or page before editing. Returns route, section, page pack, directives, patterns, shared components, visual target, health evidence, and local screenshot references when available.",
|
|
1230
|
+
inputSchema: {
|
|
1231
|
+
type: "object",
|
|
1232
|
+
properties: {
|
|
1233
|
+
route: {
|
|
1234
|
+
type: "string",
|
|
1235
|
+
description: 'Route being edited, for example "/feed". Preferred when known.'
|
|
1236
|
+
},
|
|
1237
|
+
page_id: {
|
|
1238
|
+
type: "string",
|
|
1239
|
+
description: "Page ID when route is unknown."
|
|
1240
|
+
},
|
|
1241
|
+
task: {
|
|
1242
|
+
type: "string",
|
|
1243
|
+
description: "Short task description used to rank relevant patterns and context."
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
},
|
|
1247
|
+
annotations: READ_ONLY
|
|
1248
|
+
},
|
|
1249
|
+
// 17. decantr_get_execution_pack — local read
|
|
1109
1250
|
{
|
|
1110
1251
|
name: "decantr_get_execution_pack",
|
|
1111
1252
|
title: "Get Execution Pack",
|
|
@@ -1506,78 +1647,59 @@ async function handleTool(name, args) {
|
|
|
1506
1647
|
case "decantr_suggest_patterns": {
|
|
1507
1648
|
const err = validateStringArg(args, "description");
|
|
1508
1649
|
if (err) return { error: err };
|
|
1509
|
-
const desc = args.description
|
|
1650
|
+
const desc = args.description;
|
|
1651
|
+
const route = typeof args.route === "string" ? args.route : void 0;
|
|
1652
|
+
const sourceCode = typeof args.source_code === "string" ? args.source_code : void 0;
|
|
1510
1653
|
try {
|
|
1511
1654
|
const patternsResponse = await apiClient.listContent("patterns", {
|
|
1512
1655
|
namespace: "@official",
|
|
1513
|
-
limit:
|
|
1656
|
+
limit: 250
|
|
1514
1657
|
});
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
if (desc.includes("landing") && ["hero", "cta-section", "card-grid"].includes(slug))
|
|
1535
|
-
score += 20;
|
|
1536
|
-
if (desc.includes("hero") && slug === "hero") score += 20;
|
|
1537
|
-
if (desc.includes("ecommerce") && ["card-grid", "filter-bar", "detail-header"].includes(slug))
|
|
1538
|
-
score += 15;
|
|
1539
|
-
if (desc.includes("product") && slug === "card-grid") score += 15;
|
|
1540
|
-
if (desc.includes("feed") && slug === "activity-feed") score += 15;
|
|
1541
|
-
if (desc.includes("filter") && slug === "filter-bar") score += 15;
|
|
1542
|
-
if (desc.includes("search") && slug === "filter-bar") score += 10;
|
|
1543
|
-
if (score > 0) {
|
|
1544
|
-
prelimScores.push({ slug, score, name: name2, description });
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
prelimScores.sort((a, b) => b.score - a.score);
|
|
1548
|
-
const top10 = prelimScores.slice(0, 10);
|
|
1549
|
-
const suggestions = [];
|
|
1550
|
-
for (const candidate of top10) {
|
|
1551
|
-
let fullPattern = null;
|
|
1552
|
-
try {
|
|
1553
|
-
const fetched = await apiClient.getPattern("@official", candidate.slug);
|
|
1554
|
-
fullPattern = fetched;
|
|
1555
|
-
} catch {
|
|
1556
|
-
}
|
|
1557
|
-
let score = candidate.score;
|
|
1558
|
-
if (fullPattern) {
|
|
1559
|
-
const fullSearchable = [...fullPattern.components || [], ...fullPattern.tags || []].join(" ").toLowerCase();
|
|
1560
|
-
const words = desc.split(/\s+/);
|
|
1561
|
-
for (const word of words) {
|
|
1562
|
-
if (word.length < 3) continue;
|
|
1563
|
-
if (fullSearchable.includes(word)) score += 10;
|
|
1658
|
+
const preliminary = rankPatternCandidates(
|
|
1659
|
+
{ query: desc, route, code: sourceCode, limit: 12 },
|
|
1660
|
+
patternsResponse.items.map(
|
|
1661
|
+
(item) => patternToDiscoveryCandidate({
|
|
1662
|
+
id: item.slug || item.name || "pattern",
|
|
1663
|
+
slug: item.slug,
|
|
1664
|
+
name: item.name,
|
|
1665
|
+
description: item.description
|
|
1666
|
+
})
|
|
1667
|
+
)
|
|
1668
|
+
);
|
|
1669
|
+
const fullCandidates = await Promise.all(
|
|
1670
|
+
preliminary.map(async (match) => {
|
|
1671
|
+
const slug = match.candidate.slug || match.candidate.id;
|
|
1672
|
+
try {
|
|
1673
|
+
const fetched = await apiClient.getPattern("@official", slug);
|
|
1674
|
+
return patternToDiscoveryCandidate(fetched, { slug, source: "hosted" });
|
|
1675
|
+
} catch {
|
|
1676
|
+
return match.candidate;
|
|
1564
1677
|
}
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1678
|
+
})
|
|
1679
|
+
);
|
|
1680
|
+
const suggestions = rankPatternCandidates(
|
|
1681
|
+
{ query: desc, route, code: sourceCode, limit: 5 },
|
|
1682
|
+
fullCandidates
|
|
1683
|
+
).map((match) => {
|
|
1684
|
+
const pattern = match.candidate.pattern;
|
|
1685
|
+
const preset = pattern?.presets ? Object.values(pattern.presets)[0] : null;
|
|
1686
|
+
return {
|
|
1687
|
+
id: match.candidate.slug || match.candidate.id,
|
|
1688
|
+
score: match.score,
|
|
1689
|
+
name: match.candidate.name || match.candidate.slug || match.candidate.id,
|
|
1690
|
+
description: match.candidate.description || "",
|
|
1691
|
+
components: match.candidate.components || [],
|
|
1692
|
+
interactions: match.candidate.interactions || [],
|
|
1693
|
+
layout: preset?.layout ? preset.layout.layout : "unknown",
|
|
1694
|
+
reasons: match.reasons,
|
|
1695
|
+
matched_terms: match.matchedTerms
|
|
1696
|
+
};
|
|
1697
|
+
});
|
|
1577
1698
|
return {
|
|
1578
1699
|
query: args.description,
|
|
1579
|
-
|
|
1580
|
-
|
|
1700
|
+
route,
|
|
1701
|
+
suggestions,
|
|
1702
|
+
total: preliminary.length
|
|
1581
1703
|
};
|
|
1582
1704
|
} catch (e) {
|
|
1583
1705
|
return { error: `Could not fetch patterns: ${e.message}` };
|
|
@@ -2251,6 +2373,130 @@ async function handleTool(name, args) {
|
|
|
2251
2373
|
hosted_fallback_error: hostedFallbackError ?? void 0
|
|
2252
2374
|
};
|
|
2253
2375
|
}
|
|
2376
|
+
case "decantr_prepare_task_context": {
|
|
2377
|
+
const routeArg = typeof args.route === "string" ? args.route : void 0;
|
|
2378
|
+
const pageArg = typeof args.page_id === "string" ? args.page_id : void 0;
|
|
2379
|
+
const task = typeof args.task === "string" ? args.task : "";
|
|
2380
|
+
if (!routeArg && !pageArg) {
|
|
2381
|
+
return { error: "Provide route or page_id." };
|
|
2382
|
+
}
|
|
2383
|
+
let essence;
|
|
2384
|
+
try {
|
|
2385
|
+
const result = await readEssenceFile();
|
|
2386
|
+
essence = result.essence;
|
|
2387
|
+
} catch {
|
|
2388
|
+
return { error: "No valid essence file found. Run decantr init first." };
|
|
2389
|
+
}
|
|
2390
|
+
if (!isV42(essence)) {
|
|
2391
|
+
return {
|
|
2392
|
+
error: "Task context requires Essence v4.0.0. Run `decantr migrate --to v4` first."
|
|
2393
|
+
};
|
|
2394
|
+
}
|
|
2395
|
+
const routeEntry = routeArg ? essence.blueprint.routes?.[routeArg] : null;
|
|
2396
|
+
const sectionId = routeEntry?.section;
|
|
2397
|
+
const pageId = pageArg || routeEntry?.page;
|
|
2398
|
+
const section = sectionId ? essence.blueprint.sections.find((entry) => entry.id === sectionId) : essence.blueprint.sections.find(
|
|
2399
|
+
(entry) => entry.pages.some((page2) => page2.id === pageId)
|
|
2400
|
+
);
|
|
2401
|
+
const page = section?.pages.find((entry) => entry.id === pageId) ?? null;
|
|
2402
|
+
if (!section || !page || !pageId) {
|
|
2403
|
+
return {
|
|
2404
|
+
error: "Could not resolve route/page to an Essence section page.",
|
|
2405
|
+
available_routes: Object.keys(essence.blueprint.routes ?? {}).sort(),
|
|
2406
|
+
available_pages: essence.blueprint.sections.flatMap(
|
|
2407
|
+
(entry) => entry.pages.map((pageEntry) => ({ section_id: entry.id, page_id: pageEntry.id }))
|
|
2408
|
+
)
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
const contextDir = join2(process.cwd(), ".decantr", "context");
|
|
2412
|
+
const manifest = readJsonIfExists(join2(contextDir, "pack-manifest.json"));
|
|
2413
|
+
const pageManifest = manifest?.pages.find((entry) => entry.id === pageId) ?? null;
|
|
2414
|
+
const sectionManifest = manifest?.sections.find((entry) => entry.id === section.id) ?? null;
|
|
2415
|
+
const pagePackJson = pageManifest ? readJsonIfExists(join2(contextDir, pageManifest.json)) : null;
|
|
2416
|
+
const sectionPackJson = sectionManifest ? readJsonIfExists(join2(contextDir, sectionManifest.json)) : null;
|
|
2417
|
+
const pagePackMarkdown = pageManifest && existsSync(join2(contextDir, pageManifest.markdown)) ? readFileSync(join2(contextDir, pageManifest.markdown), "utf-8") : null;
|
|
2418
|
+
const sectionContextPath = join2(contextDir, `section-${section.id}.md`);
|
|
2419
|
+
const sectionContext = existsSync(sectionContextPath) ? readFileSync(sectionContextPath, "utf-8") : null;
|
|
2420
|
+
const pagePackSummary = summarizePackJson(pagePackJson);
|
|
2421
|
+
const sectionPackSummary = summarizePackJson(sectionPackJson);
|
|
2422
|
+
const visualManifest = readJsonIfExists(join2(process.cwd(), ".decantr", "evidence", "visual-manifest.json"));
|
|
2423
|
+
const visualRoute = visualManifest?.routes?.find((entry) => entry.route === routeArg) ?? visualManifest?.routes?.find(
|
|
2424
|
+
(entry) => entry.screenshot?.includes(routeSlug(routeArg ?? pageId))
|
|
2425
|
+
) ?? null;
|
|
2426
|
+
const health = readJsonIfExists(join2(process.cwd(), ".decantr", "health-baseline-diff.json"));
|
|
2427
|
+
const themeInventory = readJsonIfExists(
|
|
2428
|
+
join2(process.cwd(), ".decantr", "theme-inventory.json")
|
|
2429
|
+
);
|
|
2430
|
+
const localLaw = localLawSummary(process.cwd());
|
|
2431
|
+
const changedFiles = changedFilesForTask(process.cwd());
|
|
2432
|
+
const changedRoutes = impactedRoutesForFiles(process.cwd(), changedFiles);
|
|
2433
|
+
const patternIds = extractPagePatternIds(page);
|
|
2434
|
+
const ranked = rankPatternCandidates(
|
|
2435
|
+
{
|
|
2436
|
+
query: [task, routeArg, page.description, ...patternIds].filter(Boolean).join(" "),
|
|
2437
|
+
limit: 5
|
|
2438
|
+
},
|
|
2439
|
+
patternIds.map((id) => patternToDiscoveryCandidate({ id, name: id, description: id }))
|
|
2440
|
+
);
|
|
2441
|
+
return {
|
|
2442
|
+
route: routeArg ?? null,
|
|
2443
|
+
page_id: pageId,
|
|
2444
|
+
section_id: section.id,
|
|
2445
|
+
section_role: section.role,
|
|
2446
|
+
shell: section.shell,
|
|
2447
|
+
task,
|
|
2448
|
+
visual_target: pagePackSummary.visualTarget ?? sectionPackSummary.visualTarget ?? essence.dna.personality?.join(". ") ?? null,
|
|
2449
|
+
directives: pagePackSummary.directives,
|
|
2450
|
+
patterns: pagePackSummary.patterns.length > 0 ? pagePackSummary.patterns : patternIds,
|
|
2451
|
+
ranked_patterns: ranked.map((match) => ({
|
|
2452
|
+
id: match.candidate.slug || match.candidate.id,
|
|
2453
|
+
score: match.score,
|
|
2454
|
+
reasons: match.reasons
|
|
2455
|
+
})),
|
|
2456
|
+
shared_components: pagePackSummary.sharedComponents,
|
|
2457
|
+
section_context: sectionContext,
|
|
2458
|
+
page_pack_excerpt: pagePackMarkdown ? pagePackMarkdown.slice(0, 12e3) : null,
|
|
2459
|
+
health_evidence: health ? {
|
|
2460
|
+
baseline_path: health.baselinePath,
|
|
2461
|
+
saved_at: health.savedAt,
|
|
2462
|
+
status_changed: health.statusChanged,
|
|
2463
|
+
score_delta: health.scoreDelta,
|
|
2464
|
+
added_findings: health.addedFindings?.slice(0, 8) ?? [],
|
|
2465
|
+
resolved_findings: health.resolvedFindings?.slice(0, 8) ?? [],
|
|
2466
|
+
changed_routes: health.changedRoutes ?? [],
|
|
2467
|
+
changed_screenshots: health.changedScreenshots ?? [],
|
|
2468
|
+
contract_drift: health.contractDrift ?? []
|
|
2469
|
+
} : null,
|
|
2470
|
+
visual_evidence: visualRoute ? {
|
|
2471
|
+
screenshot: visualRoute.screenshot ?? null,
|
|
2472
|
+
screenshot_hash: visualRoute.screenshotHash ?? null,
|
|
2473
|
+
status: visualRoute.status ?? null,
|
|
2474
|
+
error: visualRoute.error ?? null
|
|
2475
|
+
} : null,
|
|
2476
|
+
theme_inventory: themeInventory ? {
|
|
2477
|
+
modes: themeInventory.modes,
|
|
2478
|
+
variants: themeInventory.variants,
|
|
2479
|
+
path: ".decantr/theme-inventory.json"
|
|
2480
|
+
} : null,
|
|
2481
|
+
local_law: localLaw,
|
|
2482
|
+
change_impact: {
|
|
2483
|
+
changed_files: changedFiles.slice(0, 40),
|
|
2484
|
+
changed_file_count: changedFiles.length,
|
|
2485
|
+
impacted_routes: changedRoutes
|
|
2486
|
+
},
|
|
2487
|
+
verify_command: "decantr verify --brownfield --local-patterns",
|
|
2488
|
+
local_files: {
|
|
2489
|
+
page_pack: pageManifest?.markdown ?? null,
|
|
2490
|
+
section_pack: sectionManifest?.markdown ?? null,
|
|
2491
|
+
section_context: existsSync(sectionContextPath) ? `.decantr/context/section-${section.id}.md` : null,
|
|
2492
|
+
local_patterns: localLaw.patterns_path,
|
|
2493
|
+
local_rules: localLaw.rules_path,
|
|
2494
|
+
visual_manifest: existsSync(
|
|
2495
|
+
join2(process.cwd(), ".decantr", "evidence", "visual-manifest.json")
|
|
2496
|
+
) ? ".decantr/evidence/visual-manifest.json" : null
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2254
2500
|
case "decantr_get_execution_pack": {
|
|
2255
2501
|
const contextDir = join2(process.cwd(), ".decantr", "context");
|
|
2256
2502
|
const manifestPath = join2(contextDir, "pack-manifest.json");
|
|
@@ -2747,7 +2993,7 @@ function describeUpdate(operation, payload) {
|
|
|
2747
2993
|
}
|
|
2748
2994
|
|
|
2749
2995
|
// src/index.ts
|
|
2750
|
-
var VERSION = "2.
|
|
2996
|
+
var VERSION = "2.2.0";
|
|
2751
2997
|
var server = new Server({ name: "decantr", version: VERSION }, { capabilities: { tools: {} } });
|
|
2752
2998
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2753
2999
|
return { tools: TOOLS };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-GIVGJE76.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decantr/mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.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": [
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
52
52
|
"@decantr/essence-spec": "2.0.1",
|
|
53
|
-
"@decantr/
|
|
54
|
-
"@decantr/
|
|
53
|
+
"@decantr/registry": "2.2.0",
|
|
54
|
+
"@decantr/verifier": "2.2.0"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "tsup",
|