sonance-brand-mcp 1.1.0 → 1.1.3

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"
@@ -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,7 +1,7 @@
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
7
  import * as os from "os";
@@ -109,6 +109,78 @@ if (process.argv.includes("--init") || process.argv.includes("init")) {
109
109
  runInstaller();
110
110
  process.exit(0);
111
111
  }
112
+ /**
113
+ * Run the installer for Claude Code (creates .mcp.json in current directory)
114
+ */
115
+ function runClaudeCodeInstaller() {
116
+ console.log("");
117
+ console.log(" ┌─────────────────────────────────────────────────┐");
118
+ console.log(" │ │");
119
+ console.log(" │ 🎨 Sonance Brand MCP - Claude Code Setup │");
120
+ console.log(" │ │");
121
+ console.log(" └─────────────────────────────────────────────────┘");
122
+ console.log("");
123
+ const configPath = path.join(process.cwd(), ".mcp.json");
124
+ console.log(` 📍 Config file: ${configPath}`);
125
+ console.log("");
126
+ // Read existing config or create new one
127
+ let config = {};
128
+ if (fs.existsSync(configPath)) {
129
+ try {
130
+ const content = fs.readFileSync(configPath, "utf-8");
131
+ config = JSON.parse(content);
132
+ console.log(" 📄 Found existing .mcp.json");
133
+ }
134
+ catch {
135
+ console.log(" ⚠️ Could not parse existing .mcp.json, creating new one");
136
+ }
137
+ }
138
+ else {
139
+ console.log(" 📄 Creating new .mcp.json");
140
+ }
141
+ // Initialize mcpServers if not present
142
+ if (!config.mcpServers) {
143
+ config.mcpServers = {};
144
+ }
145
+ // Check if already installed
146
+ if (config.mcpServers["sonance-brand"]) {
147
+ console.log(" ✅ sonance-brand is already configured!");
148
+ console.log("");
149
+ console.log(" To use it:");
150
+ console.log(" 1. Start Claude Code in this directory: claude");
151
+ console.log(" 2. The MCP tools will be available automatically");
152
+ console.log("");
153
+ return;
154
+ }
155
+ // Add Sonance server config (using npx for auto-updates)
156
+ config.mcpServers["sonance-brand"] = {
157
+ command: "npx",
158
+ args: ["-y", "sonance-brand-mcp"],
159
+ };
160
+ // Write updated config
161
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
162
+ console.log("");
163
+ console.log(" ✅ Sonance Brand MCP installed successfully!");
164
+ console.log("");
165
+ console.log(" ┌─────────────────────────────────────────────────┐");
166
+ console.log(" │ Next steps: │");
167
+ console.log(" │ │");
168
+ console.log(" │ 1. Start Claude Code in this directory: │");
169
+ console.log(" │ $ claude │");
170
+ console.log(" │ │");
171
+ console.log(" │ 2. The MCP tools will load automatically │");
172
+ console.log(" │ │");
173
+ console.log(" └─────────────────────────────────────────────────┘");
174
+ console.log("");
175
+ console.log(" Try asking Claude:");
176
+ console.log(" \"What are the official Sonance brand colors?\"");
177
+ console.log("");
178
+ }
179
+ // Check for --init-code flag BEFORE starting MCP server
180
+ if (process.argv.includes("--init-code") || process.argv.includes("init-code")) {
181
+ runClaudeCodeInstaller();
182
+ process.exit(0);
183
+ }
112
184
  // ============================================
113
185
  // PATH RESOLUTION
114
186
  // ============================================
@@ -168,6 +240,7 @@ const server = new Server({
168
240
  capabilities: {
169
241
  tools: {},
170
242
  resources: {},
243
+ prompts: {},
171
244
  },
172
245
  });
173
246
  // ============================================
@@ -309,6 +382,35 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
309
382
  required: [],
310
383
  },
311
384
  },
