raven-mcp 1.0.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.
Files changed (35) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +72 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +629 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +51 -0
  7. package/src/data/business/growth.json +48 -0
  8. package/src/data/business/metrics.json +48 -0
  9. package/src/data/business/monetization.json +56 -0
  10. package/src/data/business/onboarding.json +48 -0
  11. package/src/data/business/retention.json +48 -0
  12. package/src/data/patterns/cta.json +51 -0
  13. package/src/data/patterns/dashboard.json +51 -0
  14. package/src/data/patterns/empty-states.json +42 -0
  15. package/src/data/patterns/error-states.json +51 -0
  16. package/src/data/patterns/forms.json +53 -0
  17. package/src/data/patterns/landing-page.json +51 -0
  18. package/src/data/patterns/loading-states.json +51 -0
  19. package/src/data/patterns/mobile-conversion.json +51 -0
  20. package/src/data/patterns/modals-dialogs.json +51 -0
  21. package/src/data/patterns/navigation.json +50 -0
  22. package/src/data/patterns/pricing-page.json +59 -0
  23. package/src/data/patterns/signup-flow.json +52 -0
  24. package/src/data/patterns/social-proof.json +49 -0
  25. package/src/data/principles/accessibility.json +151 -0
  26. package/src/data/principles/color-theory.json +100 -0
  27. package/src/data/principles/d4d.json +113 -0
  28. package/src/data/principles/gestalt.json +129 -0
  29. package/src/data/principles/laws-of-ux.json +490 -0
  30. package/src/data/principles/mobile-ux.json +126 -0
  31. package/src/data/principles/nielsen-heuristics.json +258 -0
  32. package/src/data/principles/typography.json +117 -0
  33. package/src/data/tokens/registry.json +67 -0
  34. package/src/data/tokens/systems/linear.json +134 -0
  35. package/src/data/tokens/systems/stripe.json +147 -0
