watermark-studio-mcp 1.1.0 → 1.2.0
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/README.md +20 -1
- package/package.json +4 -2
- package/src/imageProcessor.js +252 -0
- package/src/index.js +66 -1
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Watermark & Resize Studio MCP Server 🖼️⚡
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/watermark-studio-mcp)
|
|
3
4
|
[](https://smithery.ai/server/Miladmet/watermark-studio-mcp)
|
|
4
5
|
[](LICENSE)
|
|
5
6
|
[](https://nodejs.org)
|
|
@@ -16,6 +17,7 @@ Official open-source MCP server powered by [Watermark & Resize Studio](https://w
|
|
|
16
17
|
- **⚡ 100% Local Processing**: All operations run locally via `sharp` (libvips C++ engine). Zero cloud uploads, zero external API latency, zero bandwidth costs.
|
|
17
18
|
- **🛍️ E-Commerce & Platform Presets**: Instant batch-ready resizing for **Shopify (2048x2048)**, **Etsy (2000x2000)**, **Instagram Square/Story/Portrait**, **YouTube Thumbnails**, **Facebook**, and **Pinterest**.
|
|
18
19
|
- **🛡️ Copyright & Watermark Protection**: Dynamic text and logo compositing with SVG drop shadows, 6-point positioning, opacity control, and repeating tiled patterns for asset protection.
|
|
20
|
+
- **🖼️ 1200x630 OpenGraph Social Cards**: Generate viral Twitter/LinkedIn/OG preview cards with modern gradient themes (`dark-violet`, `ocean-blue`, `sunset`, `cyber-emerald`), auto text-wrapping, brand pills, and background photo/logo overlays.
|
|
19
21
|
- **🔒 Privacy EXIF / GPS Stripper**: Completely removes GPS geolocation coordinates, camera serial numbers, and creator metadata before public publishing.
|
|
20
22
|
- **🚀 WebP & MozJPEG Compression**: Reduces file sizes by 85–95% while maintaining crisp visual fidelity.
|
|
21
23
|
|
|
@@ -91,6 +93,21 @@ Bulk process an entire directory of photos in parallel: resize with platform pre
|
|
|
91
93
|
| `watermark_position`| `string` | No | `bottom-right`, `bottom-left`, `top-right`, `center` (Default: `bottom-right`). |
|
|
92
94
|
| `max_concurrency` | `number` | No | Parallel worker concurrency (Default: `4`). |
|
|
93
95
|
|
|
96
|
+
### 5. `generate_social_card`
|
|
97
|
+
Generate professional 1200x630 OpenGraph and Twitter preview cards with modern gradients, bold typography, brand pill badges, and optional background photo or logo overlays.
|
|
98
|
+
|
|
99
|
+
| Parameter | Type | Required | Description |
|
|
100
|
+
| :--- | :--- | :--- | :--- |
|
|
101
|
+
| `title` | `string` | **Yes** | Headline text for the social card (e.g., blog post title or product update). |
|
|
102
|
+
| `subtitle` | `string` | No | Supporting subheader or descriptive tagline. |
|
|
103
|
+
| `brand_name` | `string` | No | Brand/category text for top pill badge (Default: `Watermark & Resize Studio`). |
|
|
104
|
+
| `theme` | `string` | No | Gradient scheme: `dark-violet`, `ocean-blue`, `sunset`, `cyber-emerald` (Default: `dark-violet`). |
|
|
105
|
+
| `logo_path` | `string` | No | Path to brand logo PNG/SVG to overlay in top-right. |
|
|
106
|
+
| `background_image_path` | `string` | No | Path to background photo (automatically blurred with dark contrast mask). |
|
|
107
|
+
| `format` | `string` | No | `png`, `webp`, `jpeg` (Default: `png`). |
|
|
108
|
+
| `quality` | `number` | No | Compression quality from 1 to 100 (Default: `90`). |
|
|
109
|
+
| `output_path` | `string` | No | Custom destination path (Defaults to `social-card-[slug].[format]`). |
|
|
110
|
+
|
|
94
111
|
---
|
|
95
112
|
|
|
96
113
|
## 💻 Manual Configuration
|
|
@@ -156,7 +173,9 @@ This verifies:
|
|
|
156
173
|
1. Preset image resizing (Shopify, Instagram, YouTube HD) with WebP compression.
|
|
157
174
|
2. Text, logo, and tiled watermark compositing with SVG drop shadows.
|
|
158
175
|
3. EXIF, GPS, and IPTC privacy sanitization.
|
|
159
|
-
4.
|
|
176
|
+
4. Parallel multi-core batch processing of entire image folders.
|
|
177
|
+
5. 1200x630 OpenGraph social card generation with gradient themes and brand badges.
|
|
178
|
+
6. Live JSON-RPC protocol compliance over `stdio`.
|
|
160
179
|
|
|
161
180
|
---
|
|
162
181
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "watermark-studio-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "High-performance Model Context Protocol (MCP) server for batch image resizing, watermarking,
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "High-performance Model Context Protocol (MCP) server for batch image resizing, watermarking, EXIF privacy stripping, and OpenGraph social card generation by Watermark & Resize Studio.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"watermark-studio-mcp": "src/index.js"
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
"resize",
|
|
27
27
|
"watermark",
|
|
28
28
|
"exif-stripper",
|
|
29
|
+
"opengraph",
|
|
30
|
+
"social-card",
|
|
29
31
|
"shopify",
|
|
30
32
|
"instagram",
|
|
31
33
|
"etsy",
|
package/src/imageProcessor.js
CHANGED
|
@@ -654,3 +654,255 @@ export async function batchProcessFolder(args) {
|
|
|
654
654
|
};
|
|
655
655
|
}
|
|
656
656
|
|
|
657
|
+
/**
|
|
658
|
+
* Helper to wrap text into multiple lines for SVG rendering
|
|
659
|
+
*/
|
|
660
|
+
function wrapLines(text, maxChars = 34) {
|
|
661
|
+
if (!text) return [];
|
|
662
|
+
const words = text.trim().split(/\s+/);
|
|
663
|
+
const lines = [];
|
|
664
|
+
let current = '';
|
|
665
|
+
|
|
666
|
+
for (const word of words) {
|
|
667
|
+
if ((current + ' ' + word).trim().length <= maxChars) {
|
|
668
|
+
current = (current + ' ' + word).trim();
|
|
669
|
+
} else {
|
|
670
|
+
if (current) lines.push(current);
|
|
671
|
+
current = word;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (current) lines.push(current);
|
|
675
|
+
return lines;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const CARD_THEMES = {
|
|
679
|
+
'dark-violet': {
|
|
680
|
+
bg: '#0b0a13',
|
|
681
|
+
glow1: '#a855f7',
|
|
682
|
+
glow2: '#ec4899',
|
|
683
|
+
badgeBg: 'rgba(168, 85, 247, 0.2)',
|
|
684
|
+
badgeBorder: 'rgba(168, 85, 247, 0.45)',
|
|
685
|
+
badgeText: '#d8b4fe',
|
|
686
|
+
accentText: '#c084fc'
|
|
687
|
+
},
|
|
688
|
+
'ocean-blue': {
|
|
689
|
+
bg: '#080f1e',
|
|
690
|
+
glow1: '#3b82f6',
|
|
691
|
+
glow2: '#06b6d4',
|
|
692
|
+
badgeBg: 'rgba(59, 130, 246, 0.2)',
|
|
693
|
+
badgeBorder: 'rgba(59, 130, 246, 0.45)',
|
|
694
|
+
badgeText: '#93c5fd',
|
|
695
|
+
accentText: '#60a5fa'
|
|
696
|
+
},
|
|
697
|
+
'sunset': {
|
|
698
|
+
bg: '#140812',
|
|
699
|
+
glow1: '#f97316',
|
|
700
|
+
glow2: '#f43f5e',
|
|
701
|
+
badgeBg: 'rgba(249, 115, 22, 0.2)',
|
|
702
|
+
badgeBorder: 'rgba(249, 115, 22, 0.45)',
|
|
703
|
+
badgeText: '#fdba74',
|
|
704
|
+
accentText: '#fb923c'
|
|
705
|
+
},
|
|
706
|
+
'cyber-emerald': {
|
|
707
|
+
bg: '#060f11',
|
|
708
|
+
glow1: '#10b981',
|
|
709
|
+
glow2: '#14b8a6',
|
|
710
|
+
badgeBg: 'rgba(16, 185, 129, 0.2)',
|
|
711
|
+
badgeBorder: 'rgba(16, 185, 129, 0.45)',
|
|
712
|
+
badgeText: '#6ee7b7',
|
|
713
|
+
accentText: '#34d399'
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Tool 5: generate_social_card
|
|
719
|
+
* Generate high-converting 1200x630 OpenGraph & Twitter preview share cards.
|
|
720
|
+
*/
|
|
721
|
+
export async function generateSocialCard(args) {
|
|
722
|
+
const {
|
|
723
|
+
title,
|
|
724
|
+
subtitle,
|
|
725
|
+
brand_name = 'Watermark & Resize Studio',
|
|
726
|
+
logo_path,
|
|
727
|
+
background_image_path,
|
|
728
|
+
theme = 'dark-violet',
|
|
729
|
+
format = 'png',
|
|
730
|
+
quality = 90,
|
|
731
|
+
output_path
|
|
732
|
+
} = args;
|
|
733
|
+
|
|
734
|
+
if (!title) {
|
|
735
|
+
throw new Error('title is required to generate a social share card.');
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const selectedTheme = CARD_THEMES[theme] || CARD_THEMES['dark-violet'];
|
|
739
|
+
const safeTitle = escapeXml(title);
|
|
740
|
+
const safeSubtitle = subtitle ? escapeXml(subtitle) : '';
|
|
741
|
+
const safeBrand = escapeXml(brand_name.toUpperCase());
|
|
742
|
+
|
|
743
|
+
// Line wrapping
|
|
744
|
+
const titleCharLimit = safeTitle.length > 50 ? 38 : 32;
|
|
745
|
+
const titleLines = wrapLines(safeTitle, titleCharLimit);
|
|
746
|
+
const subtitleLines = safeSubtitle ? wrapLines(safeSubtitle, 48).slice(0, 2) : [];
|
|
747
|
+
|
|
748
|
+
const titleFontSize = titleLines.length > 3 ? 42 : (titleLines.length > 2 ? 48 : 54);
|
|
749
|
+
const titleLineHeight = Math.round(titleFontSize * 1.25);
|
|
750
|
+
|
|
751
|
+
let titleTspans = '';
|
|
752
|
+
titleLines.forEach((line, index) => {
|
|
753
|
+
const yOffset = index === 0 ? 0 : titleLineHeight;
|
|
754
|
+
titleTspans += `<tspan x="80" dy="${yOffset}">${line}</tspan>`;
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
let subtitleTspans = '';
|
|
758
|
+
subtitleLines.forEach((line, index) => {
|
|
759
|
+
const yOffset = index === 0 ? 0 : 34;
|
|
760
|
+
subtitleTspans += `<tspan x="80" dy="${yOffset}">${line}</tspan>`;
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// Calculate dynamic start Y based on content
|
|
764
|
+
const startY = titleLines.length > 2 ? 240 : 270;
|
|
765
|
+
const subtitleY = startY + (titleLines.length * titleLineHeight) + 25;
|
|
766
|
+
|
|
767
|
+
const svgCard = `
|
|
768
|
+
<svg width="1200" height="630" xmlns="http://www.w3.org/2000/svg">
|
|
769
|
+
<defs>
|
|
770
|
+
<radialGradient id="glow1" cx="15%" cy="20%" r="55%">
|
|
771
|
+
<stop offset="0%" stop-color="${selectedTheme.glow1}" stop-opacity="0.32"/>
|
|
772
|
+
<stop offset="100%" stop-color="${selectedTheme.bg}" stop-opacity="0"/>
|
|
773
|
+
</radialGradient>
|
|
774
|
+
<radialGradient id="glow2" cx="85%" cy="80%" r="60%">
|
|
775
|
+
<stop offset="0%" stop-color="${selectedTheme.glow2}" stop-opacity="0.25"/>
|
|
776
|
+
<stop offset="100%" stop-color="${selectedTheme.bg}" stop-opacity="0"/>
|
|
777
|
+
</radialGradient>
|
|
778
|
+
<filter id="textDrop" x="-10%" y="-10%" width="120%" height="120%">
|
|
779
|
+
<feDropShadow dx="0" dy="4" stdDeviation="6" flood-color="#000000" flood-opacity="0.8"/>
|
|
780
|
+
</filter>
|
|
781
|
+
<linearGradient id="titleGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
782
|
+
<stop offset="0%" stop-color="#ffffff"/>
|
|
783
|
+
<stop offset="100%" stop-color="#f1f5f9"/>
|
|
784
|
+
</linearGradient>
|
|
785
|
+
</defs>
|
|
786
|
+
|
|
787
|
+
<!-- Base Canvas -->
|
|
788
|
+
<rect width="1200" height="630" fill="${selectedTheme.bg}"/>
|
|
789
|
+
<rect width="1200" height="630" fill="url(#glow1)"/>
|
|
790
|
+
<rect width="1200" height="630" fill="url(#glow2)"/>
|
|
791
|
+
|
|
792
|
+
<!-- Inner Border -->
|
|
793
|
+
<rect x="25" y="25" width="1150" height="580" rx="16" fill="none" stroke="rgba(255, 255, 255, 0.08)" stroke-width="1.5"/>
|
|
794
|
+
|
|
795
|
+
<!-- Category / Brand Pill Badge -->
|
|
796
|
+
<g transform="translate(80, 75)">
|
|
797
|
+
<rect width="${Math.max(160, brand_name.length * 11 + 50)}" height="40" rx="20"
|
|
798
|
+
fill="${selectedTheme.badgeBg}" stroke="${selectedTheme.badgeBorder}" stroke-width="1.2"/>
|
|
799
|
+
<text x="22" y="25" font-family="system-ui, -apple-system, sans-serif" font-size="15px" font-weight="700"
|
|
800
|
+
fill="${selectedTheme.badgeText}" letter-spacing="1.2px">⚡ ${safeBrand}</text>
|
|
801
|
+
</g>
|
|
802
|
+
|
|
803
|
+
<!-- Main Headline Title -->
|
|
804
|
+
<text x="80" y="${startY}" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif"
|
|
805
|
+
font-size="${titleFontSize}px" font-weight="800" fill="url(#titleGrad)" filter="url(#textDrop)">
|
|
806
|
+
${titleTspans}
|
|
807
|
+
</text>
|
|
808
|
+
|
|
809
|
+
<!-- Subtitle -->
|
|
810
|
+
${subtitleLines.length > 0 ? `
|
|
811
|
+
<text x="80" y="${subtitleY}" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif"
|
|
812
|
+
font-size="24px" font-weight="500" fill="#94a3b8" filter="url(#textDrop)">
|
|
813
|
+
${subtitleTspans}
|
|
814
|
+
</text>` : ''}
|
|
815
|
+
|
|
816
|
+
<!-- Bottom Meta Bar -->
|
|
817
|
+
<line x1="80" y1="540" x2="1120" y2="540" stroke="rgba(255, 255, 255, 0.1)" stroke-width="1"/>
|
|
818
|
+
<text x="80" y="575" font-family="system-ui, -apple-system, sans-serif" font-size="16px" font-weight="600" fill="${selectedTheme.accentText}">
|
|
819
|
+
watermarkresizestudio.com
|
|
820
|
+
</text>
|
|
821
|
+
<text x="1120" y="575" text-anchor="end" font-family="system-ui, -apple-system, sans-serif" font-size="15px" font-weight="500" fill="#64748b">
|
|
822
|
+
1200 × 630 HD Social Card
|
|
823
|
+
</text>
|
|
824
|
+
</svg>
|
|
825
|
+
`;
|
|
826
|
+
|
|
827
|
+
let pipeline;
|
|
828
|
+
|
|
829
|
+
if (background_image_path) {
|
|
830
|
+
const resolvedBg = path.resolve(background_image_path);
|
|
831
|
+
if (!existsSync(resolvedBg)) {
|
|
832
|
+
throw new Error(`Background image not found at: ${resolvedBg}`);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Resize background image to 1200x630 cover and apply dark mask
|
|
836
|
+
const baseBg = await sharp(resolvedBg)
|
|
837
|
+
.resize(1200, 630, { fit: 'cover' })
|
|
838
|
+
.blur(4)
|
|
839
|
+
.toBuffer();
|
|
840
|
+
|
|
841
|
+
const darkMaskSvg = `
|
|
842
|
+
<svg width="1200" height="630">
|
|
843
|
+
<rect width="1200" height="630" fill="#0b0a13" fill-opacity="0.75"/>
|
|
844
|
+
</svg>
|
|
845
|
+
`;
|
|
846
|
+
|
|
847
|
+
pipeline = sharp(baseBg)
|
|
848
|
+
.composite([
|
|
849
|
+
{ input: Buffer.from(darkMaskSvg), top: 0, left: 0 },
|
|
850
|
+
{ input: Buffer.from(svgCard), top: 0, left: 0 }
|
|
851
|
+
]);
|
|
852
|
+
} else {
|
|
853
|
+
pipeline = sharp(Buffer.from(svgCard));
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Composite optional logo
|
|
857
|
+
if (logo_path) {
|
|
858
|
+
const resolvedLogo = path.resolve(logo_path);
|
|
859
|
+
if (existsSync(resolvedLogo)) {
|
|
860
|
+
const logoBuffer = await sharp(resolvedLogo)
|
|
861
|
+
.resize({ width: 140, height: 70, fit: 'inside', withoutEnlargement: true })
|
|
862
|
+
.toBuffer();
|
|
863
|
+
const logoMeta = await sharp(logoBuffer).metadata();
|
|
864
|
+
const logoLeft = 1120 - logoMeta.width;
|
|
865
|
+
const logoTop = 65;
|
|
866
|
+
|
|
867
|
+
pipeline = pipeline.composite([
|
|
868
|
+
{ input: logoBuffer, left: Math.round(logoLeft), top: Math.round(logoTop) }
|
|
869
|
+
]);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Format selection
|
|
874
|
+
const outFmt = format.toLowerCase();
|
|
875
|
+
const slug = title
|
|
876
|
+
.toLowerCase()
|
|
877
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
878
|
+
.replace(/^-+|-+$/g, '')
|
|
879
|
+
.substring(0, 30);
|
|
880
|
+
|
|
881
|
+
const finalOutputPath = output_path
|
|
882
|
+
? path.resolve(output_path)
|
|
883
|
+
: path.join(process.cwd(), `social-card-${slug || 'preview'}.${outFmt === 'jpeg' ? 'jpg' : outFmt}`);
|
|
884
|
+
|
|
885
|
+
if (outFmt === 'webp') {
|
|
886
|
+
pipeline = pipeline.webp({ quality });
|
|
887
|
+
} else if (outFmt === 'jpeg' || outFmt === 'jpg') {
|
|
888
|
+
pipeline = pipeline.jpeg({ quality, mozjpeg: true });
|
|
889
|
+
} else {
|
|
890
|
+
pipeline = pipeline.png({ compressionLevel: 8 });
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
await pipeline.toFile(finalOutputPath);
|
|
894
|
+
const outStats = await fs.stat(finalOutputPath);
|
|
895
|
+
|
|
896
|
+
return {
|
|
897
|
+
success: true,
|
|
898
|
+
title,
|
|
899
|
+
theme,
|
|
900
|
+
dimensions: '1200x630',
|
|
901
|
+
output_file: finalOutputPath,
|
|
902
|
+
format: outFmt,
|
|
903
|
+
file_size: formatBytes(outStats.size),
|
|
904
|
+
attribution: '🎨 Generated with Watermark & Resize Studio Social Card Engine (https://watermarkresizestudio.com)'
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
|
package/src/index.js
CHANGED
|
@@ -13,13 +13,14 @@ import {
|
|
|
13
13
|
applyWatermark,
|
|
14
14
|
stripPhotoMetadata,
|
|
15
15
|
batchProcessFolder,
|
|
16
|
+
generateSocialCard,
|
|
16
17
|
PLATFORM_PRESETS
|
|
17
18
|
} from './imageProcessor.js';
|
|
18
19
|
|
|
19
20
|
const server = new Server(
|
|
20
21
|
{
|
|
21
22
|
name: 'watermark-studio-mcp',
|
|
22
|
-
version: '1.
|
|
23
|
+
version: '1.2.0',
|
|
23
24
|
},
|
|
24
25
|
{
|
|
25
26
|
capabilities: {
|
|
@@ -246,6 +247,58 @@ const TOOLS = [
|
|
|
246
247
|
required: ['folder_path'],
|
|
247
248
|
},
|
|
248
249
|
},
|
|
250
|
+
{
|
|
251
|
+
name: 'generate_social_card',
|
|
252
|
+
description: 'Generate high-converting 1200x630 OpenGraph and Twitter preview share cards with modern gradients, bold typography, brand pill badges, and optional background photo or logo overlays.',
|
|
253
|
+
inputSchema: {
|
|
254
|
+
type: 'object',
|
|
255
|
+
properties: {
|
|
256
|
+
title: {
|
|
257
|
+
type: 'string',
|
|
258
|
+
description: 'Headline text for the social card (e.g. blog title, product update, announcement).',
|
|
259
|
+
},
|
|
260
|
+
subtitle: {
|
|
261
|
+
type: 'string',
|
|
262
|
+
description: 'Optional secondary description or tagline.',
|
|
263
|
+
},
|
|
264
|
+
brand_name: {
|
|
265
|
+
type: 'string',
|
|
266
|
+
description: 'Brand or category name displayed in the top pill badge (default: "Watermark & Resize Studio").',
|
|
267
|
+
default: 'Watermark & Resize Studio'
|
|
268
|
+
},
|
|
269
|
+
theme: {
|
|
270
|
+
type: 'string',
|
|
271
|
+
enum: ['dark-violet', 'ocean-blue', 'sunset', 'cyber-emerald'],
|
|
272
|
+
description: 'Gradient color scheme for the card. Default is dark-violet.',
|
|
273
|
+
default: 'dark-violet'
|
|
274
|
+
},
|
|
275
|
+
logo_path: {
|
|
276
|
+
type: 'string',
|
|
277
|
+
description: 'Optional path to a PNG/SVG logo to position in the top-right corner.',
|
|
278
|
+
},
|
|
279
|
+
background_image_path: {
|
|
280
|
+
type: 'string',
|
|
281
|
+
description: 'Optional path to a background photo to use behind the card (blurred and masked with dark overlay).',
|
|
282
|
+
},
|
|
283
|
+
format: {
|
|
284
|
+
type: 'string',
|
|
285
|
+
enum: ['png', 'webp', 'jpeg'],
|
|
286
|
+
description: 'Output image format. Default is png.',
|
|
287
|
+
default: 'png'
|
|
288
|
+
},
|
|
289
|
+
quality: {
|
|
290
|
+
type: 'number',
|
|
291
|
+
description: 'Image quality level (1 to 100). Default is 90.',
|
|
292
|
+
default: 90
|
|
293
|
+
},
|
|
294
|
+
output_path: {
|
|
295
|
+
type: 'string',
|
|
296
|
+
description: 'Optional output file path. Defaults to social-card-[slug].[format] in current directory.',
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
required: ['title'],
|
|
300
|
+
},
|
|
301
|
+
},
|
|
249
302
|
];
|
|
250
303
|
|
|
251
304
|
// Handle listing tools
|
|
@@ -309,6 +362,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
309
362
|
};
|
|
310
363
|
}
|
|
311
364
|
|
|
365
|
+
case 'generate_social_card': {
|
|
366
|
+
const result = await generateSocialCard(args);
|
|
367
|
+
return {
|
|
368
|
+
content: [
|
|
369
|
+
{
|
|
370
|
+
type: 'text',
|
|
371
|
+
text: JSON.stringify(result, null, 2),
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
312
377
|
default:
|
|
313
378
|
throw new McpError(
|
|
314
379
|
ErrorCode.MethodNotFound,
|