sonance-brand-mcp 1.1.0 → 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.
- package/dist/assets/components/accordion.tsx +7 -1
- package/dist/assets/components/alert.tsx +4 -2
- package/dist/assets/components/image.tsx +1 -2
- package/dist/assets/components/kbd.tsx +1 -1
- package/dist/assets/components/progress.tsx +15 -8
- package/dist/assets/components/tooltip.tsx +8 -3
- package/dist/index.js +382 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
{
|
|
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 =
|
|
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 = (
|
|
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={
|
|
93
|
+
<svg width={resolvedSize} height={resolvedSize} className="-rotate-90">
|
|
87
94
|
{/* Background circle */}
|
|
88
95
|
<circle
|
|
89
|
-
cx={
|
|
90
|
-
cy={
|
|
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={
|
|
100
|
-
cy={
|
|
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
|
|
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[
|
|
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[
|
|
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";
|
|
@@ -168,6 +168,7 @@ const server = new Server({
|
|
|
168
168
|
capabilities: {
|
|
169
169
|
tools: {},
|
|
170
170
|
resources: {},
|
|
171
|
+
prompts: {},
|
|
171
172
|
},
|
|
172
173
|
});
|
|
173
174
|
// ============================================
|
|
@@ -309,6 +310,35 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
309
310
|
required: [],
|
|
310
311
|
},
|
|
311
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
|
+
},
|
|
312
342
|
],
|
|
313
343
|
};
|
|
314
344
|
});
|
|
@@ -530,6 +560,258 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
530
560
|
};
|
|
531
561
|
}
|
|
532
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
|
+
}
|
|
533
815
|
default:
|
|
534
816
|
return {
|
|
535
817
|
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
@@ -538,6 +820,105 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
538
820
|
}
|
|
539
821
|
});
|
|
540
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
|
+
// ============================================
|
|
541
922
|
// START SERVER
|
|
542
923
|
// ============================================
|
|
543
924
|
async function main() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.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",
|