sonance-brand-mcp 1.3.116 → 1.3.117
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/index.js +179 -106
- package/dist/utils/response-formatter.d.ts +71 -0
- package/dist/utils/response-formatter.js +152 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import * as os from "os";
|
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
9
|
import { execSync } from "child_process";
|
|
10
10
|
import sharp from "sharp";
|
|
11
|
+
import { formatCodeBlock, } from "./utils/response-formatter.js";
|
|
11
12
|
// ============================================
|
|
12
13
|
// INSTALLER (--init flag)
|
|
13
14
|
// ============================================
|
|
@@ -2915,7 +2916,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2915
2916
|
try {
|
|
2916
2917
|
const content = fs.readFileSync(getAssetPath("css"), "utf-8");
|
|
2917
2918
|
return {
|
|
2918
|
-
content: [{
|
|
2919
|
+
content: [{
|
|
2920
|
+
type: "text",
|
|
2921
|
+
text: `# Sonance Brand Theme
|
|
2922
|
+
|
|
2923
|
+
${formatCodeBlock(content, { language: "css", filename: "globals.css" })}`
|
|
2924
|
+
}],
|
|
2919
2925
|
};
|
|
2920
2926
|
}
|
|
2921
2927
|
catch (e) {
|
|
@@ -2964,10 +2970,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2964
2970
|
const match = files.find(f => f.toLowerCase() === `${componentName}.tsx`);
|
|
2965
2971
|
if (match) {
|
|
2966
2972
|
const content = fs.readFileSync(path.join(componentsDir, match), "utf-8");
|
|
2973
|
+
const matchedName = match.replace('.tsx', '');
|
|
2967
2974
|
return {
|
|
2968
2975
|
content: [{
|
|
2969
2976
|
type: "text",
|
|
2970
|
-
text:
|
|
2977
|
+
text: `# ${matchedName} Component
|
|
2978
|
+
|
|
2979
|
+
${formatCodeBlock(content, { language: "tsx", filename: `components/ui/${match}` })}`
|
|
2971
2980
|
}],
|
|
2972
2981
|
};
|
|
2973
2982
|
}
|
|
@@ -2980,7 +2989,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2980
2989
|
return {
|
|
2981
2990
|
content: [{
|
|
2982
2991
|
type: "text",
|
|
2983
|
-
text:
|
|
2992
|
+
text: `# ${componentName} Component
|
|
2993
|
+
|
|
2994
|
+
${formatCodeBlock(content, { language: "tsx", filename: `components/ui/${componentName}.tsx` })}`
|
|
2984
2995
|
}],
|
|
2985
2996
|
};
|
|
2986
2997
|
}
|
|
@@ -2997,7 +3008,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2997
3008
|
return {
|
|
2998
3009
|
content: [{
|
|
2999
3010
|
type: "text",
|
|
3000
|
-
text:
|
|
3011
|
+
text: `# Sonance UI Utilities
|
|
3012
|
+
|
|
3013
|
+
These utilities are used by Sonance UI components.
|
|
3014
|
+
|
|
3015
|
+
${formatCodeBlock(content, { language: "typescript", filename: "lib/utils.ts" })}`
|
|
3001
3016
|
}],
|
|
3002
3017
|
};
|
|
3003
3018
|
}
|
|
@@ -3280,36 +3295,73 @@ ${!validation.valid ? `
|
|
|
3280
3295
|
}
|
|
3281
3296
|
case "get_full_library": {
|
|
3282
3297
|
try {
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
//
|
|
3286
|
-
|
|
3298
|
+
// Build structured response with multiple content blocks for artifact rendering
|
|
3299
|
+
const contentBlocks = [];
|
|
3300
|
+
// Validate and embed logos first
|
|
3301
|
+
validateLogoMap();
|
|
3302
|
+
const lightLogoPath = LOGO_MAP.default.light;
|
|
3303
|
+
const darkLogoPath = LOGO_MAP.default.dark;
|
|
3304
|
+
await embedLogoWithFeedback(lightLogoPath, "Brand Logo (for Light Backgrounds)", contentBlocks);
|
|
3305
|
+
await embedLogoWithFeedback(darkLogoPath, "Brand Logo (for Dark Backgrounds)", contentBlocks);
|
|
3306
|
+
// Section 1: Overview
|
|
3307
|
+
contentBlocks.push({
|
|
3308
|
+
type: "text",
|
|
3309
|
+
text: `# Sonance Component Library
|
|
3310
|
+
|
|
3311
|
+
This is the complete Sonance UI component library. Copy these files into your project.
|
|
3312
|
+
|
|
3313
|
+
## Table of Contents
|
|
3314
|
+
1. [Brand Guidelines](#brand-guidelines)
|
|
3315
|
+
2. [CSS Theme](#css-theme)
|
|
3316
|
+
3. [Utilities](#utilities)
|
|
3317
|
+
4. [UI Components](#ui-components)
|
|
3318
|
+
5. [Brand Logos](#brand-logos)`
|
|
3319
|
+
});
|
|
3320
|
+
// Section 2: Brand Guidelines
|
|
3321
|
+
let guidelines = "*Brand guidelines not found*";
|
|
3287
3322
|
try {
|
|
3288
|
-
|
|
3323
|
+
guidelines = fs.readFileSync(getAssetPath("guidelines"), "utf-8");
|
|
3289
3324
|
}
|
|
3290
3325
|
catch (e) {
|
|
3291
|
-
|
|
3326
|
+
// Use fallback
|
|
3292
3327
|
}
|
|
3293
|
-
|
|
3294
|
-
|
|
3328
|
+
contentBlocks.push({
|
|
3329
|
+
type: "text",
|
|
3330
|
+
text: `## 1. Brand Guidelines\n\n${guidelines}`
|
|
3331
|
+
});
|
|
3332
|
+
// Section 3: CSS Theme (with filename hint)
|
|
3333
|
+
let cssTheme = "/* CSS theme not found */";
|
|
3295
3334
|
try {
|
|
3296
|
-
|
|
3335
|
+
cssTheme = fs.readFileSync(getAssetPath("css"), "utf-8");
|
|
3297
3336
|
}
|
|
3298
3337
|
catch (e) {
|
|
3299
|
-
|
|
3338
|
+
// Use fallback
|
|
3300
3339
|
}
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3340
|
+
contentBlocks.push({
|
|
3341
|
+
type: "text",
|
|
3342
|
+
text: `## 2. CSS Theme
|
|
3343
|
+
|
|
3344
|
+
${formatCodeBlock(cssTheme, { language: "css", filename: "globals.css" })}`
|
|
3345
|
+
});
|
|
3346
|
+
// Section 4: Utilities (with filename hint)
|
|
3347
|
+
let utils = "// Utils not found";
|
|
3304
3348
|
try {
|
|
3305
|
-
|
|
3349
|
+
utils = fs.readFileSync(getAssetPath("utils"), "utf-8");
|
|
3306
3350
|
}
|
|
3307
3351
|
catch (e) {
|
|
3308
|
-
|
|
3352
|
+
// Use fallback
|
|
3309
3353
|
}
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3354
|
+
contentBlocks.push({
|
|
3355
|
+
type: "text",
|
|
3356
|
+
text: `## 3. Utilities
|
|
3357
|
+
|
|
3358
|
+
${formatCodeBlock(utils, { language: "typescript", filename: "lib/utils.ts" })}`
|
|
3359
|
+
});
|
|
3360
|
+
// Section 5: Components - each as a separate block
|
|
3361
|
+
contentBlocks.push({
|
|
3362
|
+
type: "text",
|
|
3363
|
+
text: `## 4. UI Components`
|
|
3364
|
+
});
|
|
3313
3365
|
try {
|
|
3314
3366
|
const componentsDir = getAssetPath("components");
|
|
3315
3367
|
const files = fs.readdirSync(componentsDir)
|
|
@@ -3318,24 +3370,35 @@ ${!validation.valid ? `
|
|
|
3318
3370
|
for (const file of files) {
|
|
3319
3371
|
const componentName = file.replace('.tsx', '');
|
|
3320
3372
|
const content = fs.readFileSync(path.join(componentsDir, file), "utf-8");
|
|
3321
|
-
|
|
3373
|
+
contentBlocks.push({
|
|
3374
|
+
type: "text",
|
|
3375
|
+
text: `### ${componentName}
|
|
3376
|
+
|
|
3377
|
+
${formatCodeBlock(content, { language: "tsx", filename: `components/ui/${file}` })}`
|
|
3378
|
+
});
|
|
3322
3379
|
}
|
|
3323
3380
|
}
|
|
3324
3381
|
catch (e) {
|
|
3325
|
-
|
|
3382
|
+
contentBlocks.push({
|
|
3383
|
+
type: "text",
|
|
3384
|
+
text: "*Components directory not found*"
|
|
3385
|
+
});
|
|
3326
3386
|
}
|
|
3327
|
-
//
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3387
|
+
// Section 6: Logo guidance
|
|
3388
|
+
contentBlocks.push({
|
|
3389
|
+
type: "text",
|
|
3390
|
+
text: `## 5. Brand Logos
|
|
3391
|
+
|
|
3392
|
+
Use these logos in your application. The Sonance+James+IPORT lockup is the default.
|
|
3393
|
+
|
|
3394
|
+
### Logo Paths
|
|
3331
3395
|
| Variant | Path | Use On |
|
|
3332
3396
|
|---------|------|--------|
|
|
3333
3397
|
| **Light Backgrounds** | \`/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png\` | White, light gray backgrounds |
|
|
3334
3398
|
| **Dark Backgrounds** | \`/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png\` | Charcoal, dark backgrounds |
|
|
3335
3399
|
|
|
3336
3400
|
### Implementation (Next.js)
|
|
3337
|
-
|
|
3338
|
-
import Image from 'next/image';
|
|
3401
|
+
${formatCodeBlock(`import Image from 'next/image';
|
|
3339
3402
|
|
|
3340
3403
|
// Light mode logo (for light backgrounds)
|
|
3341
3404
|
<Image
|
|
@@ -3355,8 +3418,7 @@ import Image from 'next/image';
|
|
|
3355
3418
|
height={40}
|
|
3356
3419
|
className="h-10 w-auto hidden dark:block"
|
|
3357
3420
|
priority
|
|
3358
|
-
|
|
3359
|
-
\`\`\`
|
|
3421
|
+
/>`, { language: "tsx", filename: "components/Logo.tsx" })}
|
|
3360
3422
|
|
|
3361
3423
|
### Sizing Guidelines
|
|
3362
3424
|
- Header/Navbar: \`h-8\` to \`h-10\` (32-40px)
|
|
@@ -3364,18 +3426,8 @@ import Image from 'next/image';
|
|
|
3364
3426
|
- Footer: \`h-6\` to \`h-8\` (24-32px)
|
|
3365
3427
|
|
|
3366
3428
|
### Other Brand Logos
|
|
3367
|
-
Use \`list_logos\` to see all available logos, or \`get_logo\` to retrieve a specific logo with implementation code
|
|
3368
|
-
|
|
3369
|
-
// Build content blocks with embedded logos
|
|
3370
|
-
const contentBlocks = [];
|
|
3371
|
-
// Validate and embed logos
|
|
3372
|
-
validateLogoMap();
|
|
3373
|
-
const lightLogoPath = LOGO_MAP.default.light;
|
|
3374
|
-
const darkLogoPath = LOGO_MAP.default.dark;
|
|
3375
|
-
await embedLogoWithFeedback(lightLogoPath, "Brand Logo (for Light Backgrounds)", contentBlocks);
|
|
3376
|
-
await embedLogoWithFeedback(darkLogoPath, "Brand Logo (for Dark Backgrounds)", contentBlocks);
|
|
3377
|
-
// Add the full library text
|
|
3378
|
-
contentBlocks.push({ type: "text", text: fullLibrary });
|
|
3429
|
+
Use \`list_logos\` to see all available logos, or \`get_logo\` to retrieve a specific logo with implementation code.`
|
|
3430
|
+
});
|
|
3379
3431
|
return {
|
|
3380
3432
|
content: contentBlocks,
|
|
3381
3433
|
};
|
|
@@ -5380,7 +5432,26 @@ npm install @radix-ui/react-slot @radix-ui/react-dialog
|
|
|
5380
5432
|
|
|
5381
5433
|
4. Copy the CSS and component files as described below
|
|
5382
5434
|
`;
|
|
5383
|
-
|
|
5435
|
+
// Build structured response with multiple content blocks for artifact rendering
|
|
5436
|
+
const contentBlocks = [];
|
|
5437
|
+
// Validate logo map on first use
|
|
5438
|
+
validateLogoMap();
|
|
5439
|
+
// Map brand to centralized LOGO_MAP (sonance uses default lockup)
|
|
5440
|
+
const brandLogoKey = brand === "sonance" ? "default" : brand;
|
|
5441
|
+
const lightLogoPath = LOGO_MAP[brandLogoKey]?.light || LOGO_MAP.default.light;
|
|
5442
|
+
const darkLogoPath = LOGO_MAP[brandLogoKey]?.dark || LOGO_MAP.default.dark;
|
|
5443
|
+
const lightEmbedded = await embedLogoWithFeedback(lightLogoPath, "Logo for Light Backgrounds", contentBlocks);
|
|
5444
|
+
const darkEmbedded = await embedLogoWithFeedback(darkLogoPath, "Logo for Dark Backgrounds", contentBlocks);
|
|
5445
|
+
if (!lightEmbedded && !darkEmbedded) {
|
|
5446
|
+
contentBlocks.push({
|
|
5447
|
+
type: "text",
|
|
5448
|
+
text: `> **Logo Note:** Brand logos could not be embedded. Use \`list_logos\` and \`get_logo\` to retrieve logo assets manually.`,
|
|
5449
|
+
});
|
|
5450
|
+
}
|
|
5451
|
+
// Section 1: Overview and Setup Instructions
|
|
5452
|
+
contentBlocks.push({
|
|
5453
|
+
type: "text",
|
|
5454
|
+
text: `# ${palette.name} App Starter Kit
|
|
5384
5455
|
|
|
5385
5456
|
**App Type:** ${appType}
|
|
5386
5457
|
**Framework:** ${framework}
|
|
@@ -5390,37 +5461,39 @@ npm install @radix-ui/react-slot @radix-ui/react-dialog
|
|
|
5390
5461
|
|
|
5391
5462
|
---
|
|
5392
5463
|
|
|
5393
|
-
${setupInstructions}
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
\`\`\`css
|
|
5400
|
-
${cssTheme}
|
|
5401
|
-
\`\`\`
|
|
5402
|
-
|
|
5403
|
-
---
|
|
5404
|
-
|
|
5405
|
-
## Utility Functions (copy to lib/utils.ts)
|
|
5406
|
-
|
|
5407
|
-
\`\`\`typescript
|
|
5408
|
-
${utils}
|
|
5409
|
-
\`\`\`
|
|
5410
|
-
|
|
5411
|
-
---
|
|
5412
|
-
|
|
5413
|
-
## Required Components (${componentSources.length} total)
|
|
5414
|
-
|
|
5415
|
-
${componentSources.map(c => `### ${c.name}.tsx
|
|
5464
|
+
${setupInstructions}`
|
|
5465
|
+
});
|
|
5466
|
+
// Section 2: Global Styles (with filename hint for artifact rendering)
|
|
5467
|
+
contentBlocks.push({
|
|
5468
|
+
type: "text",
|
|
5469
|
+
text: `## Global Styles
|
|
5416
5470
|
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5471
|
+
${formatCodeBlock(cssTheme, { language: "css", filename: "globals.css" })}`
|
|
5472
|
+
});
|
|
5473
|
+
// Section 3: Utility Functions (with filename hint)
|
|
5474
|
+
contentBlocks.push({
|
|
5475
|
+
type: "text",
|
|
5476
|
+
text: `## Utility Functions
|
|
5420
5477
|
|
|
5421
|
-
|
|
5478
|
+
${formatCodeBlock(utils, { language: "typescript", filename: "lib/utils.ts" })}`
|
|
5479
|
+
});
|
|
5480
|
+
// Section 4: Each component as a separate content block
|
|
5481
|
+
contentBlocks.push({
|
|
5482
|
+
type: "text",
|
|
5483
|
+
text: `## Required Components (${componentSources.length} total)`
|
|
5484
|
+
});
|
|
5485
|
+
for (const comp of componentSources) {
|
|
5486
|
+
contentBlocks.push({
|
|
5487
|
+
type: "text",
|
|
5488
|
+
text: `### ${comp.name}.tsx
|
|
5422
5489
|
|
|
5423
|
-
|
|
5490
|
+
${formatCodeBlock(comp.source, { language: "tsx", filename: `components/ui/${comp.name}.tsx` })}`
|
|
5491
|
+
});
|
|
5492
|
+
}
|
|
5493
|
+
// Section 5: Page Template
|
|
5494
|
+
contentBlocks.push({
|
|
5495
|
+
type: "text",
|
|
5496
|
+
text: `## Page Template: ${appType}
|
|
5424
5497
|
|
|
5425
5498
|
${layout.description}
|
|
5426
5499
|
|
|
@@ -5430,13 +5503,12 @@ ${layout.structure}
|
|
|
5430
5503
|
- Grid: \`${layout.gridLayout}\`
|
|
5431
5504
|
- Spacing: \`${layout.spacing}\`
|
|
5432
5505
|
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
## Quick Reference
|
|
5506
|
+
${formatCodeBlock(layout.template, { language: "tsx", filename: "app/page.tsx" })}`
|
|
5507
|
+
});
|
|
5508
|
+
// Section 6: Quick Reference
|
|
5509
|
+
contentBlocks.push({
|
|
5510
|
+
type: "text",
|
|
5511
|
+
text: `## Quick Reference
|
|
5440
5512
|
|
|
5441
5513
|
### ${palette.name} Brand Colors
|
|
5442
5514
|
- **Primary:** ${palette.primary} (bg-primary, text-primary)
|
|
@@ -5461,25 +5533,8 @@ ${layout.template}
|
|
|
5461
5533
|
|
|
5462
5534
|
---
|
|
5463
5535
|
|
|
5464
|
-
Now you have everything needed to build a ${palette.name}-branded ${appType} application
|
|
5465
|
-
|
|
5466
|
-
// Embed logos using centralized LOGO_MAP with proper error feedback
|
|
5467
|
-
const contentBlocks = [];
|
|
5468
|
-
// Validate logo map on first use
|
|
5469
|
-
validateLogoMap();
|
|
5470
|
-
// Map brand to centralized LOGO_MAP (sonance uses default lockup)
|
|
5471
|
-
const brandLogoKey = brand === "sonance" ? "default" : brand;
|
|
5472
|
-
const lightLogoPath = LOGO_MAP[brandLogoKey]?.light || LOGO_MAP.default.light;
|
|
5473
|
-
const darkLogoPath = LOGO_MAP[brandLogoKey]?.dark || LOGO_MAP.default.dark;
|
|
5474
|
-
const lightEmbedded = await embedLogoWithFeedback(lightLogoPath, "Logo for Light Backgrounds", contentBlocks);
|
|
5475
|
-
const darkEmbedded = await embedLogoWithFeedback(darkLogoPath, "Logo for Dark Backgrounds", contentBlocks);
|
|
5476
|
-
if (!lightEmbedded && !darkEmbedded) {
|
|
5477
|
-
contentBlocks.push({
|
|
5478
|
-
type: "text",
|
|
5479
|
-
text: `> **Logo Note:** Brand logos could not be embedded. Use \`list_logos\` and \`get_logo\` to retrieve logo assets manually.`,
|
|
5480
|
-
});
|
|
5481
|
-
}
|
|
5482
|
-
contentBlocks.push({ type: "text", text: response });
|
|
5536
|
+
Now you have everything needed to build a ${palette.name}-branded ${appType} application!`
|
|
5537
|
+
});
|
|
5483
5538
|
return {
|
|
5484
5539
|
content: contentBlocks,
|
|
5485
5540
|
};
|
|
@@ -5957,31 +6012,49 @@ Use \`redesign_app\` tool on individual files for detailed transformation instru
|
|
|
5957
6012
|
}
|
|
5958
6013
|
const components = COMPONENT_CATEGORIES[category];
|
|
5959
6014
|
const componentsDir = getAssetPath("components");
|
|
5960
|
-
|
|
5961
|
-
|
|
6015
|
+
// Build structured response with multiple content blocks
|
|
6016
|
+
const contentBlocks = [];
|
|
6017
|
+
const categoryDescription = category === "forms" ? "building forms and capturing user input" :
|
|
5962
6018
|
category === "navigation" ? "navigation and menus" :
|
|
5963
6019
|
category === "feedback" ? "user feedback and notifications" :
|
|
5964
6020
|
category === "overlays" ? "modals, dialogs, and overlays" :
|
|
5965
6021
|
category === "data-display" ? "displaying data and content" :
|
|
5966
6022
|
category === "layout" ? "page structure and layouts" :
|
|
5967
|
-
"utility purposes"
|
|
6023
|
+
"utility purposes";
|
|
6024
|
+
contentBlocks.push({
|
|
6025
|
+
type: "text",
|
|
6026
|
+
text: `# ${category.charAt(0).toUpperCase() + category.slice(1)} Components (${components.length} components)
|
|
6027
|
+
|
|
6028
|
+
Use these components for ${categoryDescription}.`
|
|
6029
|
+
});
|
|
5968
6030
|
for (const compName of components) {
|
|
5969
6031
|
const filePath = path.join(componentsDir, `${compName}.tsx`);
|
|
5970
6032
|
if (fs.existsSync(filePath)) {
|
|
5971
6033
|
try {
|
|
5972
6034
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
5973
|
-
|
|
6035
|
+
contentBlocks.push({
|
|
6036
|
+
type: "text",
|
|
6037
|
+
text: `## ${compName}
|
|
6038
|
+
|
|
6039
|
+
${formatCodeBlock(content, { language: "tsx", filename: `components/ui/${compName}.tsx` })}`
|
|
6040
|
+
});
|
|
5974
6041
|
}
|
|
5975
6042
|
catch {
|
|
5976
|
-
|
|
6043
|
+
contentBlocks.push({
|
|
6044
|
+
type: "text",
|
|
6045
|
+
text: `## ${compName}\n\n*Could not read component file*`
|
|
6046
|
+
});
|
|
5977
6047
|
}
|
|
5978
6048
|
}
|
|
5979
6049
|
else {
|
|
5980
|
-
|
|
6050
|
+
contentBlocks.push({
|
|
6051
|
+
type: "text",
|
|
6052
|
+
text: `## ${compName}\n\n*Component file not found*`
|
|
6053
|
+
});
|
|
5981
6054
|
}
|
|
5982
6055
|
}
|
|
5983
6056
|
return {
|
|
5984
|
-
content:
|
|
6057
|
+
content: contentBlocks,
|
|
5985
6058
|
};
|
|
5986
6059
|
}
|
|
5987
6060
|
default:
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response formatter utilities for artifact-friendly MCP outputs
|
|
3
|
+
*
|
|
4
|
+
* These utilities help format code blocks with proper language identifiers
|
|
5
|
+
* and filename hints that Claude Desktop can render as artifacts.
|
|
6
|
+
*/
|
|
7
|
+
export interface CodeBlockOptions {
|
|
8
|
+
language: string;
|
|
9
|
+
filename?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface MultiFileSection {
|
|
12
|
+
filename: string;
|
|
13
|
+
content: string;
|
|
14
|
+
language: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Format a code block with proper language identifier and optional filename hint
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* formatCodeBlock(cssContent, { language: 'css', filename: 'globals.css' })
|
|
22
|
+
* // Returns: ```css:globals.css\n{content}\n```
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatCodeBlock(code: string, options: CodeBlockOptions): string;
|
|
25
|
+
/**
|
|
26
|
+
* Format multiple files as separate code blocks with headers
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* formatMultiFile([
|
|
30
|
+
* { filename: 'button.tsx', content: '...', language: 'tsx' },
|
|
31
|
+
* { filename: 'card.tsx', content: '...', language: 'tsx' }
|
|
32
|
+
* ])
|
|
33
|
+
*/
|
|
34
|
+
export declare function formatMultiFile(sections: MultiFileSection[]): string;
|
|
35
|
+
/**
|
|
36
|
+
* Auto-detect the language of code content
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* detectLanguage('import React from "react"') // returns 'tsx'
|
|
40
|
+
* detectLanguage(':root { --color: red; }') // returns 'css'
|
|
41
|
+
*/
|
|
42
|
+
export declare function detectLanguage(content: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Split large content into manageable chunks based on natural boundaries
|
|
45
|
+
*
|
|
46
|
+
* @param content - The content to split
|
|
47
|
+
* @param options - Split options
|
|
48
|
+
* @returns Array of content chunks
|
|
49
|
+
*/
|
|
50
|
+
export declare function splitContent(content: string, options?: {
|
|
51
|
+
maxLines?: number;
|
|
52
|
+
splitOn?: RegExp;
|
|
53
|
+
}): string[];
|
|
54
|
+
/**
|
|
55
|
+
* Generate a table of contents from markdown content
|
|
56
|
+
*
|
|
57
|
+
* @param content - Markdown content with headings
|
|
58
|
+
* @returns Markdown-formatted table of contents
|
|
59
|
+
*/
|
|
60
|
+
export declare function generateTOC(content: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Create a section with header and formatted code block
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* createCodeSection('Global Styles', cssContent, { language: 'css', filename: 'globals.css' })
|
|
66
|
+
*/
|
|
67
|
+
export declare function createCodeSection(title: string, code: string, options: CodeBlockOptions): string;
|
|
68
|
+
/**
|
|
69
|
+
* Get file extension to language mapping
|
|
70
|
+
*/
|
|
71
|
+
export declare function extensionToLanguage(ext: string): string;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response formatter utilities for artifact-friendly MCP outputs
|
|
3
|
+
*
|
|
4
|
+
* These utilities help format code blocks with proper language identifiers
|
|
5
|
+
* and filename hints that Claude Desktop can render as artifacts.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Format a code block with proper language identifier and optional filename hint
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* formatCodeBlock(cssContent, { language: 'css', filename: 'globals.css' })
|
|
12
|
+
* // Returns: ```css:globals.css\n{content}\n```
|
|
13
|
+
*/
|
|
14
|
+
export function formatCodeBlock(code, options) {
|
|
15
|
+
const { language, filename } = options;
|
|
16
|
+
const header = filename ? `${language}:${filename}` : language;
|
|
17
|
+
return `\`\`\`${header}\n${code}\n\`\`\``;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Format multiple files as separate code blocks with headers
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* formatMultiFile([
|
|
24
|
+
* { filename: 'button.tsx', content: '...', language: 'tsx' },
|
|
25
|
+
* { filename: 'card.tsx', content: '...', language: 'tsx' }
|
|
26
|
+
* ])
|
|
27
|
+
*/
|
|
28
|
+
export function formatMultiFile(sections) {
|
|
29
|
+
return sections.map(section => {
|
|
30
|
+
const header = section.description
|
|
31
|
+
? `### ${section.filename}\n\n${section.description}\n\n`
|
|
32
|
+
: `### ${section.filename}\n\n`;
|
|
33
|
+
return header + formatCodeBlock(section.content, {
|
|
34
|
+
language: section.language,
|
|
35
|
+
filename: section.filename
|
|
36
|
+
});
|
|
37
|
+
}).join('\n\n---\n\n');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Auto-detect the language of code content
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* detectLanguage('import React from "react"') // returns 'tsx'
|
|
44
|
+
* detectLanguage(':root { --color: red; }') // returns 'css'
|
|
45
|
+
*/
|
|
46
|
+
export function detectLanguage(content) {
|
|
47
|
+
// React/TSX patterns (check first - most specific)
|
|
48
|
+
if (/^['"]use client['"]|import.*from\s+['"]react['"]|<[A-Z][a-zA-Z]*[\s/>]/m.test(content)) {
|
|
49
|
+
return 'tsx';
|
|
50
|
+
}
|
|
51
|
+
// CSS patterns
|
|
52
|
+
if (/^:root\s*\{|@tailwind|@layer|--[a-z-]+:|@import\s+['"]|@media\s*\(/m.test(content)) {
|
|
53
|
+
return 'css';
|
|
54
|
+
}
|
|
55
|
+
// TypeScript patterns (non-React)
|
|
56
|
+
if (/^interface\s+\w+|^type\s+\w+\s*=|^export\s+type\s+|:\s*(string|number|boolean|void)\s*[;,)]/m.test(content)) {
|
|
57
|
+
return 'typescript';
|
|
58
|
+
}
|
|
59
|
+
// JSON patterns
|
|
60
|
+
if (/^\s*\{[\s\S]*"[^"]+"\s*:/m.test(content)) {
|
|
61
|
+
return 'json';
|
|
62
|
+
}
|
|
63
|
+
// Python patterns
|
|
64
|
+
if (/^from\s+\w+\s+import|^import\s+\w+|^def\s+\w+\s*\(|^class\s+\w+(\s*\(|:)/m.test(content)) {
|
|
65
|
+
return 'python';
|
|
66
|
+
}
|
|
67
|
+
// Bash/Shell patterns
|
|
68
|
+
if (/^#!/m.test(content) || /^\s*(npm|npx|yarn|pnpm|git|cd|mkdir|curl|wget)\s/m.test(content)) {
|
|
69
|
+
return 'bash';
|
|
70
|
+
}
|
|
71
|
+
// HTML patterns
|
|
72
|
+
if (/^<!DOCTYPE|^<html|^<head|^<body/im.test(content)) {
|
|
73
|
+
return 'html';
|
|
74
|
+
}
|
|
75
|
+
// Markdown patterns
|
|
76
|
+
if (/^#{1,6}\s+|^\*{1,2}[^*]|^\[.*\]\(.*\)|^>\s+/m.test(content)) {
|
|
77
|
+
return 'markdown';
|
|
78
|
+
}
|
|
79
|
+
return 'text';
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Split large content into manageable chunks based on natural boundaries
|
|
83
|
+
*
|
|
84
|
+
* @param content - The content to split
|
|
85
|
+
* @param options - Split options
|
|
86
|
+
* @returns Array of content chunks
|
|
87
|
+
*/
|
|
88
|
+
export function splitContent(content, options = {}) {
|
|
89
|
+
const { maxLines = 200, splitOn = /^(?=#{1,3}\s)/m } = options;
|
|
90
|
+
const lines = content.split('\n');
|
|
91
|
+
if (lines.length <= maxLines) {
|
|
92
|
+
return [content];
|
|
93
|
+
}
|
|
94
|
+
// Try to split on natural boundaries (headers)
|
|
95
|
+
const sections = content.split(splitOn).filter(Boolean);
|
|
96
|
+
if (sections.length > 1) {
|
|
97
|
+
return sections;
|
|
98
|
+
}
|
|
99
|
+
// Fall back to line-based splitting
|
|
100
|
+
const chunks = [];
|
|
101
|
+
for (let i = 0; i < lines.length; i += maxLines) {
|
|
102
|
+
chunks.push(lines.slice(i, i + maxLines).join('\n'));
|
|
103
|
+
}
|
|
104
|
+
return chunks;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Generate a table of contents from markdown content
|
|
108
|
+
*
|
|
109
|
+
* @param content - Markdown content with headings
|
|
110
|
+
* @returns Markdown-formatted table of contents
|
|
111
|
+
*/
|
|
112
|
+
export function generateTOC(content) {
|
|
113
|
+
const headings = content.match(/^#{1,3}\s+.+$/gm) || [];
|
|
114
|
+
return headings.map(heading => {
|
|
115
|
+
const level = (heading.match(/^#+/) || [''])[0].length;
|
|
116
|
+
const text = heading.replace(/^#+\s+/, '');
|
|
117
|
+
const anchor = text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-+$/, '');
|
|
118
|
+
const indent = ' '.repeat(level - 1);
|
|
119
|
+
return `${indent}- [${text}](#${anchor})`;
|
|
120
|
+
}).join('\n');
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Create a section with header and formatted code block
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* createCodeSection('Global Styles', cssContent, { language: 'css', filename: 'globals.css' })
|
|
127
|
+
*/
|
|
128
|
+
export function createCodeSection(title, code, options) {
|
|
129
|
+
return `## ${title}\n\n${formatCodeBlock(code, options)}`;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get file extension to language mapping
|
|
133
|
+
*/
|
|
134
|
+
export function extensionToLanguage(ext) {
|
|
135
|
+
const map = {
|
|
136
|
+
'.tsx': 'tsx',
|
|
137
|
+
'.ts': 'typescript',
|
|
138
|
+
'.jsx': 'tsx',
|
|
139
|
+
'.js': 'javascript',
|
|
140
|
+
'.css': 'css',
|
|
141
|
+
'.scss': 'scss',
|
|
142
|
+
'.json': 'json',
|
|
143
|
+
'.md': 'markdown',
|
|
144
|
+
'.py': 'python',
|
|
145
|
+
'.html': 'html',
|
|
146
|
+
'.sh': 'bash',
|
|
147
|
+
'.bash': 'bash',
|
|
148
|
+
'.yaml': 'yaml',
|
|
149
|
+
'.yml': 'yaml',
|
|
150
|
+
};
|
|
151
|
+
return map[ext.toLowerCase()] || 'text';
|
|
152
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.117",
|
|
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",
|