sonance-brand-mcp 1.3.107 → 1.3.109

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.
@@ -2035,6 +2035,7 @@ export async function POST(request: Request) {
2035
2035
  let recommendedFile: { path: string; reason: string } | null = null;
2036
2036
  let deterministicMatch: { path: string; lineNumber: number } | null = null;
2037
2037
  let phase1VisibleText: string[] = []; // Store Phase 1 visible text for use in file scoring
2038
+ let smartSearchTopPath: string | null = null; // Top smart search result for fallback
2038
2039
 
2039
2040
  // ========================================================================
2040
2041
  // PHASE 0: Deterministic Element ID Search (Cursor-style explicit selection)
@@ -2141,7 +2142,7 @@ export async function POST(request: Request) {
2141
2142
  // IMPROVED: Relative scoring - compare focused element vs smart search
2142
2143
  // This prevents false positives from generic element IDs in wrong files
2143
2144
  const smartSearchTopScore = searchResults[0]?.score || 0;
2144
- const smartSearchTopPath = searchResults[0]?.path || null;
2145
+ smartSearchTopPath = searchResults[0]?.path || null; // Assign to outer scope variable
2145
2146
  const focusedTopScore = focusedElementHints[0]?.score || 0;
2146
2147
  const focusedTopPath = focusedElementHints[0]?.path || null;
2147
2148
 
@@ -2510,6 +2511,22 @@ export async function POST(request: Request) {
2510
2511
  }
2511
2512
  }
2512
2513
 
2514
+ // ========== SMART SEARCH FALLBACK ==========
2515
+ // If we still don't have a good target file (just the page wrapper),
2516
+ // use the smart search top result - it already found relevant files!
2517
+ if (actualTargetFile && actualTargetFile.path === pageContext.pageFile && smartSearchTopPath) {
2518
+ const smartSearchFullPath = path.join(projectRoot, smartSearchTopPath);
2519
+ if (fs.existsSync(smartSearchFullPath)) {
2520
+ const smartSearchContent = fs.readFileSync(smartSearchFullPath, 'utf-8');
2521
+ actualTargetFile = { path: smartSearchTopPath, content: smartSearchContent };
2522
+ debugLog("SMART SEARCH FALLBACK: Using top smart search result as target", {
2523
+ originalFile: pageContext.pageFile,
2524
+ redirectTo: smartSearchTopPath,
2525
+ contentLength: smartSearchContent.length
2526
+ });
2527
+ }
2528
+ }
2529
+
2513
2530
  debugLog("File redirect complete", {
2514
2531
  originalRecommended: recommendedFileContent?.path || 'none',
2515
2532
  actualTarget: actualTargetFile?.path || 'none',
@@ -2004,6 +2004,7 @@ export async function POST(request: Request) {
2004
2004
  let recommendedFile: { path: string; reason: string } | null = null;
2005
2005
  let deterministicMatch: { path: string; lineNumber: number } | null = null;
2006
2006
  let phase1VisibleText: string[] = []; // Store Phase 1 visible text for use in file scoring
2007
+ let smartSearchTopPath: string | null = null; // Top smart search result for fallback
2007
2008
 
2008
2009
  // ========================================================================
2009
2010
  // PHASE 0: Deterministic Element ID Search (Cursor-style explicit selection)
@@ -2110,7 +2111,7 @@ export async function POST(request: Request) {
2110
2111
  // IMPROVED: Relative scoring - compare focused element vs smart search
2111
2112
  // This prevents false positives from generic element IDs in wrong files
2112
2113
  const smartSearchTopScore = searchResults[0]?.score || 0;
2113
- const smartSearchTopPath = searchResults[0]?.path || null;
2114
+ smartSearchTopPath = searchResults[0]?.path || null; // Assign to outer scope variable
2114
2115
  const focusedTopScore = focusedElementHints[0]?.score || 0;
2115
2116
  const focusedTopPath = focusedElementHints[0]?.path || null;
2116
2117
 
@@ -2479,6 +2480,22 @@ export async function POST(request: Request) {
2479
2480
  }
2480
2481
  }
2481
2482
 
2483
+ // ========== SMART SEARCH FALLBACK ==========
2484
+ // If we still don't have a good target file (just the page wrapper),
2485
+ // use the smart search top result - it already found relevant files!
2486
+ if (actualTargetFile && actualTargetFile.path === pageContext.pageFile && smartSearchTopPath) {
2487
+ const smartSearchFullPath = path.join(projectRoot, smartSearchTopPath);
2488
+ if (fs.existsSync(smartSearchFullPath)) {
2489
+ const smartSearchContent = fs.readFileSync(smartSearchFullPath, 'utf-8');
2490
+ actualTargetFile = { path: smartSearchTopPath, content: smartSearchContent };
2491
+ debugLog("SMART SEARCH FALLBACK: Using top smart search result as target", {
2492
+ originalFile: pageContext.pageFile,
2493
+ redirectTo: smartSearchTopPath,
2494
+ contentLength: smartSearchContent.length
2495
+ });
2496
+ }
2497
+ }
2498
+
2482
2499
  debugLog("File redirect complete", {
2483
2500
  originalRecommended: recommendedFileContent?.path || 'none',
2484
2501
  actualTarget: actualTargetFile?.path || 'none',
package/dist/index.js CHANGED
@@ -1397,33 +1397,145 @@ function getImageMimeType(filePath) {
1397
1397
  default: return 'application/octet-stream';
1398
1398
  }
1399
1399
  }
1400
+ /**
1401
+ * Centralized logo path mapping for all brands
1402
+ * Light = logo for light backgrounds (typically darker logo)
1403
+ * Dark = logo for dark backgrounds (typically lighter/reversed logo)
1404
+ */
1405
+ const LOGO_MAP = {
1406
+ default: {
1407
+ light: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png",
1408
+ dark: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png",
1409
+ },
1410
+ sonance: {
1411
+ light: "/logos/sonance/Sonance_Logo_2C_Dark_RGB.png",
1412
+ dark: "/logos/sonance/Sonance_Logo_2C_Light_RGB.png",
1413
+ },
1414
+ iport: {
1415
+ light: "/logos/iport/IPORT_Sonance_LockUp_2C_Dark_RGB.png",
1416
+ dark: "/logos/iport/IPORT_Sonance_LockUp_2C_Light_RGB.png",
1417
+ },
1418
+ blaze: {
1419
+ light: "/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png",
1420
+ dark: "/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png",
1421
+ },
1422
+ james: {
1423
+ light: "/logos/james/James_Logo_Black_RGB.png",
1424
+ dark: "/logos/james/James_Logo_Reverse_RGB.png",
1425
+ },
1426
+ };
1427
+ // Validation state for logo map
1428
+ let logoMapValidated = false;
1429
+ let logoMapValidationErrors = [];
1430
+ /**
1431
+ * Validate that all logos in LOGO_MAP exist
1432
+ * Called once on first logo-using tool invocation
1433
+ */
1434
+ function validateLogoMap() {
1435
+ if (logoMapValidated) {
1436
+ return { valid: logoMapValidationErrors.length === 0, errors: logoMapValidationErrors };
1437
+ }
1438
+ const errors = [];
1439
+ for (const [key, paths] of Object.entries(LOGO_MAP)) {
1440
+ for (const [theme, logoPath] of Object.entries(paths)) {
1441
+ const resolvedPath = resolveLogoPath(logoPath);
1442
+ if (!resolvedPath || !fs.existsSync(resolvedPath)) {
1443
+ errors.push(`LOGO_MAP["${key}"].${theme}: "${logoPath}" not found`);
1444
+ }
1445
+ }
1446
+ }
1447
+ logoMapValidated = true;
1448
+ logoMapValidationErrors = errors;
1449
+ if (errors.length > 0) {
1450
+ console.error(`[Logo Validation] ${errors.length} logo(s) not found:`);
1451
+ errors.forEach(e => console.error(` - ${e}`));
1452
+ }
1453
+ return { valid: errors.length === 0, errors };
1454
+ }
1400
1455
  /**
1401
1456
  * Embed a logo as base64 image content for MCP responses
1402
- * Returns an MCP-compatible image content block or null if logo not found
1457
+ * Returns either a success result with image data or an error result with details
1403
1458
  */
1404
1459
  async function embedLogo(logoPath) {
1460
+ // Validate input
1461
+ if (!logoPath || typeof logoPath !== 'string') {
1462
+ return {
1463
+ success: false,
1464
+ error: "invalid_path",
1465
+ message: "Logo path is empty or invalid",
1466
+ requestedPath: String(logoPath),
1467
+ };
1468
+ }
1469
+ const resolvedPath = resolveLogoPath(logoPath);
1470
+ if (!resolvedPath) {
1471
+ return {
1472
+ success: false,
1473
+ error: "invalid_path",
1474
+ message: `Failed to resolve logo path: ${logoPath}`,
1475
+ requestedPath: logoPath,
1476
+ };
1477
+ }
1478
+ // Check if file exists
1479
+ if (!fs.existsSync(resolvedPath)) {
1480
+ return {
1481
+ success: false,
1482
+ error: "not_found",
1483
+ message: `Logo file does not exist`,
1484
+ requestedPath: logoPath,
1485
+ resolvedPath,
1486
+ };
1487
+ }
1405
1488
  try {
1406
- const resolvedPath = resolveLogoPath(logoPath);
1407
- if (!resolvedPath)
1408
- return null;
1409
- // Check if file exists
1410
- if (!fs.existsSync(resolvedPath)) {
1411
- console.error(`Logo not found: ${resolvedPath}`);
1412
- return null;
1413
- }
1414
1489
  // Read file as base64
1415
1490
  const fileBuffer = fs.readFileSync(resolvedPath);
1416
1491
  const base64Data = fileBuffer.toString('base64');
1417
1492
  const mimeType = getImageMimeType(resolvedPath);
1418
1493
  return {
1494
+ success: true,
1419
1495
  type: "image",
1420
1496
  data: base64Data,
1421
1497
  mimeType,
1422
1498
  };
1423
1499
  }
1424
1500
  catch (error) {
1425
- console.error(`Error embedding logo: ${error}`);
1426
- return null;
1501
+ return {
1502
+ success: false,
1503
+ error: "read_error",
1504
+ message: `Failed to read logo file: ${error instanceof Error ? error.message : String(error)}`,
1505
+ requestedPath: logoPath,
1506
+ resolvedPath,
1507
+ };
1508
+ }
1509
+ }
1510
+ /**
1511
+ * Embed a logo and add appropriate content blocks with warnings if it fails
1512
+ * Returns true if logo was embedded successfully, false otherwise
1513
+ */
1514
+ async function embedLogoWithFeedback(logoPath, label, contentBlocks, options = {}) {
1515
+ const result = await embedLogo(logoPath);
1516
+ if (result.success) {
1517
+ contentBlocks.push({
1518
+ type: "text",
1519
+ text: `## ${label}\n**Path:** \`${logoPath}\``
1520
+ });
1521
+ contentBlocks.push({
1522
+ type: "image",
1523
+ data: result.data,
1524
+ mimeType: result.mimeType,
1525
+ });
1526
+ return true;
1527
+ }
1528
+ else {
1529
+ // Add warning to response unless silent mode
1530
+ if (!options.silent) {
1531
+ contentBlocks.push({
1532
+ type: "text",
1533
+ text: `> **Warning:** Could not embed ${label.toLowerCase()}: ${result.message}\n> Requested path: \`${logoPath}\`${result.resolvedPath ? `\n> Resolved to: \`${result.resolvedPath}\`` : ''}\n> Use \`list_logos\` to see available logos.`,
1534
+ });
1535
+ }
1536
+ // Always log to console for debugging
1537
+ console.error(`[Logo Embed Failed] ${label}: ${result.message} (path: ${logoPath})`);
1538
+ return false;
1427
1539
  }
1428
1540
  }
1429
1541
  /**
@@ -2427,7 +2539,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
2427
2539
  },
2428
2540
  {
2429
2541
  name: "get_logo",
2430
- description: "Returns a brand logo image as base64-encoded data that AI can view and analyze. Use list_logos first to see available logos, then use this tool to retrieve the actual image.",
2542
+ description: "Returns a brand logo with ready-to-use implementation code. USE THIS when user asks to 'add logo', 'put the logo', 'include the logo', or needs a specific logo for their code. Returns: the logo image, React/Next.js code snippets, sizing guidelines, and recommendations for light/dark mode variants. Use list_logos first to see available logos.",
2431
2543
  inputSchema: {
2432
2544
  type: "object",
2433
2545
  properties: {
@@ -2439,6 +2551,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
2439
2551
  required: ["logo_path"],
2440
2552
  },
2441
2553
  },
2554
+ {
2555
+ name: "diagnose_logos",
2556
+ description: "Diagnose logo loading issues. Returns validation status, path resolution examples, and environment info. Use this when logos fail to embed or appear in responses.",
2557
+ inputSchema: {
2558
+ type: "object",
2559
+ properties: {},
2560
+ required: [],
2561
+ },
2562
+ },
2442
2563
  {
2443
2564
  name: "get_full_library",
2444
2565
  description: "RECOMMENDED FOR APP REDESIGNS & FULL APPLICATIONS: Returns the complete component library including brand guidelines, CSS theme, utilities, and all component source code. USE THIS TOOL when the user asks to: redesign an application, rebrand an app, build a full application, update an entire codebase to use Sonance/IPORT/Blaze brand, or any multi-file/multi-component project. This is the go-to tool for comprehensive brand implementation across an entire project.",
@@ -2890,6 +3011,87 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2890
3011
  const base64Data = imageBuffer.toString('base64');
2891
3012
  const mimeType = getImageMimeType(resolvedPath);
2892
3013
  const fileName = path.basename(resolvedPath);
3014
+ // Determine logo variant info for recommendations
3015
+ const isLightLogo = /light|reverse|white/i.test(fileName);
3016
+ const isDarkLogo = /dark|black/i.test(fileName);
3017
+ const backgroundGuidance = isLightLogo
3018
+ ? "**Use on:** Dark backgrounds (charcoal, dark gray, black)"
3019
+ : isDarkLogo
3020
+ ? "**Use on:** Light backgrounds (white, light gray)"
3021
+ : "**Use on:** Check filename for light/dark variant guidance";
3022
+ // Suggest complementary variant
3023
+ let variantSuggestion = "";
3024
+ if (isLightLogo) {
3025
+ const darkVariant = fileName.replace(/light/gi, "Dark").replace(/reverse/gi, "Dark").replace(/white/gi, "Black");
3026
+ variantSuggestion = `**For light backgrounds:** Look for \`${darkVariant}\` or a "Dark" variant`;
3027
+ }
3028
+ else if (isDarkLogo) {
3029
+ const lightVariant = fileName.replace(/dark/gi, "Light").replace(/black/gi, "Reverse");
3030
+ variantSuggestion = `**For dark backgrounds:** Look for \`${lightVariant}\` or a "Light/Reverse" variant`;
3031
+ }
3032
+ // Determine brand for alt text
3033
+ const brandMatch = logoPath.match(/\/(sonance|iport|blaze|james|trufig)/i);
3034
+ const brandName = brandMatch ? brandMatch[1].charAt(0).toUpperCase() + brandMatch[1].slice(1) : "Brand";
3035
+ // Normalize the path for code examples (ensure it starts with /logos/)
3036
+ const normalizedPath = logoPath.startsWith('/') ? logoPath : `/logos/${logoPath.replace(/^logos\//, '')}`;
3037
+ const implementationGuide = `## Logo: ${fileName}
3038
+
3039
+ ${backgroundGuidance}
3040
+ ${variantSuggestion}
3041
+
3042
+ ---
3043
+
3044
+ ### Implementation (Next.js with next/image)
3045
+ \`\`\`tsx
3046
+ import Image from 'next/image';
3047
+
3048
+ // In your component:
3049
+ <Image
3050
+ src="${normalizedPath}"
3051
+ alt="${brandName}"
3052
+ width={150}
3053
+ height={40}
3054
+ className="h-10 w-auto"
3055
+ priority // Add if logo is above the fold
3056
+ />
3057
+ \`\`\`
3058
+
3059
+ ### Implementation (React/HTML)
3060
+ \`\`\`tsx
3061
+ <img
3062
+ src="${normalizedPath}"
3063
+ alt="${brandName}"
3064
+ className="h-10 w-auto"
3065
+ />
3066
+ \`\`\`
3067
+
3068
+ ### With Dark Mode Support
3069
+ \`\`\`tsx
3070
+ {/* Use CSS to swap logos based on theme */}
3071
+ <img
3072
+ src="${normalizedPath}"
3073
+ alt="${brandName}"
3074
+ className="h-10 w-auto dark:hidden" {/* Light mode */}
3075
+ />
3076
+ <img
3077
+ src="${normalizedPath.replace(/dark/gi, 'Light').replace(/black/gi, 'Reverse')}"
3078
+ alt="${brandName}"
3079
+ className="h-10 w-auto hidden dark:block" {/* Dark mode */}
3080
+ />
3081
+ \`\`\`
3082
+
3083
+ ### Sizing Guidelines
3084
+ | Location | Tailwind Class | Pixels |
3085
+ |----------|---------------|--------|
3086
+ | Header/Navbar | \`h-8\` to \`h-10\` | 32-40px |
3087
+ | Hero Section | \`h-12\` to \`h-16\` | 48-64px |
3088
+ | Footer | \`h-6\` to \`h-8\` | 24-32px |
3089
+ | Mobile Header | \`h-6\` to \`h-8\` | 24-32px |
3090
+
3091
+ ---
3092
+
3093
+ **File Info:** ${imageBuffer.length} bytes, ${mimeType}
3094
+ **Full Path:** \`${normalizedPath}\``;
2893
3095
  return {
2894
3096
  content: [
2895
3097
  {
@@ -2899,7 +3101,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2899
3101
  },
2900
3102
  {
2901
3103
  type: "text",
2902
- text: `**Logo:** ${fileName}\n**Path:** ${logoPath}\n**Size:** ${imageBuffer.length} bytes\n**Format:** ${mimeType}`,
3104
+ text: implementationGuide,
2903
3105
  },
2904
3106
  ],
2905
3107
  };
@@ -2911,6 +3113,88 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2911
3113
  };
2912
3114
  }
2913
3115
  }
