smart-image-scraper-mcp 2.5.2 → 2.7.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/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  ## ✨ 核心特性
9
9
 
10
- ### 🚀 高性能架构 (v2.4.0)
10
+ ### 🚀 高性能架构 (v2.7.0)
11
11
  - **多请求并行**:同时处理 5 个 MCP 请求
12
12
  - **并行翻页搜索**:同时获取多页结果,速度提升 5x
13
13
  - **HTTP 连接池**:Keep-Alive 复用 TCP 连接
@@ -23,7 +23,8 @@
23
23
  - **尺寸过滤**:small/medium/large/wallpaper
24
24
  - **宽高比过滤**:wide(横屏)/tall(竖屏)/square(正方形)
25
25
  - **尺寸统一**:下载后自动裁剪/缩放到指定尺寸
26
- - **质量优先**:自动按图片质量排序,高清优先
26
+ - **质量控制**:三种质量模式(fast/balanced/high)
27
+ - **文件大小过滤**:按最小文件大小筛选高质量图片
27
28
 
28
29
  ## 安装
29
30
 
@@ -100,6 +101,9 @@ npm install
100
101
  | `count` | number | ❌ | 每个关键词获取的图片数量,默认 10 |
101
102
  | `source` | string | ❌ | 搜索源,支持 `bing` 和 `google`,默认 `bing` |
102
103
  | `size` | string | ❌ | 图片尺寸:`all`, `small`, `medium`, `large`, `wallpaper`,默认 `all` |
104
+ | `aspect` | string | ❌ | 宽高比:`all`, `wide`, `tall`, `square`,默认 `all` |
105
+ | `quality` | string | ❌ | 质量模式:`fast`, `balanced`, `high`,默认 `balanced` |
106
+ | `minFileSize` | string | ❌ | 最小文件大小:`any`, `50kb`, `100kb`, `200kb`, `500kb`, `1mb`,默认 `any` |
103
107
  | `safeSearch` | string | ❌ | 安全搜索:`off`, `moderate`, `strict`,默认 `moderate` |
104
108
 
105
109
  **使用示例**:
@@ -128,6 +132,16 @@ npm install
128
132
  "size": "wallpaper",
129
133
  "safeSearch": "strict"
130
134
  }
