placeholder-image-mcp 1.0.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 +80 -0
- package/dist/colors.d.ts +22 -0
- package/dist/colors.js +80 -0
- package/dist/generate.d.ts +33 -0
- package/dist/generate.js +122 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +121 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# placeholder-image-mcp
|
|
2
|
+
|
|
3
|
+
Generates PNG placeholder images with colored background and text. Support batch generate.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/RainbowCockroach/placeholder-image-mcp.git
|
|
9
|
+
cd placeholder-image-mcp
|
|
10
|
+
npm install
|
|
11
|
+
npm run build
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Then register it as an MCP server in your agent's config (check the documentation in of your AI agent). Or just ask it to install this MCP for you. It's good at doing that.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
Once connected, just ask your agent to generate placeholder images. It will call the tool automatically.
|
|
19
|
+
|
|
20
|
+
**Examples:**
|
|
21
|
+
|
|
22
|
+
- "Create 64x64 image and save it to desktop"
|
|
23
|
+
- "Create three placeholder banners at 100x100 with text "frog", save to {some folder}"
|
|
24
|
+
- "Create 200x200 pink image, show its dimension"
|
|
25
|
+
|
|
26
|
+
Two calling modes:
|
|
27
|
+
|
|
28
|
+
### Individual images
|
|
29
|
+
|
|
30
|
+
Each image has its own size, text, color, and output path:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"images": [
|
|
35
|
+
{ "width": 800, "height": 600, "text": "ss", "path": "hero.png" },
|
|
36
|
+
{
|
|
37
|
+
"width": 400,
|
|
38
|
+
"height": 300,
|
|
39
|
+
"text": "Frog",
|
|
40
|
+
"color": "#1a874f",
|
|
41
|
+
"path": "frog.png"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Batch (same config, multiple files)
|
|
48
|
+
|
|
49
|
+
One config applied to multiple paths — each file gets a different random color:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"config": { "width": 600, "height": 400, "text": "Yayaya" },
|
|
54
|
+
"paths": ["is-cucumber.png", "fruit.png", "or-veggie.png"]
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Parameters
|
|
59
|
+
|
|
60
|
+
| Parameter | Type | Required | Description |
|
|
61
|
+
| --------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
62
|
+
| `width` | number | yes | Width in pixels (1–8192) |
|
|
63
|
+
| `height` | number | yes | Height in pixels (1–8192) |
|
|
64
|
+
| `text` | string | no | Centered text. Use `"ss"` to show dimensions (e.g. `800×600`). Blank by default. |
|
|
65
|
+
| `color` | string | no | Hex background color (e.g. `#A8D8EA`). Omit for random. |
|
|
66
|
+
| `path` | string | yes | Output path. Relative paths resolve against the `CLAUDE_CWD` env var if set, otherwise the process working directory. |
|
|
67
|
+
|
|
68
|
+
## Color palette
|
|
69
|
+
|
|
70
|
+
30 soft pastel colors chosen to look good as backgrounds:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
#A8D8EA #AA96DA #FCBAD3 #FFFFD2 #B5EAD7 #E2F0CB #C7CEEA
|
|
74
|
+
#FFB7B2 #FFDAC1 #F0E6EF #95B8D1 #DDA0DD #98D8C8 #F7DC6F
|
|
75
|
+
#AED6F1 #D5AAFF #85E3FF #BAFFC9 #FFE156 #FF9AA2 #D4A5A5
|
|
76
|
+
#A0CED9 #FFC75F #C3B1E1 #B4F8C8 #FFE5B4 #E0BBE4 #957DAD
|
|
77
|
+
#D291BC #FEC8D8
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Text is automatically black or white based on WCAG 2.0 contrast ratio.
|
package/dist/colors.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const PALETTE: readonly ["#A8D8EA", "#AA96DA", "#FCBAD3", "#FFFFD2", "#B5EAD7", "#E2F0CB", "#C7CEEA", "#FFB7B2", "#FFDAC1", "#F0E6EF", "#95B8D1", "#DDA0DD", "#98D8C8", "#F7DC6F", "#AED6F1", "#D5AAFF", "#85E3FF", "#BAFFC9", "#FFE156", "#FF9AA2", "#D4A5A5", "#A0CED9", "#FFC75F", "#C3B1E1", "#B4F8C8", "#FFE5B4", "#E0BBE4", "#957DAD", "#D291BC", "#FEC8D8"];
|
|
2
|
+
/**
|
|
3
|
+
* Parse a hex color string to RGB components.
|
|
4
|
+
*/
|
|
5
|
+
export declare function hexToRgb(hex: string): {
|
|
6
|
+
r: number;
|
|
7
|
+
g: number;
|
|
8
|
+
b: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Compute relative luminance per WCAG 2.0.
|
|
12
|
+
* Returns a value between 0 (black) and 1 (white).
|
|
13
|
+
*/
|
|
14
|
+
export declare function relativeLuminance(hex: string): number;
|
|
15
|
+
/**
|
|
16
|
+
* Pick a high-contrast text color (black or white) for the given background.
|
|
17
|
+
*/
|
|
18
|
+
export declare function contrastTextColor(bgHex: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Pick a random color from the palette, optionally excluding certain colors.
|
|
21
|
+
*/
|
|
22
|
+
export declare function randomColor(exclude?: Set<string>): string;
|
package/dist/colors.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PALETTE = void 0;
|
|
4
|
+
exports.hexToRgb = hexToRgb;
|
|
5
|
+
exports.relativeLuminance = relativeLuminance;
|
|
6
|
+
exports.contrastTextColor = contrastTextColor;
|
|
7
|
+
exports.randomColor = randomColor;
|
|
8
|
+
// Curated palette of easy-on-the-eyes colors
|
|
9
|
+
// Muted pastels and soft tones that work well as placeholder backgrounds
|
|
10
|
+
exports.PALETTE = [
|
|
11
|
+
"#A8D8EA", // soft sky blue
|
|
12
|
+
"#AA96DA", // lavender
|
|
13
|
+
"#FCBAD3", // soft pink
|
|
14
|
+
"#FFFFD2", // cream yellow
|
|
15
|
+
"#B5EAD7", // mint green
|
|
16
|
+
"#E2F0CB", // light lime
|
|
17
|
+
"#C7CEEA", // periwinkle
|
|
18
|
+
"#FFB7B2", // salmon pink
|
|
19
|
+
"#FFDAC1", // peach
|
|
20
|
+
"#F0E6EF", // pale mauve
|
|
21
|
+
"#95B8D1", // steel blue
|
|
22
|
+
"#DDA0DD", // plum
|
|
23
|
+
"#98D8C8", // seafoam
|
|
24
|
+
"#F7DC6F", // soft gold
|
|
25
|
+
"#AED6F1", // light cornflower
|
|
26
|
+
"#D5AAFF", // soft violet
|
|
27
|
+
"#85E3FF", // baby blue
|
|
28
|
+
"#BAFFC9", // light mint
|
|
29
|
+
"#FFE156", // warm yellow
|
|
30
|
+
"#FF9AA2", // rose
|
|
31
|
+
"#D4A5A5", // dusty rose
|
|
32
|
+
"#A0CED9", // powder blue
|
|
33
|
+
"#FFC75F", // mango
|
|
34
|
+
"#C3B1E1", // wisteria
|
|
35
|
+
"#B4F8C8", // spring green
|
|
36
|
+
"#FFE5B4", // bisque
|
|
37
|
+
"#E0BBE4", // thistle
|
|
38
|
+
"#957DAD", // muted purple
|
|
39
|
+
"#D291BC", // orchid pink
|
|
40
|
+
"#FEC8D8", // blush
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Parse a hex color string to RGB components.
|
|
44
|
+
*/
|
|
45
|
+
function hexToRgb(hex) {
|
|
46
|
+
const clean = hex.replace("#", "");
|
|
47
|
+
return {
|
|
48
|
+
r: parseInt(clean.substring(0, 2), 16),
|
|
49
|
+
g: parseInt(clean.substring(2, 4), 16),
|
|
50
|
+
b: parseInt(clean.substring(4, 6), 16),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Compute relative luminance per WCAG 2.0.
|
|
55
|
+
* Returns a value between 0 (black) and 1 (white).
|
|
56
|
+
*/
|
|
57
|
+
function relativeLuminance(hex) {
|
|
58
|
+
const { r, g, b } = hexToRgb(hex);
|
|
59
|
+
const [rs, gs, bs] = [r / 255, g / 255, b / 255].map((c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
|
|
60
|
+
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Pick a high-contrast text color (black or white) for the given background.
|
|
64
|
+
*/
|
|
65
|
+
function contrastTextColor(bgHex) {
|
|
66
|
+
return relativeLuminance(bgHex) > 0.4 ? "#222222" : "#FFFFFF";
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Pick a random color from the palette, optionally excluding certain colors.
|
|
70
|
+
*/
|
|
71
|
+
function randomColor(exclude) {
|
|
72
|
+
const available = exclude
|
|
73
|
+
? exports.PALETTE.filter((c) => !exclude.has(c))
|
|
74
|
+
: [...exports.PALETTE];
|
|
75
|
+
if (available.length === 0) {
|
|
76
|
+
// All colors used — reset and allow duplicates
|
|
77
|
+
return exports.PALETTE[Math.floor(Math.random() * exports.PALETTE.length)];
|
|
78
|
+
}
|
|
79
|
+
return available[Math.floor(Math.random() * available.length)];
|
|
80
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface ImageConfig {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
text?: string;
|
|
5
|
+
color?: string;
|
|
6
|
+
path: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Generate a single placeholder image and save it to disk.
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateImage(config: ImageConfig, assignedColor?: string): Promise<{
|
|
12
|
+
path: string;
|
|
13
|
+
color: string;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Generate multiple placeholder images.
|
|
19
|
+
* - If `images` is provided: each entry is a full config.
|
|
20
|
+
* - If `config` + `count` + `paths` is provided: generate `count` images
|
|
21
|
+
* from one config template, each with a different random color.
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateImages(params: {
|
|
24
|
+
images: ImageConfig[];
|
|
25
|
+
} | {
|
|
26
|
+
config: Omit<ImageConfig, "path">;
|
|
27
|
+
paths: string[];
|
|
28
|
+
}): Promise<{
|
|
29
|
+
path: string;
|
|
30
|
+
color: string;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
}[]>;
|
package/dist/generate.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateImage = generateImage;
|
|
7
|
+
exports.generateImages = generateImages;
|
|
8
|
+
const sharp_1 = __importDefault(require("sharp"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const colors_js_1 = require("./colors.js");
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a file path relative to the Claude Code project directory.
|
|
14
|
+
* - If the path is absolute, use it as-is
|
|
15
|
+
* - If relative, resolve it relative to CLAUDE_CWD env var (if set) or process.cwd()
|
|
16
|
+
*/
|
|
17
|
+
function resolvePath(filePath) {
|
|
18
|
+
if (path_1.default.isAbsolute(filePath)) {
|
|
19
|
+
return filePath;
|
|
20
|
+
}
|
|
21
|
+
// Use CLAUDE_CWD if set (for multi-folder workspaces or future enhancements)
|
|
22
|
+
// Otherwise use process.cwd()
|
|
23
|
+
const baseDir = process.env.CLAUDE_CWD || process.cwd();
|
|
24
|
+
return path_1.default.resolve(baseDir, filePath);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Escape special XML characters for safe SVG embedding.
|
|
28
|
+
*/
|
|
29
|
+
function escapeXml(str) {
|
|
30
|
+
return str
|
|
31
|
+
.replace(/&/g, "&")
|
|
32
|
+
.replace(/</g, "<")
|
|
33
|
+
.replace(/>/g, ">")
|
|
34
|
+
.replace(/"/g, """)
|
|
35
|
+
.replace(/'/g, "'");
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build an SVG string for the placeholder image.
|
|
39
|
+
*/
|
|
40
|
+
function buildSvg(width, height, bgColor, text) {
|
|
41
|
+
const textColor = (0, colors_js_1.contrastTextColor)(bgColor);
|
|
42
|
+
// Scale font size relative to image dimensions
|
|
43
|
+
const minDim = Math.min(width, height);
|
|
44
|
+
let fontSize = Math.max(12, Math.floor(minDim / 8));
|
|
45
|
+
// For long text, shrink font to fit within ~80% of width
|
|
46
|
+
if (text.length > 0) {
|
|
47
|
+
const maxTextWidth = width * 0.8;
|
|
48
|
+
const estimatedCharWidth = fontSize * 0.6;
|
|
49
|
+
const estimatedTextWidth = text.length * estimatedCharWidth;
|
|
50
|
+
if (estimatedTextWidth > maxTextWidth) {
|
|
51
|
+
fontSize = Math.max(10, Math.floor(maxTextWidth / (text.length * 0.6)));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
let textElement = "";
|
|
55
|
+
if (text.length > 0) {
|
|
56
|
+
textElement = `
|
|
57
|
+
<text
|
|
58
|
+
x="50%" y="50%"
|
|
59
|
+
dominant-baseline="central"
|
|
60
|
+
text-anchor="middle"
|
|
61
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
62
|
+
font-size="${fontSize}"
|
|
63
|
+
font-weight="600"
|
|
64
|
+
fill="${textColor}"
|
|
65
|
+
>${escapeXml(text)}</text>`;
|
|
66
|
+
}
|
|
67
|
+
return `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
|
|
68
|
+
<rect width="100%" height="100%" fill="${bgColor}"/>
|
|
69
|
+
${textElement}
|
|
70
|
+
</svg>`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Generate a single placeholder image and save it to disk.
|
|
74
|
+
*/
|
|
75
|
+
async function generateImage(config, assignedColor) {
|
|
76
|
+
const bgColor = config.color || assignedColor || (0, colors_js_1.randomColor)();
|
|
77
|
+
// Resolve text
|
|
78
|
+
let displayText = "";
|
|
79
|
+
if (config.text === "ss") {
|
|
80
|
+
displayText = `${config.width}\u00D7${config.height}`;
|
|
81
|
+
}
|
|
82
|
+
else if (config.text !== undefined && config.text !== "") {
|
|
83
|
+
displayText = config.text;
|
|
84
|
+
}
|
|
85
|
+
const svg = buildSvg(config.width, config.height, bgColor, displayText);
|
|
86
|
+
// Ensure output directory exists
|
|
87
|
+
const outputPath = resolvePath(config.path);
|
|
88
|
+
const dir = path_1.default.dirname(outputPath);
|
|
89
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
90
|
+
await (0, sharp_1.default)(Buffer.from(svg)).png().toFile(outputPath);
|
|
91
|
+
return {
|
|
92
|
+
path: outputPath,
|
|
93
|
+
color: bgColor,
|
|
94
|
+
width: config.width,
|
|
95
|
+
height: config.height,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Generate multiple placeholder images.
|
|
100
|
+
* - If `images` is provided: each entry is a full config.
|
|
101
|
+
* - If `config` + `count` + `paths` is provided: generate `count` images
|
|
102
|
+
* from one config template, each with a different random color.
|
|
103
|
+
*/
|
|
104
|
+
async function generateImages(params) {
|
|
105
|
+
const usedColors = new Set();
|
|
106
|
+
if ("images" in params) {
|
|
107
|
+
const results = await Promise.all(params.images.map((img) => {
|
|
108
|
+
const color = img.color || (0, colors_js_1.randomColor)(usedColors);
|
|
109
|
+
usedColors.add(color);
|
|
110
|
+
return generateImage(img, color);
|
|
111
|
+
}));
|
|
112
|
+
return results;
|
|
113
|
+
}
|
|
114
|
+
// Batch mode: one config, multiple paths
|
|
115
|
+
const { config, paths } = params;
|
|
116
|
+
const results = await Promise.all(paths.map((p) => {
|
|
117
|
+
const color = config.color || (0, colors_js_1.randomColor)(usedColors);
|
|
118
|
+
usedColors.add(color);
|
|
119
|
+
return generateImage({ ...config, path: p }, color);
|
|
120
|
+
}));
|
|
121
|
+
return results;
|
|
122
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
|
+
const zod_1 = require("zod");
|
|
7
|
+
const generate_js_1 = require("./generate.js");
|
|
8
|
+
const ImageConfigSchema = zod_1.z.object({
|
|
9
|
+
width: zod_1.z.number().int().min(1).max(8192).describe("Image width in pixels"),
|
|
10
|
+
height: zod_1.z.number().int().min(1).max(8192).describe("Image height in pixels"),
|
|
11
|
+
text: zod_1.z
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe('Text to display centered on image. Use "ss" to show dimensions (e.g. "800×600"). Omit or "" for blank.'),
|
|
15
|
+
color: zod_1.z
|
|
16
|
+
.string()
|
|
17
|
+
.regex(/^#[0-9a-fA-F]{6}$/)
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Background color as hex (e.g. #A8D8EA). Omit for random from curated palette."),
|
|
20
|
+
path: zod_1.z.string().describe("Output file path for the PNG"),
|
|
21
|
+
});
|
|
22
|
+
const server = new mcp_js_1.McpServer({
|
|
23
|
+
name: "placeholder-image",
|
|
24
|
+
version: "1.0.0",
|
|
25
|
+
});
|
|
26
|
+
server.tool("generate_placeholder", "Generate one or more placeholder PNG images with colored backgrounds and optional centered text. Supports two modes: (1) provide an array of individual image configs, or (2) provide a single config template with multiple output paths to generate batch images with different random colors.", {
|
|
27
|
+
images: zod_1.z
|
|
28
|
+
.array(ImageConfigSchema)
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Array of image configurations. Each gets its own size, text, color, and output path."),
|
|
31
|
+
config: zod_1.z
|
|
32
|
+
.object({
|
|
33
|
+
width: zod_1.z
|
|
34
|
+
.number()
|
|
35
|
+
.int()
|
|
36
|
+
.min(1)
|
|
37
|
+
.max(8192)
|
|
38
|
+
.describe("Image width in pixels"),
|
|
39
|
+
height: zod_1.z
|
|
40
|
+
.number()
|
|
41
|
+
.int()
|
|
42
|
+
.min(1)
|
|
43
|
+
.max(8192)
|
|
44
|
+
.describe("Image height in pixels"),
|
|
45
|
+
text: zod_1.z
|
|
46
|
+
.string()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Text to display centered on image. Use "ss" to show dimensions. Omit or "" for blank.'),
|
|
49
|
+
color: zod_1.z
|
|
50
|
+
.string()
|
|
51
|
+
.regex(/^#[0-9a-fA-F]{6}$/)
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Background color as hex. Omit for random (each image gets a different color)."),
|
|
54
|
+
})
|
|
55
|
+
.optional()
|
|
56
|
+
.describe("Single config template for batch mode. Use with `paths` to generate multiple images."),
|
|
57
|
+
paths: zod_1.z
|
|
58
|
+
.array(zod_1.z.string())
|
|
59
|
+
.optional()
|
|
60
|
+
.describe("Output file paths for batch mode. Each path produces one image with a different random color."),
|
|
61
|
+
}, async (params) => {
|
|
62
|
+
try {
|
|
63
|
+
// Validate: must provide either `images` or `config` + `paths`
|
|
64
|
+
if (params.images && params.images.length > 0) {
|
|
65
|
+
const results = await (0, generate_js_1.generateImages)({
|
|
66
|
+
images: params.images,
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `Generated ${results.length} image(s):\n${results
|
|
73
|
+
.map((r) => ` - ${r.path} (${r.width}x${r.height}, ${r.color})`)
|
|
74
|
+
.join("\n")}`,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (params.config && params.paths && params.paths.length > 0) {
|
|
80
|
+
const results = await (0, generate_js_1.generateImages)({
|
|
81
|
+
config: params.config,
|
|
82
|
+
paths: params.paths,
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: `Generated ${results.length} image(s):\n${results
|
|
89
|
+
.map((r) => ` - ${r.path} (${r.width}x${r.height}, ${r.color})`)
|
|
90
|
+
.join("\n")}`,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: "text",
|
|
99
|
+
text: "Error: Provide either `images` (array of configs) or `config` + `paths` (batch mode).",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
isError: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return {
|
|
107
|
+
content: [
|
|
108
|
+
{
|
|
109
|
+
type: "text",
|
|
110
|
+
text: `Error generating images: ${error instanceof Error ? error.message : String(error)}`,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
isError: true,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
async function main() {
|
|
118
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
119
|
+
await server.connect(transport);
|
|
120
|
+
}
|
|
121
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "placeholder-image-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for generating placeholder PNG images",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"placeholder-image-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepare": "npm run build",
|
|
15
|
+
"start": "node dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/RainbowCockroach/placeholder-image-mcp.git"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"placeholder",
|
|
24
|
+
"image",
|
|
25
|
+
"png"
|
|
26
|
+
],
|
|
27
|
+
"author": "RGB Cockroach",
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"type": "commonjs",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/RainbowCockroach/placeholder-image-mcp/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/RainbowCockroach/placeholder-image-mcp#readme",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
36
|
+
"sharp": "^0.34.5",
|
|
37
|
+
"zod": "^4.3.6"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^25.5.0",
|
|
41
|
+
"typescript": "^5.9.3"
|
|
42
|
+
}
|
|
43
|
+
}
|