3116
+ case "diagnose_logos": {
3117
+ // Run validation and collect diagnostic info
3118
+ const validation = validateLogoMap();
3119
+ const samplePath = "/logos/sonance/Sonance_Logo_2C_Dark_RGB.png";
3120
+ const resolvedSample = resolveLogoPath(samplePath);
3121
+ // Count logos in manifest or directory
3122
+ let logoCount = "Unknown";
3123
+ try {
3124
+ if (IS_BUNDLED) {
3125
+ const manifestPath = getAssetPath("logos");
3126
+ if (fs.existsSync(manifestPath)) {
3127
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
3128
+ logoCount = `${manifest.length} logos in manifest`;
3129
+ }
3130
+ else {
3131
+ logoCount = "Manifest file not found";
3132
+ }
3133
+ }
3134
+ else {
3135
+ const logosDir = getAssetPath("logos");
3136
+ if (fs.existsSync(logosDir)) {
3137
+ // Recursive count
3138
+ const countFiles = (dir) => {
3139
+ let count = 0;
3140
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
3141
+ for (const entry of entries) {
3142
+ if (entry.isDirectory()) {
3143
+ count += countFiles(path.join(dir, entry.name));
3144
+ }
3145
+ else if (/\.(png|svg|jpg|jpeg)$/i.test(entry.name)) {
3146
+ count++;
3147
+ }
3148
+ }
3149
+ return count;
3150
+ };
3151
+ logoCount = `${countFiles(logosDir)} logos in directory`;
3152
+ }
3153
+ else {
3154
+ logoCount = "Logos directory not found";
3155
+ }
3156
+ }
3157
+ }
3158
+ catch (e) {
3159
+ logoCount = `Error counting: ${e}`;
3160
+ }
3161
+ const diagnostics = `# Logo System Diagnostics
3162
+
3163
+ ## Environment
3164
+ - **Bundled Mode:** ${IS_BUNDLED}
3165
+ - **Assets Path:** ${BUNDLED_ASSETS}
3166
+ - **Assets Exist:** ${fs.existsSync(BUNDLED_ASSETS)}
3167
+ - **Dev Root:** ${DEV_PROJECT_ROOT}
3168
+
3169
+ ## Logo Map Validation
3170
+ - **Status:** ${validation.valid ? "PASS - All logos found" : "FAIL - Some logos missing"}
3171
+ ${validation.errors.length > 0 ? `- **Errors:**\n${validation.errors.map(e => ` - ${e}`).join('\n')}` : "- **Errors:** None"}
3172
+
3173
+ ## Path Resolution Test
3174
+ - **Input:** \`${samplePath}\`
3175
+ - **Resolved:** \`${resolvedSample || "NULL - Resolution failed"}\`
3176
+ - **Exists:** ${resolvedSample ? (fs.existsSync(resolvedSample) ? "Yes" : "No - File not found") : "N/A"}
3177
+
3178
+ ## Logo Inventory
3179
+ - **Count:** ${logoCount}
3180
+ - **Source:** ${IS_BUNDLED ? getAssetPath("logos") : getAssetPath("logos")}
3181
+
3182
+ ## Centralized LOGO_MAP
3183
+ ${Object.entries(LOGO_MAP).map(([key, paths]) => `- **${key}:**
3184
+ - light: \`${paths.light}\`
3185
+ - dark: \`${paths.dark}\``).join('\n')}
3186
+
3187
+ ## Recommendations
3188
+ ${!validation.valid ? `
3189
+ 1. Check that logo files exist at the expected paths
3190
+ 2. Run \`npm run mcp:build\` to rebundle assets if in bundled mode
3191
+ 3. Verify \`public/logos/\` directory structure in development mode
3192
+ ` : "Logo system is functioning correctly. If you still experience issues, check the specific logo path being requested."}
3193
+ `;
3194
+ return {
3195
+ content: [{ type: "text", text: diagnostics }],
3196
+ };
3197
+ }
2914
3198
  case "get_full_library": {
2915
3199
  try {
2916
3200
  let fullLibrary = "# Sonance Component Library\n\n";
@@ -2957,8 +3241,60 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2957
3241
  catch (e) {
2958
3242
  fullLibrary += "*Components directory not found*\n";
2959
3243
  }
3244
+ // Add logo section with implementation guidance
3245
+ fullLibrary += "\n\n---\n\n## 5. Brand Logos\n\n";
3246
+ fullLibrary += "Use these logos in your application. The Sonance+James+IPORT lockup is the default.\n\n";
3247
+ fullLibrary += `### Logo Paths
3248
+ | Variant | Path | Use On |
3249
+ |---------|------|--------|
3250
+ | **Light Backgrounds** | \`/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png\` | White, light gray backgrounds |
3251
+ | **Dark Backgrounds** | \`/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png\` | Charcoal, dark backgrounds |
3252
+
3253
+ ### Implementation (Next.js)
3254
+ \`\`\`tsx
3255
+ import Image from 'next/image';
3256
+
3257
+ // Light mode logo (for light backgrounds)
3258
+ <Image
3259
+ src="/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png"
3260
+ alt="Sonance"
3261
+ width={150}
3262
+ height={40}
3263
+ className="h-10 w-auto dark:hidden"
3264
+ priority
3265
+ />
3266
+
3267
+ // Dark mode logo (for dark backgrounds)
3268
+ <Image
3269
+ src="/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png"
3270
+ alt="Sonance"
3271
+ width={150}
3272
+ height={40}
3273
+ className="h-10 w-auto hidden dark:block"
3274
+ priority
3275
+ />
3276
+ \`\`\`
3277
+
3278
+ ### Sizing Guidelines
3279
+ - Header/Navbar: \`h-8\` to \`h-10\` (32-40px)
3280
+ - Hero Section: \`h-12\` to \`h-16\` (48-64px)
3281
+ - Footer: \`h-6\` to \`h-8\` (24-32px)
3282
+
3283
+ ### Other Brand Logos
3284
+ Use \`list_logos\` to see all available logos, or \`get_logo\` to retrieve a specific logo with implementation code.
3285
+ `;
3286
+ // Build content blocks with embedded logos
3287
+ const contentBlocks = [];
3288
+ // Validate and embed logos
3289
+ validateLogoMap();
3290
+ const lightLogoPath = LOGO_MAP.default.light;
3291
+ const darkLogoPath = LOGO_MAP.default.dark;
3292
+ await embedLogoWithFeedback(lightLogoPath, "Brand Logo (for Light Backgrounds)", contentBlocks);
3293
+ await embedLogoWithFeedback(darkLogoPath, "Brand Logo (for Dark Backgrounds)", contentBlocks);
3294
+ // Add the full library text
3295
+ contentBlocks.push({ type: "text", text: fullLibrary });
2960
3296
  return {
2961
- content: [{ type: "text", text: fullLibrary }],
3297
+ content: contentBlocks,
2962
3298
  };