385
+ {
386
+ name: "design_component",
387
+ 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.",
388
+ inputSchema: {
389
+ type: "object",
390
+ properties: {
391
+ brand: {
392
+ type: "string",
393
+ enum: ["sonance", "iport", "blaze"],
394
+ description: "Which brand's styling to use. Defaults to 'sonance' if not specified.",
395
+ },
396
+ theme: {
397
+ type: "string",
398
+ enum: ["light", "dark"],
399
+ description: "The color scheme to use. Defaults to 'light' if not specified.",
400
+ },
401
+ logo_preference: {
402
+ type: "string",
403
+ enum: ["default", "sonance", "iport", "blaze"],
404
+ 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.",
405
+ },
406
+ component_description: {
407
+ type: "string",
408
+ description: "What the user wants to design (e.g., 'hero section', 'pricing card', 'navigation bar')",
409
+ },
410
+ },
411
+ required: ["component_description"],
412
+ },
413
+ },
312
414
  ],
313
415
  };
314
416
  });
@@ -530,6 +632,258 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
530
632
  };
531
633
  }
532
634
  }
635
+ case "design_component": {
636
+ const rawArgs = args;
637
+ if (!rawArgs.component_description) {
638
+ return {
639
+ content: [{ type: "text", text: "Error: component_description is required. What would you like to design?" }],
640
+ isError: true,
641
+ };
642
+ }
643
+ // Apply smart defaults and track what was defaulted
644
+ const defaultsUsed = [];
645
+ const brand = rawArgs.brand || (() => { defaultsUsed.push("brand → Sonance"); return "sonance"; })();
646
+ const theme = rawArgs.theme || (() => { defaultsUsed.push("theme → Light"); return "light"; })();
647
+ const logoPreference = rawArgs.logo_preference || (() => { defaultsUsed.push("logo → Default lockup"); return "default"; })();
648
+ const component_description = rawArgs.component_description;
649
+ // Logo path mapping based on preference and theme
650
+ // Light theme = dark logo (for light backgrounds), Dark theme = light logo (for dark backgrounds)
651
+ const logoMap = {
652
+ default: {
653
+ light: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png",
654
+ dark: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png",
655
+ },
656
+ sonance: {
657
+ light: "/logos/sonance/Sonance_Logo_2C_Dark_RGB.png",
658
+ dark: "/logos/sonance/Sonance_Logo_2C_Light_RGB.png",
659
+ },
660
+ iport: {
661
+ light: "/logos/iport/IPORT_Sonance_LockUp_2C_Dark_RGB.png",
662
+ dark: "/logos/iport/IPORT_Sonance_LockUp_2C_Light_RGB.png",
663
+ },
664
+ blaze: {
665
+ light: "/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png",
666
+ dark: "/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png",
667
+ },
668
+ };
669
+ const logoPath = logoMap[logoPreference]?.[theme] || logoMap.default[theme];
670
+ const logoPreferenceLabel = logoPreference === "default"
671
+ ? "Sonance + James + IPORT Lockup"
672
+ : `${logoPreference.charAt(0).toUpperCase() + logoPreference.slice(1)} Only`;
673
+ // Brand-specific design tokens
674
+ const brandTokens = {
675
+ sonance: {
676
+ light: `## Sonance Light Theme Design Tokens
677
+
678
+ ### Colors
679
+ - **Primary/Accent**: \`#00D3C8\` (Sonance Teal) — \`bg-sonance-blue\`, \`text-sonance-blue\`
680
+ - **Text Primary**: \`#333F48\` (Charcoal) — \`text-sonance-charcoal\`
681
+ - **Background**: \`#FFFFFF\` or \`#F5F5F5\` — \`bg-white\`, \`bg-sonance-light-gray\`
682
+ - **Borders/Dividers**: \`#D9D9D6\` — \`border-sonance-light-gray\`
683
+
684
+ ### Implementation Rules
685
+ - Use light backgrounds (white or light gray)
686
+ - Charcoal text on light backgrounds for readability
687
+ - Teal accent for CTAs, links, and interactive elements
688
+ - Minimal borders, prefer subtle shadows for elevation
689
+
690
+ ### Tailwind Classes
691
+ \`\`\`jsx
692
+ // Primary Button
693
+ <button className="bg-sonance-blue text-white px-6 py-3 text-sm font-medium uppercase tracking-wide">
694
+
695
+ // Card
696
+ <div className="bg-white border border-sonance-light-gray rounded-sm p-6 shadow-sm">
697
+
698
+ // Section
699
+ <section className="bg-sonance-light-gray text-sonance-charcoal">
700
+ \`\`\``,
701
+ dark: `## Sonance Dark Theme Design Tokens
702
+
703
+ ### Colors
704
+ - **Primary/Accent**: \`#00D3C8\` (Sonance Teal) — \`bg-sonance-blue\`, \`text-sonance-blue\`
705
+ - **Text Primary**: \`#FFFFFF\` — \`text-white\`
706
+ - **Background**: \`#333F48\` (Charcoal) — \`bg-sonance-charcoal\`
707
+ - **Text Secondary**: \`#D9D9D6\` — \`text-sonance-light-gray\`
708
+
709
+ ### Implementation Rules
710
+ - Use charcoal background for dark sections
711
+ - White text on charcoal for primary content
712
+ - Teal accent for CTAs and highlights
713
+ - Use light-gray for secondary text and borders
714
+
715
+ ### Tailwind Classes
716
+ \`\`\`jsx
717
+ // Primary Button
718
+ <button className="bg-sonance-blue text-sonance-charcoal px-6 py-3 text-sm font-medium uppercase tracking-wide">
719
+
720
+ // Card
721
+ <div className="bg-sonance-charcoal border border-white/10 rounded-sm p-6">
722
+
723
+ // Section
724
+ <section className="bg-sonance-charcoal text-white">
725
+ \`\`\``,
726
+ },
727
+ iport: {
728
+ light: `## IPORT Light Theme Design Tokens
729
+
730
+ ### Colors
731
+ - **Primary/Accent**: \`#FC4C02\` (IPORT Orange) — \`bg-iport-orange\`, \`text-iport-orange\`
732
+ - **Text Primary**: \`#0F161D\` — \`text-iport-dark\`
733
+ - **Background**: \`#FFFFFF\` — \`bg-white\`
734
+ - **Card Background**: \`#F5F5F5\` — \`bg-gray-100\`
735
+
736
+ ### Implementation Rules
737
+ - IPORT typically uses dark UI, but light mode should use orange accents sparingly
738
+ - Orange for CTAs and interactive elements only
739
+ - Dark text on light backgrounds
740
+ - Clean, minimal aesthetic
741
+
742
+ ### Tailwind Classes
743
+ \`\`\`jsx
744
+ // Primary Button
745
+ <button className="bg-iport-orange text-white px-6 py-3 text-sm font-medium uppercase tracking-wide">
746
+
747
+ // Card
748
+ <div className="bg-white border border-gray-200 rounded-sm p-6">
749
+
750
+ // Section
751
+ <section className="bg-gray-100 text-iport-dark">
752
+ \`\`\``,
753
+ dark: `## IPORT Dark Theme Design Tokens
754
+
755
+ ### Colors
756
+ - **Primary/Accent**: \`#FC4C02\` (IPORT Orange) — \`bg-iport-orange\`, \`text-iport-orange\`
757
+ - **Text Primary**: \`#FFFFFF\` — \`text-iport-white\`, \`text-white\`
758
+ - **Background**: \`#0F161D\` — \`bg-iport-dark\`
759
+ - **Card Background**: \`#1C1E20\` — \`bg-iport-dark-gray\`
760
+
761
+ ### Implementation Rules
762
+ - IPORT's signature is dark UI with orange accents
763
+ - Use orange for primary CTAs and interactive elements
764
+ - White text on dark backgrounds
765
+ - Cards should use slightly lighter dark gray for contrast
766
+
767
+ ### Tailwind Classes
768
+ \`\`\`jsx
769
+ // Primary Button
770
+ <button className="bg-iport-orange text-white px-6 py-3 text-sm font-medium uppercase tracking-wide hover:bg-iport-orange/90">
771
+
772
+ // Card
773
+ <div className="bg-iport-dark-gray border border-white/10 rounded-sm p-6">
774
+
775
+ // Section
776
+ <section className="bg-iport-dark text-iport-white">
777
+ \`\`\``,
778
+ },
779
+ blaze: {
780
+ light: `## Blaze Audio Light Theme Design Tokens
781
+
782
+ ### Colors
783
+ - **Primary Accent**: \`#00A3E1\` (Blaze Blue) — \`bg-blaze-blue\`, \`text-blaze-blue\`
784
+ - **Secondary Accent**: \`#C02B0A\` (Blaze Red) — \`bg-blaze-red\` (for alerts/emphasis)
785
+ - **Text Primary**: \`#28282B\` — \`text-blaze-dark-gray\`
786
+ - **Background**: \`#FFFFFF\` — \`bg-white\`
787
+
788
+ ### Implementation Rules
789
+ - Blaze typically uses dark UI, but light mode should feature blue accents
790
+ - Blue for primary CTAs, red for alerts or secondary emphasis
791
+ - Dark text on light backgrounds
792
+ - Modern, tech-forward aesthetic
793
+
794
+ ### Tailwind Classes
795
+ \`\`\`jsx
796
+ // Primary Button
797
+ <button className="bg-blaze-blue text-white px-6 py-3 text-sm font-medium uppercase tracking-wide">
798
+
799
+ // Alert/Emphasis Button
800
+ <button className="bg-blaze-red text-white px-6 py-3 text-sm font-medium uppercase tracking-wide">
801
+
802
+ // Card
803
+ <div className="bg-white border border-gray-200 rounded-sm p-6">
804
+ \`\`\``,
805
+ dark: `## Blaze Audio Dark Theme Design Tokens
806
+
807
+ ### Colors
808
+ - **Primary Accent**: \`#00A3E1\` (Blaze Blue) — \`bg-blaze-blue\`, \`text-blaze-blue\`
809
+ - **Secondary Accent**: \`#C02B0A\` (Blaze Red) — \`bg-blaze-red\` (for alerts/emphasis)
810
+ - **Text Primary**: \`#FFFFFF\` — \`text-blaze-white\`, \`text-white\`
811
+ - **Background**: \`#28282B\` — \`bg-blaze-dark-gray\`
812
+ - **Card Background**: \`#313131\` — \`bg-blaze-gray\`
813
+
814
+ ### Implementation Rules
815
+ - Blaze's signature is dark UI with blue accents
816
+ - Blue for primary CTAs and interactive elements
817
+ - Red for alerts, errors, or secondary emphasis
818
+ - White text on dark backgrounds
819
+ - Cards use slightly lighter gray for contrast
820
+
821
+ ### Tailwind Classes
822
+ \`\`\`jsx
823
+ // Primary Button
824
+ <button className="bg-blaze-blue text-white px-6 py-3 text-sm font-medium uppercase tracking-wide hover:bg-blaze-blue/90">
825
+
826
+ // Alert Button
827
+ <button className="bg-blaze-red text-white px-6 py-3 text-sm font-medium uppercase tracking-wide">
828
+
829
+ // Card
830
+ <div className="bg-blaze-gray border border-white/10 rounded-sm p-6">
831
+
832
+ // Section
833
+ <section className="bg-blaze-dark-gray text-blaze-white">
834
+ \`\`\``,
835
+ },
836
+ };
837
+ const tokens = brandTokens[brand]?.[theme];
838
+ if (!tokens) {
839
+ return {
840
+ content: [{ type: "text", text: `Invalid brand/theme combination: ${brand}/${theme}` }],
841
+ isError: true,
842
+ };
843
+ }
844
+ // Build defaults notice if any were applied
845
+ const defaultsNotice = defaultsUsed.length > 0
846
+ ? `\n> **Note**: Defaults applied: ${defaultsUsed.join(", ")}. Specify \`brand\`, \`theme\`, or \`logo_preference\` explicitly for different styling.\n`
847
+ : "";
848
+ const response = `# Design Context for: ${component_description}
849
+
850
+ **Brand**: ${brand.toUpperCase()}
851
+ **Theme**: ${theme.charAt(0).toUpperCase() + theme.slice(1)}
852
+ **Logo**: ${logoPreferenceLabel}
853
+ ${defaultsNotice}
854
+ ${tokens}
855
+
856
+ ## Logo Asset
857
+ - **Selected**: ${logoPreferenceLabel}
858
+ - **Path**: \`${logoPath}\`
859
+ - **Usage**:
860
+ \`\`\`jsx
861
+ <img src="${logoPath}" alt="${logoPreferenceLabel}" className="h-8 w-auto" />
862
+ // or with Next.js Image:
863
+ <Image src="${logoPath}" alt="${logoPreferenceLabel}" width={200} height={40} />
864
+ \`\`\`
865
+
866
+ > **Tip**: To use a different logo, specify \`logo_preference\`: "default" (Sonance+James+IPORT), "sonance", "iport", or "blaze"
867
+
868
+ ## Typography (All Brands)
869
+ - **Font Family**: Montserrat
870
+ - **Headlines**: font-weight 300 (Light) or 500 (Medium), letter-spacing -0.02em
871
+ - **Body**: font-weight 400 (Regular), line-height 1.6
872
+ - **Buttons/CTAs**: font-weight 500, uppercase, letter-spacing 0.08em
873
+
874
+ ## Design Principles
875
+ 1. Generous whitespace — layouts should feel "breathable"
876
+ 2. Minimal borders — use subtle dividers, not heavy borders
877
+ 3. Refined shadows — subtle elevation, not aggressive drop shadows
878
+ 4. Premium feel — every element should feel high-end and intentional
879
+
880
+ ---
881
+
882
+ Now design the **${component_description}** following these tokens and principles.`;
883
+ return {
884
+ content: [{ type: "text", text: response }],
885
+ };
886
+ }
533
887
  default:
534
888
  return {
535
889
  content: [{ type: "text", text: `Unknown tool: ${name}` }],
@@ -538,6 +892,105 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
538
892
  }
539
893
  });
540
894
  // ============================================
895
+ // PROMPTS (user-initiated templates)
896
+ // ============================================
897
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
898
+ prompts: [
899
+ {
900
+ name: "design",
901
+ description: "Design a new Sonance-branded UI component with the correct brand colors and styling",
902
+ arguments: [
903
+ {
904
+ name: "component",
905
+ description: "What to design (e.g., hero section, pricing card, navigation bar)",
906
+ required: true,
907
+ },
908
+ {
909
+ name: "brand",
910
+ description: "Which brand: sonance, iport, or blaze",
911
+ required: true,
912
+ },
913
+ {
914
+ name: "theme",
915
+ description: "Color scheme: light or dark",
916
+ required: true,
917
+ },
918
+ ],
919
+ },
920
+ {
921
+ name: "brand-check",
922
+ description: "Verify if code follows Sonance brand guidelines",
923
+ arguments: [
924
+ {
925
+ name: "code",
926
+ description: "The code to check against brand guidelines",
927
+ required: true,
928
+ },
929
+ ],
930
+ },
931
+ ],
932
+ }));
933
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
934
+ const { name, arguments: promptArgs } = request.params;
935
+ if (name === "design") {
936
+ const component = promptArgs?.component || "component";
937
+ const brand = promptArgs?.brand || "sonance";
938
+ const theme = promptArgs?.theme || "light";
939
+ return {
940
+ messages: [
941
+ {
942
+ role: "user",
943
+ content: {
944
+ type: "text",
945
+ text: `Design a ${component} for the ${brand.toUpperCase()} brand using ${theme} theme.
946
+
947
+ Please use the design_component tool first to get the correct design tokens, then create the component following Sonance brand guidelines.
948
+
949
+ Requirements:
950
+ - Brand: ${brand}
951
+ - Theme: ${theme} mode
952
+ - Component: ${component}
953
+
954
+ Follow these steps:
955
+ 1. Call design_component with the brand, theme, and component description
956
+ 2. Use the returned design tokens and Tailwind classes
957
+ 3. Generate clean, production-ready React/JSX code
958
+ 4. Follow typography rules (Montserrat font, correct weights)
959
+ 5. Apply design principles (generous whitespace, minimal borders, premium feel)`,
960
+ },
961
+ },
962
+ ],
963
+ };
964
+ }
965
+ if (name === "brand-check") {
966
+ const code = promptArgs?.code || "";
967
+ return {
968
+ messages: [
969
+ {
970
+ role: "user",
971
+ content: {
972
+ type: "text",
973
+ text: `Check this code against Sonance brand guidelines:
974
+
975
+ \`\`\`
976
+ ${code}
977
+ \`\`\`
978
+
979
+ Use get_brand_guidelines to verify:
980
+ 1. Correct color usage (no hardcoded colors, proper semantic tokens)
981
+ 2. Typography (Montserrat font, correct weights - never bold/700 for headlines)
982
+ 3. Design patterns (generous whitespace, minimal borders, premium feel)
983
+ 4. Component patterns (correct button styles, card styling, etc.)
984
+
985
+ Provide specific feedback on what matches and what needs to change.`,
986
+ },
987
+ },
988
+ ],
989
+ };
990
+ }
991
+ throw new Error(`Unknown prompt: ${name}`);
992
+ });
993
+ // ============================================
541
994
  // START SERVER
542
995
  // ============================================
543
996
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.1.3",
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",