package/dist/index.js ADDED
@@ -0,0 +1,629 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { readFileSync, readdirSync, existsSync } from "fs";
6
+ import { join, dirname } from "path";
7
+ import { fileURLToPath } from "url";
8
+ // ── Path setup ──────────────────────────────────────────────────────
9
+ var __dirname = dirname(fileURLToPath(import.meta.url));
10
+ var PKG_ROOT = join(__dirname, "..");
11
+ var DATA_DIR = join(PKG_ROOT, "src", "data");
12
+ var PRINCIPLES_DIR = join(DATA_DIR, "principles");
13
+ var PATTERNS_DIR = join(DATA_DIR, "patterns");
14
+ var BUSINESS_DIR = join(DATA_DIR, "business");
15
+ var TOKENS_DIR = join(DATA_DIR, "tokens");
16
+ var SYSTEMS_DIR = join(TOKENS_DIR, "systems");
17
+ // ── Data loading ────────────────────────────────────────────────────
18
+ function loadJsonDir(dir) {
19
+ if (!existsSync(dir))
20
+ return [];
21
+ var files = readdirSync(dir).filter(f => f.endsWith(".json"));
22
+ var results = [];
23
+ for (var file of files) {
24
+ var raw = readFileSync(join(dir, file), "utf-8");
25
+ var parsed = JSON.parse(raw);
26
+ if (Array.isArray(parsed)) {
27
+ results = results.concat(parsed);
28
+ }
29
+ else {
30
+ results.push(parsed);
31
+ }
32
+ }
33
+ return results;
34
+ }
35
+ var allPrinciples = [];
36
+ var allPatterns = [];
37
+ var allBusiness = [];
38
+ function loadAllData() {
39
+ allPrinciples = loadJsonDir(PRINCIPLES_DIR);
40
+ allPatterns = loadJsonDir(PATTERNS_DIR);
41
+ allBusiness = loadJsonDir(BUSINESS_DIR);
42
+ }
43
+ loadAllData();
44
+ // ── Token helpers (from reference) ──────────────────────────────────
45
+ function loadRegistry() {
46
+ var raw = readFileSync(join(TOKENS_DIR, "registry.json"), "utf-8");
47
+ return JSON.parse(raw);
48
+ }
49
+ function loadSystem(id) {
50
+ var filePath = join(SYSTEMS_DIR, id + ".json");
51
+ if (!existsSync(filePath))
52
+ return null;
53
+ var raw = readFileSync(filePath, "utf-8");
54
+ return JSON.parse(raw);
55
+ }
56
+ function getAvailableSystemIds() {
57
+ if (!existsSync(SYSTEMS_DIR))
58
+ return [];
59
+ return readdirSync(SYSTEMS_DIR)
60
+ .filter(f => f.endsWith(".json"))
61
+ .map(f => f.replace(".json", ""));
62
+ }
63
+ function flattenTokens(obj, prefix) {
64
+ var results = [];
65
+ var parentType = obj["$type"];
66
+ for (var key of Object.keys(obj)) {
67
+ if (key.startsWith("$"))
68
+ continue;
69
+ var val = obj[key];
70
+ var currentPath = prefix ? prefix + "." + key : key;
71
+ if (val && typeof val === "object" && "$value" in val) {
72
+ results.push({
73
+ path: currentPath,
74
+ value: val["$value"],
75
+ type: val["$type"] || parentType,
76
+ description: val["$description"]
77
+ });
78
+ }
79
+ else if (val && typeof val === "object") {
80
+ results = results.concat(flattenTokens(val, currentPath));
81
+ }
82
+ }
83
+ return results;
84
+ }
85
+ function filterTokensByGroup(tokens, group) {
86
+ var filtered = {};
87
+ for (var key of Object.keys(tokens)) {
88
+ if (key.startsWith("$")) {
89
+ filtered[key] = tokens[key];
90
+ continue;
91
+ }
92
+ var lk = key.toLowerCase();
93
+ var lg = group.toLowerCase();
94
+ if (lk === lg || lk.startsWith(lg + "-") || lk.startsWith(lg + "_")) {
95
+ filtered[key] = tokens[key];
96
+ }
97
+ }
98
+ return filtered;
99
+ }
100
+ function tokensToCSS(tokens, prefix) {
101
+ var flat = flattenTokens(tokens, "");
102
+ var lines = flat.map(t => {
103
+ var varName = "--" + prefix + "-" + t.path.replace(/\./g, "-");
104
+ var value = typeof t.value === "object" ? JSON.stringify(t.value) : t.value;
105
+ return " " + varName + ": " + value + ";";
106
+ });
107
+ return ":root {\n" + lines.join("\n") + "\n}";
108
+ }
109
+ function tokensToCSSByGroup(tokens, prefix) {
110
+ var sections = [];
111
+ for (var key of Object.keys(tokens)) {
112
+ if (key.startsWith("$"))
113
+ continue;
114
+ var val = tokens[key];
115
+ if (val && typeof val === "object" && !("$value" in val)) {
116
+ sections.push(" /* " + key + " */");
117
+ var flat = flattenTokens(val, key);
118
+ for (var t of flat) {
119
+ var varName = "--" + prefix + "-" + t.path.replace(/\./g, "-");
120
+ var value = typeof t.value === "object" ? JSON.stringify(t.value) : t.value;
121
+ sections.push(" " + varName + ": " + value + ";");
122
+ }
123
+ sections.push("");
124
+ }
125
+ }
126
+ return ":root {\n" + sections.join("\n") + "}";
127
+ }
128
+ // ── Search and matching helpers ─────────────────────────────────────
129
+ function matchesTags(tags, query) {
130
+ var terms = query.toLowerCase().split(/[\s,]+/).filter(Boolean);
131
+ return terms.some(term => tags.some(tag => tag.includes(term) || term.includes(tag)));
132
+ }
133
+ function textSearch(text, query) {
134
+ var lower = text.toLowerCase();
135
+ var terms = query.toLowerCase().split(/[\s,]+/).filter(Boolean);
136
+ return terms.some(term => lower.includes(term));
137
+ }
138
+ function formatPrinciple(p, format) {
139
+ if (format === "brief") {
140
+ return { id: p.id, name: p.name, summary: p.summary };
141
+ }
142
+ if (format === "checklist") {
143
+ return {
144
+ id: p.id,
145
+ name: p.name,
146
+ summary: p.summary,
147
+ implications: p.implications,
148
+ violations: p.violations
149
+ };
150
+ }
151
+ return p;
152
+ }
153
+ // ── Server ──────────────────────────────────────────────────────────
154
+ var server = new McpServer({
155
+ name: "raven-mcp",
156
+ version: "1.0.0"
157
+ });
158
+ // ── Tool 1: get_principles ──────────────────────────────────────────
159
+ server.tool("get_principles", "Get design principles relevant to a UI context. Returns usability heuristics, laws of UX, Gestalt principles, accessibility requirements, typography rules, and color theory — matched to what you're designing.", {
160
+ context: z.string().describe("What you're designing (e.g. 'signup form', 'pricing page', 'mobile nav', 'dark dashboard')"),
161
+ category: z.string().optional().describe("Filter to category: nielsen-heuristics, laws-of-ux, gestalt, accessibility, typography, color-theory, mobile-ux, d4d"),
162
+ format: z.enum(["full", "checklist", "brief"]).optional().describe("Output format: full (all details), checklist (implications + violations), brief (just summary). Default: full")
163
+ }, async ({ context, category, format }) => {
164
+ var fmt = format || "full";
165
+ var results = allPrinciples.filter(p => {
166
+ if (category && p.category !== category)
167
+ return false;
168
+ // Match on applies_to tags or text search across fields
169
+ var tagMatch = p.applies_to ? matchesTags(p.applies_to, context) : false;
170
+ var textMatch = textSearch(p.name + " " + p.summary + " " + p.description, context);
171
+ return tagMatch || textMatch;
172
+ });
173
+ // If no specific matches, return all in the category (if specified)
174
+ if (results.length === 0 && category) {
175
+ results = allPrinciples.filter(p => p.category === category);
176
+ }
177
+ // If still nothing, do a broader search
178
+ if (results.length === 0) {
179
+ results = allPrinciples.filter(p => textSearch(p.name + " " + p.summary + " " + p.description + " " + (p.applies_to || []).join(" "), context));
180
+ }
181
+ var formatted = results.map(p => formatPrinciple(p, fmt));
182
+ return {
183
+ content: [{
184
+ type: "text",
185
+ text: JSON.stringify({
186
+ context,
187
+ count: formatted.length,
188
+ principles: formatted
189
+ }, null, 2)
190
+ }]
191
+ };
192
+ });
193
+ // ── Tool 2: get_pattern ─────────────────────────────────────────────
194
+ server.tool("get_pattern", "Get proven UI/UX patterns for a specific design type. Returns do's, don'ts, evidence, and checklists for signup flows, pricing pages, navigation, forms, landing pages, dashboards, modals, empty states, error states, loading states, CTAs, social proof, and mobile conversion.", {
195
+ type: z.string().describe("Pattern type (e.g. 'signup-flow', 'pricing-page', 'navigation', 'forms', 'landing-page', 'dashboard', 'modals-dialogs', 'empty-states', 'error-states', 'loading-states', 'cta', 'social-proof', 'mobile-conversion')"),
196
+ platform: z.enum(["desktop", "mobile", "responsive"]).optional().describe("Filter patterns by platform context"),
197
+ goal: z.enum(["conversion", "usability", "accessibility", "delight"]).optional().describe("Filter by primary goal")
198
+ }, async ({ type, platform, goal }) => {
199
+ // Direct ID match first
200
+ var pattern = allPatterns.find(p => p.id === type);
201
+ // Fuzzy match on name/summary
202
+ if (!pattern) {
203
+ pattern = allPatterns.find(p => textSearch(p.id + " " + p.name + " " + p.summary, type));
204
+ }
205
+ if (!pattern) {
206
+ var available = allPatterns.map(p => p.id);
207
+ return {
208
+ content: [{
209
+ type: "text",
210
+ text: JSON.stringify({
211
+ error: "Pattern '" + type + "' not found.",
212
+ available_patterns: available,
213
+ hint: "Try one of the available pattern IDs listed above."
214
+ }, null, 2)
215
+ }]
216
+ };
217
+ }
218
+ // Filter patterns by goal if specified
219
+ var result = { ...pattern };
220
+ if (goal) {
221
+ result.filtered_by_goal = goal;
222
+ }
223
+ // Add platform-specific notes
224
+ if (platform === "mobile") {
225
+ result.platform_note = "Mobile context: prioritize thumb zones, 44px+ touch targets, bottom sheet patterns, and single-column layouts.";
226
+ }
227
+ else if (platform === "desktop") {
228
+ result.platform_note = "Desktop context: leverage hover states, keyboard shortcuts, multi-column layouts where appropriate, and command palettes.";
229
+ }
230
+ // Cross-reference principles
231
+ if (pattern.principles_referenced && pattern.principles_referenced.length > 0) {
232
+ result.related_principles = allPrinciples
233
+ .filter(p => pattern.principles_referenced.includes(p.id))
234
+ .map(p => ({ id: p.id, name: p.name, summary: p.summary }));
235
+ }
236
+ return {
237
+ content: [{
238
+ type: "text",
239
+ text: JSON.stringify(result, null, 2)
240
+ }]
241
+ };
242
+ });
243
+ // ── Tool 3: get_business_strategy ───────────────────────────────────
244
+ server.tool("get_business_strategy", "Get business and monetization strategies for digital products. Covers monetization models, retention strategies, onboarding optimization, growth mechanics, and product metrics frameworks.", {
245
+ type: z.string().describe("Strategy type: monetization, retention, onboarding, growth, metrics"),
246
+ stage: z.enum(["startup", "growth", "mature"]).optional().describe("Company stage for contextual filtering")
247
+ }, async ({ type, stage }) => {
248
+ var strategy = allBusiness.find(b => b.id === type);
249
+ if (!strategy) {
250
+ strategy = allBusiness.find(b => textSearch(b.id + " " + b.name + " " + b.summary, type));
251
+ }
252
+ if (!strategy) {
253
+ var available = allBusiness.map(b => b.id);
254
+ return {
255
+ content: [{
256
+ type: "text",
257
+ text: JSON.stringify({
258
+ error: "Strategy type '" + type + "' not found.",
259
+ available_types: available
260
+ }, null, 2)
261
+ }]
262
+ };
263
+ }
264
+ var result = { ...strategy };
265
+ if (stage) {
266
+ result.stage_context = stage;
267
+ var stageNotes = {
268
+ startup: "Focus on finding product-market fit. Prioritize speed to value, user activation, and finding your aha moment. Don't over-optimize monetization yet.",
269
+ growth: "Focus on scalable acquisition channels, retention optimization, and expansion revenue. Build the systems that compound.",
270
+ mature: "Focus on efficiency, net dollar retention, reducing churn, and finding new growth vectors. Optimize unit economics."
271
+ };
272
+ result.stage_guidance = stageNotes[stage] || "";
273
+ }
274
+ return {
275
+ content: [{
276
+ type: "text",
277
+ text: JSON.stringify(result, null, 2)
278
+ }]
279
+ };
280
+ });
281
+ // ── Tool 4: evaluate_design ─────────────────────────────────────────
282
+ server.tool("evaluate_design", "Evaluate a design description against UX principles. Returns relevant principles, potential violations, and improvement suggestions.", {
283
+ description: z.string().describe("Description of the design to evaluate"),
284
+ goals: z.array(z.string()).optional().describe("What to evaluate for (e.g. ['conversion', 'accessibility', 'mobile-usability'])"),
285
+ context: z.string().optional().describe("What the design is (e.g. 'pricing page for SaaS product')")
286
+ }, async ({ description, goals, context }) => {
287
+ var searchText = description + " " + (context || "") + " " + (goals || []).join(" ");
288
+ // Find relevant principles
289
+ var relevant = allPrinciples.filter(p => {
290
+ var tagMatch = p.applies_to ? matchesTags(p.applies_to, searchText) : false;
291
+ var textMatch = textSearch(p.name + " " + p.summary + " " + p.description + " " + p.violations.join(" "), searchText);
292
+ return tagMatch || textMatch;
293
+ });
294
+ // Find relevant patterns
295
+ var relevantPatterns = allPatterns.filter(p => textSearch(p.id + " " + p.name + " " + p.summary, searchText));
296
+ // Build evaluation
297
+ var evaluation = {
298
+ design_description: description,
299
+ context: context || "Not specified",
300
+ goals: goals || ["general usability"],
301
+ principles_to_check: relevant.map(p => ({
302
+ id: p.id,
303
+ name: p.name,
304
+ summary: p.summary,
305
+ common_violations: p.violations,
306
+ what_to_verify: p.implications
307
+ })),
308
+ applicable_patterns: relevantPatterns.map(p => ({
309
+ id: p.id,
310
+ name: p.name,
311
+ checklist: p.checklist
312
+ })),
313
+ evaluation_guidance: "Review the design against each principle's common violations and each pattern's checklist. Flag any items that the current design may violate.",
314
+ total_principles: relevant.length,
315
+ total_patterns: relevantPatterns.length
316
+ };
317
+ return {
318
+ content: [{
319
+ type: "text",
320
+ text: JSON.stringify(evaluation, null, 2)
321
+ }]
322
+ };
323
+ });
324
+ // ── Tool 5: search_knowledge ────────────────────────────────────────
325
+ server.tool("search_knowledge", "Search across all design principles, UI patterns, and business strategies. Use when you need to find specific guidance or don't know which category to look in.", {
326
+ query: z.string().describe("Search term (e.g. 'touch targets', 'pricing psychology', 'color contrast')"),
327
+ layer: z.enum(["principles", "patterns", "business", "all"]).optional().describe("Which layer to search: principles, patterns, business, or all (default)")
328
+ }, async ({ query, layer }) => {
329
+ var searchLayer = layer || "all";
330
+ var results = [];
331
+ if (searchLayer === "all" || searchLayer === "principles") {
332
+ var matchedPrinciples = allPrinciples.filter(p => textSearch([p.id, p.name, p.category, p.summary, p.description, ...p.applies_to, ...p.implications, ...p.violations].join(" "), query));
333
+ results = results.concat(matchedPrinciples.map(p => ({
334
+ layer: "principles",
335
+ id: p.id,
336
+ name: p.name,
337
+ category: p.category,
338
+ summary: p.summary,
339
+ relevance: "principle"
340
+ })));
341
+ }
342
+ if (searchLayer === "all" || searchLayer === "patterns") {
343
+ var matchedPatterns = allPatterns.filter(p => {
344
+ var allText = [p.id, p.name, p.summary, ...p.checklist,
345
+ ...p.patterns.map(pp => pp.name + " " + pp.description + " " + pp.do.join(" ") + " " + pp.dont.join(" "))
346
+ ].join(" ");
347
+ return textSearch(allText, query);
348
+ });
349
+ results = results.concat(matchedPatterns.map(p => ({
350
+ layer: "patterns",
351
+ id: p.id,
352
+ name: p.name,
353
+ category: p.category,
354
+ summary: p.summary,
355
+ relevance: "pattern"
356
+ })));
357
+ }
358
+ if (searchLayer === "all" || searchLayer === "business") {
359
+ var matchedBusiness = allBusiness.filter(b => {
360
+ var allText = [b.id, b.name, b.summary,
361
+ ...b.strategies.map(s => s.name + " " + s.description + " " + s.when_to_use)
362
+ ].join(" ");
363
+ return textSearch(allText, query);
364
+ });
365
+ results = results.concat(matchedBusiness.map(b => ({
366
+ layer: "business",
367
+ id: b.id,
368
+ name: b.name,
369
+ category: b.category,
370
+ summary: b.summary,
371
+ relevance: "strategy"
372
+ })));
373
+ }
374
+ return {
375
+ content: [{
376
+ type: "text",
377
+ text: JSON.stringify({
378
+ query,
379
+ layer: searchLayer,
380
+ count: results.length,
381
+ results
382
+ }, null, 2)
383
+ }]
384
+ };
385
+ });
386
+ // ── Tool 6: get_checklist ───────────────────────────────────────────
387
+ server.tool("get_checklist", "Get a pre-publish checklist for a specific UI type. Returns actionable yes/no items to verify before shipping.", {
388
+ type: z.string().describe("What you're shipping (e.g. 'signup form', 'pricing page', 'dashboard', 'landing page', 'modal')"),
389
+ platform: z.enum(["desktop", "mobile", "responsive"]).optional().describe("Platform context for platform-specific checks")
390
+ }, async ({ type, platform }) => {
391
+ // Gather checklists from matching patterns
392
+ var matchedPatterns = allPatterns.filter(p => textSearch(p.id + " " + p.name + " " + p.summary, type));
393
+ var checklists = [];
394
+ for (var pattern of matchedPatterns) {
395
+ checklists.push({
396
+ source: pattern.name,
397
+ items: pattern.checklist
398
+ });
399
+ }
400
+ // Add universal accessibility checks
401
+ var accessibilityChecklist = [
402
+ "Text meets WCAG AA contrast ratio (4.5:1 for normal text, 3:1 for large text)?",
403
+ "All interactive elements are keyboard accessible?",
404
+ "All images have appropriate alt text?",
405
+ "Form inputs have associated labels?",
406
+ "Focus indicators are visible?",
407
+ "Touch targets are at least 44x44px on mobile?"
408
+ ];
409
+ // Add platform-specific checks
410
+ var platformChecklist = [];
411
+ if (platform === "mobile" || platform === "responsive") {
412
+ platformChecklist = [
413
+ "Font size is at least 16px to prevent iOS auto-zoom?",
414
+ "Touch targets are at least 44x44px?",
415
+ "Primary actions are in the thumb zone (bottom half)?",
416
+ "Forms use appropriate input modes (email, tel, number)?",
417
+ "Layout is single-column on small screens?",
418
+ "Page loads in under 3 seconds on mobile?"
419
+ ];
420
+ }
421
+ if (platform === "desktop" || platform === "responsive") {
422
+ platformChecklist = platformChecklist.concat([
423
+ "Hover states on all interactive elements?",
424
+ "Keyboard shortcuts for primary actions?",
425
+ "Responsive at common desktop widths (1024, 1280, 1440, 1920)?",
426
+ "Command palette or search available (Cmd+K)?"
427
+ ]);
428
+ }
429
+ var result = {
430
+ type,
431
+ platform: platform || "responsive",
432
+ pattern_checklists: checklists,
433
+ accessibility_checklist: accessibilityChecklist,
434
+ platform_checklist: platformChecklist.length > 0 ? platformChecklist : undefined,
435
+ total_items: checklists.reduce((sum, c) => sum + c.items.length, 0) + accessibilityChecklist.length + platformChecklist.length
436
+ };
437
+ return {
438
+ content: [{
439
+ type: "text",
440
+ text: JSON.stringify(result, null, 2)
441
+ }]
442
+ };
443
+ });
444
+ // ── Tool 7: get_d4d_framework ───────────────────────────────────────
445
+ server.tool("get_d4d_framework", "Get the Design for Delight (D4D) framework templates. Returns customer problem statement, ideal state, hypothesis, LOFA, and experiment templates for structured product thinking.", {
446
+ stage: z.enum(["frame", "empathy", "broad", "narrow", "experiment", "recommendation", "full"]).optional().describe("Which stage of the D4D loop to return. Default: full (all stages)")
447
+ }, async ({ stage }) => {
448
+ var stageFilter = stage || "full";
449
+ var d4dPrinciple = allPrinciples.find(p => p.id === "d4d-framework");
450
+ if (!d4dPrinciple || !d4dPrinciple.templates) {
451
+ return {
452
+ content: [{
453
+ type: "text",
454
+ text: JSON.stringify({ error: "D4D framework data not found." }, null, 2)
455
+ }]
456
+ };
457
+ }
458
+ var templates = d4dPrinciple.templates;
459
+ if (stageFilter === "full") {
460
+ return {
461
+ content: [{
462
+ type: "text",
463
+ text: JSON.stringify({
464
+ framework: "Design for Delight (D4D)",
465
+ description: d4dPrinciple.description,
466
+ templates: {
467
+ customer_problem_statement: templates.customer_problem_statement,
468
+ ideal_state: templates.ideal_state,
469
+ hypothesis_statement: templates.hypothesis_statement,
470
+ lofa: templates.lofa,
471
+ scrappy_experiment: templates.scrappy_experiment
472
+ },
473
+ operating_loop: templates.operating_loop
474
+ }, null, 2)
475
+ }]
476
+ };
477
+ }
478
+ // Return specific stage
479
+ var loopStages = templates.operating_loop?.stages || [];
480
+ var matchedStage = loopStages.find((s) => s.stage === stageFilter);
481
+ var stageTemplates = {};
482
+ if (stageFilter === "frame") {
483
+ stageTemplates = {
484
+ customer_problem_statement: templates.customer_problem_statement,
485
+ ideal_state: templates.ideal_state
486
+ };
487
+ }
488
+ else if (stageFilter === "experiment") {
489
+ stageTemplates = {
490
+ hypothesis_statement: templates.hypothesis_statement,
491
+ lofa: templates.lofa,
492
+ scrappy_experiment: templates.scrappy_experiment
493
+ };
494
+ }
495
+ return {
496
+ content: [{
497
+ type: "text",
498
+ text: JSON.stringify({
499
+ framework: "Design for Delight (D4D)",
500
+ stage: matchedStage || { stage: stageFilter, name: stageFilter },
501
+ templates: Object.keys(stageTemplates).length > 0 ? stageTemplates : undefined,
502
+ all_stages: loopStages.map((s) => s.stage)
503
+ }, null, 2)
504
+ }]
505
+ };
506
+ });
507
+ // ── Tool 8: list_design_systems ─────────────────────────────────────
508
+ server.tool("list_design_systems", "Browse available design systems for tokens. Filter by category (fintech, productivity, developer, component-library, design-system) or search by name.", {
509
+ category: z.string().optional().describe("Filter by category: fintech, productivity, developer, component-library, design-system"),
510
+ search: z.string().optional().describe("Search by name or description")
511
+ }, async ({ category, search }) => {
512
+ var registry = loadRegistry();
513
+ var systems = registry.systems;
514
+ var available = getAvailableSystemIds();
515
+ if (category) {
516
+ systems = systems.filter((s) => s.category === category);
517
+ }
518
+ if (search) {
519
+ var q = search.toLowerCase();
520
+ systems = systems.filter((s) => s.name.toLowerCase().includes(q) ||
521
+ s.description.toLowerCase().includes(q) ||
522
+ (s.tags && s.tags.some((t) => t.includes(q))));
523
+ }
524
+ systems = systems.map((s) => ({
525
+ ...s,
526
+ tokens_available: available.includes(s.id)
527
+ }));
528
+ return {
529
+ content: [{
530
+ type: "text",
531
+ text: JSON.stringify({ count: systems.length, systems }, null, 2)
532
+ }]
533
+ };
534
+ });
535
+ // ── Tool 9: get_design_system ───────────────────────────────────────
536
+ server.tool("get_design_system", "Get design tokens for a specific design system. Returns colors, typography, spacing, radii, elevation, and motion tokens in W3C DTCG, CSS custom properties, or flat format.", {
537
+ id: z.string().describe("Design system ID (e.g. 'stripe', 'linear')"),
538
+ group: z.string().optional().describe("Filter to a token group: color, color-dark, color-light, typography, spacing, radius, elevation, motion"),
539
+ format: z.enum(["dtcg", "css", "flat"]).optional().describe("Output format: dtcg (W3C standard), css (custom properties), flat (key-value). Default: dtcg")
540
+ }, async ({ id, group, format }) => {
541
+ var tokens = loadSystem(id);
542
+ if (!tokens) {
543
+ return {
544
+ content: [{
545
+ type: "text",
546
+ text: "Design system '" + id + "' not found. Use list_design_systems to see available systems."
547
+ }]
548
+ };
549
+ }
550
+ var output = tokens;
551
+ if (group) {
552
+ output = filterTokensByGroup(tokens, group);
553
+ }
554
+ var fmt = format || "dtcg";
555
+ var text;
556
+ if (fmt === "css") {
557
+ text = group
558
+ ? tokensToCSS(output, id)
559
+ : tokensToCSSByGroup(output, id);
560
+ }
561
+ else if (fmt === "flat") {
562
+ var flat = flattenTokens(output, "");
563
+ text = JSON.stringify(flat, null, 2);
564
+ }
565
+ else {
566
+ text = JSON.stringify(output, null, 2);
567
+ }
568
+ return {
569
+ content: [{
570
+ type: "text",
571
+ text
572
+ }]
573
+ };
574
+ });
575
+ // ── Tool 10: compose_system ─────────────────────────────────────────
576
+ server.tool("compose_system", "Mix tokens from different design systems to create a custom composite. Example: Linear's colors + Stripe's typography.", {
577
+ compositions: z.array(z.object({
578
+ system: z.string().describe("Source design system ID"),
579
+ group: z.string().describe("Token group to take (color, typography, spacing, radius, elevation, motion)")
580
+ })).describe("Array of system-group pairs to compose"),
581
+ format: z.enum(["dtcg", "css"]).optional().describe("Output format. Default: dtcg")
582
+ }, async ({ compositions, format }) => {
583
+ var composed = {
584
+ "$name": "Custom Composition",
585
+ "$description": "Composed from: " + compositions.map(c => c.system + "/" + c.group).join(", ")
586
+ };
587
+ for (var comp of compositions) {
588
+ var tokens = loadSystem(comp.system);
589
+ if (!tokens) {
590
+ return {
591
+ content: [{
592
+ type: "text",
593
+ text: "System '" + comp.system + "' not found. Available: " + getAvailableSystemIds().join(", ")
594
+ }]
595
+ };
596
+ }
597
+ var filtered = filterTokensByGroup(tokens, comp.group);
598
+ for (var key of Object.keys(filtered)) {
599
+ if (!key.startsWith("$")) {
600
+ composed[key] = filtered[key];
601
+ }
602
+ }
603
+ }
604
+ var fmt = format || "dtcg";
605
+ var text;
606
+ if (fmt === "css") {
607
+ text = tokensToCSSByGroup(composed, "custom");
608
+ }
609
+ else {
610
+ text = JSON.stringify(composed, null, 2);
611
+ }
612
+ return {
613
+ content: [{
614
+ type: "text",
615
+ text
616
+ }]
617
+ };
618
+ });
619
+ // ── Start ───────────────────────────────────────────────────────────
620
+ async function main() {
621
+ var transport = new StdioServerTransport();
622
+ await server.connect(transport);
623
+ console.error("raven-mcp server running on stdio — design intelligence ready");
624
+ }
625
+ main().catch(err => {
626
+ console.error("Fatal:", err);
627
+ process.exit(1);
628
+ });
629
+ //# sourceMappingURL=index.js.map