@decantr/mcp-server 1.0.0-beta.1

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 ADDED
@@ -0,0 +1,96 @@
1
+ # @decantr/mcp-server
2
+
3
+ Design intelligence for AI-generated UI. Make Claude, Cursor, and Windsurf generate better code.
4
+
5
+ - **Structured design context** -- gives your AI assistant patterns, layouts, and component specs instead of letting it guess
6
+ - **Drift detection** -- catches when generated code deviates from your design intent
7
+ - **Zero config** -- run with `npx`, no API keys or accounts required
8
+
9
+ ## Quick Setup
10
+
11
+ ### Claude Desktop
12
+
13
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "decantr": {
19
+ "command": "npx",
20
+ "args": ["@decantr/mcp-server"]
21
+ }
22
+ }
23
+ }
24
+ ```
25
+
26
+ Restart Claude Desktop. The Decantr tools will appear automatically.
27
+
28
+ ### Cursor
29
+
30
+ Create `.cursor/mcp.json` in your project root:
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "decantr": {
36
+ "command": "npx",
37
+ "args": ["@decantr/mcp-server"]
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ Restart Cursor. The tools are available in Agent mode.
44
+
45
+ ### Windsurf
46
+
47
+ Add to your Windsurf MCP config (`~/.windsurf/mcp.json`):
48
+
49
+ ```json
50
+ {
51
+ "mcpServers": {
52
+ "decantr": {
53
+ "command": "npx",
54
+ "args": ["@decantr/mcp-server"]
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ ## Tools
61
+
62
+ | Tool | Description | Example Input |
63
+ |------|-------------|---------------|
64
+ | `decantr_create_essence` | Generate an Essence spec skeleton from a project description | `{ "description": "SaaS dashboard with analytics and billing", "framework": "react" }` |
65
+ | `decantr_read_essence` | Read the current `decantr.essence.json` from the working directory | `{}` or `{ "path": "./custom.essence.json" }` |
66
+ | `decantr_validate` | Validate an Essence file against the schema and guard rules | `{ "path": "./decantr.essence.json" }` |
67
+ | `decantr_search_registry` | Search the community registry for patterns, archetypes, recipes, and styles | `{ "query": "kanban", "type": "pattern" }` |
68
+ | `decantr_resolve_pattern` | Get full pattern details: layout spec, components, presets, code examples | `{ "id": "data-table", "preset": "product" }` |
69
+ | `decantr_resolve_archetype` | Get archetype details: default pages, layouts, features, suggested theme | `{ "id": "saas-dashboard" }` |
70
+ | `decantr_resolve_recipe` | Get recipe decoration rules: shell styles, spatial hints, visual effects | `{ "id": "auradecantism" }` |
71
+ | `decantr_resolve_blueprint` | Get a full app composition with page structure and personality traits | `{ "id": "ecommerce" }` |
72
+ | `decantr_suggest_patterns` | Given a page description, get ranked pattern suggestions | `{ "description": "dashboard with metrics and charts" }` |
73
+ | `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" }` |
74
+
75
+ ## How It Works
76
+
77
+ An Essence spec (`decantr.essence.json`) captures your design intent -- archetype, theme, page structure, patterns, and guard rules -- in a single declarative file. The MCP server exposes this spec and the Decantr registry to your AI assistant, giving it concrete layout specs, component lists, and decoration rules instead of relying on the model's generic training data. The result is generated code that follows a coherent design system, and drift detection that catches deviations before they ship.
78
+
79
+ ## Example Workflow
80
+
81
+ **Prompt:** "Build me a SaaS dashboard with user analytics, a data table of recent signups, and a settings page."
82
+
83
+ The AI assistant calls these tools behind the scenes:
84
+
85
+ 1. `decantr_create_essence` -- generates a spec skeleton matched to the `saas-dashboard` archetype
86
+ 2. `decantr_resolve_archetype` -- pulls default pages, layouts, and features for a SaaS dashboard
87
+ 3. `decantr_suggest_patterns` -- recommends `kpi-grid`, `chart-grid`, `data-table`, and `form-sections` for the described pages
88
+ 4. `decantr_resolve_pattern` -- fetches layout specs and component lists for each pattern
89
+ 5. `decantr_resolve_recipe` -- loads decoration rules (spacing, borders, effects) for the theme
90
+ 6. `decantr_check_drift` -- validates the generated code against the Essence spec before presenting it
91
+
92
+ The AI now generates code with the right layout structure, correct components, and consistent styling -- not a generic guess.
93
+
94
+ ## License
95
+
96
+ MIT
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,510 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import {
7
+ ListToolsRequestSchema,
8
+ CallToolRequestSchema
9
+ } from "@modelcontextprotocol/sdk/types.js";
10
+
11
+ // src/tools.ts
12
+ import { readFile } from "fs/promises";
13
+ import { join as join2 } from "path";
14
+ import { validateEssence, evaluateGuard } from "@decantr/essence-spec";
15
+ import { createRegistryClient, resolvePatternPreset } from "@decantr/registry";
16
+
17
+ // src/helpers.ts
18
+ import { createResolver } from "@decantr/registry";
19
+ import { join } from "path";
20
+ var MAX_INPUT_LENGTH = 1e3;
21
+ function validateStringArg(args, field) {
22
+ const val = args[field];
23
+ if (!val || typeof val !== "string") {
24
+ return `Required parameter "${field}" must be a non-empty string.`;
25
+ }
26
+ if (val.length > MAX_INPUT_LENGTH) {
27
+ return `Parameter "${field}" exceeds maximum length of ${MAX_INPUT_LENGTH} characters.`;
28
+ }
29
+ return null;
30
+ }
31
+ var _resolver = null;
32
+ function getResolver() {
33
+ if (!_resolver) {
34
+ const envRoot = process.env.DECANTR_CONTENT_ROOT;
35
+ const bundledRoot = join(import.meta.dirname, "..", "..", "..", "content");
36
+ const npmRoot = join(process.cwd(), "node_modules", "@decantr", "content");
37
+ _resolver = createResolver({
38
+ contentRoot: envRoot || bundledRoot,
39
+ overridePaths: envRoot ? [] : [npmRoot]
40
+ });
41
+ }
42
+ return _resolver;
43
+ }
44
+
45
+ // src/tools.ts
46
+ var READ_ONLY = {
47
+ readOnlyHint: true,
48
+ destructiveHint: false,
49
+ idempotentHint: true,
50
+ openWorldHint: false
51
+ };
52
+ var TOOLS = [
53
+ {
54
+ name: "decantr_read_essence",
55
+ title: "Read Essence",
56
+ description: "Read and return the current decantr.essence.json file from the working directory.",
57
+ inputSchema: {
58
+ type: "object",
59
+ properties: {
60
+ path: { type: "string", description: "Optional path to essence file. Defaults to ./decantr.essence.json." }
61
+ }
62
+ },
63
+ annotations: READ_ONLY
64
+ },
65
+ {
66
+ name: "decantr_validate",
67
+ title: "Validate Essence",
68
+ description: "Validate a decantr.essence.json file against the schema and guard rules. Returns errors and warnings.",
69
+ inputSchema: {
70
+ type: "object",
71
+ properties: {
72
+ path: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." }
73
+ }
74
+ },
75
+ annotations: READ_ONLY
76
+ },
77
+ {
78
+ name: "decantr_search_registry",
79
+ title: "Search Registry",
80
+ description: "Search the Decantr community content registry for patterns, archetypes, recipes, and styles.",
81
+ inputSchema: {
82
+ type: "object",
83
+ properties: {
84
+ query: { type: "string", description: 'Search query (e.g. "kanban", "neon", "dashboard")' },
85
+ type: { type: "string", description: "Filter by type: pattern, archetype, recipe, style" }
86
+ },
87
+ required: ["query"]
88
+ },
89
+ annotations: READ_ONLY
90
+ },
91
+ {
92
+ name: "decantr_resolve_pattern",
93
+ title: "Resolve Pattern",
94
+ description: "Get full pattern details including layout spec, components, presets, and code examples.",
95
+ inputSchema: {
96
+ type: "object",
97
+ properties: {
98
+ id: { type: "string", description: 'Pattern ID (e.g. "hero", "data-table", "kpi-grid")' },
99
+ preset: { type: "string", description: 'Optional preset name (e.g. "product", "content")' }
100
+ },
101
+ required: ["id"]
102
+ },
103
+ annotations: READ_ONLY
104
+ },
105
+ {
106
+ name: "decantr_resolve_archetype",
107
+ title: "Resolve Archetype",
108
+ description: "Get archetype details including default pages, layouts, features, and suggested theme.",
109
+ inputSchema: {
110
+ type: "object",
111
+ properties: {
112
+ id: { type: "string", description: 'Archetype ID (e.g. "saas-dashboard", "ecommerce")' }
113
+ },
114
+ required: ["id"]
115
+ },
116
+ annotations: READ_ONLY
117
+ },
118
+ {
119
+ name: "decantr_resolve_recipe",
120
+ title: "Resolve Recipe",
121
+ description: "Get recipe decoration rules including shell styles, spatial hints, visual effects, and pattern preferences.",
122
+ inputSchema: {
123
+ type: "object",
124
+ properties: {
125
+ id: { type: "string", description: 'Recipe ID (e.g. "auradecantism")' }
126
+ },
127
+ required: ["id"]
128
+ },
129
+ annotations: READ_ONLY
130
+ },
131
+ {
132
+ name: "decantr_resolve_blueprint",
133
+ title: "Resolve Blueprint",
134
+ description: "Get a blueprint (app composition) with its archetype list, suggested theme, personality traits, and full page structure.",
135
+ inputSchema: {
136
+ type: "object",
137
+ properties: {
138
+ id: { type: "string", description: 'Blueprint ID (e.g. "saas-dashboard", "ecommerce", "portfolio")' }
139
+ },
140
+ required: ["id"]
141
+ },
142
+ annotations: READ_ONLY
143
+ },
144
+ {
145
+ name: "decantr_suggest_patterns",
146
+ title: "Suggest Patterns",
147
+ description: "Given a page description, suggest appropriate patterns from the registry. Returns ranked pattern matches with layout specs and component lists.",
148
+ inputSchema: {
149
+ type: "object",
150
+ properties: {
151
+ description: { type: "string", description: 'Description of the page or section (e.g. "dashboard with metrics and charts", "settings form with toggles")' }
152
+ },
153
+ required: ["description"]
154
+ },
155
+ annotations: READ_ONLY
156
+ },
157
+ {
158
+ name: "decantr_check_drift",
159
+ title: "Check Drift",
160
+ description: "Check if code changes violate the design intent captured in the Essence spec. Returns guard rule violations with severity and fix suggestions.",
161
+ inputSchema: {
162
+ type: "object",
163
+ properties: {
164
+ path: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." },
165
+ page_id: { type: "string", description: 'Page ID being modified (e.g. "overview", "settings")' },
166
+ components_used: {
167
+ type: "array",
168
+ items: { type: "string" },
169
+ description: "List of component names used in the generated code"
170
+ },
171
+ theme_used: { type: "string", description: "Theme/style name used in the generated code" }
172
+ }
173
+ },
174
+ annotations: READ_ONLY
175
+ },
176
+ {
177
+ name: "decantr_create_essence",
178
+ title: "Create Essence",
179
+ description: "Generate a valid Essence spec skeleton from a project description. Returns a structured essence.json template based on the closest matching archetype and blueprint.",
180
+ inputSchema: {
181
+ type: "object",
182
+ properties: {
183
+ description: { type: "string", description: 'Natural language project description (e.g. "SaaS dashboard with analytics, user management, and billing")' },
184
+ framework: { type: "string", description: 'Target framework (e.g. "react", "vue", "svelte"). Defaults to "react".' }
185
+ },
186
+ required: ["description"]
187
+ },
188
+ annotations: READ_ONLY
189
+ }
190
+ ];
191
+ async function handleTool(name, args) {
192
+ switch (name) {
193
+ case "decantr_read_essence": {
194
+ const essencePath = args.path || join2(process.cwd(), "decantr.essence.json");
195
+ try {
196
+ const raw = await readFile(essencePath, "utf-8");
197
+ return JSON.parse(raw);
198
+ } catch (e) {
199
+ return { error: `Could not read essence file: ${e.message}` };
200
+ }
201
+ }
202
+ case "decantr_validate": {
203
+ const essencePath = args.path || join2(process.cwd(), "decantr.essence.json");
204
+ let essence;
205
+ try {
206
+ essence = JSON.parse(await readFile(essencePath, "utf-8"));
207
+ } catch (e) {
208
+ return { valid: false, errors: [`Could not read: ${e.message}`], guardViolations: [] };
209
+ }
210
+ const result = validateEssence(essence);
211
+ let guardViolations = [];
212
+ if (result.valid && typeof essence === "object" && essence !== null) {
213
+ try {
214
+ guardViolations = evaluateGuard(essence, {});
215
+ } catch {
216
+ }
217
+ }
218
+ return { ...result, guardViolations };
219
+ }
220
+ case "decantr_search_registry": {
221
+ const err = validateStringArg(args, "query");
222
+ if (err) return { error: err };
223
+ try {
224
+ const client = createRegistryClient();
225
+ const results = await client.search(args.query, args.type);
226
+ return {
227
+ total: results.length,
228
+ results: results.map((r) => ({
229
+ type: r.type,
230
+ id: r.id,
231
+ name: r.name,
232
+ description: r.description,
233
+ install: `decantr registry add ${r.type}/${r.id}`
234
+ }))
235
+ };
236
+ } catch (e) {
237
+ return { error: `Search failed: ${e.message}` };
238
+ }
239
+ }
240
+ case "decantr_resolve_pattern": {
241
+ const err = validateStringArg(args, "id");
242
+ if (err) return { error: err };
243
+ const resolver = getResolver();
244
+ const resolved = await resolver.resolve("pattern", args.id);
245
+ if (!resolved) {
246
+ return { found: false, message: `Pattern "${args.id}" not found.` };
247
+ }
248
+ const result = { found: true, ...resolved.item };
249
+ if (args.preset && typeof args.preset === "string") {
250
+ const preset = resolvePatternPreset(resolved.item, args.preset);
251
+ if (preset) result.resolvedPreset = preset;
252
+ }
253
+ return result;
254
+ }
255
+ case "decantr_resolve_archetype": {
256
+ const err = validateStringArg(args, "id");
257
+ if (err) return { error: err };
258
+ const resolver = getResolver();
259
+ const resolved = await resolver.resolve("archetype", args.id);
260
+ if (!resolved) {
261
+ return { found: false, message: `Archetype "${args.id}" not found.` };
262
+ }
263
+ return { found: true, ...resolved.item };
264
+ }
265
+ case "decantr_resolve_recipe": {
266
+ const err = validateStringArg(args, "id");
267
+ if (err) return { error: err };
268
+ const resolver = getResolver();
269
+ const resolved = await resolver.resolve("recipe", args.id);
270
+ if (!resolved) {
271
+ return { found: false, message: `Recipe "${args.id}" not found.` };
272
+ }
273
+ return { found: true, ...resolved.item };
274
+ }
275
+ case "decantr_resolve_blueprint": {
276
+ const err = validateStringArg(args, "id");
277
+ if (err) return { error: err };
278
+ const resolver = getResolver();
279
+ const resolved = await resolver.resolve("blueprint", args.id);
280
+ if (!resolved) {
281
+ return { found: false, message: `Blueprint "${args.id}" not found.` };
282
+ }
283
+ return { found: true, ...resolved.item };
284
+ }
285
+ case "decantr_suggest_patterns": {
286
+ const err = validateStringArg(args, "description");
287
+ if (err) return { error: err };
288
+ const desc = args.description.toLowerCase();
289
+ const resolver = getResolver();
290
+ const patternIds = [
291
+ "hero",
292
+ "kpi-grid",
293
+ "data-table",
294
+ "card-grid",
295
+ "chart-grid",
296
+ "filter-bar",
297
+ "form-sections",
298
+ "detail-header",
299
+ "activity-feed",
300
+ "cta-section"
301
+ ];
302
+ const suggestions = [];
303
+ for (const id of patternIds) {
304
+ const resolved = await resolver.resolve("pattern", id);
305
+ if (!resolved) continue;
306
+ const p = resolved.item;
307
+ const searchable = [
308
+ p.name || "",
309
+ p.description || "",
310
+ ...p.components || [],
311
+ ...p.tags || []
312
+ ].join(" ").toLowerCase();
313
+ let score = 0;
314
+ const words = desc.split(/\s+/);
315
+ for (const word of words) {
316
+ if (word.length < 3) continue;
317
+ if (searchable.includes(word)) score += 10;
318
+ }
319
+ if (desc.includes("dashboard") && ["kpi-grid", "chart-grid", "data-table", "filter-bar"].includes(id)) score += 20;
320
+ if (desc.includes("metric") && id === "kpi-grid") score += 15;
321
+ if (desc.includes("chart") && id === "chart-grid") score += 15;
322
+ if (desc.includes("table") && id === "data-table") score += 15;
323
+ if (desc.includes("form") && id === "form-sections") score += 15;
324
+ if (desc.includes("setting") && id === "form-sections") score += 15;
325
+ if (desc.includes("landing") && ["hero", "cta-section", "card-grid"].includes(id)) score += 20;
326
+ if (desc.includes("hero") && id === "hero") score += 20;
327
+ if (desc.includes("ecommerce") && ["card-grid", "filter-bar", "detail-header"].includes(id)) score += 15;
328
+ if (desc.includes("product") && id === "card-grid") score += 15;
329
+ if (desc.includes("feed") && id === "activity-feed") score += 15;
330
+ if (desc.includes("filter") && id === "filter-bar") score += 15;
331
+ if (desc.includes("search") && id === "filter-bar") score += 10;
332
+ if (score > 0) {
333
+ const preset = p.presets && typeof p.presets === "object" ? Object.values(p.presets)[0] : null;
334
+ suggestions.push({
335
+ id,
336
+ score,
337
+ name: p.name || id,
338
+ description: p.description || "",
339
+ components: p.components || [],
340
+ layout: preset?.layout ? preset.layout.layout || "grid" : "grid"
341
+ });
342
+ }
343
+ }
344
+ suggestions.sort((a, b) => b.score - a.score);
345
+ return {
346
+ query: args.description,
347
+ suggestions: suggestions.slice(0, 5),
348
+ total: suggestions.length
349
+ };
350
+ }
351
+ case "decantr_check_drift": {
352
+ const essencePath = args.path || join2(process.cwd(), "decantr.essence.json");
353
+ let essence;
354
+ try {
355
+ essence = JSON.parse(await readFile(essencePath, "utf-8"));
356
+ } catch (e) {
357
+ return { error: `Could not read essence: ${e.message}` };
358
+ }
359
+ const validation = validateEssence(essence);
360
+ if (!validation.valid) {
361
+ return { drifted: true, reason: "invalid_essence", errors: validation.errors };
362
+ }
363
+ const violations = [];
364
+ if (args.theme_used && typeof args.theme_used === "string") {
365
+ const expectedTheme = essence.theme;
366
+ if (expectedTheme?.style && args.theme_used !== expectedTheme.style) {
367
+ violations.push({
368
+ rule: "theme-match",
369
+ severity: "critical",
370
+ message: `Theme drift: code uses "${args.theme_used}" but Essence specifies "${expectedTheme.style}". Do not switch themes.`
371
+ });
372
+ }
373
+ }
374
+ if (args.page_id && typeof args.page_id === "string") {
375
+ const structure = essence.structure;
376
+ if (structure && !structure.find((p) => p.id === args.page_id)) {
377
+ violations.push({
378
+ rule: "page-exists",
379
+ severity: "critical",
380
+ message: `Page "${args.page_id}" not found in Essence structure. Add it to the Essence before generating code for it.`
381
+ });
382
+ }
383
+ }
384
+ try {
385
+ const guardViolations = evaluateGuard(essence, {
386
+ pageId: args.page_id
387
+ });
388
+ for (const gv of guardViolations) {
389
+ violations.push({
390
+ rule: gv.rule || "guard",
391
+ severity: gv.severity || "warning",
392
+ message: gv.message || "Guard violation"
393
+ });
394
+ }
395
+ } catch {
396
+ }
397
+ return {
398
+ drifted: violations.length > 0,
399
+ violations,
400
+ checkedAgainst: essencePath
401
+ };
402
+ }
403
+ case "decantr_create_essence": {
404
+ const err = validateStringArg(args, "description");
405
+ if (err) return { error: err };
406
+ const desc = args.description.toLowerCase();
407
+ const framework = args.framework || "react";
408
+ const archetypeScores = [];
409
+ const archetypeIds = [
410
+ "saas-dashboard",
411
+ "ecommerce",
412
+ "portfolio",
413
+ "content-site",
414
+ "financial-dashboard",
415
+ "cloud-platform",
416
+ "gaming-platform",
417
+ "ecommerce-admin",
418
+ "workbench"
419
+ ];
420
+ const resolver = getResolver();
421
+ for (const id of archetypeIds) {
422
+ let score = 0;
423
+ if (desc.includes("dashboard") && id.includes("dashboard")) score += 20;
424
+ if (desc.includes("saas") && id.includes("saas")) score += 20;
425
+ if (desc.includes("ecommerce") && id.includes("ecommerce")) score += 20;
426
+ if (desc.includes("shop") && id.includes("ecommerce")) score += 15;
427
+ if (desc.includes("portfolio") && id.includes("portfolio")) score += 20;
428
+ if (desc.includes("blog") && id.includes("content")) score += 15;
429
+ if (desc.includes("content") && id.includes("content")) score += 15;
430
+ if (desc.includes("finance") && id.includes("financial")) score += 20;
431
+ if (desc.includes("cloud") && id.includes("cloud")) score += 15;
432
+ if (desc.includes("game") && id.includes("gaming")) score += 15;
433
+ if (desc.includes("admin") && id.includes("admin")) score += 15;
434
+ if (desc.includes("analytics") && id.includes("dashboard")) score += 10;
435
+ if (desc.includes("tool") && id === "workbench") score += 10;
436
+ if (score > 0) archetypeScores.push({ id, score });
437
+ }
438
+ archetypeScores.sort((a, b) => b.score - a.score);
439
+ const bestMatch = archetypeScores[0]?.id || "saas-dashboard";
440
+ const archetypeResult = await resolver.resolve("archetype", bestMatch);
441
+ const archetype = archetypeResult?.item;
442
+ const pages = archetype?.pages;
443
+ const structure = (pages || [{ id: "home", shell: "full-bleed", default_layout: ["hero"] }]).map((p) => ({
444
+ id: p.id,
445
+ shell: p.shell || "sidebar-main",
446
+ layout: p.default_layout || []
447
+ }));
448
+ const essence = {
449
+ version: "2.0.0",
450
+ archetype: bestMatch,
451
+ theme: {
452
+ style: "auradecantism",
453
+ mode: "dark",
454
+ recipe: "auradecantism",
455
+ shape: "rounded"
456
+ },
457
+ personality: ["professional"],
458
+ platform: { type: "spa", routing: "hash" },
459
+ structure,
460
+ features: archetype?.features || [],
461
+ guard: { enforce_style: true, enforce_recipe: true, mode: "strict" },
462
+ density: { level: "comfortable", content_gap: "_gap4" },
463
+ target: framework,
464
+ _generated: {
465
+ matched_archetype: bestMatch,
466
+ confidence: archetypeScores[0]?.score || 0,
467
+ alternatives: archetypeScores.slice(1, 4).map((a) => a.id),
468
+ description: args.description
469
+ }
470
+ };
471
+ return {
472
+ essence,
473
+ archetype: bestMatch,
474
+ instructions: `Save this as decantr.essence.json in your project root. Review the structure (pages, patterns) and adjust to match your needs. The guard rules will validate your code against this spec.`
475
+ };
476
+ }
477
+ default:
478
+ return { error: `Unknown tool: ${name}` };
479
+ }
480
+ }
481
+
482
+ // src/index.ts
483
+ var VERSION = "0.1.0";
484
+ var server = new Server(
485
+ { name: "decantr", version: VERSION },
486
+ { capabilities: { tools: {} } }
487
+ );
488
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
489
+ return { tools: TOOLS };
490
+ });
491
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
492
+ const { name, arguments: args } = request.params;
493
+ try {
494
+ const result = await handleTool(name, args ?? {});
495
+ const response = {
496
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
497
+ };
498
+ if (result && typeof result === "object" && "error" in result) {
499
+ response.isError = true;
500
+ }
501
+ return response;
502
+ } catch (err) {
503
+ return {
504
+ content: [{ type: "text", text: JSON.stringify({ error: err.message }) }],
505
+ isError: true
506
+ };
507
+ }
508
+ });
509
+ var transport = new StdioServerTransport();
510
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@decantr/mcp-server",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "MCP server for Decantr — exposes design intelligence tools to AI coding assistants",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/decantr-ai/decantr.git",
9
+ "directory": "packages/mcp-server"
10
+ },
11
+ "homepage": "https://decantr.ai",
12
+ "type": "module",
13
+ "bin": {
14
+ "decantr-mcp": "./dist/index.js"
15
+ },
16
+ "main": "dist/index.js",
17
+ "types": "dist/index.d.ts",
18
+ "files": ["dist"],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest"
26
+ },
27
+ "dependencies": {
28
+ "@modelcontextprotocol/sdk": "^1.0.0",
29
+ "@decantr/essence-spec": "workspace:*",
30
+ "@decantr/registry": "workspace:*"
31
+ }
32
+ }