135
+
136
+ // 高质量模式 - 获取验证过的高清图片
137
+ {
138
+ "query": "风景",
139
+ "mode": "link",
140
+ "count": 10,
141
+ "size": "large",
142
+ "quality": "high",
143
+ "minFileSize": "100kb"
144
+ }
131
145
  ```
132
146
 
133
147
  ## 架构设计
@@ -267,6 +281,7 @@ npm test
267
281
 
268
282
  | 版本 | 日期 | 主要更新 |
269
283
  |------|------|----------|
284
+ | 2.7.0 | 2026-02 | 新增质量控制:quality 参数(fast/balanced/high)、minFileSize 文件大小过滤 |
270
285
  | 2.4.0 | 2026-02 | 修复所有分析问题:超时策略统一、缓存键一致、版本号动态读取 |
271
286
  | 2.3.0 | 2026-02 | 性能优化:HTTP 连接池、并行翻页搜索、内存泄漏修复 |
272
287
  | 2.2.0 | 2026-02 | 请求队列管理系统、自动资源释放 |
@@ -289,8 +304,11 @@ A: 检查网络连接,或尝试更换搜索源(bing → google)
289
304
  ### Q: 下载速度慢?
290
305
  A: 可以设置 `HTTP_PROXY` 环境变量使用代理
291
306
 
292
- ### Q: 如何查看队列状态?
293
- A: 日志中会显示 `[Queue]` 相关信息
307
+ ### Q: Windsurf 中连续调用多次后卡住?
308
+ A: 这是 Windsurf MCP 客户端的已知限制。建议:
309
+ 1. **使用批量查询**:`"skiing,ice skating,snowmobile"` 一次获取多个关键词
310
+ 2. **开新会话**:每3次调用后开一个新的聊天会话
311
+ 3. 等待 Windsurf 官方修复
294
312
 
295
313
  ## License
296
314
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-image-scraper-mcp",
3
- "version": "2.5.2",
3
+ "version": "2.7.0",
4
4
  "description": "全网智能图片抓取 MCP 服务器 - 支持 Bing/Google 图片搜索、验证和下载",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -51,10 +51,10 @@ export const config = {
51
51
  USER_AGENT: process.env.USER_AGENT || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
52
52
  REQUEST_TIMEOUT: parseEnvInt(process.env.REQUEST_TIMEOUT, 10000), // 10秒超时
53
53
 
54
- // 并发控制
55
- MAX_KEYWORD_CONCURRENCY: parseEnvInt(process.env.MAX_KEYWORD_CONCURRENCY, 2),
56
- MAX_VALIDATE_CONCURRENCY: parseEnvInt(process.env.MAX_VALIDATE_CONCURRENCY, 10),
57
- MAX_DOWNLOAD_CONCURRENCY: parseEnvInt(process.env.MAX_DOWNLOAD_CONCURRENCY, 5),
54
+ // 并发控制 - 降低默认值避免资源耗尽
55
+ MAX_KEYWORD_CONCURRENCY: parseEnvInt(process.env.MAX_KEYWORD_CONCURRENCY, 1),
56
+ MAX_VALIDATE_CONCURRENCY: parseEnvInt(process.env.MAX_VALIDATE_CONCURRENCY, 8),
57
+ MAX_DOWNLOAD_CONCURRENCY: parseEnvInt(process.env.MAX_DOWNLOAD_CONCURRENCY, 3),
58
58
 
59
59
  // 存储配置
60
60
  SAVE_ROOT: validatedSaveRoot,
@@ -0,0 +1,340 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * 全网智能图片抓取 MCP 服务器
5
+ * 基于 Model Context Protocol 的图片搜索、验证、下载工具
6
+ *
7
+ * 设计原则(模仿主流 MCP 实现):
8
+ * - 简洁:最小化基础设施代码
9
+ * - 无状态:每个请求独立处理
10
+ * - 可靠:简单的错误处理,避免资源泄漏
11
+ */
12
+
13
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
14
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
15
+ import {
16
+ CallToolRequestSchema,
17
+ ListToolsRequestSchema,
18
+ } from '@modelcontextprotocol/sdk/types.js';
19
+ import { createRequire } from 'module';
20
+
21
+ import { Orchestrator } from './services/orchestrator.js';
22
+ import config from './config/index.js';
23
+
24
+ // 从 package.json 读取版本号
25
+ const require = createRequire(import.meta.url);
26
+ const packageJson = require('../package.json');
27
+
28
+ // 创建 MCP 服务器
29
+ const server = new Server(
30
+ {
31
+ name: 'smart-image-scraper',
32
+ version: packageJson.version, // 动态读取版本号
33
+ },
34
+ {
35
+ capabilities: {
36
+ tools: {},
37
+ },
38
+ }
39
+ );
40
+
41
+ // 创建编排器实例
42
+ const orchestrator = new Orchestrator();
43
+
44
+ // 定义 Tool Schema
45
+ const SMART_SCRAPER_TOOL = {
46
+ name: 'smart_scraper',
47
+ description: `全网智能图片抓取工具 - 从 Bing/Google 搜索、验证、下载高质量图片。
48
+
49
+ 【核心功能】
50
+ 1. 搜索图片链接 (mode=link) - 返回验证过的图片URL列表
51
+ 2. 下载图片 (mode=download) - 下载到本地,自动按质量排序优先高清
52
+ 3. 尺寸统一 (targetSize) - 下载后自动裁剪/缩放到指定尺寸
53
+ 4. 宽高比过滤 (aspect) - 横向/竖向/正方形
54
+
55
+ 【参数选择指南】
56
+ - 用户要"找/搜索/查找图片" → mode="link"
57
+ - 用户要"下载/保存/获取图片" → mode="download"
58
+ - 用户要"高清/大图/壁纸" → size="large" 或 "wallpaper"
59
+ - 用户要"电脑壁纸/横屏/横向" → aspect="wide"
60
+ - 用户要"手机壁纸/竖屏/竖向" → aspect="tall"
61
+ - 用户要"统一尺寸/固定大小" → targetSize="1920x1080" 或预设名
62
+ - 用户要"多种类型图片" → query="猫,狗,鸟"(英文逗号分隔)
63
+
64
+ 【预设尺寸名称】
65
+ - 电脑壁纸: desktop_1080p(1920x1080), desktop_2k(2560x1440), desktop_4k(3840x2160)
66
+ - 手机壁纸: mobile_hd(1080x1920), mobile_2k(1440x2560)
67
+ - 正方形: square_1080(1080x1080), square_512(512x512)
68
+ - 社交媒体: instagram(1080x1080), twitter(1200x675), facebook(1200x630)
69
+
70
+ 【调用示例】
71
+ 1. 搜索5张猫的图片: {"query":"可爱的猫","mode":"link","count":5}
72
+ 2. 下载10张高清风景图: {"query":"风景","mode":"download","count":10,"size":"large"}
73
+ 3. 下载电脑壁纸并统一为1080p: {"query":"风景","mode":"download","count":10,"aspect":"wide","targetSize":"desktop_1080p"}
74
+ 4. 下载手机壁纸: {"query":"动漫","mode":"download","count":10,"aspect":"tall","targetSize":"mobile_hd"}
75
+ 5. 批量下载多类图片: {"query":"猫,狗,兔子","mode":"download","count":5}`,
76
+ inputSchema: {
77
+ type: 'object',
78
+ properties: {
79
+ query: {
80
+ type: 'string',
81
+ description: '搜索关键词。批量搜索用英文逗号分隔,如 "猫,狗,鸟"。建议使用具体描述性词语如"可爱的橘猫"而非"猫"',
82
+ },
83
+ mode: {
84
+ type: 'string',
85
+ enum: ['link', 'download'],
86
+ description: "运行模式。link=仅返回验证过的图片URL列表(用户只需要链接时使用);download=下载图片到本地文件系统(用户说下载/保存时使用)",
87
+ },
88
+ count: {
89
+ type: 'number',
90
+ description: '每个关键词获取的图片数量。范围1-100,推荐1-20。用户说"几张"用5-10,说"很多"用20-30',
91
+ default: 10,
92
+ },
93
+ source: {
94
+ type: 'string',
95
+ enum: ['bing', 'google'],
96
+ description: '搜索引擎。bing更稳定推荐优先使用,google结果可能更丰富但可能被限制',
97
+ default: 'bing',
98
+ },
99
+ size: {
100
+ type: 'string',
101
+ enum: ['all', 'small', 'medium', 'large', 'wallpaper'],
102
+ description: '图片尺寸。all=不限;small=小图/图标;medium=中图;large=大图/高清;wallpaper=壁纸级别(1080p+)',
103
+ default: 'all',
104
+ },
105
+ aspect: {
106
+ type: 'string',
107
+ enum: ['all', 'wide', 'tall', 'square'],
108
+ description: '图片宽高比。all=不限;wide=横向/宽屏(电脑壁纸);tall=纵向/竖屏(手机壁纸);square=正方形',
109
+ default: 'all',
110
+ },
111
+ targetSize: {
112
+ type: 'string',
113
+ description: '目标尺寸,下载后统一裁剪/缩放到此尺寸。格式: "宽x高"(如"1920x1080")或预设名(desktop_1080p/desktop_2k/desktop_4k/mobile_hd/mobile_2k/square_1080/instagram/twitter/facebook)',
114
+ },
115
+ fit: {
116
+ type: 'string',
117
+ enum: ['cover', 'contain', 'fill'],
118
+ description: '尺寸处理时的适应模式。cover=裁剪填充(默认,不留白);contain=包含留白;fill=拉伸填充',
119
+ default: 'cover',
120
+ },
121
+ safeSearch: {
122
+ type: 'string',
123
+ enum: ['off', 'moderate', 'strict'],
124
+ description: '安全搜索。off=关闭;moderate=中等过滤(默认);strict=严格过滤(儿童/家庭内容)',
125
+ default: 'moderate',
126
+ },
127
+ },
128
+ required: ['query', 'mode'],
129
+ },
130
+ };
131
+
132
+ // 注册工具列表(主流做法:简单返回)
133
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
134
+ tools: [SMART_SCRAPER_TOOL],
135
+ }));
136
+
137
+ // 注册工具调用(主流做法:每个请求创建新实例,避免状态污染)
138
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
139
+ const { name, arguments: args } = request.params;
140
+
141
+ if (name !== 'smart_scraper') {
142
+ return {
143
+ content: [
144
+ {
145
+ type: 'text',
146
+ text: `未知工具: ${name}`,
147
+ },
148
+ ],
149
+ isError: true,
150
+ };
151
+ }
152
+
153
+ try {
154
+ // 参数校验
155
+ if (!args.query || typeof args.query !== 'string') {
156
+ return {
157
+ content: [
158
+ {
159
+ type: 'text',
160
+ text: '错误: 请提供有效的搜索关键词 (query)',
161
+ },
162
+ ],
163
+ isError: true,
164
+ };
165
+ }
166
+
167
+ if (!args.mode || !['link', 'download'].includes(args.mode)) {
168
+ return {
169
+ content: [
170
+ {
171
+ type: 'text',
172
+ text: "错误: 请指定有效的运行模式 (mode): 'link' 或 'download'",
173
+ },
174
+ ],
175
+ isError: true,
176
+ };
177
+ }
178
+
179
+ // 验证 count 参数
180
+ let count = parseInt(args.count, 10) || 10;
181
+ if (count < 1) count = 1;
182
+ if (count > 100) count = 100; // 限制最大数量
183
+
184
+ // 验证 query 长度
185
+ const query = args.query.trim();
186
+ if (query.length === 0) {
187
+ return {
188
+ content: [
189
+ {
190
+ type: 'text',
191
+ text: '错误: 搜索关键词不能为空',
192
+ },
193
+ ],
194
+ isError: true,
195
+ };
196
+ }
197
+ if (query.length > 500) {
198
+ return {
199
+ content: [
200
+ {
201
+ type: 'text',
202
+ text: '错误: 搜索关键词过长(最大500字符)',
203
+ },
204
+ ],
205
+ isError: true,
206
+ };
207
+ }
208
+
209
+ // 验证 source 参数
210
+ const source = args.source || 'bing';
211
+ if (!['bing', 'google'].includes(source)) {
212
+ return {
213
+ content: [
214
+ {
215
+ type: 'text',
216
+ text: "错误: 无效的搜索源,请使用 'bing' 或 'google'",
217
+ },
218
+ ],
219
+ isError: true,
220
+ };
221
+ }
222
+
223
+ // 检查是否正在关闭
224
+ if (gracefulShutdown.isShuttingDownNow()) {
225
+ return {
226
+ content: [{ type: 'text', text: '服务器正在关闭,无法处理新请求' }],
227
+ isError: true,
228
+ };
229
+ }
230
+
231
+ // 开始操作追踪
232
+ const operation = gracefulShutdown.startOperation(`scraper:${query.substring(0, 20)}`);
233
+ const startTime = Date.now();
234
+
235
+ try {
236
+ // 执行任务(requestQueue 已有超时机制,这里不再重复设置)
237
+ logger.info('Executing smart_scraper', { args });
238
+
239
+ const result = await orchestrator.execute({
240
+ query: query,
241
+ mode: args.mode,
242
+ count: count,
243
+ source: source,
244
+ size: args.size || 'all',
245
+ aspect: args.aspect || 'all',
246
+ targetSize: args.targetSize || null,
247
+ fit: args.fit || 'cover',
248
+ safeSearch: args.safeSearch || 'moderate',
249
+ });
250
+
251
+ // 记录成功指标
252
+ metrics.recordSearch(source, true, Date.now() - startTime, result.results?.length || 0);
253
+
254
+ // 格式化输出
255
+ const formattedResult = orchestrator.formatResult(result);
256
+
257
+ const totalTime = Date.now() - requestStartTime;
258
+ logger.info(`[MCP] 请求完成: ${totalTime}ms, requestId=${result.requestId}`);
259
+
260
+ // 每2个请求清理一次缓存和资源,避免内存累积
261
+ requestCount++;
262
+ if (requestCount >= 2) {
263
+ searchCache.clear();
264
+ validationCache.clear();
265
+ // 强制垃圾回收(如果可用)
266
+ if (global.gc) {
267
+ try { global.gc(); } catch (e) { /* ignore */ }
268
+ }
269
+ requestCount = 0;
270
+ logger.info('[MCP] 缓存已清理');
271
+ }
272
+
273
+ const response = {
274
+ content: [
275
+ {
276
+ type: 'text',
277
+ text: formattedResult,
278
+ },
279
+ ],
280
+ };
281
+
282
+ return response;
283
+ } catch (innerError) {
284
+ // 记录失败指标
285
+ metrics.recordSearch(source, false, Date.now() - startTime);
286
+ metrics.recordError(innerError);
287
+ logger.error(`[MCP] 内部错误: ${innerError.message}`);
288
+ throw innerError;
289
+ } finally {
290
+ // 结束操作追踪
291
+ operation.end();
292
+ }
293
+ } catch (error) {
294
+ const totalTime = Date.now() - requestStartTime;
295
+ logger.error(`[MCP] 请求失败: ${totalTime}ms, error=${error.message}`);
296
+ return {
297
+ content: [
298
+ {
299
+ type: 'text',
300
+ text: `## ❌ 执行错误\n\n**错误信息**: ${error.message}\n\n请检查网络连接或稍后重试。`,
301
+ },
302
+ ],
303
+ isError: true,
304
+ };
305
+ }
306
+ });
307
+
308
+ // 注册关闭回调
309
+ gracefulShutdown.onShutdown(async () => {
310
+ logger.info('Closing MCP server connection...');
311
+ try {
312
+ await server.close();
313
+ } catch (error) {
314
+ logger.warn('Error closing server', { error: error.message });
315
+ }
316
+ });
317
+
318
+ // 启动服务器
319
+ async function main() {
320
+ logger.info('Starting Smart Image Scraper MCP Server...');
321
+ logger.info('Configuration', {
322
+ saveRoot: config.SAVE_ROOT,
323
+ maxKeywordConcurrency: config.MAX_KEYWORD_CONCURRENCY,
324
+ maxDownloadConcurrency: config.MAX_DOWNLOAD_CONCURRENCY,
325
+ });
326
+
327
+ // 执行初始健康检查
328
+ const healthResult = await healthChecker.runAllChecks();
329
+ logger.info('Initial health check', { status: healthResult.status });
330
+
331
+ const transport = new StdioServerTransport();
332
+ await server.connect(transport);
333
+
334
+ logger.info('MCP Server is running and ready to accept requests');
335
+ }
336
+
337
+ main().catch((error) => {
338
+ logger.error('Server startup error', { error: error.message });
339
+ process.exit(1);
340
+ });