@c5t8fbt-wy/mcp-image-extractor 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ifmelate
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,263 @@
1
+ # MCP Image Extractor (Enhanced Fork)
2
+
3
+ Enhanced MCP server for extracting and converting images to base64 for LLM analysis with **configurable quality settings**.
4
+
5
+ ## What's New in This Fork
6
+
7
+ - **Configurable Processing** - Control image dimensions and quality via environment variables
8
+ - **Increased Limits** - 50MB default download limit (vs 10MB in original)
9
+ - **Better Documentation** - Comprehensive guides for optimization
10
+ - **Active Maintenance** - Regular updates and improvements
11
+
12
+ ## Features
13
+
14
+ This MCP server provides tools for AI assistants to:
15
+ - Extract images from local files
16
+ - Extract images from URLs
17
+ - Process base64-encoded images
18
+ - **NEW**: Configurable dimensions (512×512 to 1568×1568)
19
+ - **NEW**: Adjustable compression quality
20
+ - **NEW**: Environment variable configuration
21
+
22
+ ## Installation
23
+
24
+ ### Recommended: Using npx (Easiest)
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "image-extractor": {
30
+ "command": "npx",
31
+ "args": ["-y", "@c5t8fbt-wy/mcp-image-extractor"],
32
+ "env": {
33
+ "DEFAULT_MAX_WIDTH": "1024",
34
+ "DEFAULT_MAX_HEIGHT": "1024",
35
+ "COMPRESSION_QUALITY": "90"
36
+ }
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ### Alternative: Local Installation
43
+
44
+ ```bash
45
+ # Clone your fork
46
+ git clone https://github.com/C5T8fBt-WY/mcp-image-extractor_fork.git
47
+ cd mcp-image-extractor_fork
48
+ npm install
49
+ npm run build
50
+ ```
51
+
52
+ Then configure:
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "image-extractor": {
58
+ "command": "node",
59
+ "args": ["c:/path/to/mcp-image-extractor_fork/dist/index.js"],
60
+ "env": {
61
+ "DEFAULT_MAX_WIDTH": "1024",
62
+ "COMPRESSION_QUALITY": "90"
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ## Configuration Options
70
+
71
+ ### Environment Variables
72
+
73
+ | Variable | Default | Description |
74
+ | ----------------------- | ---------- | --------------------------------- |
75
+ | `DEFAULT_MAX_WIDTH` | `512` | Maximum image width (pixels) |
76
+ | `DEFAULT_MAX_HEIGHT` | `512` | Maximum image height (pixels) |
77
+ | `COMPRESSION_QUALITY` | `80` | JPEG/WebP quality (1-100) |
78
+ | `PNG_COMPRESSION_LEVEL` | `9` | PNG compression (0-9, lossless) |
79
+ | `MAX_IMAGE_SIZE` | `52428800` | Max download size (50MB in bytes) |
80
+ | `ALLOWED_DOMAINS` | Empty | Comma-separated allowed domains |
81
+
82
+ ### Quality Presets
83
+
84
+ #### Minimal Context Usage (Default)
85
+ ```json
86
+ "env": {
87
+ "DEFAULT_MAX_WIDTH": "512",
88
+ "DEFAULT_MAX_HEIGHT": "512",
89
+ "COMPRESSION_QUALITY": "80"
90
+ }
91
+ ```
92
+ **Best for:** Processing many images, limited context
93
+
94
+ #### Balanced Quality
95
+ ```json
96
+ "env": {
97
+ "DEFAULT_MAX_WIDTH": "768",
98
+ "DEFAULT_MAX_HEIGHT": "768",
99
+ "COMPRESSION_QUALITY": "85"
100
+ }
101
+ ```
102
+ **Best for:** Reading small text, charts, diagrams
103
+
104
+ #### Maximum Quality (Claude-compatible)
105
+ ```json
106
+ "env": {
107
+ "DEFAULT_MAX_WIDTH": "1568",
108
+ "DEFAULT_MAX_HEIGHT": "1568",
109
+ "COMPRESSION_QUALITY": "90",
110
+ "MAX_IMAGE_SIZE": "104857600"
111
+ }
112
+ ```
113
+ **Best for:** Detailed screenshots, technical documentation
114
+
115
+ ## Available Tools
116
+
117
+ ### extract_image_from_file
118
+
119
+ Extract and analyze images from local file paths.
120
+
121
+ **Parameters:**
122
+ - `file_path` (required): Path to the image file
123
+
124
+ **Example:**
125
+ ```
126
+ Analyze the test failure: test-results/checkout-failed.png
127
+ ```
128
+
129
+ ### extract_image_from_url
130
+
131
+ Extract and analyze images from web URLs.
132
+
133
+ **Parameters:**
134
+ - `url` (required): URL of the image
135
+
136
+ **Example:**
137
+ ```
138
+ What's in this image? https://example.com/chart.png
139
+ ```
140
+
141
+ ### extract_image_from_base64
142
+
143
+ Process base64-encoded images.
144
+
145
+ **Parameters:**
146
+ - `base64` (required): Base64-encoded image data
147
+ - `mime_type` (optional, default: "image/png"): MIME type
148
+
149
+ ## Use Cases
150
+
151
+ ✅ **Analyzing Playwright Test Results**
152
+ ```
153
+ "Analyze this test failure screenshot: ./test-results/login-error.png"
154
+ ```
155
+
156
+ ✅ **Processing External Images**
157
+ ```
158
+ "Extract the chart from https://reports.company.com/Q4-summary.png"
159
+ ```
160
+
161
+ ✅ **High-Resolution Screenshots**
162
+ - Supports large images (up to 50MB by default)
163
+ - Configurable output dimensions for quality control
164
+ - Automatic format detection and optimization
165
+
166
+ ## Context Usage Examples
167
+
168
+ | Configuration | Dimensions | Approx Tokens | Use Case |
169
+ | ------------- | ---------- | ------------- | -------------------- |
170
+ | Default | 512×288 | ~900 | Quick analysis |
171
+ | Balanced | 768×432 | ~2,100 | Charts & diagrams |
172
+ | Maximum | 1568×882 | ~8,000 | Detailed screenshots |
173
+
174
+ ## Troubleshooting
175
+
176
+ ### "maxContentLength exceeded" Error
177
+
178
+ Increase the download limit:
179
+ ```json
180
+ "env": {
181
+ "MAX_IMAGE_SIZE": "104857600"
182
+ }
183
+ ```
184
+
185
+ ### Images Too Blurry
186
+
187
+ Increase dimensions:
188
+ ```json
189
+ "env": {
190
+ "DEFAULT_MAX_WIDTH": "1024",
191
+ "DEFAULT_MAX_HEIGHT": "1024"
192
+ }
193
+ ```
194
+
195
+ ### "Unsupported image format" (BMP files)
196
+
197
+ BMP files with .png extension need conversion:
198
+ ```powershell
199
+ # PowerShell
200
+ Add-Type -AssemblyName System.Drawing
201
+ $bmp = [System.Drawing.Image]::FromFile("path/to/file.png")
202
+ $bmp.Save("path/to/file_actual.png", [System.Drawing.Imaging.ImageFormat]::Png)
203
+ $bmp.Dispose()
204
+ ```
205
+
206
+ ## Publishing to npm
207
+
208
+ This fork is published as `@c5t8fbt-wy/mcp-image-extractor` on npm.
209
+
210
+ To publish updates:
211
+
212
+ ```bash
213
+ # Update version in package.json
214
+ npm version patch # or minor/major
215
+
216
+ # Build
217
+ npm run build
218
+
219
+ # Publish (requires npm login)
220
+ npm publish
221
+ ```
222
+
223
+ ## Differences from Original
224
+
225
+ | Feature | Original | This Fork |
226
+ | ----------------------- | -------- | ------------- |
227
+ | Max download size | 10MB | 50MB |
228
+ | Configurable dimensions | ❌ | ✅ |
229
+ | Configurable quality | ❌ | ✅ |
230
+ | Environment vars | Limited | Full |
231
+ | Documentation | Basic | Comprehensive |
232
+
233
+ ## Development
234
+
235
+ ```bash
236
+ # Install dependencies
237
+ npm install
238
+
239
+ # Run in dev mode
240
+ npm run dev
241
+
242
+ # Build
243
+ npm run build
244
+
245
+ # Run tests
246
+ npm test
247
+ ```
248
+
249
+ ## License
250
+
251
+ MIT
252
+
253
+ ## Links
254
+
255
+ - **GitHub**: https://github.com/C5T8fBt-WY/mcp-image-extractor_fork
256
+ - **npm**: https://www.npmjs.com/package/@c5t8fbt-wy/mcp-image-extractor
257
+ - **Original**: https://github.com/ifmelate/mcp-image-extractor
258
+
259
+ ## Credits
260
+
261
+ Based on [mcp-image-extractor](https://github.com/ifmelate/mcp-image-extractor) by [@ifmelate](https://github.com/ifmelate)
262
+
263
+ Enhanced with configurable settings and improved documentation by [@C5T8fBt-WY](https://github.com/C5T8fBt-WY)
@@ -0,0 +1,389 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.extractImageFromFile = extractImageFromFile;
40
+ exports.extractImageFromUrl = extractImageFromUrl;
41
+ exports.extractImageFromBase64 = extractImageFromBase64;
42
+ const axios_1 = __importDefault(require("axios"));
43
+ const sharp_1 = __importDefault(require("sharp"));
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ // Configuration
47
+ const MAX_IMAGE_SIZE = parseInt(process.env.MAX_IMAGE_SIZE || '52428800', 10); // 50MB default (increased from 10MB)
48
+ const ALLOWED_DOMAINS = process.env.ALLOWED_DOMAINS ? process.env.ALLOWED_DOMAINS.split(',') : [];
49
+ // Default max dimensions for optimal LLM context usage (can be overridden via env vars)
50
+ const DEFAULT_MAX_WIDTH = parseInt(process.env.DEFAULT_MAX_WIDTH || '512', 10);
51
+ const DEFAULT_MAX_HEIGHT = parseInt(process.env.DEFAULT_MAX_HEIGHT || '512', 10);
52
+ // Compression quality (can be overridden via env var, 1-100, higher = better quality but larger file)
53
+ const COMPRESSION_QUALITY = parseInt(process.env.COMPRESSION_QUALITY || '80', 10);
54
+ // PNG compression level (can be overridden via env var, 0-9, higher = smaller file but slower)
55
+ const PNG_COMPRESSION_LEVEL = parseInt(process.env.PNG_COMPRESSION_LEVEL || '9', 10);
56
+ const COMPRESSION_OPTIONS = {
57
+ jpeg: { quality: COMPRESSION_QUALITY },
58
+ jpg: { quality: COMPRESSION_QUALITY },
59
+ png: { quality: COMPRESSION_QUALITY, compressionLevel: PNG_COMPRESSION_LEVEL },
60
+ webp: { quality: COMPRESSION_QUALITY },
61
+ gif: {},
62
+ svg: {},
63
+ avif: { quality: COMPRESSION_QUALITY },
64
+ tiff: { quality: COMPRESSION_QUALITY }
65
+ };
66
+ // Helper function to compress image based on format
67
+ async function compressImage(imageBuffer, formatStr) {
68
+ const sharpInstance = (0, sharp_1.default)(imageBuffer);
69
+ const format = formatStr.toLowerCase();
70
+ // Check if format is supported
71
+ if (format in COMPRESSION_OPTIONS) {
72
+ const options = COMPRESSION_OPTIONS[format];
73
+ // Use specific methods based on format
74
+ switch (format) {
75
+ case 'jpeg':
76
+ case 'jpg':
77
+ return await sharpInstance.jpeg(options).toBuffer();
78
+ case 'png':
79
+ return await sharpInstance.png(options).toBuffer();
80
+ case 'webp':
81
+ return await sharpInstance.webp(options).toBuffer();
82
+ case 'avif':
83
+ return await sharpInstance.avif(options).toBuffer();
84
+ case 'tiff':
85
+ return await sharpInstance.tiff(options).toBuffer();
86
+ // For formats without specific compression options
87
+ case 'gif':
88
+ case 'svg':
89
+ return await sharpInstance.toBuffer();
90
+ }
91
+ }
92
+ // Default to jpeg if format not supported
93
+ return await sharpInstance.jpeg(COMPRESSION_OPTIONS.jpeg).toBuffer();
94
+ }
95
+ // Extract image from file
96
+ async function extractImageFromFile(params) {
97
+ try {
98
+ const { file_path, resize, max_width, max_height } = params;
99
+ // Check if file exists
100
+ if (!fs.existsSync(file_path)) {
101
+ return {
102
+ content: [{ type: "text", text: `Error: File ${file_path} does not exist` }],
103
+ isError: true
104
+ };
105
+ }
106
+ // Read file
107
+ let imageBuffer = fs.readFileSync(file_path);
108
+ // Check size
109
+ if (imageBuffer.length > MAX_IMAGE_SIZE) {
110
+ return {
111
+ content: [{ type: "text", text: `Error: Image size exceeds maximum allowed size of ${MAX_IMAGE_SIZE} bytes` }],
112
+ isError: true
113
+ };
114
+ }
115
+ // Process the image
116
+ let metadata = await (0, sharp_1.default)(imageBuffer).metadata();
117
+ // Always resize to ensure the base64 representation is reasonable
118
+ // This will help avoid consuming too much of the context window
119
+ if (metadata.width && metadata.height) {
120
+ // Use provided dimensions or fallback to defaults for optimal LLM context usage
121
+ const targetWidth = Math.min(metadata.width, DEFAULT_MAX_WIDTH);
122
+ const targetHeight = Math.min(metadata.height, DEFAULT_MAX_HEIGHT);
123
+ // Only resize if needed
124
+ if (metadata.width > targetWidth || metadata.height > targetHeight) {
125
+ imageBuffer = await (0, sharp_1.default)(imageBuffer)
126
+ .resize({
127
+ width: targetWidth,
128
+ height: targetHeight,
129
+ fit: 'inside',
130
+ withoutEnlargement: true
131
+ })
132
+ .toBuffer();
133
+ // Update metadata after resize
134
+ metadata = await (0, sharp_1.default)(imageBuffer).metadata();
135
+ }
136
+ }
137
+ // Determine mime type based on file extension
138
+ const fileExt = path.extname(file_path).toLowerCase();
139
+ let mimeType = 'image/jpeg';
140
+ let format = 'jpeg';
141
+ if (fileExt === '.png') {
142
+ mimeType = 'image/png';
143
+ format = 'png';
144
+ }
145
+ else if (fileExt === '.jpg' || fileExt === '.jpeg') {
146
+ mimeType = 'image/jpeg';
147
+ format = 'jpeg';
148
+ }
149
+ else if (fileExt === '.gif') {
150
+ mimeType = 'image/gif';
151
+ format = 'gif';
152
+ }
153
+ else if (fileExt === '.webp') {
154
+ mimeType = 'image/webp';
155
+ format = 'webp';
156
+ }
157
+ else if (fileExt === '.svg') {
158
+ mimeType = 'image/svg+xml';
159
+ format = 'svg';
160
+ }
161
+ else if (fileExt === '.avif') {
162
+ mimeType = 'image/avif';
163
+ format = 'avif';
164
+ }
165
+ // Compress the image based on its format
166
+ try {
167
+ imageBuffer = await compressImage(imageBuffer, format);
168
+ }
169
+ catch (compressionError) {
170
+ console.warn('Compression warning, using original image:', compressionError);
171
+ // Continue with the original image if compression fails
172
+ }
173
+ // Convert to base64
174
+ const base64 = imageBuffer.toString('base64');
175
+ // Return both text and image content
176
+ return {
177
+ content: [
178
+ {
179
+ type: "text",
180
+ text: JSON.stringify({
181
+ width: metadata.width,
182
+ height: metadata.height,
183
+ format: metadata.format,
184
+ size: imageBuffer.length
185
+ })
186
+ },
187
+ {
188
+ type: "image",
189
+ data: base64,
190
+ mimeType: mimeType
191
+ }
192
+ ]
193
+ };
194
+ }
195
+ catch (error) {
196
+ console.error('Error processing image file:', error);
197
+ return {
198
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
199
+ isError: true
200
+ };
201
+ }
202
+ }
203
+ // Extract image from URL
204
+ async function extractImageFromUrl(params) {
205
+ try {
206
+ const { url, resize, max_width, max_height } = params;
207
+ // Validate URL
208
+ if (!url.startsWith('http://') && !url.startsWith('https://')) {
209
+ return {
210
+ content: [{ type: "text", text: "Error: URL must start with http:// or https://" }],
211
+ isError: true
212
+ };
213
+ }
214
+ // Domain validation if ALLOWED_DOMAINS is set
215
+ if (ALLOWED_DOMAINS.length > 0) {
216
+ const urlObj = new URL(url);
217
+ const domain = urlObj.hostname;
218
+ const isAllowed = ALLOWED_DOMAINS.some((allowedDomain) => domain === allowedDomain || domain.endsWith(`.${allowedDomain}`));
219
+ if (!isAllowed) {
220
+ return {
221
+ content: [{ type: "text", text: `Error: Domain ${domain} is not in the allowed domains list` }],
222
+ isError: true
223
+ };
224
+ }
225
+ }
226
+ // Fetch the image
227
+ const response = await axios_1.default.get(url, {
228
+ responseType: 'arraybuffer',
229
+ maxContentLength: MAX_IMAGE_SIZE,
230
+ });
231
+ // Process the image
232
+ let imageBuffer = Buffer.from(response.data);
233
+ let metadata = await (0, sharp_1.default)(imageBuffer).metadata();
234
+ // Always resize to ensure the base64 representation is reasonable
235
+ // This will help avoid consuming too much of the context window
236
+ if (metadata.width && metadata.height) {
237
+ // Use provided dimensions or fallback to defaults for optimal LLM context usage
238
+ const targetWidth = Math.min(metadata.width, DEFAULT_MAX_WIDTH);
239
+ const targetHeight = Math.min(metadata.height, DEFAULT_MAX_HEIGHT);
240
+ // Only resize if needed
241
+ if (metadata.width > targetWidth || metadata.height > targetHeight) {
242
+ imageBuffer = await (0, sharp_1.default)(imageBuffer)
243
+ .resize({
244
+ width: targetWidth,
245
+ height: targetHeight,
246
+ fit: 'inside',
247
+ withoutEnlargement: true
248
+ })
249
+ .toBuffer();
250
+ // Update metadata after resize
251
+ metadata = await (0, sharp_1.default)(imageBuffer).metadata();
252
+ }
253
+ }
254
+ // Compress the image based on its format
255
+ try {
256
+ const format = metadata.format || 'jpeg';
257
+ imageBuffer = await compressImage(imageBuffer, format);
258
+ }
259
+ catch (compressionError) {
260
+ console.warn('Compression warning, using original image:', compressionError);
261
+ // Continue with the original image if compression fails
262
+ }
263
+ // Convert to base64
264
+ const base64 = imageBuffer.toString('base64');
265
+ const mimeType = response.headers['content-type'] || 'image/jpeg';
266
+ // Return both text and image content
267
+ return {
268
+ content: [
269
+ {
270
+ type: "text",
271
+ text: JSON.stringify({
272
+ width: metadata.width,
273
+ height: metadata.height,
274
+ format: metadata.format,
275
+ size: imageBuffer.length
276
+ })
277
+ },
278
+ {
279
+ type: "image",
280
+ data: base64,
281
+ mimeType: mimeType
282
+ }
283
+ ]
284
+ };
285
+ }
286
+ catch (error) {
287
+ console.error('Error processing image from URL:', error);
288
+ return {
289
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
290
+ isError: true
291
+ };
292
+ }
293
+ }
294
+ // Extract image from base64
295
+ async function extractImageFromBase64(params) {
296
+ try {
297
+ const { base64, mime_type, resize, max_width, max_height } = params;
298
+ // Decode base64
299
+ let imageBuffer;
300
+ try {
301
+ imageBuffer = Buffer.from(base64, 'base64');
302
+ // Quick validation - valid base64 strings should be decodable
303
+ if (imageBuffer.length === 0) {
304
+ throw new Error("Invalid base64 string - decoded to empty buffer");
305
+ }
306
+ }
307
+ catch (e) {
308
+ return {
309
+ content: [{ type: "text", text: `Error: Invalid base64 string - ${e instanceof Error ? e.message : String(e)}` }],
310
+ isError: true
311
+ };
312
+ }
313
+ // Check size
314
+ if (imageBuffer.length > MAX_IMAGE_SIZE) {
315
+ return {
316
+ content: [{ type: "text", text: `Error: Image size exceeds maximum allowed size of ${MAX_IMAGE_SIZE} bytes` }],
317
+ isError: true
318
+ };
319
+ }
320
+ // Process the image
321
+ let metadata;
322
+ try {
323
+ metadata = await (0, sharp_1.default)(imageBuffer).metadata();
324
+ }
325
+ catch (e) {
326
+ return {
327
+ content: [{ type: "text", text: `Error: Could not process image data - ${e instanceof Error ? e.message : String(e)}` }],
328
+ isError: true
329
+ };
330
+ }
331
+ // Always resize to ensure the base64 representation is reasonable
332
+ // This will help avoid consuming too much of the context window
333
+ if (metadata.width && metadata.height) {
334
+ // Use provided dimensions or fallback to defaults for optimal LLM context usage
335
+ const targetWidth = Math.min(metadata.width, DEFAULT_MAX_WIDTH);
336
+ const targetHeight = Math.min(metadata.height, DEFAULT_MAX_HEIGHT);
337
+ // Only resize if needed
338
+ if (metadata.width > targetWidth || metadata.height > targetHeight) {
339
+ imageBuffer = await (0, sharp_1.default)(imageBuffer)
340
+ .resize({
341
+ width: targetWidth,
342
+ height: targetHeight,
343
+ fit: 'inside',
344
+ withoutEnlargement: true
345
+ })
346
+ .toBuffer();
347
+ // Update metadata after resize
348
+ metadata = await (0, sharp_1.default)(imageBuffer).metadata();
349
+ }
350
+ }
351
+ // Compress the image based on its format
352
+ try {
353
+ const format = metadata.format || mime_type.split('/')[1] || 'jpeg';
354
+ imageBuffer = await compressImage(imageBuffer, format);
355
+ }
356
+ catch (compressionError) {
357
+ console.warn('Compression warning, using original image:', compressionError);
358
+ // Continue with the original image if compression fails
359
+ }
360
+ // Convert back to base64
361
+ const processedBase64 = imageBuffer.toString('base64');
362
+ // Return both text and image content
363
+ return {
364
+ content: [
365
+ {
366
+ type: "text",
367
+ text: JSON.stringify({
368
+ width: metadata.width,
369
+ height: metadata.height,
370
+ format: metadata.format,
371
+ size: imageBuffer.length
372
+ })
373
+ },
374
+ {
375
+ type: "image",
376
+ data: processedBase64,
377
+ mimeType: mime_type
378
+ }
379
+ ]
380
+ };
381
+ }
382
+ catch (error) {
383
+ console.error('Error processing base64 image:', error);
384
+ return {
385
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
386
+ isError: true
387
+ };
388
+ }
389
+ }
package/dist/index.js ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
38
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
39
+ const zod_1 = require("zod");
40
+ const dotenv = __importStar(require("dotenv"));
41
+ const image_utils_1 = require("./image-utils");
42
+ dotenv.config();
43
+ // Create an MCP server
44
+ const server = new mcp_js_1.McpServer({
45
+ name: "mcp-image-extractor",
46
+ description: "MCP server for analyzing of images from files, URLs, and base64 data for visual content understanding, text extraction (OCR), and object recognition in screenshots and photos",
47
+ version: "1.0.0"
48
+ });
49
+ // Add extract_image_from_file tool
50
+ server.tool("extract_image_from_file", "Extract and analyze images from local file paths. Supports visual content understanding, OCR text extraction, and object recognition for screenshots, photos, diagrams, and documents.", {
51
+ file_path: zod_1.z.string().describe("Path to the image file to analyze (supports screenshots, photos, diagrams, and documents in PNG, JPG, GIF, WebP formats)"),
52
+ resize: zod_1.z.boolean().default(true).describe("For backward compatibility only. Images are always automatically resized to optimal dimensions (max 512x512) for LLM analysis"),
53
+ max_width: zod_1.z.number().default(512).describe("For backward compatibility only. Default maximum width is now 512px"),
54
+ max_height: zod_1.z.number().default(512).describe("For backward compatibility only. Default maximum height is now 512px")
55
+ }, async (args, extra) => {
56
+ const result = await (0, image_utils_1.extractImageFromFile)(args);
57
+ return result;
58
+ });
59
+ // Add extract_image_from_url tool
60
+ server.tool("extract_image_from_url", "Extract and analyze images from web URLs. Perfect for analyzing web screenshots, online photos, diagrams, or any image accessible via HTTP/HTTPS for visual content analysis and text extraction.", {
61
+ url: zod_1.z.string().describe("URL of the image to analyze for visual content, text extraction, or object recognition (supports web screenshots, photos, diagrams)"),
62
+ resize: zod_1.z.boolean().default(true).describe("For backward compatibility only. Images are always automatically resized to optimal dimensions (max 512x512) for LLM analysis"),
63
+ max_width: zod_1.z.number().default(512).describe("For backward compatibility only. Default maximum width is now 512px"),
64
+ max_height: zod_1.z.number().default(512).describe("For backward compatibility only. Default maximum height is now 512px")
65
+ }, async (args, extra) => {
66
+ const result = await (0, image_utils_1.extractImageFromUrl)(args);
67
+ return result;
68
+ });
69
+ // Add extract_image_from_base64 tool
70
+ server.tool("extract_image_from_base64", "Extract and analyze images from base64-encoded data. Ideal for processing screenshots from clipboard, dynamically generated images, or images embedded in applications without requiring file system access.", {
71
+ base64: zod_1.z.string().describe("Base64-encoded image data to analyze (useful for screenshots, images from clipboard, or dynamically generated visuals)"),
72
+ mime_type: zod_1.z.string().default("image/png").describe("MIME type of the image (e.g., image/png, image/jpeg)"),
73
+ resize: zod_1.z.boolean().default(true).describe("For backward compatibility only. Images are always automatically resized to optimal dimensions (max 512x512) for LLM analysis"),
74
+ max_width: zod_1.z.number().default(512).describe("For backward compatibility only. Default maximum width is now 512px"),
75
+ max_height: zod_1.z.number().default(512).describe("For backward compatibility only. Default maximum height is now 512px")
76
+ }, async (args, extra) => {
77
+ const result = await (0, image_utils_1.extractImageFromBase64)(args);
78
+ return result;
79
+ });
80
+ // Start the server using stdio transport
81
+ const transport = new stdio_js_1.StdioServerTransport();
82
+ server.connect(transport).catch((error) => {
83
+ console.error('Error starting MCP server:', error);
84
+ process.exit(1);
85
+ });
86
+ console.log('MCP Image Extractor server started in stdio mode');
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@c5t8fbt-wy/mcp-image-extractor",
3
+ "version": "1.2.0",
4
+ "description": "Enhanced MCP server for extracting and converting images to base64 for LLM analysis with configurable quality settings",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "mcp-image-extractor": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"",
16
+ "start": "node dist/index.js",
17
+ "dev": "ts-node src/index.ts",
18
+ "lint": "eslint . --ext .ts",
19
+ "test": "jest --no-cache --passWithNoTests",
20
+ "test:ci": "jest --ci",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "keywords": [
24
+ "mcp",
25
+ "model-context-protocol",
26
+ "image",
27
+ "base64",
28
+ "llm",
29
+ "smithery",
30
+ "image-processing",
31
+ "sharp",
32
+ "configurable"
33
+ ],
34
+ "author": "C5T8fBt-WY",
35
+ "license": "MIT",
36
+ "dependencies": {
37
+ "@modelcontextprotocol/sdk": "^1.6.1",
38
+ "axios": "^1.6.2",
39
+ "dotenv": "^16.3.1",
40
+ "sharp": "^0.33.0",
41
+ "zod": "^3.22.4"
42
+ },
43
+ "devDependencies": {
44
+ "@deploya/smithery-cli": "^0.0.1",
45
+ "@smithery/cli": "^1.1.46",
46
+ "@types/axios": "^0.14.4",
47
+ "@types/dotenv": "^8.2.3",
48
+ "@types/jest": "^29.5.12",
49
+ "@types/node": "^20.10.0",
50
+ "@types/sharp": "^0.32.0",
51
+ "@typescript-eslint/eslint-plugin": "^6.12.0",
52
+ "@typescript-eslint/parser": "^6.12.0",
53
+ "eslint": "^8.54.0",
54
+ "jest": "^29.7.0",
55
+ "ts-jest": "^29.1.2",
56
+ "ts-node": "^10.9.1",
57
+ "typescript": "^5.3.2"
58
+ },
59
+ "smithery": {
60
+ "name": "@c5t8fbt-wy/mcp-image-extractor",
61
+ "description": "Enhanced MCP server for extracting and converting images to base64 for LLM analysis",
62
+ "version": "1.2.0",
63
+ "author": "C5T8fBt-WY",
64
+ "license": "MIT"
65
+ },
66
+ "repository": {
67
+ "type": "git",
68
+ "url": "https://github.com/C5T8fBt-WY/mcp-image-extractor_fork"
69
+ },
70
+ "homepage": "https://github.com/C5T8fBt-WY/mcp-image-extractor_fork#readme",
71
+ "bugs": {
72
+ "url": "https://github.com/C5T8fBt-WY/mcp-image-extractor_fork/issues"
73
+ },
74
+ "engines": {
75
+ "node": ">=16.0.0"
76
+ },
77
+ "publishConfig": {
78
+ "access": "public",
79
+ "registry": "https://registry.npmjs.org/"
80
+ }
81
+ }