@mcpcn/mcp-image-compressor 1.0.3 → 1.0.4

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.
Files changed (3) hide show
  1. package/README.md +286 -286
  2. package/dist/index.js +124 -30
  3. package/package.json +42 -42
package/README.md CHANGED
@@ -1,287 +1,287 @@
1
- # Image Processor MCP Server
2
-
3
- 这是一个用于图片处理的 MCP 服务器,提供了多种图片处理功能。
4
-
5
- ## 功能特性
6
-
7
- 1. 图片格式转换
8
- 2. 图片压缩到指定大小
9
- 3. 图片压缩到原始大小的指定百分比
10
- 4. 图片尺寸缩放
11
- 5. 读取图片元数据
12
- 6. 图像旋转
13
- 7. 图像翻转
14
- 8. 添加文字水印
15
-
16
- ## 安装
17
-
18
- ```bash
19
- npm install
20
- ```
21
-
22
- ## 构建
23
-
24
- ```bash
25
- npm run build
26
- ```
27
-
28
- ## 运行服务器
29
-
30
- ```bash
31
- npm start
32
- ```
33
-
34
- ## 开发模式
35
-
36
- ```bash
37
- npm run dev
38
- ```
39
-
40
- ## MCP 工具
41
-
42
- ### 1. image_convert_format
43
-
44
- 将图片转换为指定格式。
45
-
46
- **参数:**
47
-
48
- - `inputPaths`: 原图片路径数组,支持图片文件和目录
49
- - `targetFormat`: 要转换为的格式(jpg/png/webp/bmp等)
50
- - `outputPath`: 转换后的保存路径(可选,不指定则和原图片目录相同)
51
-
52
- **示例:**
53
-
54
- ```json
55
- {
56
- "inputPaths": ["path/to/image.jpg", "path/to/images/"],
57
- "targetFormat": "png",
58
- "outputPath": "path/to/output/"
59
- }
60
- ```
61
-
62
- ### 2. image_compress_to_size
63
-
64
- 将图片压缩到指定的文件大小。
65
-
66
- **参数:**
67
-
68
- - `inputPaths`: 原图片路径数组,支持图片文件和目录
69
- - `size`: 要压缩到的的目标大小(如1mb/500kb)
70
- - `overwrite`: 是否覆盖原图(可选,默认为false)
71
- - `outputPath`: 存储路径(可选,不覆盖原图时如果没传则为原图所在目录)
72
-
73
- **示例:**
74
-
75
- ```json
76
- {
77
- "inputPaths": ["path/to/image.jpg"],
78
- "size": "500kb",
79
- "overwrite": false,
80
- "outputPath": "path/to/output/"
81
- }
82
- ```
83
-
84
- ### 3. image_compress_to_percent
85
-
86
- 将图片压缩到原始大小的指定百分比。
87
-
88
- **参数:**
89
-
90
- - `inputPaths`: 原图片路径数组,支持图片文件和目录
91
- - `percent`: 要压缩到原size的百分之多少(如50或50%)
92
- - `overwrite`: 是否覆盖原图(可选,默认为false)
93
- - `outputPath`: 存储路径(可选,不覆盖原图时如果没传则为原图所在目录)
94
-
95
- **示例:**
96
-
97
- ```json
98
- {
99
- "inputPaths": ["path/to/image.jpg"],
100
- "percent": "50%",
101
- "overwrite": false,
102
- "outputPath": "path/to/output/"
103
- }
104
- ```
105
-
106
- ### 4. image_resize
107
-
108
- 调整图片尺寸。
109
-
110
- **参数:**
111
-
112
- - `inputPaths`: 原图片路径数组,支持图片文件和目录
113
- - `width`: 宽修改为多少(可选)
114
- - `height`: 高修改为多少(可选)
115
- - `overwrite`: 是否覆盖原图(可选,默认为false)
116
- - `outputPath`: 存储路径(可选,不覆盖原图时如果没传则为原图所在目录)
117
-
118
- **示例:**
119
-
120
- ```json
121
- {
122
- "inputPaths": ["path/to/image.jpg"],
123
- "width": 800,
124
- "height": 600,
125
- "overwrite": false,
126
- "outputPath": "path/to/output/"
127
- }
128
- ```
129
-
130
- ### 5. image_metadata
131
-
132
- 读取图片元数据。
133
-
134
- **参数:**
135
-
136
- - `inputPaths`: 待处理图片路径
137
-
138
- **示例:**
139
-
140
- ```json
141
- {
142
- "inputPaths": ["path/to/image.jpg"]
143
- }
144
- ```
145
-
146
- **返回示例:**
147
-
148
- ```json
149
- {
150
- "success": true,
151
- "message": "成功读取了 1 个图片的元数据",
152
- "metadata": {
153
- "image.jpg": {
154
- "format": "jpeg",
155
- "width": 1920,
156
- "height": 1080,
157
- "space": "srgb",
158
- "channels": 3,
159
- "depth": "uchar",
160
- "density": 72,
161
- "isProgressive": false,
162
- "hasProfile": true,
163
- "hasAlpha": false
164
- }
165
- }
166
- }
167
- ```
168
-
169
- ### 6. image_rotate
170
-
171
- 图像旋转。
172
-
173
- **参数:**
174
-
175
- - `inputPaths`: 待处理图片路径
176
- - `angle`: 旋转角度(顺时针为正值,逆时针为负值)
177
- - `overwrite`: 是否覆盖原图(可选,默认为false)
178
- - `outputPath`: 存储路径(可选,不覆盖原图时如果没传则为原图所在目录)
179
-
180
- **示例:**
181
-
182
- ```json
183
- {
184
- "inputPaths": ["path/to/image.jpg"],
185
- "angle": 90,
186
- "overwrite": false,
187
- "outputPath": "path/to/output/"
188
- }
189
- ```
190
-
191
- ### 7. image_flip
192
-
193
- 图像翻转。
194
-
195
- **参数:**
196
-
197
- - `inputPaths`: 待处理图片路径
198
- - `flipType`: 翻转方式("horizontal": 水平翻转, "vertical": 垂直翻转)
199
- - `overwrite`: 是否覆盖原图(可选,默认为false)
200
- - `outputPath`: 存储路径(可选,不覆盖原图时如果没传则为原图所在目录)
201
-
202
- **示例:**
203
-
204
- ```json
205
- {
206
- "inputPaths": ["path/to/image.jpg"],
207
- "flipType": "horizontal",
208
- "overwrite": false,
209
- "outputPath": "path/to/output/"
210
- }
211
- ```
212
-
213
- ### 8. image_watermark
214
-
215
- 添加文字水印。
216
-
217
- **参数:**
218
-
219
- - `inputPaths`: 待处理图片路径
220
- - `text`: 水印内容(文字)
221
- - `position`: 水印添加方式(必填,可能的取值为:top-left(左上)、top-right(右上)、bottom-left(左下)、bottom-right(右下)、center(中央)、tile(铺满))
222
- - `density`: 满铺密度(可选参数,在水印添加方式为铺满时有效,取值范围为1-10的整数,1最稀疏,10最密)
223
- - `fontSize`: 文字尺寸(可选参数,单位为像素,不传则根据图片尺寸自动计算)
224
- - `overwrite`: 是否覆盖原图(可选,默认为false)
225
- - `outputPath`: 存储路径(可选,不覆盖原图时如果没传则为原图所在目录)
226
-
227
- **示例:**
228
-
229
- ```json
230
- {
231
- "inputPaths": ["path/to/image.jpg"],
232
- "text": "版权所有",
233
- "position": "bottom-right",
234
- "overwrite": false,
235
- "outputPath": "path/to/output/"
236
- }
237
- ```
238
-
239
- 铺满水印示例:
240
-
241
- ```json
242
- {
243
- "inputPaths": ["path/to/image.jpg"],
244
- "text": "CONFIDENTIAL",
245
- "position": "tile",
246
- "density": 5,
247
- "fontSize": 20,
248
- "overwrite": false,
249
- "outputPath": "path/to/output/"
250
- }
251
- ```
252
-
253
- ## 注意事项
254
-
255
- 1. 所有功能都支持批量处理多个文件
256
- 2. 支持处理整个目录中的图片(仅处理目录下的图片文件,不处理子目录中的)
257
- 3. 支持的图片格式:jpeg、jpg、png、webp、gif、avif、tiff、bmp
258
- 4. 压缩功能使用二分查找算法来找到最佳的压缩质量
259
- 5. 当指定输出目录时,如果目录不存在会自动创建
260
- 6. 所有错误都会被优雅地处理并记录,不会中断批处理过程
261
-
262
- ## 使用作为 MCP 服务器
263
-
264
- 该服务器实现了 Model Context Protocol (MCP) 规范,可以连接到支持 MCP 的客户端。
265
-
266
- ### 配置环境变量
267
-
268
- 在使用前,确保已安装所有依赖,并构建项目:
269
-
270
- ```bash
271
- npm install
272
- npm run build
273
- ```
274
-
275
- ### 启动服务器
276
-
277
- ```bash
278
- npm start
279
- ```
280
-
281
-
282
-
283
- ## 依赖
284
-
285
- - sharp: 用于图片处理
286
- - @modelcontextprotocol/sdk: MCP SDK
1
+ # Image Processor MCP Server
2
+
3
+ 这是一个用于图片处理的 MCP 服务器,提供了多种图片处理功能。
4
+
5
+ ## 功能特性
6
+
7
+ 1. 图片格式转换
8
+ 2. 图片压缩到指定大小
9
+ 3. 图片压缩到原始大小的指定百分比
10
+ 4. 图片尺寸缩放
11
+ 5. 读取图片元数据
12
+ 6. 图像旋转
13
+ 7. 图像翻转
14
+ 8. 添加文字水印
15
+
16
+ ## 安装
17
+
18
+ ```bash
19
+ npm install
20
+ ```
21
+
22
+ ## 构建
23
+
24
+ ```bash
25
+ npm run build
26
+ ```
27
+
28
+ ## 运行服务器
29
+
30
+ ```bash
31
+ npm start
32
+ ```
33
+
34
+ ## 开发模式
35
+
36
+ ```bash
37
+ npm run dev
38
+ ```
39
+
40
+ ## MCP 工具
41
+
42
+ ### 1. image_convert_format
43
+
44
+ 将图片转换为指定格式。
45
+
46
+ **参数:**
47
+
48
+ - `inputPaths`: 原图片路径数组,支持图片文件和目录
49
+ - `targetFormat`: 要转换为的格式(jpg/png/webp/bmp等)
50
+ - `outputPath`: 转换后的保存路径(可选,不指定则和原图片目录相同)
51
+
52
+ **示例:**
53
+
54
+ ```json
55
+ {
56
+ "inputPaths": ["path/to/image.jpg", "path/to/images/"],
57
+ "targetFormat": "png",
58
+ "outputPath": "path/to/output/"
59
+ }
60
+ ```
61
+
62
+ ### 2. image_compress_to_size
63
+
64
+ 将图片压缩到指定的文件大小。
65
+
66
+ **参数:**
67
+
68
+ - `inputPaths`: 原图片路径数组,支持图片文件和目录
69
+ - `size`: 要压缩到的的目标大小(如1mb/500kb)
70
+ - `overwrite`: 是否覆盖原图(可选,默认为false)
71
+ - `outputPath`: 存储路径(可选,不覆盖原图时如果没传则为原图所在目录)
72
+
73
+ **示例:**
74
+
75
+ ```json
76
+ {
77
+ "inputPaths": ["path/to/image.jpg"],
78
+ "size": "500kb",
79
+ "overwrite": false,
80
+ "outputPath": "path/to/output/"
81
+ }
82
+ ```
83
+
84
+ ### 3. image_compress_to_percent
85
+
86
+ 将图片压缩到原始大小的指定百分比。
87
+
88
+ **参数:**
89
+
90
+ - `inputPaths`: 原图片路径数组,支持图片文件和目录
91
+ - `percent`: 要压缩到原size的百分之多少(如50或50%)
92
+ - `overwrite`: 是否覆盖原图(可选,默认为false)
93
+ - `outputPath`: 存储路径(可选,不覆盖原图时如果没传则为原图所在目录)
94
+
95
+ **示例:**
96
+
97
+ ```json
98
+ {
99
+ "inputPaths": ["path/to/image.jpg"],
100
+ "percent": "50%",
101
+ "overwrite": false,
102
+ "outputPath": "path/to/output/"
103
+ }
104
+ ```
105
+
106
+ ### 4. image_resize
107
+
108
+ 调整图片尺寸。
109
+
110
+ **参数:**
111
+
112
+ - `inputPaths`: 原图片路径数组,支持图片文件和目录
113
+ - `width`: 宽修改为多少(可选)
114
+ - `height`: 高修改为多少(可选)
115
+ - `overwrite`: 是否覆盖原图(可选,默认为false)
116
+ - `outputPath`: 存储路径(可选,不覆盖原图时如果没传则为原图所在目录)
117
+
118
+ **示例:**
119
+
120
+ ```json
121
+ {
122
+ "inputPaths": ["path/to/image.jpg"],
123
+ "width": 800,
124
+ "height": 600,
125
+ "overwrite": false,
126
+ "outputPath": "path/to/output/"
127
+ }
128
+ ```
129
+
130
+ ### 5. image_metadata
131
+
132
+ 读取图片元数据。
133
+
134
+ **参数:**
135
+
136
+ - `inputPaths`: 待处理图片路径
137
+
138
+ **示例:**
139
+
140
+ ```json
141
+ {
142
+ "inputPaths": ["path/to/image.jpg"]
143
+ }
144
+ ```
145
+
146
+ **返回示例:**
147
+
148
+ ```json
149
+ {
150
+ "success": true,
151
+ "message": "成功读取了 1 个图片的元数据",
152
+ "metadata": {
153
+ "image.jpg": {
154
+ "format": "jpeg",
155
+ "width": 1920,
156
+ "height": 1080,
157
+ "space": "srgb",
158
+ "channels": 3,
159
+ "depth": "uchar",
160
+ "density": 72,
161
+ "isProgressive": false,
162
+ "hasProfile": true,
163
+ "hasAlpha": false
164
+ }
165
+ }
166
+ }
167
+ ```
168
+
169
+ ### 6. image_rotate
170
+
171
+ 图像旋转。
172
+
173
+ **参数:**
174
+
175
+ - `inputPaths`: 待处理图片路径
176
+ - `angle`: 旋转角度(顺时针为正值,逆时针为负值)
177
+ - `overwrite`: 是否覆盖原图(可选,默认为false)
178
+ - `outputPath`: 存储路径(可选,不覆盖原图时如果没传则为原图所在目录)
179
+
180
+ **示例:**
181
+
182
+ ```json
183
+ {
184
+ "inputPaths": ["path/to/image.jpg"],
185
+ "angle": 90,
186
+ "overwrite": false,
187
+ "outputPath": "path/to/output/"
188
+ }
189
+ ```
190
+
191
+ ### 7. image_flip
192
+
193
+ 图像翻转。
194
+
195
+ **参数:**
196
+
197
+ - `inputPaths`: 待处理图片路径
198
+ - `flipType`: 翻转方式("horizontal": 水平翻转, "vertical": 垂直翻转)
199
+ - `overwrite`: 是否覆盖原图(可选,默认为false)
200
+ - `outputPath`: 存储路径(可选,不覆盖原图时如果没传则为原图所在目录)
201
+
202
+ **示例:**
203
+
204
+ ```json
205
+ {
206
+ "inputPaths": ["path/to/image.jpg"],
207
+ "flipType": "horizontal",
208
+ "overwrite": false,
209
+ "outputPath": "path/to/output/"
210
+ }
211
+ ```
212
+
213
+ ### 8. image_watermark
214
+
215
+ 添加文字水印。
216
+
217
+ **参数:**
218
+
219
+ - `inputPaths`: 待处理图片路径
220
+ - `text`: 水印内容(文字)
221
+ - `position`: 水印添加方式(必填,可能的取值为:top-left(左上)、top-right(右上)、bottom-left(左下)、bottom-right(右下)、center(中央)、tile(铺满))
222
+ - `density`: 满铺密度(可选参数,在水印添加方式为铺满时有效,取值范围为1-10的整数,1最稀疏,10最密)
223
+ - `fontSize`: 文字尺寸(可选参数,单位为像素,不传则根据图片尺寸自动计算)
224
+ - `overwrite`: 是否覆盖原图(可选,默认为false)
225
+ - `outputPath`: 存储路径(可选,不覆盖原图时如果没传则为原图所在目录)
226
+
227
+ **示例:**
228
+
229
+ ```json
230
+ {
231
+ "inputPaths": ["path/to/image.jpg"],
232
+ "text": "版权所有",
233
+ "position": "bottom-right",
234
+ "overwrite": false,
235
+ "outputPath": "path/to/output/"
236
+ }
237
+ ```
238
+
239
+ 铺满水印示例:
240
+
241
+ ```json
242
+ {
243
+ "inputPaths": ["path/to/image.jpg"],
244
+ "text": "CONFIDENTIAL",
245
+ "position": "tile",
246
+ "density": 5,
247
+ "fontSize": 20,
248
+ "overwrite": false,
249
+ "outputPath": "path/to/output/"
250
+ }
251
+ ```
252
+
253
+ ## 注意事项
254
+
255
+ 1. 所有功能都支持批量处理多个文件
256
+ 2. 支持处理整个目录中的图片(仅处理目录下的图片文件,不处理子目录中的)
257
+ 3. 支持的图片格式:jpeg、jpg、png、webp、gif、avif、tiff、bmp
258
+ 4. 压缩功能使用二分查找算法来找到最佳的压缩质量
259
+ 5. 当指定输出目录时,如果目录不存在会自动创建
260
+ 6. 所有错误都会被优雅地处理并记录,不会中断批处理过程
261
+
262
+ ## 使用作为 MCP 服务器
263
+
264
+ 该服务器实现了 Model Context Protocol (MCP) 规范,可以连接到支持 MCP 的客户端。
265
+
266
+ ### 配置环境变量
267
+
268
+ 在使用前,确保已安装所有依赖,并构建项目:
269
+
270
+ ```bash
271
+ npm install
272
+ npm run build
273
+ ```
274
+
275
+ ### 启动服务器
276
+
277
+ ```bash
278
+ npm start
279
+ ```
280
+
281
+
282
+
283
+ ## 依赖
284
+
285
+ - sharp: 用于图片处理
286
+ - @modelcontextprotocol/sdk: MCP SDK
287
287
  - TypeScript: 用于类型检查和编译