2963
3299
  }
2964
3300
  catch (e) {
@@ -3000,31 +3336,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3000
3336
  else if (!rawArgs.theme) {
3001
3337
  defaultsUsed.push("theme → Light");
3002
3338
  }
3003
- // Logo path mapping based on preference and theme
3004
- // Light theme = dark logo (for light backgrounds), Dark theme = light logo (for dark backgrounds)
3005
- const logoMap = {
3006
- default: {
3007
- light: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png",
3008
- dark: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png",
3009
- },
3010
- sonance: {
3011
- light: "/logos/sonance/Sonance_Logo_2C_Dark_RGB.png",
3012
- dark: "/logos/sonance/Sonance_Logo_2C_Light_RGB.png",
3013
- },
3014
- iport: {
3015
- light: "/logos/iport/IPORT_Sonance_LockUp_2C_Dark_RGB.png",
3016
- dark: "/logos/iport/IPORT_Sonance_LockUp_2C_Light_RGB.png",
3017
- },
3018
- blaze: {
3019
- light: "/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png",
3020
- dark: "/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png",
3021
- },
3022
- james: {
3023
- light: "/logos/james/James_Logo_Black_RGB.png",
3024
- dark: "/logos/james/James_Logo_Reverse_RGB.png",
3025
- },
3026
- };
3027
- const logoPath = logoMap[logoPreference]?.[theme] || logoMap.default[theme];
3339
+ // Use centralized LOGO_MAP for logo paths
3340
+ const logoPath = LOGO_MAP[logoPreference]?.[theme] || LOGO_MAP.default[theme];
3028
3341
  const logoPreferenceLabel = logoPreference === "default"
3029
3342
  ? "Sonance + James + IPORT Lockup"
3030
3343
  : logoPreference === "james"
@@ -3366,8 +3679,8 @@ const example = "Sonance";
3366
3679
  let response;
3367
3680
  if (isDualThemeMode) {
3368
3681
  // DUAL-THEME MODE: Provide both light and dark tokens for UI
3369
- const lightLogoPath = logoMap[logoPreference]?.light || logoMap.default.light;
3370
- const darkLogoPath = logoMap[logoPreference]?.dark || logoMap.default.dark;
3682
+ const lightLogoPath = LOGO_MAP[logoPreference]?.light || LOGO_MAP.default.light;
3683
+ const darkLogoPath = LOGO_MAP[logoPreference]?.dark || LOGO_MAP.default.dark;
3371
3684
  response = `# Design Context for: ${component_description}
3372
3685
 
3373
3686
  **Brand**: ${brand.toUpperCase()}
@@ -3525,30 +3838,26 @@ ${COMPONENT_CATEGORIES[detectedComponentType]?.slice(0, 8).join(", ") || "See co
3525
3838
 
3526
3839
  Now design the **${component_description}** following these tokens and principles.`;
3527
3840
  }
3528
- // Embed logos in response
3841
+ // Embed logos in response with proper error feedback
3529
3842
  const contentBlocks = [];
3843
+ // Validate logo map on first use
3844
+ validateLogoMap();
3530
3845
  if (isDualThemeMode) {
3531
3846
  // Dual-theme mode: embed both light and dark theme logos
3532
- const lightLogoPath = logoMap[logoPreference]?.light || logoMap.default.light;
3533
- const darkLogoPath = logoMap[logoPreference]?.dark || logoMap.default.dark;
3534
- const lightLogo = await embedLogo(lightLogoPath);
3535
- const darkLogo = await embedLogo(darkLogoPath);
3536
- if (lightLogo) {
3537
- contentBlocks.push({ type: "text", text: `## Logo for Light Backgrounds\n**Path:** \`${lightLogoPath}\`` });
3538
- contentBlocks.push(lightLogo);
3539
- }
3540
- if (darkLogo) {
3541
- contentBlocks.push({ type: "text", text: `## Logo for Dark Backgrounds\n**Path:** \`${darkLogoPath}\`` });
3542
- contentBlocks.push(darkLogo);
3847
+ const lightLogoPath = LOGO_MAP[logoPreference]?.light || LOGO_MAP.default.light;
3848
+ const darkLogoPath = LOGO_MAP[logoPreference]?.dark || LOGO_MAP.default.dark;
3849
+ const lightEmbedded = await embedLogoWithFeedback(lightLogoPath, "Logo for Light Backgrounds", contentBlocks);
3850
+ const darkEmbedded = await embedLogoWithFeedback(darkLogoPath, "Logo for Dark Backgrounds", contentBlocks);
3851
+ if (!lightEmbedded && !darkEmbedded) {
3852
+ contentBlocks.push({
3853
+ type: "text",
3854
+ text: `> **Note:** No logos were embedded. The design guidance below remains valid, but you'll need to manually obtain the logo files. Use \`get_logo\` with paths from \`list_logos\`.`,
3855
+ });
3543
3856
  }
3544
3857
  }
3545
3858
  else {
3546
3859
  // Single-theme mode: embed the appropriate logo
3547
- const embeddedLogo = await embedLogo(logoPath);
3548
- if (embeddedLogo) {
3549
- contentBlocks.push({ type: "text", text: `## Brand Logo (${theme === "light" ? "for Light Backgrounds" : "for Dark Backgrounds"})\n**Path:** \`${logoPath}\`` });
3550
- contentBlocks.push(embeddedLogo);
3551
- }
3860
+ await embedLogoWithFeedback(logoPath, `Brand Logo (${theme === "light" ? "for Light Backgrounds" : "for Dark Backgrounds"})`, contentBlocks);
3552
3861
  }
3553
3862
  // Add the main response text
3554
3863
  contentBlocks.push({ type: "text", text: response });
@@ -4303,8 +4612,8 @@ h2_style.paragraph_format.space_after = Pt(8)
4303
4612
  # ============================================
4304
4613
  # ADD CONTENT
4305
4614
  # ============================================
4306
- # Add logo (if available)
4307
- # doc.add_picture('path/to/logo.png', width=Inches(2))
4615
+ # Add logo - save the embedded logo above to 'logo.png', then:
4616
+ doc.add_picture('logo.png', width=Inches(2))
4308
4617
 
4309
4618
  # Title
4310
4619
  doc.add_paragraph('${templateType === 'proposal' ? 'Project Proposal' : templateType === 'spec-sheet' ? 'Product Specification' : templateType === 'invoice' ? 'Invoice' : 'Report Title'}', style='${palette.name}Title')
@@ -4422,8 +4731,8 @@ doc = SimpleDocTemplate(
4422
4731
  # Content
4423
4732
  content = []
4424
4733
 
4425
- # Logo (optional)
4426
- # content.append(Image('logo.png', width=2*inch, height=0.5*inch))
4734
+ # Logo - save the embedded logo above to 'logo.png', then:
4735
+ content.append(Image('logo.png', width=2*inch, height=0.5*inch))
4427
4736
 
4428
4737
  # Title
4429
4738
  content.append(Paragraph('${templateType === 'proposal' ? 'Project Proposal' : templateType === 'spec-sheet' ? 'Product Specification' : templateType === 'invoice' ? 'Invoice' : 'Report Title'}', title_style))
@@ -4565,8 +4874,32 @@ ${palette.preferredTheme === "dark" ? `- **Note**: ${palette.name} prefers dark
4565
4874
  isError: true,
4566
4875
  };
4567
4876
  }
4877
+ // Embed logos for document templates using centralized LOGO_MAP
4878
+ const templateContentBlocks = [];
4879
+ // Validate logo map on first use
4880
+ validateLogoMap();
4881
+ // Map brand to centralized LOGO_MAP (sonance uses default lockup)
4882
+ const brandLogoKey = targetBrand === "sonance" ? "default" : targetBrand;
4883
+ const lightLogoPath = LOGO_MAP[brandLogoKey]?.light || LOGO_MAP.default.light;
4884
+ const darkLogoPath = LOGO_MAP[brandLogoKey]?.dark || LOGO_MAP.default.dark;
4885
+ // Embed logos with guidance
4886
+ const lightEmbedded = await embedLogoWithFeedback(lightLogoPath, `## Logo for Light Backgrounds\n**Save this image as \`logo_dark.png\` for use in your document**\nUse on white/light backgrounds.`, templateContentBlocks);
4887
+ const darkEmbedded = await embedLogoWithFeedback(darkLogoPath, `## Logo for Dark Backgrounds\n**Save this image as \`logo_light.png\` for use in your document**\nUse on dark/colored backgrounds.`, templateContentBlocks);
4888
+ if (!lightEmbedded && !darkEmbedded) {
4889
+ templateContentBlocks.push({
4890
+ type: "text",
4891
+ text: `> **Logo Note:** Brand logos could not be embedded automatically. Use the \`get_logo\` tool to retrieve ${palette.name} logos.`,
4892
+ });
4893
+ }
4894
+ // Add logo usage instructions
4895
+ templateContentBlocks.push({
4896
+ type: "text",
4897
+ text: `## Logo Usage\n\nTo add the logo to your document:\n1. Save the appropriate logo image above (right-click → Save as)\n2. Use the saved path in your code:\n\n**Word (python-docx):**\n\`\`\`python\ndoc.add_picture('logo_dark.png', width=Inches(2)) # For light backgrounds\n\`\`\`\n\n**PDF (ReportLab):**\n\`\`\`python\ncontent.append(Image('logo_dark.png', width=2*inch, height=0.5*inch))\n\`\`\`\n`,
4898
+ });
4899
+ // Add the main template text
4900
+ templateContentBlocks.push({ type: "text", text: template });
4568
4901
  return {
4569
- content: [{ type: "text", text: template }],
4902
+ content: templateContentBlocks,
4570
4903
  };
4571
4904
  }
4572
4905
  case "rebrand_document": {
@@ -4837,35 +5170,22 @@ ${rebrandArgs.source_format === "word" ? `
4837
5170
 
4838
5171
  **Remember:** The goal is to make the document unmistakably ${palette.name} while preserving its original structure and content.
4839
5172
  `;
4840
- // Embed logos for rebrand_document
4841
- // Use the Sonance+James+IPORT lockup as default for Sonance brand
4842
- const rebrandLogoMap = {
4843
- sonance: {
4844
- light: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png",
4845
- dark: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png",
4846
- },
4847
- iport: {
4848
- light: "/logos/iport/IPORT_Sonance_LockUp_2C_Dark_RGB.png",
4849
- dark: "/logos/iport/IPORT_Sonance_LockUp_2C_Light_RGB.png",
4850
- },
4851
- blaze: {
4852
- light: "/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png",
4853
- dark: "/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png",
4854
- },
4855
- };
5173
+ // Embed logos for rebrand_document using centralized LOGO_MAP
4856
5174
  const rebrandContentBlocks = [];
5175
+ // Validate logo map on first use
5176
+ validateLogoMap();
4857
5177
  // For documents, provide both light and dark logo variants
4858
- const lightLogoPath = rebrandLogoMap[targetBrand].light;
4859
- const darkLogoPath = rebrandLogoMap[targetBrand].dark;
4860
- const lightLogo = await embedLogo(lightLogoPath);
4861
- const darkLogo = await embedLogo(darkLogoPath);
4862
- if (lightLogo) {
4863
- rebrandContentBlocks.push({ type: "text", text: `## Logo for Light Backgrounds\n**Use on white/light document backgrounds**\n**Path:** \`${lightLogoPath}\`` });
4864
- rebrandContentBlocks.push(lightLogo);
4865
- }
4866
- if (darkLogo) {
4867
- rebrandContentBlocks.push({ type: "text", text: `## Logo for Dark Backgrounds\n**Use on dark/colored document backgrounds**\n**Path:** \`${darkLogoPath}\`` });
4868
- rebrandContentBlocks.push(darkLogo);
5178
+ // Map brand to centralized LOGO_MAP (sonance uses default lockup)
5179
+ const brandLogoKey = targetBrand === "sonance" ? "default" : targetBrand;
5180
+ const lightLogoPath = LOGO_MAP[brandLogoKey]?.light || LOGO_MAP.default.light;
5181
+ const darkLogoPath = LOGO_MAP[brandLogoKey]?.dark || LOGO_MAP.default.dark;
5182
+ const lightEmbedded = await embedLogoWithFeedback(lightLogoPath, "Logo for Light Backgrounds\n**Use on white/light document backgrounds**", rebrandContentBlocks);
5183
+ const darkEmbedded = await embedLogoWithFeedback(darkLogoPath, "Logo for Dark Backgrounds\n**Use on dark/colored document backgrounds**", rebrandContentBlocks);
5184
+ if (!lightEmbedded && !darkEmbedded) {
5185
+ rebrandContentBlocks.push({
5186
+ type: "text",
5187
+ text: `> **Logo Note:** Brand logos could not be embedded automatically. Download the appropriate ${palette.name} logo from your brand assets folder or use the \`get_logo\` tool.`,
5188
+ });
4869
5189
  }
4870
5190
  // Add the main guide text
4871
5191
  rebrandContentBlocks.push({ type: "text", text: rebrandGuide });
@@ -5060,31 +5380,21 @@ ${layout.template}
5060
5380
 
5061
5381
  Now you have everything needed to build a ${palette.name}-branded ${appType} application!
5062
5382
  `;
5063
- // Embed logos
5064
- const logoMap = {
5065
- sonance: {
5066
- light: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png",
5067
- dark: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png",
5068
- },
5069
- iport: {
5070
- light: "/logos/iport/IPORT_Sonance_LockUp_2C_Dark_RGB.png",
5071
- dark: "/logos/iport/IPORT_Sonance_LockUp_2C_Light_RGB.png",
5072
- },
5073
- blaze: {
5074
- light: "/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png",
5075
- dark: "/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png",
5076
- },
5077
- };
5383
+ // Embed logos using centralized LOGO_MAP with proper error feedback
5078
5384
  const contentBlocks = [];
5079
- const lightLogo = await embedLogo(logoMap[brand].light);
5080
- const darkLogo = await embedLogo(logoMap[brand].dark);
5081
- if (lightLogo) {
5082
- contentBlocks.push({ type: "text", text: `## Logo for Light Backgrounds\n**Path:** \`${logoMap[brand].light}\`` });
5083
- contentBlocks.push(lightLogo);
5084
- }
5085
- if (darkLogo) {
5086
- contentBlocks.push({ type: "text", text: `## Logo for Dark Backgrounds\n**Path:** \`${logoMap[brand].dark}\`` });
5087
- contentBlocks.push(darkLogo);
5385
+ // Validate logo map on first use
5386
+ validateLogoMap();
5387
+ // Map brand to centralized LOGO_MAP (sonance uses default lockup)
5388
+ const brandLogoKey = brand === "sonance" ? "default" : brand;
5389
+ const lightLogoPath = LOGO_MAP[brandLogoKey]?.light || LOGO_MAP.default.light;
5390
+ const darkLogoPath = LOGO_MAP[brandLogoKey]?.dark || LOGO_MAP.default.dark;
5391
+ const lightEmbedded = await embedLogoWithFeedback(lightLogoPath, "Logo for Light Backgrounds", contentBlocks);
5392
+ const darkEmbedded = await embedLogoWithFeedback(darkLogoPath, "Logo for Dark Backgrounds", contentBlocks);
5393
+ if (!lightEmbedded && !darkEmbedded) {
5394
+ contentBlocks.push({
5395
+ type: "text",
5396
+ text: `> **Logo Note:** Brand logos could not be embedded. Use \`list_logos\` and \`get_logo\` to retrieve logo assets manually.`,
5397
+ });
5088
5398
  }
5089
5399
  contentBlocks.push({ type: "text", text: response });
5090
5400
  return {
@@ -5359,9 +5669,54 @@ className="hover:bg-primary/90 focus-visible:ring-2 focus-visible:ring-primary"
5359
5669
  - [ ] Dark mode support added
5360
5670
  - [ ] Hover states on interactive elements
5361
5671
  - [ ] Focus states for accessibility
5672
+
5673
+ ---
5674
+
5675
+ ## Brand Logos
5676
+
5677
+ Use these logos when adding branding to your application:
5678
+
5679
+ ### Logo Paths
5680
+ | Variant | Path | Use On |
5681
+ |---------|------|--------|
5682
+ | **Light Backgrounds** | \`${LOGO_MAP[brand === "sonance" ? "default" : brand]?.light || LOGO_MAP.default.light}\` | White, light gray |
5683
+ | **Dark Backgrounds** | \`${LOGO_MAP[brand === "sonance" ? "default" : brand]?.dark || LOGO_MAP.default.dark}\` | Charcoal, dark |
5684
+
5685
+ ### Quick Implementation
5686
+ \`\`\`tsx
5687
+ import Image from 'next/image';
5688
+
5689
+ <Image
5690
+ src="${LOGO_MAP[brand === "sonance" ? "default" : brand]?.light || LOGO_MAP.default.light}"
5691
+ alt="${palette.name}"
5692
+ width={150}
5693
+ height={40}
5694
+ className="h-10 w-auto dark:hidden"
5695
+ priority
5696
+ />
5697
+ <Image
5698
+ src="${LOGO_MAP[brand === "sonance" ? "default" : brand]?.dark || LOGO_MAP.default.dark}"
5699
+ alt="${palette.name}"
5700
+ width={150}
5701
+ height={40}
5702
+ className="h-10 w-auto hidden dark:block"
5703
+ priority
5704
+ />
5705
+ \`\`\`
5362
5706
  `;
5707
+ // Build content blocks with embedded logos
5708
+ const contentBlocks = [];
5709
+ // Validate and embed logos for the target brand
5710
+ validateLogoMap();
5711
+ const brandLogoKey = brand === "sonance" ? "default" : brand;
5712
+ const lightLogoPath = LOGO_MAP[brandLogoKey]?.light || LOGO_MAP.default.light;
5713
+ const darkLogoPath = LOGO_MAP[brandLogoKey]?.dark || LOGO_MAP.default.dark;
5714
+ await embedLogoWithFeedback(lightLogoPath, `${palette.name} Logo (for Light Backgrounds)`, contentBlocks);
5715
+ await embedLogoWithFeedback(darkLogoPath, `${palette.name} Logo (for Dark Backgrounds)`, contentBlocks);
5716
+ // Add the analysis text
5717
+ contentBlocks.push({ type: "text", text: response });
5363
5718
  return {
5364
- content: [{ type: "text", text: response }],
5719
+ content: contentBlocks,
5365
5720
  };
5366
5721
  }
5367
5722
  case "analyze_for_redesign": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.3.107",
3
+ "version": "1.3.109",
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",