@mcpcn/mcp-image-compressor 1.0.1 → 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.
- package/README.md +286 -286
- package/dist/index.js +223 -101
- 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
|
@@ -41,7 +41,16 @@ const sharp = __importStar(require("sharp"));
|
|
|
41
41
|
const fs = __importStar(require("fs"));
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
// 支持的图片格式
|
|
44
|
-
const SUPPORTED_FORMATS = [
|
|
44
|
+
const SUPPORTED_FORMATS = [
|
|
45
|
+
"jpeg",
|
|
46
|
+
"jpg",
|
|
47
|
+
"png",
|
|
48
|
+
"webp",
|
|
49
|
+
"gif",
|
|
50
|
+
"avif",
|
|
51
|
+
"tiff",
|
|
52
|
+
"bmp",
|
|
53
|
+
];
|
|
45
54
|
// 工具函数:检查文件是否是图片
|
|
46
55
|
function isImage(filePath) {
|
|
47
56
|
const ext = path.extname(filePath).toLowerCase().slice(1);
|
|
@@ -52,8 +61,8 @@ function getImagesFromDirectory(dirPath) {
|
|
|
52
61
|
try {
|
|
53
62
|
const files = fs.readdirSync(dirPath);
|
|
54
63
|
return files
|
|
55
|
-
.map(file => path.join(dirPath, file))
|
|
56
|
-
.filter(filePath => {
|
|
64
|
+
.map((file) => path.join(dirPath, file))
|
|
65
|
+
.filter((filePath) => {
|
|
57
66
|
const stat = fs.statSync(filePath);
|
|
58
67
|
return stat.isFile() && isImage(filePath);
|
|
59
68
|
});
|
|
@@ -88,28 +97,28 @@ function parseSize(sizeStr) {
|
|
|
88
97
|
const s = sizeStr.trim().toLowerCase();
|
|
89
98
|
const match = s.match(/^(\d+(?:\.\d+)?)(b|kb|k|mb|m|gb|g)?$/);
|
|
90
99
|
if (!match) {
|
|
91
|
-
throw new Error(
|
|
100
|
+
throw new Error("Invalid size format. Examples: 200k, 500kb, 1m, 1mb, 1g, 1024");
|
|
92
101
|
}
|
|
93
102
|
const value = parseFloat(match[1]);
|
|
94
|
-
const unit =
|
|
103
|
+
const unit = match[2] || "b";
|
|
95
104
|
if (value <= 0) {
|
|
96
|
-
throw new Error(
|
|
105
|
+
throw new Error("Size must be greater than 0");
|
|
97
106
|
}
|
|
98
107
|
let multiplier = 1;
|
|
99
108
|
switch (unit) {
|
|
100
|
-
case
|
|
109
|
+
case "b":
|
|
101
110
|
multiplier = 1;
|
|
102
111
|
break;
|
|
103
|
-
case
|
|
104
|
-
case
|
|
112
|
+
case "kb":
|
|
113
|
+
case "k":
|
|
105
114
|
multiplier = 1024;
|
|
106
115
|
break;
|
|
107
|
-
case
|
|
108
|
-
case
|
|
116
|
+
case "mb":
|
|
117
|
+
case "m":
|
|
109
118
|
multiplier = 1024 * 1024;
|
|
110
119
|
break;
|
|
111
|
-
case
|
|
112
|
-
case
|
|
120
|
+
case "gb":
|
|
121
|
+
case "g":
|
|
113
122
|
multiplier = 1024 * 1024 * 1024;
|
|
114
123
|
break;
|
|
115
124
|
default:
|
|
@@ -126,7 +135,7 @@ function parsePercent(percentStr) {
|
|
|
126
135
|
}
|
|
127
136
|
const percent = parseFloat(match[1]);
|
|
128
137
|
if (percent <= 0 || percent > 100) {
|
|
129
|
-
throw new Error(
|
|
138
|
+
throw new Error("Percent must be between 0 and 100");
|
|
130
139
|
}
|
|
131
140
|
return percent;
|
|
132
141
|
}
|
|
@@ -149,33 +158,48 @@ async function compressToSize(inputPaths, targetSize, overwrite = false, outputP
|
|
|
149
158
|
const images = processInputPaths(inputPaths);
|
|
150
159
|
const targetBytes = parseSize(targetSize);
|
|
151
160
|
const results = [];
|
|
161
|
+
const errors = [];
|
|
152
162
|
for (const imagePath of images) {
|
|
153
163
|
try {
|
|
154
|
-
|
|
155
|
-
imagePath
|
|
156
|
-
generateOutputPath(imagePath, outputPath, `_${targetSize.toLowerCase()}`);
|
|
157
|
-
const image = sharp.default(imagePath);
|
|
158
|
-
const metadata = await image.metadata();
|
|
164
|
+
let outputFilePath = overwrite
|
|
165
|
+
? imagePath
|
|
166
|
+
: generateOutputPath(imagePath, outputPath, `_${targetSize.toLowerCase()}`);
|
|
159
167
|
const stats = fs.statSync(imagePath);
|
|
160
|
-
|
|
168
|
+
const originalSize = stats.size;
|
|
169
|
+
if (originalSize <= targetBytes) {
|
|
161
170
|
if (!overwrite) {
|
|
162
171
|
await fs.promises.copyFile(imagePath, outputFilePath);
|
|
163
172
|
}
|
|
164
173
|
results.push(outputFilePath);
|
|
165
174
|
continue;
|
|
166
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
|
+
};
|
|
167
195
|
// 使用二分查找找到合适的质量值
|
|
168
|
-
let left =
|
|
169
|
-
let right =
|
|
170
|
-
let bestQuality = 100;
|
|
196
|
+
let left = 5;
|
|
197
|
+
let right = 95;
|
|
171
198
|
let bestBuffer = null;
|
|
172
199
|
while (left <= right) {
|
|
173
200
|
const quality = Math.floor((left + right) / 2);
|
|
174
|
-
const buffer = await
|
|
175
|
-
.jpeg({ quality })
|
|
176
|
-
.toBuffer();
|
|
201
|
+
const buffer = await encodeWithQuality(quality);
|
|
177
202
|
if (buffer.length <= targetBytes) {
|
|
178
|
-
bestQuality = quality;
|
|
179
203
|
bestBuffer = buffer;
|
|
180
204
|
left = quality + 1;
|
|
181
205
|
}
|
|
@@ -183,19 +207,69 @@ async function compressToSize(inputPaths, targetSize, overwrite = false, outputP
|
|
|
183
207
|
right = quality - 1;
|
|
184
208
|
}
|
|
185
209
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
+
}
|
|
192
262
|
}
|
|
263
|
+
await fs.promises.writeFile(outputFilePath, outputBuffer);
|
|
264
|
+
results.push(outputFilePath);
|
|
193
265
|
}
|
|
194
266
|
catch (error) {
|
|
195
|
-
|
|
267
|
+
const errorMsg = `Error compressing ${imagePath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
268
|
+
console.error(errorMsg);
|
|
269
|
+
errors.push(errorMsg);
|
|
196
270
|
}
|
|
197
271
|
}
|
|
198
|
-
return results;
|
|
272
|
+
return { results, errors };
|
|
199
273
|
}
|
|
200
274
|
/**
|
|
201
275
|
* 图片压缩到原始大小的百分比
|
|
@@ -204,27 +278,47 @@ async function compressToPercent(inputPaths, percent, overwrite = false, outputP
|
|
|
204
278
|
const images = processInputPaths(inputPaths);
|
|
205
279
|
const targetPercent = parsePercent(percent);
|
|
206
280
|
const results = [];
|
|
281
|
+
const errors = [];
|
|
207
282
|
for (const imagePath of images) {
|
|
208
283
|
try {
|
|
209
|
-
const outputFilePath = overwrite
|
|
210
|
-
imagePath
|
|
211
|
-
generateOutputPath(imagePath, outputPath, `_${targetPercent}%`);
|
|
284
|
+
const outputFilePath = overwrite
|
|
285
|
+
? imagePath
|
|
286
|
+
: generateOutputPath(imagePath, outputPath, `_${targetPercent}%`);
|
|
212
287
|
const stats = fs.statSync(imagePath);
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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;
|
|
220
317
|
let bestBuffer = null;
|
|
221
318
|
while (left <= right) {
|
|
222
319
|
const quality = Math.floor((left + right) / 2);
|
|
223
|
-
const buffer = await
|
|
224
|
-
.jpeg({ quality })
|
|
225
|
-
.toBuffer();
|
|
320
|
+
const buffer = await encodeWithQuality(quality);
|
|
226
321
|
if (buffer.length <= targetBytes) {
|
|
227
|
-
bestQuality = quality;
|
|
228
322
|
bestBuffer = buffer;
|
|
229
323
|
left = quality + 1;
|
|
230
324
|
}
|
|
@@ -232,19 +326,30 @@ async function compressToPercent(inputPaths, percent, overwrite = false, outputP
|
|
|
232
326
|
right = quality - 1;
|
|
233
327
|
}
|
|
234
328
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
+
}
|
|
241
342
|
}
|
|
343
|
+
await fs.promises.writeFile(outputFilePath, outputBuffer);
|
|
344
|
+
results.push(outputFilePath);
|
|
242
345
|
}
|
|
243
346
|
catch (error) {
|
|
244
|
-
|
|
347
|
+
const errorMsg = `Error compressing ${imagePath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
348
|
+
console.error(errorMsg);
|
|
349
|
+
errors.push(errorMsg);
|
|
245
350
|
}
|
|
246
351
|
}
|
|
247
|
-
return results;
|
|
352
|
+
return { results, errors };
|
|
248
353
|
}
|
|
249
354
|
const COMPRESS_TO_SIZE_TOOL = {
|
|
250
355
|
name: "image_compress_to_size",
|
|
@@ -255,23 +360,23 @@ const COMPRESS_TO_SIZE_TOOL = {
|
|
|
255
360
|
inputPaths: {
|
|
256
361
|
type: "array",
|
|
257
362
|
items: { type: "string" },
|
|
258
|
-
description: "原图片路径数组,支持图片文件和目录"
|
|
363
|
+
description: "原图片路径数组,支持图片文件和目录",
|
|
259
364
|
},
|
|
260
365
|
size: {
|
|
261
366
|
type: "string",
|
|
262
|
-
description: "要压缩到的的目标大小(如1mb/500kb)"
|
|
367
|
+
description: "要压缩到的的目标大小(如1mb/500kb)",
|
|
263
368
|
},
|
|
264
369
|
overwrite: {
|
|
265
370
|
type: "boolean",
|
|
266
|
-
description: "是否覆盖原图(可选,默认为false)"
|
|
371
|
+
description: "是否覆盖原图(可选,默认为false)",
|
|
267
372
|
},
|
|
268
373
|
outputPath: {
|
|
269
374
|
type: "string",
|
|
270
|
-
description: "存储路径(可选,不覆盖原图时如果没传则为原图所在目录)"
|
|
271
|
-
}
|
|
375
|
+
description: "存储路径(可选,不覆盖原图时如果没传则为原图所在目录)",
|
|
376
|
+
},
|
|
272
377
|
},
|
|
273
|
-
required: ["inputPaths", "size"]
|
|
274
|
-
}
|
|
378
|
+
required: ["inputPaths", "size"],
|
|
379
|
+
},
|
|
275
380
|
};
|
|
276
381
|
const COMPRESS_TO_PERCENT_TOOL = {
|
|
277
382
|
name: "image_compress_to_percent",
|
|
@@ -282,41 +387,45 @@ const COMPRESS_TO_PERCENT_TOOL = {
|
|
|
282
387
|
inputPaths: {
|
|
283
388
|
type: "array",
|
|
284
389
|
items: { type: "string" },
|
|
285
|
-
description: "原图片路径数组,支持图片文件和目录"
|
|
390
|
+
description: "原图片路径数组,支持图片文件和目录",
|
|
286
391
|
},
|
|
287
392
|
percent: {
|
|
288
393
|
type: "string",
|
|
289
|
-
description: "要压缩到原size的百分之多少(如50或50%)"
|
|
394
|
+
description: "要压缩到原size的百分之多少(如50或50%)",
|
|
290
395
|
},
|
|
291
396
|
overwrite: {
|
|
292
397
|
type: "boolean",
|
|
293
|
-
description: "是否覆盖原图(可选,默认为false)"
|
|
398
|
+
description: "是否覆盖原图(可选,默认为false)",
|
|
294
399
|
},
|
|
295
400
|
outputPath: {
|
|
296
401
|
type: "string",
|
|
297
|
-
description: "存储路径(可选,不覆盖原图时如果没传则为原图所在目录)"
|
|
298
|
-
}
|
|
402
|
+
description: "存储路径(可选,不覆盖原图时如果没传则为原图所在目录)",
|
|
403
|
+
},
|
|
299
404
|
},
|
|
300
|
-
required: ["inputPaths", "percent"]
|
|
301
|
-
}
|
|
405
|
+
required: ["inputPaths", "percent"],
|
|
406
|
+
},
|
|
302
407
|
};
|
|
303
|
-
const IMAGE_TOOLS = [
|
|
304
|
-
COMPRESS_TO_SIZE_TOOL,
|
|
305
|
-
COMPRESS_TO_PERCENT_TOOL,
|
|
306
|
-
];
|
|
408
|
+
const IMAGE_TOOLS = [COMPRESS_TO_SIZE_TOOL, COMPRESS_TO_PERCENT_TOOL];
|
|
307
409
|
async function handleCompressToSize(inputPaths, size, overwrite = false, outputPath) {
|
|
308
410
|
try {
|
|
309
|
-
const results = await compressToSize(inputPaths, size, overwrite, outputPath);
|
|
411
|
+
const { results, errors } = await compressToSize(inputPaths, size, overwrite, outputPath);
|
|
412
|
+
const hasErrors = errors.length > 0;
|
|
413
|
+
const totalProcessed = results.length + errors.length;
|
|
310
414
|
return {
|
|
311
|
-
content: [
|
|
415
|
+
content: [
|
|
416
|
+
{
|
|
312
417
|
type: "text",
|
|
313
418
|
text: JSON.stringify({
|
|
314
|
-
success:
|
|
315
|
-
message:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
419
|
+
success: !hasErrors,
|
|
420
|
+
message: hasErrors
|
|
421
|
+
? `处理了 ${totalProcessed} 张图片,成功压缩 ${results.length} 张,失败 ${errors.length} 张到目标大小 ${size}`
|
|
422
|
+
: `成功压缩了 ${results.length} 张图片到目标大小 ${size}`,
|
|
423
|
+
compressedFiles: results,
|
|
424
|
+
errors: hasErrors ? errors : undefined,
|
|
425
|
+
}, null, 2),
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
isError: hasErrors,
|
|
320
429
|
};
|
|
321
430
|
}
|
|
322
431
|
catch (error) {
|
|
@@ -325,17 +434,24 @@ async function handleCompressToSize(inputPaths, size, overwrite = false, outputP
|
|
|
325
434
|
}
|
|
326
435
|
async function handleCompressToPercent(inputPaths, percent, overwrite = false, outputPath) {
|
|
327
436
|
try {
|
|
328
|
-
const results = await compressToPercent(inputPaths, percent, overwrite, outputPath);
|
|
437
|
+
const { results, errors } = await compressToPercent(inputPaths, percent, overwrite, outputPath);
|
|
438
|
+
const hasErrors = errors.length > 0;
|
|
439
|
+
const totalProcessed = results.length + errors.length;
|
|
329
440
|
return {
|
|
330
|
-
content: [
|
|
441
|
+
content: [
|
|
442
|
+
{
|
|
331
443
|
type: "text",
|
|
332
444
|
text: JSON.stringify({
|
|
333
|
-
success:
|
|
334
|
-
message:
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
445
|
+
success: !hasErrors,
|
|
446
|
+
message: hasErrors
|
|
447
|
+
? `处理了 ${totalProcessed} 张图片,成功压缩 ${results.length} 张,失败 ${errors.length} 张到原始大小的 ${parsePercent(percent)}%`
|
|
448
|
+
: `成功压缩了 ${results.length} 张图片到原始大小的 ${parsePercent(percent)}%`,
|
|
449
|
+
compressedFiles: results,
|
|
450
|
+
errors: hasErrors ? errors : undefined,
|
|
451
|
+
}, null, 2),
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
isError: hasErrors,
|
|
339
455
|
};
|
|
340
456
|
}
|
|
341
457
|
catch (error) {
|
|
@@ -359,30 +475,36 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
359
475
|
try {
|
|
360
476
|
switch (request.params.name) {
|
|
361
477
|
case "image_compress_to_size": {
|
|
362
|
-
const { inputPaths, size, overwrite, outputPath } = request.params
|
|
478
|
+
const { inputPaths, size, overwrite, outputPath } = request.params
|
|
479
|
+
.arguments;
|
|
363
480
|
return await handleCompressToSize(inputPaths, size, overwrite, outputPath);
|
|
364
481
|
}
|
|
365
482
|
case "image_compress_to_percent": {
|
|
366
|
-
const { inputPaths, percent, overwrite, outputPath } = request.params
|
|
483
|
+
const { inputPaths, percent, overwrite, outputPath } = request.params
|
|
484
|
+
.arguments;
|
|
367
485
|
return await handleCompressToPercent(inputPaths, percent, overwrite, outputPath);
|
|
368
486
|
}
|
|
369
487
|
default:
|
|
370
488
|
return {
|
|
371
|
-
content: [
|
|
489
|
+
content: [
|
|
490
|
+
{
|
|
372
491
|
type: "text",
|
|
373
|
-
text: `未知工具: ${request.params.name}
|
|
374
|
-
}
|
|
375
|
-
|
|
492
|
+
text: `未知工具: ${request.params.name}`,
|
|
493
|
+
},
|
|
494
|
+
],
|
|
495
|
+
isError: true,
|
|
376
496
|
};
|
|
377
497
|
}
|
|
378
498
|
}
|
|
379
499
|
catch (error) {
|
|
380
500
|
return {
|
|
381
|
-
content: [
|
|
501
|
+
content: [
|
|
502
|
+
{
|
|
382
503
|
type: "text",
|
|
383
|
-
text: `错误: ${error instanceof Error ? error.message : String(error)}
|
|
384
|
-
}
|
|
385
|
-
|
|
504
|
+
text: `错误: ${error instanceof Error ? error.message : String(error)}`,
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
isError: true,
|
|
386
508
|
};
|
|
387
509
|
}
|
|
388
510
|
});
|
package/package.json
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@mcpcn/mcp-image-compressor",
|
|
3
|
-
"version": "1.0.
|
|
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
|
-
}
|
|
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
|
+
}
|