package/dist/index.js CHANGED
@@ -161,29 +161,45 @@ async function compressToSize(inputPaths, targetSize, overwrite = false, outputP
161
161
  const errors = [];
162
162
  for (const imagePath of images) {
163
163
  try {
164
- const outputFilePath = overwrite
164
+ let outputFilePath = overwrite
165
165
  ? imagePath
166
166
  : generateOutputPath(imagePath, outputPath, `_${targetSize.toLowerCase()}`);
167
- const image = sharp.default(imagePath);
168
- const metadata = await image.metadata();
169
167
  const stats = fs.statSync(imagePath);
170
- if (stats.size <= targetBytes) {
168
+ const originalSize = stats.size;
169
+ if (originalSize <= targetBytes) {
171
170
  if (!overwrite) {
172
171
  await fs.promises.copyFile(imagePath, outputFilePath);
173
172
  }
174
173
  results.push(outputFilePath);
175
174
  continue;
176
175
  }
176
+ const ext = path.extname(imagePath).toLowerCase().slice(1);
177
+ const encodeWithQuality = async (q) => {
178
+ const img = sharp.default(imagePath);
179
+ if (ext === "jpg" || ext === "jpeg") {
180
+ return img.jpeg({ quality: q, mozjpeg: true }).toBuffer();
181
+ }
182
+ if (ext === "png") {
183
+ const clamped = Math.max(1, Math.min(q, 100));
184
+ return img.png({ quality: clamped, compressionLevel: 9, palette: true }).toBuffer();
185
+ }
186
+ if (ext === "webp") {
187
+ return img.webp({ quality: q }).toBuffer();
188
+ }
189
+ if (ext === "avif") {
190
+ return img.avif({ quality: q }).toBuffer();
191
+ }
192
+ // 其他统一转jpeg
193
+ return img.jpeg({ quality: q, mozjpeg: true }).toBuffer();
194
+ };
177
195
  // 使用二分查找找到合适的质量值
178
- let left = 1;
179
- let right = 100;
180
- let bestQuality = 100;
196
+ let left = 5;
197
+ let right = 95;
181
198
  let bestBuffer = null;
182
199
  while (left <= right) {
183
200
  const quality = Math.floor((left + right) / 2);
184
- const buffer = await image.jpeg({ quality }).toBuffer();
201
+ const buffer = await encodeWithQuality(quality);
185
202
  if (buffer.length <= targetBytes) {
186
- bestQuality = quality;
187
203
  bestBuffer = buffer;
188
204
  left = quality + 1;
189
205
  }
@@ -191,13 +207,61 @@ async function compressToSize(inputPaths, targetSize, overwrite = false, outputP
191
207
  right = quality - 1;
192
208
  }
193
209
  }
194
- if (bestBuffer) {
195
- await fs.promises.writeFile(outputFilePath, bestBuffer);
196
- results.push(outputFilePath);
197
- }
198
- else {
199
- throw new Error("Could not achieve target size while maintaining acceptable quality");
210
+ let outputBuffer = bestBuffer;
211
+ let outputExt = path.extname(outputFilePath).toLowerCase();
212
+ if (!outputBuffer) {
213
+ const fallback = await encodeWithQuality(Math.max(10, right));
214
+ if (fallback.length < originalSize) {
215
+ outputBuffer = fallback;
216
+ }
217
+ else {
218
+ // PNG等难以降体积时,尝试有损WebP再二分
219
+ const tryWebp = async () => {
220
+ let l = 5, r = 95;
221
+ let buf = null;
222
+ while (l <= r) {
223
+ const q = Math.floor((l + r) / 2);
224
+ const b = await sharp.default(imagePath).webp({ quality: q }).toBuffer();
225
+ if (b.length <= targetBytes) {
226
+ buf = b;
227
+ l = q + 1;
228
+ }
229
+ else {
230
+ r = q - 1;
231
+ }
232
+ }
233
+ // 兜底:若仍未达到,但比原图小,也接受
234
+ if (!buf) {
235
+ const b = await sharp.default(imagePath).webp({ quality: Math.max(10, r) }).toBuffer();
236
+ if (b.length < originalSize)
237
+ return b;
238
+ }
239
+ return buf;
240
+ };
241
+ const webpBuf = await tryWebp();
242
+ if (webpBuf) {
243
+ outputBuffer = webpBuf;
244
+ // 将输出后缀改为 .webp
245
+ if (!overwrite) {
246
+ const parsed = path.parse(outputFilePath);
247
+ outputFilePath = path.join(parsed.dir, `${parsed.name}.webp`);
248
+ outputExt = '.webp';
249
+ }
250
+ else {
251
+ outputExt = '.webp';
252
+ }
253
+ }
254
+ else {
255
+ if (!overwrite) {
256
+ await fs.promises.copyFile(imagePath, outputFilePath);
257
+ }
258
+ results.push(outputFilePath);
259
+ continue;
260
+ }
261
+ }
200
262
  }
263
+ await fs.promises.writeFile(outputFilePath, outputBuffer);
264
+ results.push(outputFilePath);
201
265
  }
202
266
  catch (error) {
203
267
  const errorMsg = `Error compressing ${imagePath}: ${error instanceof Error ? error.message : String(error)}`;
@@ -221,19 +285,40 @@ async function compressToPercent(inputPaths, percent, overwrite = false, outputP
221
285
  ? imagePath
222
286
  : generateOutputPath(imagePath, outputPath, `_${targetPercent}%`);
223
287
  const stats = fs.statSync(imagePath);
224
- const targetBytes = Math.floor(stats.size * (targetPercent / 100));
225
- const image = sharp.default(imagePath);
226
- const metadata = await image.metadata();
227
- // 使用二分查找找到合适的质量值
228
- let left = 1;
229
- let right = 100;
230
- let bestQuality = 100;
288
+ const originalSize = stats.size;
289
+ if (targetPercent >= 100) {
290
+ if (!overwrite) {
291
+ await fs.promises.copyFile(imagePath, outputFilePath);
292
+ }
293
+ results.push(outputFilePath);
294
+ continue;
295
+ }
296
+ const targetBytes = Math.floor(originalSize * (targetPercent / 100));
297
+ const ext = path.extname(imagePath).toLowerCase().slice(1);
298
+ const encodeWithQuality = async (q) => {
299
+ const img = sharp.default(imagePath);
300
+ if (ext === "jpg" || ext === "jpeg") {
301
+ return img.jpeg({ quality: q, mozjpeg: true }).toBuffer();
302
+ }
303
+ if (ext === "png") {
304
+ const clamped = Math.max(1, Math.min(q, 100));
305
+ return img.png({ quality: clamped, compressionLevel: 9, palette: true }).toBuffer();
306
+ }
307
+ if (ext === "webp") {
308
+ return img.webp({ quality: q }).toBuffer();
309
+ }
310
+ if (ext === "avif") {
311
+ return img.avif({ quality: q }).toBuffer();
312
+ }
313
+ return img.jpeg({ quality: q, mozjpeg: true }).toBuffer();
314
+ };
315
+ let left = 5;
316
+ let right = 95;
231
317
  let bestBuffer = null;
232
318
  while (left <= right) {
233
319
  const quality = Math.floor((left + right) / 2);
234
- const buffer = await image.jpeg({ quality }).toBuffer();
320
+ const buffer = await encodeWithQuality(quality);
235
321
  if (buffer.length <= targetBytes) {
236
- bestQuality = quality;
237
322
  bestBuffer = buffer;
238
323
  left = quality + 1;
239
324
  }
@@ -241,13 +326,22 @@ async function compressToPercent(inputPaths, percent, overwrite = false, outputP
241
326
  right = quality - 1;
242
327
  }
243
328
  }
244
- if (bestBuffer) {
245
- await fs.promises.writeFile(outputFilePath, bestBuffer);
246
- results.push(outputFilePath);
247
- }
248
- else {
249
- throw new Error("Could not achieve target size while maintaining acceptable quality");
329
+ let outputBuffer = bestBuffer;
330
+ if (!outputBuffer) {
331
+ const fallback = await encodeWithQuality(Math.max(10, right));
332
+ if (fallback.length < originalSize) {
333
+ outputBuffer = fallback;
334
+ }
335
+ else {
336
+ if (!overwrite) {
337
+ await fs.promises.copyFile(imagePath, outputFilePath);
338
+ }
339
+ results.push(outputFilePath);
340
+ continue;
341
+ }
250
342
  }
343
+ await fs.promises.writeFile(outputFilePath, outputBuffer);
344
+ results.push(outputFilePath);
251
345
  }
252
346
  catch (error) {
253
347
  const errorMsg = `Error compressing ${imagePath}: ${error instanceof Error ? error.message : String(error)}`;
package/package.json CHANGED
@@ -1,42 +1,42 @@
1
- {
2
- "name": "@mcpcn/mcp-image-compressor",
3
- "version": "1.0.3",
4
- "description": "MCP server for image compression",
5
- "main": "dist/index.js",
6
- "scripts": {
7
- "build": "tsc && chmod +x dist/index.js",
8
- "start": "node dist/index.js",
9
- "dev": "ts-node index.ts",
10
- "serve": "node dist/index.js"
11
- },
12
- "bin": {
13
- "mcp-image-compressor": "dist/index.js"
14
- },
15
- "files": [
16
- "dist",
17
- "README.md"
18
- ],
19
- "publishConfig": {
20
- "access": "public"
21
- },
22
- "keywords": [
23
- "mcp",
24
- "image",
25
- "compressor",
26
- "sharp",
27
- "model-context-protocol"
28
- ],
29
- "author": "mcpcn",
30
- "license": "MIT",
31
- "dependencies": {
32
- "@modelcontextprotocol/sdk": "1.12.0",
33
- "sharp": "^0.33.5",
34
- "semver": "^7.6.3"
35
- },
36
- "devDependencies": {
37
- "@types/node": "^20.0.0",
38
- "@types/sharp": "^0.31.1",
39
- "ts-node": "^10.9.1",
40
- "typescript": "^5.0.0"
41
- }
42
- }
1
+ {
2
+ "name": "@mcpcn/mcp-image-compressor",
3
+ "version": "1.0.4",
4
+ "description": "MCP server for image compression",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "start": "node dist/index.js",
9
+ "dev": "ts-node index.ts",
10
+ "serve": "node dist/index.js"
11
+ },
12
+ "bin": {
13
+ "mcp-image-compressor": "dist/index.js"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "keywords": [
23
+ "mcp",
24
+ "image",
25
+ "compressor",
26
+ "sharp",
27
+ "model-context-protocol"
28
+ ],
29
+ "author": "mcpcn",
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "1.12.0",
33
+ "sharp": "^0.33.5",
34
+ "semver": "^7.6.3"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^20.0.0",
38
+ "@types/sharp": "^0.31.1",
39
+ "ts-node": "^10.9.1",
40
+ "typescript": "^5.0.0"
41
+ }
42
+ }