sonance-brand-mcp 1.3.40 → 1.3.42
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.
|
@@ -589,6 +589,27 @@ export async function POST(request: Request) {
|
|
|
589
589
|
});
|
|
590
590
|
}
|
|
591
591
|
|
|
592
|
+
// ========== SMART CONTEXT BUDGETING ==========
|
|
593
|
+
// Total budget: 100k chars (~25k tokens, safe for Claude)
|
|
594
|
+
// Priority: Recommended file (full) > Page file (limited) > Other components (dynamic)
|
|
595
|
+
const TOTAL_CONTEXT_BUDGET = 100000;
|
|
596
|
+
const MAX_RECOMMENDED_FILE = 50000; // 50k chars max for recommended file
|
|
597
|
+
const MAX_PAGE_FILE = 2000; // Page file is just a wrapper
|
|
598
|
+
const MAX_GLOBALS_CSS = 1500;
|
|
599
|
+
const MAX_FILES = 25;
|
|
600
|
+
|
|
601
|
+
let usedContext = 0;
|
|
602
|
+
|
|
603
|
+
// Extract recommended file from component sources (to show first, avoid duplication)
|
|
604
|
+
let recommendedFileContent: { path: string; content: string } | null = null;
|
|
605
|
+
if (recommendedFile) {
|
|
606
|
+
const idx = pageContext.componentSources.findIndex(c => c.path === recommendedFile.path);
|
|
607
|
+
if (idx !== -1) {
|
|
608
|
+
recommendedFileContent = pageContext.componentSources[idx];
|
|
609
|
+
pageContext.componentSources.splice(idx, 1); // Remove to avoid duplication
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
592
613
|
// Build text content
|
|
593
614
|
let textContent = `VISION MODE EDIT REQUEST
|
|
594
615
|
|
|
@@ -597,64 +618,70 @@ User Request: "${userPrompt}"
|
|
|
597
618
|
|
|
598
619
|
`;
|
|
599
620
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
⚡ RECOMMENDED FILE TO EDIT
|
|
604
|
-
═══════════════════════════════════════════════════════════════════════════════
|
|
605
|
-
|
|
606
|
-
Based on the screenshot analysis, the component you should edit is:
|
|
607
|
-
**${recommendedFile.path}**
|
|
608
|
-
Reason: ${recommendedFile.reason}
|
|
609
|
-
|
|
610
|
-
The page file (${pageContext.pageFile}) is just a wrapper - the actual UI elements are in the component above.
|
|
611
|
-
STRONGLY PREFER editing the recommended file unless you have a specific reason not to.
|
|
621
|
+
if (focusedElements && focusedElements.length > 0) {
|
|
622
|
+
textContent += `FOCUSED ELEMENTS (user clicked on these):
|
|
623
|
+
${focusedElements.map((el) => `- ${el.name} (${el.type}) at (${el.coordinates.x}, ${el.coordinates.y})`).join("\n")}
|
|
612
624
|
|
|
613
625
|
`;
|
|
614
626
|
}
|
|
615
627
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
628
|
+
// ========== TARGET COMPONENT (RECOMMENDED FILE) - SHOWN FIRST ==========
|
|
629
|
+
if (recommendedFileContent) {
|
|
630
|
+
const content = recommendedFileContent.content.length > MAX_RECOMMENDED_FILE
|
|
631
|
+
? recommendedFileContent.content.substring(0, MAX_RECOMMENDED_FILE) + "\n// ... (file truncated at 50k chars, showing component definition and main logic)"
|
|
632
|
+
: recommendedFileContent.content;
|
|
633
|
+
|
|
634
|
+
textContent += `═══════════════════════════════════════════════════════════════════════════════
|
|
635
|
+
⚡ TARGET COMPONENT - YOU MUST EDIT THIS FILE
|
|
636
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
637
|
+
|
|
638
|
+
This is the component that renders the UI you see in the screenshot.
|
|
639
|
+
File: ${recommendedFileContent.path}
|
|
640
|
+
|
|
641
|
+
\`\`\`tsx
|
|
642
|
+
${content}
|
|
643
|
+
\`\`\`
|
|
619
644
|
|
|
620
645
|
`;
|
|
646
|
+
usedContext += content.length;
|
|
647
|
+
debugLog("Added TARGET COMPONENT to context", {
|
|
648
|
+
path: recommendedFileContent.path,
|
|
649
|
+
originalSize: recommendedFileContent.content.length,
|
|
650
|
+
includedSize: content.length
|
|
651
|
+
});
|
|
621
652
|
}
|
|
622
653
|
|
|
623
|
-
|
|
654
|
+
// ========== PAGE CONTEXT (wrapper - de-emphasized) ==========
|
|
655
|
+
const pageContentTruncated = pageContext.pageContent.substring(0, MAX_PAGE_FILE);
|
|
656
|
+
const pageWasTruncated = pageContext.pageContent.length > MAX_PAGE_FILE;
|
|
657
|
+
|
|
658
|
+
textContent += `PAGE CONTEXT (wrapper only${recommendedFileContent ? " - DO NOT edit this, edit the TARGET COMPONENT above" : ""}):
|
|
624
659
|
|
|
625
660
|
Page File: ${pageContext.pageFile || "Not found"}
|
|
626
|
-
${pageContext.pageContent ? `\`\`\`tsx\n${
|
|
661
|
+
${pageContext.pageContent ? `\`\`\`tsx\n${pageContentTruncated}${pageWasTruncated ? "\n// ... (wrapper truncated)" : ""}\n\`\`\`` : ""}
|
|
627
662
|
|
|
628
663
|
`;
|
|
664
|
+
usedContext += pageContentTruncated.length;
|
|
629
665
|
|
|
666
|
+
// ========== SUPPORTING COMPONENTS (dynamic budget) ==========
|
|
630
667
|
if (pageContext.componentSources.length > 0) {
|
|
631
|
-
|
|
632
|
-
const
|
|
633
|
-
const
|
|
634
|
-
const MAX_PER_FILE_SECONDARY = 1500; // Remaining files get less
|
|
635
|
-
const MAX_FILES = 30; // Limit total number of files
|
|
636
|
-
|
|
637
|
-
let usedContext = pageContext.pageContent.length + pageContext.globalsCSS.length;
|
|
638
|
-
const truncatedComponents = pageContext.componentSources.slice(0, MAX_FILES);
|
|
668
|
+
const remainingBudget = TOTAL_CONTEXT_BUDGET - usedContext - MAX_GLOBALS_CSS - 5000; // Reserve 5k for instructions
|
|
669
|
+
const filesToInclude = pageContext.componentSources.slice(0, MAX_FILES);
|
|
670
|
+
const perFileLimit = Math.max(1000, Math.floor(remainingBudget / Math.max(filesToInclude.length, 1)));
|
|
639
671
|
|
|
640
|
-
textContent += `
|
|
672
|
+
textContent += `SUPPORTING COMPONENTS (${filesToInclude.length} files, ~${Math.round(perFileLimit/1000)}k chars each):\n`;
|
|
641
673
|
|
|
642
|
-
for (
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const maxSize = isPriority ? MAX_PER_FILE_PRIORITY : MAX_PER_FILE_SECONDARY;
|
|
646
|
-
|
|
647
|
-
// Stop if we've used too much context
|
|
648
|
-
if (usedContext > MAX_TOTAL_CONTEXT) {
|
|
649
|
-
textContent += `\n// ... (${truncatedComponents.length - i} more files omitted to stay within context limits)\n`;
|
|
674
|
+
for (const comp of filesToInclude) {
|
|
675
|
+
if (usedContext > TOTAL_CONTEXT_BUDGET - 10000) {
|
|
676
|
+
textContent += `\n// ... (remaining files omitted to stay within context limits)\n`;
|
|
650
677
|
break;
|
|
651
678
|
}
|
|
652
679
|
|
|
653
|
-
const truncatedContent = comp.content.substring(0,
|
|
654
|
-
const wasTruncated = comp.content.length >
|
|
680
|
+
const truncatedContent = comp.content.substring(0, perFileLimit);
|
|
681
|
+
const wasTruncated = comp.content.length > perFileLimit;
|
|
655
682
|
|
|
656
683
|
textContent += `
|
|
657
|
-
File: ${comp.path}
|
|
684
|
+
File: ${comp.path}
|
|
658
685
|
\`\`\`tsx
|
|
659
686
|
${truncatedContent}${wasTruncated ? "\n// ... (truncated)" : ""}
|
|
660
687
|
\`\`\`
|
|
@@ -663,25 +690,50 @@ ${truncatedContent}${wasTruncated ? "\n// ... (truncated)" : ""}
|
|
|
663
690
|
}
|
|
664
691
|
}
|
|
665
692
|
|
|
693
|
+
// ========== GLOBALS CSS ==========
|
|
694
|
+
const globalsTruncated = pageContext.globalsCSS.substring(0, MAX_GLOBALS_CSS);
|
|
666
695
|
textContent += `
|
|
667
|
-
GLOBALS.CSS (
|
|
696
|
+
GLOBALS.CSS (theme variables):
|
|
668
697
|
\`\`\`css
|
|
669
|
-
${
|
|
698
|
+
${globalsTruncated}${pageContext.globalsCSS.length > MAX_GLOBALS_CSS ? "\n/* ... (truncated) */" : ""}
|
|
670
699
|
\`\`\`
|
|
671
700
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
701
|
+
`;
|
|
702
|
+
|
|
703
|
+
// ========== VALID FILES LIST ==========
|
|
704
|
+
const validFilesList: string[] = [];
|
|
705
|
+
const recommendedPath = recommendedFile?.path;
|
|
706
|
+
|
|
707
|
+
// Add recommended file first with marker
|
|
708
|
+
if (recommendedPath) {
|
|
709
|
+
validFilesList.push(`- ${recommendedPath} (*** TARGET - EDIT THIS FILE ***)`);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Add page file (if not the recommended file)
|
|
713
|
+
if (pageContext.pageFile && pageContext.pageFile !== recommendedPath) {
|
|
714
|
+
validFilesList.push(`- ${pageContext.pageFile} (wrapper only)`);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Add other component sources (excluding recommended file)
|
|
718
|
+
for (const comp of pageContext.componentSources) {
|
|
719
|
+
if (comp.path !== recommendedPath) {
|
|
720
|
+
validFilesList.push(`- ${comp.path}`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
textContent += `VALID FILES YOU MAY EDIT:
|
|
725
|
+
${validFilesList.join("\n")}
|
|
675
726
|
|
|
676
727
|
INSTRUCTIONS:
|
|
677
728
|
1. Look at the screenshot and identify elements mentioned in the user's request
|
|
678
|
-
2.
|
|
729
|
+
2. The TARGET COMPONENT shown first contains the UI - EDIT THAT FILE
|
|
679
730
|
3. Make SURGICAL EDITS - change only the specific lines needed
|
|
680
731
|
4. PRESERVE all existing logic, hooks, API calls, and error handling
|
|
681
|
-
5. Return the
|
|
732
|
+
5. Return patches in the specified format (search/replace)
|
|
682
733
|
6. Only use file paths from the VALID FILES list above
|
|
734
|
+
7. DO NOT edit the page wrapper unless the TARGET COMPONENT is unavailable
|
|
683
735
|
|
|
684
|
-
CRITICAL:
|
|
736
|
+
CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
685
737
|
|
|
686
738
|
messageContent.push({
|
|
687
739
|
type: "text",
|
|
@@ -599,6 +599,27 @@ export async function POST(request: Request) {
|
|
|
599
599
|
});
|
|
600
600
|
}
|
|
601
601
|
|
|
602
|
+
// ========== SMART CONTEXT BUDGETING ==========
|
|
603
|
+
// Total budget: 100k chars (~25k tokens, safe for Claude)
|
|
604
|
+
// Priority: Recommended file (full) > Page file (limited) > Other components (dynamic)
|
|
605
|
+
const TOTAL_CONTEXT_BUDGET = 100000;
|
|
606
|
+
const MAX_RECOMMENDED_FILE = 50000; // 50k chars max for recommended file
|
|
607
|
+
const MAX_PAGE_FILE = 2000; // Page file is just a wrapper
|
|
608
|
+
const MAX_GLOBALS_CSS = 1500;
|
|
609
|
+
const MAX_FILES = 25;
|
|
610
|
+
|
|
611
|
+
let usedContext = 0;
|
|
612
|
+
|
|
613
|
+
// Extract recommended file from component sources (to show first, avoid duplication)
|
|
614
|
+
let recommendedFileContent: { path: string; content: string } | null = null;
|
|
615
|
+
if (recommendedFile) {
|
|
616
|
+
const idx = pageContext.componentSources.findIndex(c => c.path === recommendedFile.path);
|
|
617
|
+
if (idx !== -1) {
|
|
618
|
+
recommendedFileContent = pageContext.componentSources[idx];
|
|
619
|
+
pageContext.componentSources.splice(idx, 1); // Remove to avoid duplication
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
602
623
|
// Build text content
|
|
603
624
|
let textContent = `VISION MODE EDIT REQUEST
|
|
604
625
|
|
|
@@ -607,64 +628,70 @@ User Request: "${userPrompt}"
|
|
|
607
628
|
|
|
608
629
|
`;
|
|
609
630
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
⚡ RECOMMENDED FILE TO EDIT
|
|
614
|
-
═══════════════════════════════════════════════════════════════════════════════
|
|
615
|
-
|
|
616
|
-
Based on the screenshot analysis, the component you should edit is:
|
|
617
|
-
**${recommendedFile.path}**
|
|
618
|
-
Reason: ${recommendedFile.reason}
|
|
619
|
-
|
|
620
|
-
The page file (${pageContext.pageFile}) is just a wrapper - the actual UI elements are in the component above.
|
|
621
|
-
STRONGLY PREFER editing the recommended file unless you have a specific reason not to.
|
|
631
|
+
if (focusedElements && focusedElements.length > 0) {
|
|
632
|
+
textContent += `FOCUSED ELEMENTS (user clicked on these):
|
|
633
|
+
${focusedElements.map((el) => `- ${el.name} (${el.type}) at (${el.coordinates.x}, ${el.coordinates.y})`).join("\n")}
|
|
622
634
|
|
|
623
635
|
`;
|
|
624
636
|
}
|
|
625
637
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
638
|
+
// ========== TARGET COMPONENT (RECOMMENDED FILE) - SHOWN FIRST ==========
|
|
639
|
+
if (recommendedFileContent) {
|
|
640
|
+
const content = recommendedFileContent.content.length > MAX_RECOMMENDED_FILE
|
|
641
|
+
? recommendedFileContent.content.substring(0, MAX_RECOMMENDED_FILE) + "\n// ... (file truncated at 50k chars, showing component definition and main logic)"
|
|
642
|
+
: recommendedFileContent.content;
|
|
643
|
+
|
|
644
|
+
textContent += `═══════════════════════════════════════════════════════════════════════════════
|
|
645
|
+
⚡ TARGET COMPONENT - YOU MUST EDIT THIS FILE
|
|
646
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
647
|
+
|
|
648
|
+
This is the component that renders the UI you see in the screenshot.
|
|
649
|
+
File: ${recommendedFileContent.path}
|
|
650
|
+
|
|
651
|
+
\`\`\`tsx
|
|
652
|
+
${content}
|
|
653
|
+
\`\`\`
|
|
629
654
|
|
|
630
655
|
`;
|
|
656
|
+
usedContext += content.length;
|
|
657
|
+
debugLog("Added TARGET COMPONENT to context", {
|
|
658
|
+
path: recommendedFileContent.path,
|
|
659
|
+
originalSize: recommendedFileContent.content.length,
|
|
660
|
+
includedSize: content.length
|
|
661
|
+
});
|
|
631
662
|
}
|
|
632
663
|
|
|
633
|
-
|
|
664
|
+
// ========== PAGE CONTEXT (wrapper - de-emphasized) ==========
|
|
665
|
+
const pageContentTruncated = pageContext.pageContent.substring(0, MAX_PAGE_FILE);
|
|
666
|
+
const pageWasTruncated = pageContext.pageContent.length > MAX_PAGE_FILE;
|
|
667
|
+
|
|
668
|
+
textContent += `PAGE CONTEXT (wrapper only${recommendedFileContent ? " - DO NOT edit this, edit the TARGET COMPONENT above" : ""}):
|
|
634
669
|
|
|
635
670
|
Page File: ${pageContext.pageFile || "Not found"}
|
|
636
|
-
${pageContext.pageContent ? `\`\`\`tsx\n${
|
|
671
|
+
${pageContext.pageContent ? `\`\`\`tsx\n${pageContentTruncated}${pageWasTruncated ? "\n// ... (wrapper truncated)" : ""}\n\`\`\`` : ""}
|
|
637
672
|
|
|
638
673
|
`;
|
|
674
|
+
usedContext += pageContentTruncated.length;
|
|
639
675
|
|
|
676
|
+
// ========== SUPPORTING COMPONENTS (dynamic budget) ==========
|
|
640
677
|
if (pageContext.componentSources.length > 0) {
|
|
641
|
-
|
|
642
|
-
const
|
|
643
|
-
const
|
|
644
|
-
const MAX_PER_FILE_SECONDARY = 1500; // Remaining files get less
|
|
645
|
-
const MAX_FILES = 30; // Limit total number of files
|
|
646
|
-
|
|
647
|
-
let usedContext = pageContext.pageContent.length + pageContext.globalsCSS.length;
|
|
648
|
-
const truncatedComponents = pageContext.componentSources.slice(0, MAX_FILES);
|
|
678
|
+
const remainingBudget = TOTAL_CONTEXT_BUDGET - usedContext - MAX_GLOBALS_CSS - 5000; // Reserve 5k for instructions
|
|
679
|
+
const filesToInclude = pageContext.componentSources.slice(0, MAX_FILES);
|
|
680
|
+
const perFileLimit = Math.max(1000, Math.floor(remainingBudget / Math.max(filesToInclude.length, 1)));
|
|
649
681
|
|
|
650
|
-
textContent += `
|
|
682
|
+
textContent += `SUPPORTING COMPONENTS (${filesToInclude.length} files, ~${Math.round(perFileLimit/1000)}k chars each):\n`;
|
|
651
683
|
|
|
652
|
-
for (
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
const maxSize = isPriority ? MAX_PER_FILE_PRIORITY : MAX_PER_FILE_SECONDARY;
|
|
656
|
-
|
|
657
|
-
// Stop if we've used too much context
|
|
658
|
-
if (usedContext > MAX_TOTAL_CONTEXT) {
|
|
659
|
-
textContent += `\n// ... (${truncatedComponents.length - i} more files omitted to stay within context limits)\n`;
|
|
684
|
+
for (const comp of filesToInclude) {
|
|
685
|
+
if (usedContext > TOTAL_CONTEXT_BUDGET - 10000) {
|
|
686
|
+
textContent += `\n// ... (remaining files omitted to stay within context limits)\n`;
|
|
660
687
|
break;
|
|
661
688
|
}
|
|
662
689
|
|
|
663
|
-
const truncatedContent = comp.content.substring(0,
|
|
664
|
-
const wasTruncated = comp.content.length >
|
|
690
|
+
const truncatedContent = comp.content.substring(0, perFileLimit);
|
|
691
|
+
const wasTruncated = comp.content.length > perFileLimit;
|
|
665
692
|
|
|
666
693
|
textContent += `
|
|
667
|
-
File: ${comp.path}
|
|
694
|
+
File: ${comp.path}
|
|
668
695
|
\`\`\`tsx
|
|
669
696
|
${truncatedContent}${wasTruncated ? "\n// ... (truncated)" : ""}
|
|
670
697
|
\`\`\`
|
|
@@ -673,25 +700,50 @@ ${truncatedContent}${wasTruncated ? "\n// ... (truncated)" : ""}
|
|
|
673
700
|
}
|
|
674
701
|
}
|
|
675
702
|
|
|
703
|
+
// ========== GLOBALS CSS ==========
|
|
704
|
+
const globalsTruncated = pageContext.globalsCSS.substring(0, MAX_GLOBALS_CSS);
|
|
676
705
|
textContent += `
|
|
677
|
-
GLOBALS.CSS (
|
|
706
|
+
GLOBALS.CSS (theme variables):
|
|
678
707
|
\`\`\`css
|
|
679
|
-
${
|
|
708
|
+
${globalsTruncated}${pageContext.globalsCSS.length > MAX_GLOBALS_CSS ? "\n/* ... (truncated) */" : ""}
|
|
680
709
|
\`\`\`
|
|
681
710
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
711
|
+
`;
|
|
712
|
+
|
|
713
|
+
// ========== VALID FILES LIST ==========
|
|
714
|
+
const validFilesList: string[] = [];
|
|
715
|
+
const recommendedPath = recommendedFile?.path;
|
|
716
|
+
|
|
717
|
+
// Add recommended file first with marker
|
|
718
|
+
if (recommendedPath) {
|
|
719
|
+
validFilesList.push(`- ${recommendedPath} (*** TARGET - EDIT THIS FILE ***)`);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Add page file (if not the recommended file)
|
|
723
|
+
if (pageContext.pageFile && pageContext.pageFile !== recommendedPath) {
|
|
724
|
+
validFilesList.push(`- ${pageContext.pageFile} (wrapper only)`);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Add other component sources (excluding recommended file)
|
|
728
|
+
for (const comp of pageContext.componentSources) {
|
|
729
|
+
if (comp.path !== recommendedPath) {
|
|
730
|
+
validFilesList.push(`- ${comp.path}`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
textContent += `VALID FILES YOU MAY EDIT:
|
|
735
|
+
${validFilesList.join("\n")}
|
|
685
736
|
|
|
686
737
|
INSTRUCTIONS:
|
|
687
738
|
1. Look at the screenshot and identify elements mentioned in the user's request
|
|
688
|
-
2.
|
|
739
|
+
2. The TARGET COMPONENT shown first contains the UI - EDIT THAT FILE
|
|
689
740
|
3. Choose which of the VALID FILES above need modifications
|
|
690
741
|
4. Generate complete modified code for each file
|
|
691
742
|
5. Provide previewCSS for immediate visual feedback
|
|
692
743
|
6. Return as JSON in the specified format
|
|
744
|
+
7. DO NOT edit the page wrapper unless the TARGET COMPONENT is unavailable
|
|
693
745
|
|
|
694
|
-
CRITICAL:
|
|
746
|
+
CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
695
747
|
|
|
696
748
|
messageContent.push({
|
|
697
749
|
type: "text",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.42",
|
|
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",
|