sharp-remote-mcp / server.js
senku21230's picture
Create server.js
ec7423b verified
import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import sharp from 'sharp';
import { z } from 'zod';
const app = express();
app.use(express.json({ limit: '50mb' }));
const PORT = process.env.PORT || 3000;
async function urlToBuffer(url) {
const res = await fetch(url);
return Buffer.from(await res.arrayBuffer());
}
app.get('/', (req, res) => {
res.json({ status: 'ok', service: 'sharp-remote-mcp', tools: 30 });
});
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
app.all('/mcp', async (req, res) => {
const server = new McpServer({ name: 'sharp-mcp', version: '1.0.0' });
// ===== FORMAT CONVERSION (৬টা) =====
server.tool('to_png', { imageUrl: z.string() }, async ({ imageUrl }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).png().toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('to_jpeg', { imageUrl: z.string(), quality: z.number().optional() }, async ({ imageUrl, quality = 85 }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).jpeg({ quality }).toBuffer();
return { content: [{ type: 'text', text: `data:image/jpeg;base64,${out.toString('base64')}` }] };
});
server.tool('to_webp', { imageUrl: z.string(), quality: z.number().optional() }, async ({ imageUrl, quality = 85 }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).webp({ quality }).toBuffer();
return { content: [{ type: 'text', text: `data:image/webp;base64,${out.toString('base64')}` }] };
});
server.tool('to_avif', { imageUrl: z.string(), quality: z.number().optional() }, async ({ imageUrl, quality = 50 }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).avif({ quality }).toBuffer();
return { content: [{ type: 'text', text: `data:image/avif;base64,${out.toString('base64')}` }] };
});
server.tool('to_tiff', { imageUrl: z.string() }, async ({ imageUrl }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).tiff().toBuffer();
return { content: [{ type: 'text', text: `data:image/tiff;base64,${out.toString('base64')}` }] };
});
server.tool('to_gif', { imageUrl: z.string() }, async ({ imageUrl }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).gif().toBuffer();
return { content: [{ type: 'text', text: `data:image/gif;base64,${out.toString('base64')}` }] };
});
// ===== RESIZE & CROP (৪টা) =====
server.tool('resize_image', {
imageUrl: z.string(),
width: z.number(),
height: z.number()
}, async ({ imageUrl, width, height }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).resize(width, height, { fit: 'inside' }).toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('crop_image', {
imageUrl: z.string(),
left: z.number(),
top: z.number(),
width: z.number(),
height: z.number()
}, async ({ imageUrl, left, top, width, height }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).extract({ left, top, width, height }).toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('extend_image', {
imageUrl: z.string(),
top: z.number().optional(),
bottom: z.number().optional(),
left: z.number().optional(),
right: z.number().optional()
}, async ({ imageUrl, top = 0, bottom = 0, left = 0, right = 0 }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).extend({ top, bottom, left, right, background: { r: 255, g: 255, b: 255, alpha: 1 } }).toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('trim_image', { imageUrl: z.string() }, async ({ imageUrl }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).trim().toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
// ===== ROTATION & FLIP (৪টা) =====
server.tool('rotate_image', {
imageUrl: z.string(),
angle: z.number()
}, async ({ imageUrl, angle }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).rotate(angle).toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('flip_image', { imageUrl: z.string() }, async ({ imageUrl }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).flip().toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('flop_image', { imageUrl: z.string() }, async ({ imageUrl }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).flop().toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('auto_rotate', { imageUrl: z.string() }, async ({ imageUrl }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).rotate().toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
// ===== COLOR & EFFECTS (৮টা) =====
server.tool('grayscale', { imageUrl: z.string() }, async ({ imageUrl }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).grayscale().toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('tint_image', {
imageUrl: z.string(),
r: z.number(),
g: z.number(),
b: z.number()
}, async ({ imageUrl, r, g, b }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).tint({ r, g, b }).toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('blur_image', {
imageUrl: z.string(),
sigma: z.number().optional()
}, async ({ imageUrl, sigma = 3 }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).blur(sigma).toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('sharpen_image', {
imageUrl: z.string(),
sigma: z.number().optional()
}, async ({ imageUrl, sigma = 1 }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).sharpen({ sigma }).toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('brightness_contrast', {
imageUrl: z.string(),
brightness: z.number().optional(),
contrast: z.number().optional()
}, async ({ imageUrl, brightness = 1, contrast = 1 }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).modulate({ brightness }).linear(contrast, 0).toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('gamma_correction', {
imageUrl: z.string(),
gamma: z.number().optional()
}, async ({ imageUrl, gamma = 2.2 }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).gamma(gamma).toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('negate_image', { imageUrl: z.string() }, async ({ imageUrl }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).negate().toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('normalize_image', { imageUrl: z.string() }, async ({ imageUrl }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).normalize().toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
// ===== WATERMARK & COMPOSITE (৩টা) =====
server.tool('add_watermark', {
imageUrl: z.string(),
watermarkUrl: z.string(),
gravity: z.enum(['centre', 'north', 'south', 'east', 'west', 'northeast', 'northwest', 'southeast', 'southwest']).optional()
}, async ({ imageUrl, watermarkUrl, gravity = 'southeast' }) => {
const buf = await urlToBuffer(imageUrl);
const watermarkBuf = await urlToBuffer(watermarkUrl);
const out = await sharp(buf).composite([{ input: watermarkBuf, gravity }]).toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('composite_images', {
baseUrl: z.string(),
overlayUrl: z.string(),
left: z.number().optional(),
top: z.number().optional()
}, async ({ baseUrl, overlayUrl, left = 0, top = 0 }) => {
const buf = await urlToBuffer(baseUrl);
const overlayBuf = await urlToBuffer(overlayUrl);
const out = await sharp(buf).composite([{ input: overlayBuf, left, top }]).toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
server.tool('add_text', {
imageUrl: z.string(),
text: z.string(),
fontSize: z.number().optional()
}, async ({ imageUrl, text, fontSize = 32 }) => {
const buf = await urlToBuffer(imageUrl);
const svgText = `<svg><text x="10" y="${fontSize}" font-size="${fontSize}" fill="white">${text}</text></svg>`;
const out = await sharp(buf).composite([{ input: Buffer.from(svgText), gravity: 'southwest' }]).toBuffer();
return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
});
// ===== INFO & METADATA (৩টা) =====
server.tool('get_image_info', { imageUrl: z.string() }, async ({ imageUrl }) => {
const buf = await urlToBuffer(imageUrl);
const info = await sharp(buf).metadata();
return { content: [{ type: 'text', text: JSON.stringify(info, null, 2) }] };
});
server.tool('get_metadata', { imageUrl: z.string() }, async ({ imageUrl }) => {
const buf = await urlToBuffer(imageUrl);
const metadata = await sharp(buf).metadata();
return { content: [{ type: 'text', text: JSON.stringify({ width: metadata.width, height: metadata.height, format: metadata.format, size: metadata.size, channels: metadata.channels }, null, 2) }] };
});
server.tool('get_stats', { imageUrl: z.string() }, async ({ imageUrl }) => {
const buf = await urlToBuffer(imageUrl);
const stats = await sharp(buf).stats();
return { content: [{ type: 'text', text: JSON.stringify(stats, null, 2) }] };
});
// ===== COMPRESSION (২টা) =====
server.tool('compress_image', {
imageUrl: z.string(),
quality: z.number().optional()
}, async ({ imageUrl, quality = 60 }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).webp({ quality }).toBuffer();
return { content: [{ type: 'text', text: `data:image/webp;base64,${out.toString('base64')}` }] };
});
server.tool('optimize_image', { imageUrl: z.string() }, async ({ imageUrl }) => {
const buf = await urlToBuffer(imageUrl);
const out = await sharp(buf).webp({ quality: 80, effort: 6 }).toBuffer();
return { content: [{ type: 'text', text: `data:image/webp;base64,${out.toString('base64')}` }] };
});
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
app.listen(PORT, () => {
console.log(`Sharp Remote MCP running on port ${PORT} — 30 tools ready!`);
});