senku21230 commited on
Commit
ec7423b
·
verified ·
1 Parent(s): 785a0a6

Create server.js

Browse files
Files changed (1) hide show
  1. server.js +285 -0
server.js ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
+ import sharp from 'sharp';
5
+ import { z } from 'zod';
6
+
7
+ const app = express();
8
+ app.use(express.json({ limit: '50mb' }));
9
+
10
+ const PORT = process.env.PORT || 3000;
11
+
12
+ async function urlToBuffer(url) {
13
+ const res = await fetch(url);
14
+ return Buffer.from(await res.arrayBuffer());
15
+ }
16
+
17
+ app.get('/', (req, res) => {
18
+ res.json({ status: 'ok', service: 'sharp-remote-mcp', tools: 30 });
19
+ });
20
+
21
+ app.get('/health', (req, res) => {
22
+ res.json({ status: 'ok' });
23
+ });
24
+
25
+ app.all('/mcp', async (req, res) => {
26
+ const server = new McpServer({ name: 'sharp-mcp', version: '1.0.0' });
27
+
28
+ // ===== FORMAT CONVERSION (৬টা) =====
29
+
30
+ server.tool('to_png', { imageUrl: z.string() }, async ({ imageUrl }) => {
31
+ const buf = await urlToBuffer(imageUrl);
32
+ const out = await sharp(buf).png().toBuffer();
33
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
34
+ });
35
+
36
+ server.tool('to_jpeg', { imageUrl: z.string(), quality: z.number().optional() }, async ({ imageUrl, quality = 85 }) => {
37
+ const buf = await urlToBuffer(imageUrl);
38
+ const out = await sharp(buf).jpeg({ quality }).toBuffer();
39
+ return { content: [{ type: 'text', text: `data:image/jpeg;base64,${out.toString('base64')}` }] };
40
+ });
41
+
42
+ server.tool('to_webp', { imageUrl: z.string(), quality: z.number().optional() }, async ({ imageUrl, quality = 85 }) => {
43
+ const buf = await urlToBuffer(imageUrl);
44
+ const out = await sharp(buf).webp({ quality }).toBuffer();
45
+ return { content: [{ type: 'text', text: `data:image/webp;base64,${out.toString('base64')}` }] };
46
+ });
47
+
48
+ server.tool('to_avif', { imageUrl: z.string(), quality: z.number().optional() }, async ({ imageUrl, quality = 50 }) => {
49
+ const buf = await urlToBuffer(imageUrl);
50
+ const out = await sharp(buf).avif({ quality }).toBuffer();
51
+ return { content: [{ type: 'text', text: `data:image/avif;base64,${out.toString('base64')}` }] };
52
+ });
53
+
54
+ server.tool('to_tiff', { imageUrl: z.string() }, async ({ imageUrl }) => {
55
+ const buf = await urlToBuffer(imageUrl);
56
+ const out = await sharp(buf).tiff().toBuffer();
57
+ return { content: [{ type: 'text', text: `data:image/tiff;base64,${out.toString('base64')}` }] };
58
+ });
59
+
60
+ server.tool('to_gif', { imageUrl: z.string() }, async ({ imageUrl }) => {
61
+ const buf = await urlToBuffer(imageUrl);
62
+ const out = await sharp(buf).gif().toBuffer();
63
+ return { content: [{ type: 'text', text: `data:image/gif;base64,${out.toString('base64')}` }] };
64
+ });
65
+
66
+ // ===== RESIZE & CROP (৪টা) =====
67
+
68
+ server.tool('resize_image', {
69
+ imageUrl: z.string(),
70
+ width: z.number(),
71
+ height: z.number()
72
+ }, async ({ imageUrl, width, height }) => {
73
+ const buf = await urlToBuffer(imageUrl);
74
+ const out = await sharp(buf).resize(width, height, { fit: 'inside' }).toBuffer();
75
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
76
+ });
77
+
78
+ server.tool('crop_image', {
79
+ imageUrl: z.string(),
80
+ left: z.number(),
81
+ top: z.number(),
82
+ width: z.number(),
83
+ height: z.number()
84
+ }, async ({ imageUrl, left, top, width, height }) => {
85
+ const buf = await urlToBuffer(imageUrl);
86
+ const out = await sharp(buf).extract({ left, top, width, height }).toBuffer();
87
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
88
+ });
89
+
90
+ server.tool('extend_image', {
91
+ imageUrl: z.string(),
92
+ top: z.number().optional(),
93
+ bottom: z.number().optional(),
94
+ left: z.number().optional(),
95
+ right: z.number().optional()
96
+ }, async ({ imageUrl, top = 0, bottom = 0, left = 0, right = 0 }) => {
97
+ const buf = await urlToBuffer(imageUrl);
98
+ const out = await sharp(buf).extend({ top, bottom, left, right, background: { r: 255, g: 255, b: 255, alpha: 1 } }).toBuffer();
99
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
100
+ });
101
+
102
+ server.tool('trim_image', { imageUrl: z.string() }, async ({ imageUrl }) => {
103
+ const buf = await urlToBuffer(imageUrl);
104
+ const out = await sharp(buf).trim().toBuffer();
105
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
106
+ });
107
+
108
+ // ===== ROTATION & FLIP (৪টা) =====
109
+
110
+ server.tool('rotate_image', {
111
+ imageUrl: z.string(),
112
+ angle: z.number()
113
+ }, async ({ imageUrl, angle }) => {
114
+ const buf = await urlToBuffer(imageUrl);
115
+ const out = await sharp(buf).rotate(angle).toBuffer();
116
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
117
+ });
118
+
119
+ server.tool('flip_image', { imageUrl: z.string() }, async ({ imageUrl }) => {
120
+ const buf = await urlToBuffer(imageUrl);
121
+ const out = await sharp(buf).flip().toBuffer();
122
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
123
+ });
124
+
125
+ server.tool('flop_image', { imageUrl: z.string() }, async ({ imageUrl }) => {
126
+ const buf = await urlToBuffer(imageUrl);
127
+ const out = await sharp(buf).flop().toBuffer();
128
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
129
+ });
130
+
131
+ server.tool('auto_rotate', { imageUrl: z.string() }, async ({ imageUrl }) => {
132
+ const buf = await urlToBuffer(imageUrl);
133
+ const out = await sharp(buf).rotate().toBuffer();
134
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
135
+ });
136
+
137
+ // ===== COLOR & EFFECTS (৮টা) =====
138
+
139
+ server.tool('grayscale', { imageUrl: z.string() }, async ({ imageUrl }) => {
140
+ const buf = await urlToBuffer(imageUrl);
141
+ const out = await sharp(buf).grayscale().toBuffer();
142
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
143
+ });
144
+
145
+ server.tool('tint_image', {
146
+ imageUrl: z.string(),
147
+ r: z.number(),
148
+ g: z.number(),
149
+ b: z.number()
150
+ }, async ({ imageUrl, r, g, b }) => {
151
+ const buf = await urlToBuffer(imageUrl);
152
+ const out = await sharp(buf).tint({ r, g, b }).toBuffer();
153
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
154
+ });
155
+
156
+ server.tool('blur_image', {
157
+ imageUrl: z.string(),
158
+ sigma: z.number().optional()
159
+ }, async ({ imageUrl, sigma = 3 }) => {
160
+ const buf = await urlToBuffer(imageUrl);
161
+ const out = await sharp(buf).blur(sigma).toBuffer();
162
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
163
+ });
164
+
165
+ server.tool('sharpen_image', {
166
+ imageUrl: z.string(),
167
+ sigma: z.number().optional()
168
+ }, async ({ imageUrl, sigma = 1 }) => {
169
+ const buf = await urlToBuffer(imageUrl);
170
+ const out = await sharp(buf).sharpen({ sigma }).toBuffer();
171
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
172
+ });
173
+
174
+ server.tool('brightness_contrast', {
175
+ imageUrl: z.string(),
176
+ brightness: z.number().optional(),
177
+ contrast: z.number().optional()
178
+ }, async ({ imageUrl, brightness = 1, contrast = 1 }) => {
179
+ const buf = await urlToBuffer(imageUrl);
180
+ const out = await sharp(buf).modulate({ brightness }).linear(contrast, 0).toBuffer();
181
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
182
+ });
183
+
184
+ server.tool('gamma_correction', {
185
+ imageUrl: z.string(),
186
+ gamma: z.number().optional()
187
+ }, async ({ imageUrl, gamma = 2.2 }) => {
188
+ const buf = await urlToBuffer(imageUrl);
189
+ const out = await sharp(buf).gamma(gamma).toBuffer();
190
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
191
+ });
192
+
193
+ server.tool('negate_image', { imageUrl: z.string() }, async ({ imageUrl }) => {
194
+ const buf = await urlToBuffer(imageUrl);
195
+ const out = await sharp(buf).negate().toBuffer();
196
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
197
+ });
198
+
199
+ server.tool('normalize_image', { imageUrl: z.string() }, async ({ imageUrl }) => {
200
+ const buf = await urlToBuffer(imageUrl);
201
+ const out = await sharp(buf).normalize().toBuffer();
202
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
203
+ });
204
+
205
+ // ===== WATERMARK & COMPOSITE (৩টা) =====
206
+
207
+ server.tool('add_watermark', {
208
+ imageUrl: z.string(),
209
+ watermarkUrl: z.string(),
210
+ gravity: z.enum(['centre', 'north', 'south', 'east', 'west', 'northeast', 'northwest', 'southeast', 'southwest']).optional()
211
+ }, async ({ imageUrl, watermarkUrl, gravity = 'southeast' }) => {
212
+ const buf = await urlToBuffer(imageUrl);
213
+ const watermarkBuf = await urlToBuffer(watermarkUrl);
214
+ const out = await sharp(buf).composite([{ input: watermarkBuf, gravity }]).toBuffer();
215
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
216
+ });
217
+
218
+ server.tool('composite_images', {
219
+ baseUrl: z.string(),
220
+ overlayUrl: z.string(),
221
+ left: z.number().optional(),
222
+ top: z.number().optional()
223
+ }, async ({ baseUrl, overlayUrl, left = 0, top = 0 }) => {
224
+ const buf = await urlToBuffer(baseUrl);
225
+ const overlayBuf = await urlToBuffer(overlayUrl);
226
+ const out = await sharp(buf).composite([{ input: overlayBuf, left, top }]).toBuffer();
227
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
228
+ });
229
+
230
+ server.tool('add_text', {
231
+ imageUrl: z.string(),
232
+ text: z.string(),
233
+ fontSize: z.number().optional()
234
+ }, async ({ imageUrl, text, fontSize = 32 }) => {
235
+ const buf = await urlToBuffer(imageUrl);
236
+ const svgText = `<svg><text x="10" y="${fontSize}" font-size="${fontSize}" fill="white">${text}</text></svg>`;
237
+ const out = await sharp(buf).composite([{ input: Buffer.from(svgText), gravity: 'southwest' }]).toBuffer();
238
+ return { content: [{ type: 'text', text: `data:image/png;base64,${out.toString('base64')}` }] };
239
+ });
240
+
241
+ // ===== INFO & METADATA (৩টা) =====
242
+
243
+ server.tool('get_image_info', { imageUrl: z.string() }, async ({ imageUrl }) => {
244
+ const buf = await urlToBuffer(imageUrl);
245
+ const info = await sharp(buf).metadata();
246
+ return { content: [{ type: 'text', text: JSON.stringify(info, null, 2) }] };
247
+ });
248
+
249
+ server.tool('get_metadata', { imageUrl: z.string() }, async ({ imageUrl }) => {
250
+ const buf = await urlToBuffer(imageUrl);
251
+ const metadata = await sharp(buf).metadata();
252
+ return { content: [{ type: 'text', text: JSON.stringify({ width: metadata.width, height: metadata.height, format: metadata.format, size: metadata.size, channels: metadata.channels }, null, 2) }] };
253
+ });
254
+
255
+ server.tool('get_stats', { imageUrl: z.string() }, async ({ imageUrl }) => {
256
+ const buf = await urlToBuffer(imageUrl);
257
+ const stats = await sharp(buf).stats();
258
+ return { content: [{ type: 'text', text: JSON.stringify(stats, null, 2) }] };
259
+ });
260
+
261
+ // ===== COMPRESSION (২টা) =====
262
+
263
+ server.tool('compress_image', {
264
+ imageUrl: z.string(),
265
+ quality: z.number().optional()
266
+ }, async ({ imageUrl, quality = 60 }) => {
267
+ const buf = await urlToBuffer(imageUrl);
268
+ const out = await sharp(buf).webp({ quality }).toBuffer();
269
+ return { content: [{ type: 'text', text: `data:image/webp;base64,${out.toString('base64')}` }] };
270
+ });
271
+
272
+ server.tool('optimize_image', { imageUrl: z.string() }, async ({ imageUrl }) => {
273
+ const buf = await urlToBuffer(imageUrl);
274
+ const out = await sharp(buf).webp({ quality: 80, effort: 6 }).toBuffer();
275
+ return { content: [{ type: 'text', text: `data:image/webp;base64,${out.toString('base64')}` }] };
276
+ });
277
+
278
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
279
+ await server.connect(transport);
280
+ await transport.handleRequest(req, res, req.body);
281
+ });
282
+
283
+ app.listen(PORT, () => {
284
+ console.log(`Sharp Remote MCP running on port ${PORT} — 30 tools ready!`);
285
+ });