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.
- 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 +454 -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";
|
|
@@ -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.
|
|
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",
|