Spaces:
Sleeping
Sleeping
| 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!`); | |
| }); |