sonance-brand-mcp 1.0.1 → 1.1.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.
@@ -14,6 +14,7 @@ const AccordionContext = createContext<AccordionContextValue | null>(null);
14
14
 
15
15
  interface AccordionProps {
16
16
  type?: "single" | "multiple";
17
+ collapsible?: boolean;
17
18
  defaultValue?: string | string[];
18
19
  className?: string;
19
20
  children: React.ReactNode;
@@ -21,6 +22,7 @@ interface AccordionProps {
21
22
 
22
23
  export function Accordion({
23
24
  type = "single",
25
+ collapsible = false,
24
26
  defaultValue,
25
27
  className,
26
28
  children,
@@ -33,7 +35,11 @@ export function Accordion({
33
35
  const toggleItem = (value: string) => {
34
36
  setOpenItems((prev) => {
35
37
  if (type === "single") {
36
- return prev.includes(value) ? [] : [value];
38
+ // If collapsible, allow closing the open item; otherwise, keep it open
39
+ if (prev.includes(value)) {
40
+ return collapsible ? [] : prev;
41
+ }
42
+ return [value];
37
43
  }
38
44
  return prev.includes(value)
39
45
  ? prev.filter((v) => v !== value)
@@ -33,12 +33,14 @@ interface AlertProps
33
33
  extends React.HTMLAttributes<HTMLDivElement>,
34
34
  VariantProps<typeof alertVariants> {
35
35
  title?: string;
36
+ dismissible?: boolean;
36
37
  onClose?: () => void;
37
38
  }
38
39
 
39
40
  export const Alert = forwardRef<HTMLDivElement, AlertProps>(
40
- ({ className, variant = "default", title, children, onClose, ...props }, ref) => {
41
+ ({ className, variant = "default", title, children, dismissible, onClose, ...props }, ref) => {
41
42
  const Icon = iconMap[variant || "default"];
43
+ const showCloseButton = dismissible || onClose;
42
44
 
43
45
  return (
44
46
  <div
@@ -60,7 +62,7 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
60
62
  </div>
61
63
  )}
62
64
  </div>
63
- {onClose && (
65
+ {showCloseButton && (
64
66
  <button
65
67
  onClick={onClose}
66
68
  className="shrink-0 rounded-sm p-1 opacity-70 transition-opacity hover:opacity-100"
@@ -68,3 +68,16 @@ export const CardContent = forwardRef<
68
68
 
69
69
  CardContent.displayName = "CardContent";
70
70
 
71
+ export const CardFooter = forwardRef<
72
+ HTMLDivElement,
73
+ React.HTMLAttributes<HTMLDivElement>
74
+ >(({ className, ...props }, ref) => (
75
+ <div
76
+ ref={ref}
77
+ className={cn("flex items-center p-6 pt-0", className)}
78
+ {...props}
79
+ />
80
+ ));
81
+
82
+ CardFooter.displayName = "CardFooter";
83
+
@@ -121,8 +121,7 @@ export function ZoomImage({ zoomScale = 1.1, className, ...props }: ZoomImagePro
121
121
  <Image
122
122
  {...props}
123
123
  className={cn(
124
- "transition-transform duration-300 hover:scale-[var(--zoom-scale)]",
125
- props.className
124
+ "transition-transform duration-300 hover:scale-[var(--zoom-scale)]"
126
125
  )}
127
126
  style={{ "--zoom-scale": zoomScale } as React.CSSProperties}
128
127
  />
@@ -27,7 +27,7 @@ const kbdVariants = cva(
27
27
  interface KbdProps
28
28
  extends React.HTMLAttributes<HTMLElement>,
29
29
  VariantProps<typeof kbdVariants> {
30
- keys?: string | string[];
30
+ keys?: string | readonly string[];
31
31
  }
32
32
 
33
33
  export const Kbd = forwardRef<HTMLElement, KbdProps>(
@@ -62,32 +62,39 @@ Progress.displayName = "Progress";
62
62
  interface CircularProgressProps {
63
63
  value?: number;
64
64
  max?: number;
65
- size?: number;
65
+ size?: "sm" | "md" | "lg" | number;
66
66
  strokeWidth?: number;
67
67
  showValue?: boolean;
68
68
  className?: string;
69
69
  }
70
70
 
71
+ const circularSizeMap = {
72
+ sm: 32,
73
+ md: 48,
74
+ lg: 64,
75
+ };
76
+
71
77
  export function CircularProgress({
72
78
  value = 0,
73
79
  max = 100,
74
- size = 48,
80
+ size = "md",
75
81
  strokeWidth = 4,
76
82
  showValue = false,
77
83
  className,
78
84
  }: CircularProgressProps) {
85
+ const resolvedSize = typeof size === "number" ? size : circularSizeMap[size];
79
86
  const percentage = Math.min(100, Math.max(0, (value / max) * 100));
80
- const radius = (size - strokeWidth) / 2;
87
+ const radius = (resolvedSize - strokeWidth) / 2;
81
88
  const circumference = 2 * Math.PI * radius;
82
89
  const strokeDashoffset = circumference - (percentage / 100) * circumference;
83
90
 
84
91
  return (
85
92
  <div className={cn("relative inline-flex items-center justify-center", className)}>
86
- <svg width={size} height={size} className="-rotate-90">
93
+ <svg width={resolvedSize} height={resolvedSize} className="-rotate-90">
87
94
  {/* Background circle */}
88
95
  <circle
89
- cx={size / 2}
90
- cy={size / 2}
96
+ cx={resolvedSize / 2}
97
+ cy={resolvedSize / 2}
91
98
  r={radius}
92
99
  fill="none"
93
100
  stroke="currentColor"
@@ -96,8 +103,8 @@ export function CircularProgress({
96
103
  />
97
104
  {/* Progress circle */}
98
105
  <circle
99
- cx={size / 2}
100
- cy={size / 2}
106
+ cx={resolvedSize / 2}
107
+ cy={resolvedSize / 2}
101
108
  r={radius}
102
109
  fill="none"
103
110
  stroke="currentColor"
@@ -8,7 +8,9 @@ type TooltipPosition = "top" | "bottom" | "left" | "right";
8
8
  interface TooltipProps {
9
9
  content: React.ReactNode;
10
10
  children: React.ReactNode;
11
+ /** @deprecated Use `side` instead */
11
12
  position?: TooltipPosition;
13
+ side?: TooltipPosition;
12
14
  delay?: number;
13
15
  className?: string;
14
16
  }
@@ -16,10 +18,13 @@ interface TooltipProps {
16
18
  export function Tooltip({
17
19
  content,
18
20
  children,
19
- position = "top",
21
+ position,
22
+ side = "top",
20
23
  delay = 200,
21
24
  className,
22
25
  }: TooltipProps) {
26
+ // Support both `position` (legacy) and `side` (preferred)
27
+ const resolvedPosition = position ?? side;
23
28
  const [isVisible, setIsVisible] = useState(false);
24
29
  const timeoutRef = useRef<NodeJS.Timeout | null>(null);
25
30
 
@@ -73,7 +78,7 @@ export function Tooltip({
73
78
  className={cn(
74
79
  "absolute z-50 px-3 py-1.5 text-xs font-medium text-white bg-sonance-charcoal whitespace-nowrap",
75
80
  "animate-in fade-in zoom-in-95 duration-150",
76
- positionClasses[position],
81
+ positionClasses[resolvedPosition],
77
82
  className
78
83
  )}
79
84
  >
@@ -81,7 +86,7 @@ export function Tooltip({
81
86
  <span
82
87
  className={cn(
83
88
  "absolute border-4",
84
- arrowClasses[position]
89
+ arrowClasses[resolvedPosition]
85
90
  )}
86
91
  />
87
92
  </div>
package/dist/index.js CHANGED
@@ -1,11 +1,115 @@
1
1
  #!/usr/bin/env node
2
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
5
  import * as fs from "fs";
6
6
  import * as path from "path";
7
+ import * as os from "os";
7
8
  import { fileURLToPath } from "url";
8
9
  // ============================================
10
+ // INSTALLER (--init flag)
11
+ // ============================================
12
+ /**
13
+ * Get the Claude Desktop config path based on OS
14
+ */
15
+ function getClaudeConfigPath() {
16
+ const platform = os.platform();
17
+ if (platform === "darwin") {
18
+ // macOS
19
+ return path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
20
+ }
21
+ else if (platform === "win32") {
22
+ // Windows
23
+ return path.join(os.homedir(), "AppData", "Roaming", "Claude", "claude_desktop_config.json");
24
+ }
25
+ else {
26
+ // Linux
27
+ return path.join(os.homedir(), ".config", "Claude", "claude_desktop_config.json");
28
+ }
29
+ }
30
+ /**
31
+ * Run the installer to add sonance-brand to Claude Desktop config
32
+ */
33
+ function runInstaller() {
34
+ console.log("");
35
+ console.log(" ┌─────────────────────────────────────────────────┐");
36
+ console.log(" │ │");
37
+ console.log(" │ 🎨 Sonance Brand MCP - Installer │");
38
+ console.log(" │ │");
39
+ console.log(" └─────────────────────────────────────────────────┘");
40
+ console.log("");
41
+ const configPath = getClaudeConfigPath();
42
+ const configDir = path.dirname(configPath);
43
+ console.log(` 📍 Claude config: ${configPath}`);
44
+ console.log("");
45
+ // Create config directory if it doesn't exist
46
+ if (!fs.existsSync(configDir)) {
47
+ console.log(" 📁 Creating Claude config directory...");
48
+ fs.mkdirSync(configDir, { recursive: true });
49
+ }
50
+ // Read existing config or create new one
51
+ let config = {};
52
+ if (fs.existsSync(configPath)) {
53
+ try {
54
+ const content = fs.readFileSync(configPath, "utf-8");
55
+ config = JSON.parse(content);
56
+ console.log(" 📄 Found existing Claude config");
57
+ }
58
+ catch {
59
+ console.log(" ⚠️ Could not parse existing config, creating new one");
60
+ }
61
+ }
62
+ else {
63
+ console.log(" 📄 Creating new Claude config");
64
+ }
65
+ // Initialize mcpServers if not present
66
+ if (!config.mcpServers) {
67
+ config.mcpServers = {};
68
+ }
69
+ // Check if already installed
70
+ if (config.mcpServers["sonance-brand"]) {
71
+ console.log(" ✅ sonance-brand is already configured!");
72
+ console.log("");
73
+ console.log(" To use it:");
74
+ console.log(" 1. Restart Claude Desktop (Cmd+Q / Alt+F4, then reopen)");
75
+ console.log(" 2. Look for the 🔨 hammer icon in the chat input");
76
+ console.log("");
77
+ return;
78
+ }
79
+ // Add Sonance server config (using npx for auto-updates)
80
+ config.mcpServers["sonance-brand"] = {
81
+ command: "npx",
82
+ args: ["-y", "sonance-brand-mcp"],
83
+ };
84
+ // Write updated config
85
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
86
+ console.log("");
87
+ console.log(" ✅ Sonance Brand MCP installed successfully!");
88
+ console.log("");
89
+ console.log(" ┌─────────────────────────────────────────────────┐");
90
+ console.log(" │ Next steps: │");
91
+ console.log(" │ │");
92
+ console.log(" │ 1. Quit Claude Desktop completely │");
93
+ console.log(" │ • Mac: Press Cmd+Q │");
94
+ console.log(" │ • Windows: Press Alt+F4 │");
95
+ console.log(" │ │");
96
+ console.log(" │ 2. Reopen Claude Desktop │");
97
+ console.log(" │ │");
98
+ console.log(" │ 3. Click the 🔨 hammer icon to verify │");
99
+ console.log(" │ You should see 'sonance-brand' listed │");
100
+ console.log(" │ │");
101
+ console.log(" └─────────────────────────────────────────────────┘");
102
+ console.log("");
103
+ console.log(" Try asking Claude:");
104
+ console.log(" \"What are the official Sonance brand colors?\"");
105
+ console.log("");
106
+ }
107
+ // Check for --init flag BEFORE starting MCP server
108
+ if (process.argv.includes("--init") || process.argv.includes("init")) {
109
+ runInstaller();
110
+ process.exit(0);
111
+ }
112
+ // ============================================
9
113
  // PATH RESOLUTION
10
114
  // ============================================
11
115
  const __filename = fileURLToPath(import.meta.url);
@@ -64,6 +168,7 @@ const server = new Server({
64
168
  capabilities: {
65
169
  tools: {},
66
170
  resources: {},
171
+ prompts: {},
67
172
  },
68
173
  });
69
174
  // ============================================
@@ -205,6 +310,35 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
205
310
  required: [],
206
311
  },
207
312
  },
313
+ {
314
+ name: "design_component",
315
+ description: "Returns brand-specific design tokens, colors, implementation rules, and logo assets for designing a component. Smart defaults: Sonance/Light/Default logo lockup if not specified.",
316
+ inputSchema: {
317
+ type: "object",
318
+ properties: {
319
+ brand: {
320
+ type: "string",
321
+ enum: ["sonance", "iport", "blaze"],
322
+ description: "Which brand's styling to use. Defaults to 'sonance' if not specified.",
323
+ },
324
+ theme: {
325
+ type: "string",
326
+ enum: ["light", "dark"],
327
+ description: "The color scheme to use. Defaults to 'light' if not specified.",
328
+ },
329
+ logo_preference: {
330
+ type: "string",
331
+ enum: ["default", "sonance", "iport", "blaze"],
332
+ description: "Which logo to use. 'default' = Sonance+James+IPORT lockup. Or specify 'sonance', 'iport', or 'blaze' for individual brand logos. Defaults to 'default' if not specified.",
333
+ },
334
+ component_description: {
335
+ type: "string",
336
+ description: "What the user wants to design (e.g., 'hero section', 'pricing card', 'navigation bar')",
337
+ },
338
+ },
339
+ required: ["component_description"],
340
+ },
341
+ },
208
342
  ],
209
343
  };
210
344
  });
@@ -426,6 +560,258 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
426
560
  };
427
561
  }
428
562
  }
563
+ case "design_component": {
564
+ const rawArgs = args;
565
+ if (!rawArgs.component_description) {
566
+ return {
567
+ content: [{ type: "text", text: "Error: component_description is required. What would you like to design?" }],
568
+ isError: true,
569
+ };
570
+ }
571
+ // Apply smart defaults and track what was defaulted
572
+ const defaultsUsed = [];
573
+ const brand = rawArgs.brand || (() => { defaultsUsed.push("brand → Sonance"); return "sonance"; })();
574
+ const theme = rawArgs.theme || (() => { defaultsUsed.push("theme → Light"); return "light"; })();
575
+ const logoPreference = rawArgs.logo_preference || (() => { defaultsUsed.push("logo → Default lockup"); return "default"; })();
576
+ const component_description = rawArgs.component_description;
577
+ // Logo path mapping based on preference and theme
578
+ // Light theme = dark logo (for light backgrounds), Dark theme = light logo (for dark backgrounds)
579
+ const logoMap = {
580
+ default: {
581
+ light: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png",
582
+ dark: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png",
583
+ },
584
+ sonance: {
585
+ light: "/logos/sonance/Sonance_Logo_2C_Dark_RGB.png",
586
+ dark: "/logos/sonance/Sonance_Logo_2C_Light_RGB.png",
587
+ },
588
+ iport: {
589
+ light: "/logos/iport/IPORT_Sonance_LockUp_2C_Dark_RGB.png",
590
+ dark: "/logos/iport/IPORT_Sonance_LockUp_2C_Light_RGB.png",
591
+ },
592
+ blaze: {
593
+ light: "/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png",
594
+ dark: "/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png",
595
+ },
596
+ };
597
+ const logoPath = logoMap[logoPreference]?.[theme] || logoMap.default[theme];
598
+ const logoPreferenceLabel = logoPreference === "default"
599
+ ? "Sonance + James + IPORT Lockup"
600
+ : `${logoPreference.charAt(0).toUpperCase() + logoPreference.slice(1)} Only`;
601
+ // Brand-specific design tokens
602
+ const brandTokens = {
603
+ sonance: {
604
+ light: `## Sonance Light Theme Design Tokens
605
+
606
+ ### Colors
607
+ - **Primary/Accent**: \`#00D3C8\` (Sonance Teal) — \`bg-sonance-blue\`, \`text-sonance-blue\`
608
+ - **Text Primary**: \`#333F48\` (Charcoal) — \`text-sonance-charcoal\`
609
+ - **Background**: \`#FFFFFF\` or \`#F5F5F5\` — \`bg-white\`, \`bg-sonance-light-gray\`
610
+ - **Borders/Dividers**: \`#D9D9D6\` — \`border-sonance-light-gray\`
611
+
612
+ ### Implementation Rules
613
+ - Use light backgrounds (white or light gray)
614
+ - Charcoal text on light backgrounds for readability
615
+ - Teal accent for CTAs, links, and interactive elements
616
+ - Minimal borders, prefer subtle shadows for elevation
617
+
618
+ ### Tailwind Classes
619
+ \`\`\`jsx
620
+ // Primary Button
621
+ <button className="bg-sonance-blue text-white px-6 py-3 text-sm font-medium uppercase tracking-wide">
622
+
623
+ // Card
624
+ <div className="bg-white border border-sonance-light-gray rounded-sm p-6 shadow-sm">
625
+
626
+ // Section
627
+ <section className="bg-sonance-light-gray text-sonance-charcoal">
628
+ \`\`\``,
629
+ dark: `## Sonance Dark Theme Design Tokens
630
+
631
+ ### Colors
632
+ - **Primary/Accent**: \`#00D3C8\` (Sonance Teal) — \`bg-sonance-blue\`, \`text-sonance-blue\`
633
+ - **Text Primary**: \`#FFFFFF\` — \`text-white\`
634
+ - **Background**: \`#333F48\` (Charcoal) — \`bg-sonance-charcoal\`
635
+ - **Text Secondary**: \`#D9D9D6\` — \`text-sonance-light-gray\`
636
+
637
+ ### Implementation Rules
638
+ - Use charcoal background for dark sections
639
+ - White text on charcoal for primary content
640
+ - Teal accent for CTAs and highlights
641
+ - Use light-gray for secondary text and borders
642
+
643
+ ### Tailwind Classes
644
+ \`\`\`jsx
645
+ // Primary Button
646
+ <button className="bg-sonance-blue text-sonance-charcoal px-6 py-3 text-sm font-medium uppercase tracking-wide">
647
+
648
+ // Card
649
+ <div className="bg-sonance-charcoal border border-white/10 rounded-sm p-6">
650
+
651
+ // Section
652
+ <section className="bg-sonance-charcoal text-white">
653
+ \`\`\``,
654
+ },
655
+ iport: {
656
+ light: `## IPORT Light Theme Design Tokens
657
+
658
+ ### Colors
659
+ - **Primary/Accent**: \`#FC4C02\` (IPORT Orange) — \`bg-iport-orange\`, \`text-iport-orange\`
660
+ - **Text Primary**: \`#0F161D\` — \`text-iport-dark\`
661
+ - **Background**: \`#FFFFFF\` — \`bg-white\`
662
+ - **Card Background**: \`#F5F5F5\` — \`bg-gray-100\`
663
+
664
+ ### Implementation Rules
665
+ - IPORT typically uses dark UI, but light mode should use orange accents sparingly
666
+ - Orange for CTAs and interactive elements only
667
+ - Dark text on light backgrounds
668
+ - Clean, minimal aesthetic
669
+
670
+ ### Tailwind Classes
671
+ \`\`\`jsx
672
+ // Primary Button
673
+ <button className="bg-iport-orange text-white px-6 py-3 text-sm font-medium uppercase tracking-wide">
674
+
675
+ // Card
676
+ <div className="bg-white border border-gray-200 rounded-sm p-6">
677
+
678
+ // Section
679
+ <section className="bg-gray-100 text-iport-dark">
680
+ \`\`\``,
681
+ dark: `## IPORT Dark Theme Design Tokens
682
+
683
+ ### Colors
684
+ - **Primary/Accent**: \`#FC4C02\` (IPORT Orange) — \`bg-iport-orange\`, \`text-iport-orange\`
685
+ - **Text Primary**: \`#FFFFFF\` — \`text-iport-white\`, \`text-white\`
686
+ - **Background**: \`#0F161D\` — \`bg-iport-dark\`
687
+ - **Card Background**: \`#1C1E20\` — \`bg-iport-dark-gray\`
688
+
689
+ ### Implementation Rules
690
+ - IPORT's signature is dark UI with orange accents
691
+ - Use orange for primary CTAs and interactive elements
692
+ - White text on dark backgrounds
693
+ - Cards should use slightly lighter dark gray for contrast
694
+
695
+ ### Tailwind Classes
696
+ \`\`\`jsx
697
+ // Primary Button
698
+ <button className="bg-iport-orange text-white px-6 py-3 text-sm font-medium uppercase tracking-wide hover:bg-iport-orange/90">
699
+
700
+ // Card
701
+ <div className="bg-iport-dark-gray border border-white/10 rounded-sm p-6">
702
+
703
+ // Section
704
+ <section className="bg-iport-dark text-iport-white">
705
+ \`\`\``,
706
+ },
707
+ blaze: {
708
+ light: `## Blaze Audio Light Theme Design Tokens
709
+
710
+ ### Colors
711
+ - **Primary Accent**: \`#00A3E1\` (Blaze Blue) — \`bg-blaze-blue\`, \`text-blaze-blue\`
712
+ - **Secondary Accent**: \`#C02B0A\` (Blaze Red) — \`bg-blaze-red\` (for alerts/emphasis)
713
+ - **Text Primary**: \`#28282B\` — \`text-blaze-dark-gray\`
714
+ - **Background**: \`#FFFFFF\` — \`bg-white\`
715
+
716
+ ### Implementation Rules
717
+ - Blaze typically uses dark UI, but light mode should feature blue accents
718
+ - Blue for primary CTAs, red for alerts or secondary emphasis
719
+ - Dark text on light backgrounds
720
+ - Modern, tech-forward aesthetic
721
+
722
+ ### Tailwind Classes
723
+ \`\`\`jsx
724
+ // Primary Button
725
+ <button className="bg-blaze-blue text-white px-6 py-3 text-sm font-medium uppercase tracking-wide">
726
+
727
+ // Alert/Emphasis Button
728
+ <button className="bg-blaze-red text-white px-6 py-3 text-sm font-medium uppercase tracking-wide">
729
+
730
+ // Card
731
+ <div className="bg-white border border-gray-200 rounded-sm p-6">
732
+ \`\`\``,
733
+ dark: `## Blaze Audio Dark Theme Design Tokens
734
+
735
+ ### Colors
736
+ - **Primary Accent**: \`#00A3E1\` (Blaze Blue) — \`bg-blaze-blue\`, \`text-blaze-blue\`
737
+ - **Secondary Accent**: \`#C02B0A\` (Blaze Red) — \`bg-blaze-red\` (for alerts/emphasis)
738
+ - **Text Primary**: \`#FFFFFF\` — \`text-blaze-white\`, \`text-white\`
739
+ - **Background**: \`#28282B\` — \`bg-blaze-dark-gray\`
740
+ - **Card Background**: \`#313131\` — \`bg-blaze-gray\`
741
+
742
+ ### Implementation Rules
743
+ - Blaze's signature is dark UI with blue accents
744
+ - Blue for primary CTAs and interactive elements
745
+ - Red for alerts, errors, or secondary emphasis
746
+ - White text on dark backgrounds
747
+ - Cards use slightly lighter gray for contrast
748
+
749
+ ### Tailwind Classes
750
+ \`\`\`jsx
751
+ // Primary Button
752
+ <button className="bg-blaze-blue text-white px-6 py-3 text-sm font-medium uppercase tracking-wide hover:bg-blaze-blue/90">
753
+
754
+ // Alert Button
755
+ <button className="bg-blaze-red text-white px-6 py-3 text-sm font-medium uppercase tracking-wide">
756
+
757
+ // Card
758
+ <div className="bg-blaze-gray border border-white/10 rounded-sm p-6">
759
+
760
+ // Section
761
+ <section className="bg-blaze-dark-gray text-blaze-white">
762
+ \`\`\``,
763
+ },
764
+ };
765
+ const tokens = brandTokens[brand]?.[theme];
766
+ if (!tokens) {
767
+ return {
768
+ content: [{ type: "text", text: `Invalid brand/theme combination: ${brand}/${theme}` }],
769
+ isError: true,
770
+ };
771
+ }
772
+ // Build defaults notice if any were applied
773
+ const defaultsNotice = defaultsUsed.length > 0
774
+ ? `\n> **Note**: Defaults applied: ${defaultsUsed.join(", ")}. Specify \`brand\`, \`theme\`, or \`logo_preference\` explicitly for different styling.\n`
775
+ : "";
776
+ const response = `# Design Context for: ${component_description}
777
+
778
+ **Brand**: ${brand.toUpperCase()}
779
+ **Theme**: ${theme.charAt(0).toUpperCase() + theme.slice(1)}
780
+ **Logo**: ${logoPreferenceLabel}
781
+ ${defaultsNotice}
782
+ ${tokens}
783
+
784
+ ## Logo Asset
785
+ - **Selected**: ${logoPreferenceLabel}
786
+ - **Path**: \`${logoPath}\`
787
+ - **Usage**:
788
+ \`\`\`jsx
789
+ <img src="${logoPath}" alt="${logoPreferenceLabel}" className="h-8 w-auto" />
790
+ // or with Next.js Image:
791
+ <Image src="${logoPath}" alt="${logoPreferenceLabel}" width={200} height={40} />
792
+ \`\`\`
793
+
794
+ > **Tip**: To use a different logo, specify \`logo_preference\`: "default" (Sonance+James+IPORT), "sonance", "iport", or "blaze"
795
+
796
+ ## Typography (All Brands)
797
+ - **Font Family**: Montserrat
798
+ - **Headlines**: font-weight 300 (Light) or 500 (Medium), letter-spacing -0.02em
799
+ - **Body**: font-weight 400 (Regular), line-height 1.6
800
+ - **Buttons/CTAs**: font-weight 500, uppercase, letter-spacing 0.08em
801
+
802
+ ## Design Principles
803
+ 1. Generous whitespace — layouts should feel "breathable"
804
+ 2. Minimal borders — use subtle dividers, not heavy borders
805
+ 3. Refined shadows — subtle elevation, not aggressive drop shadows
806
+ 4. Premium feel — every element should feel high-end and intentional
807
+
808
+ ---
809
+
810
+ Now design the **${component_description}** following these tokens and principles.`;
811
+ return {
812
+ content: [{ type: "text", text: response }],
813
+ };
814
+ }
429
815
  default:
430
816
  return {
431
817
  content: [{ type: "text", text: `Unknown tool: ${name}` }],
@@ -434,6 +820,105 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
434
820
  }
435
821
  });
436
822
  // ============================================
823
+ // PROMPTS (user-initiated templates)
824
+ // ============================================
825
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
826
+ prompts: [
827
+ {
828
+ name: "design",
829
+ description: "Design a new Sonance-branded UI component with the correct brand colors and styling",
830
+ arguments: [
831
+ {
832
+ name: "component",
833
+ description: "What to design (e.g., hero section, pricing card, navigation bar)",
834
+ required: true,
835
+ },
836
+ {
837
+ name: "brand",
838
+ description: "Which brand: sonance, iport, or blaze",
839
+ required: true,
840
+ },
841
+ {
842
+ name: "theme",
843
+ description: "Color scheme: light or dark",
844
+ required: true,
845
+ },
846
+ ],
847
+ },
848
+ {
849
+ name: "brand-check",
850
+ description: "Verify if code follows Sonance brand guidelines",
851
+ arguments: [
852
+ {
853
+ name: "code",
854
+ description: "The code to check against brand guidelines",
855
+ required: true,
856
+ },
857
+ ],
858
+ },
859
+ ],
860
+ }));
861
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
862
+ const { name, arguments: promptArgs } = request.params;
863
+ if (name === "design") {
864
+ const component = promptArgs?.component || "component";
865
+ const brand = promptArgs?.brand || "sonance";
866
+ const theme = promptArgs?.theme || "light";
867
+ return {
868
+ messages: [
869
+ {
870
+ role: "user",
871
+ content: {
872
+ type: "text",
873
+ text: `Design a ${component} for the ${brand.toUpperCase()} brand using ${theme} theme.
874
+
875
+ Please use the design_component tool first to get the correct design tokens, then create the component following Sonance brand guidelines.
876
+
877
+ Requirements:
878
+ - Brand: ${brand}
879
+ - Theme: ${theme} mode
880
+ - Component: ${component}
881
+
882
+ Follow these steps:
883
+ 1. Call design_component with the brand, theme, and component description
884
+ 2. Use the returned design tokens and Tailwind classes
885
+ 3. Generate clean, production-ready React/JSX code
886
+ 4. Follow typography rules (Montserrat font, correct weights)
887
+ 5. Apply design principles (generous whitespace, minimal borders, premium feel)`,
888
+ },
889
+ },
890
+ ],
891
+ };
892
+ }
893
+ if (name === "brand-check") {
894
+ const code = promptArgs?.code || "";
895
+ return {
896
+ messages: [
897
+ {
898
+ role: "user",
899
+ content: {
900
+ type: "text",
901
+ text: `Check this code against Sonance brand guidelines:
902
+
903
+ \`\`\`
904
+ ${code}
905
+ \`\`\`
906
+
907
+ Use get_brand_guidelines to verify:
908
+ 1. Correct color usage (no hardcoded colors, proper semantic tokens)
909
+ 2. Typography (Montserrat font, correct weights - never bold/700 for headlines)
910
+ 3. Design patterns (generous whitespace, minimal borders, premium feel)
911
+ 4. Component patterns (correct button styles, card styling, etc.)
912
+
913
+ Provide specific feedback on what matches and what needs to change.`,
914
+ },
915
+ },
916
+ ],
917
+ };
918
+ }
919
+ throw new Error(`Unknown prompt: ${name}`);
920
+ });
921
+ // ============================================
437
922
  // START SERVER
438
923
  // ============================================
439
924
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "MCP Server for Sonance Brand Guidelines and Component Library - gives Claude instant access to brand colors, typography, and UI components.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",