smart-image-scraper-mcp 2.9.3 → 2.10.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-image-scraper-mcp",
3
- "version": "2.9.3",
3
+ "version": "2.10.1",
4
4
  "description": "全网智能图片抓取 MCP 服务器 - 支持 Bing/Google 图片搜索、验证和下载",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -175,7 +175,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
175
175
  };
176
176
  }
177
177
 
178
- try {
178
+ // MCP 层最外层超时保护(60秒硬限制)
179
+ const MCP_TIMEOUT = 60000;
180
+
181
+ const executeWithTimeout = async () => {
179
182
  // 主流做法:每个请求创建新的 Orchestrator 实例,确保无状态
180
183
  const orchestrator = new Orchestrator();
181
184
 
@@ -211,13 +214,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
211
214
  return {
212
215
  content: [{ type: 'text', text: formattedResult }],
213
216
  };
217
+ };
218
+
219
+ try {
220
+ // 使用 Promise.race 确保一定会在超时内返回
221
+ const result = await Promise.race([
222
+ executeWithTimeout(),
223
+ new Promise((_, reject) =>
224
+ setTimeout(() => reject(new Error('MCP_TIMEOUT: 请求超时(60秒),请减少关键词数量或稍后重试')), MCP_TIMEOUT)
225
+ )
226
+ ]);
227
+ return result;
214
228
  } catch (error) {
215
229
  // 主流做法:简洁的错误处理,使用 stderr 输出日志
216
230
  console.error(`[MCP Error] ${error.message}`);
217
231
  return {
218
232
  content: [{
219
233
  type: 'text',
220
- text: `## ❌ 执行错误\n\n**错误信息**: ${error.message}\n\n请检查网络连接或稍后重试。`
234
+ text: `## ❌ 执行错误\n\n**错误信息**: ${error.message}\n\n请减少关键词数量或稍后重试。`
221
235
  }],
222
236
  isError: true,
223
237
  };
@@ -403,7 +403,7 @@ export class Orchestrator {
403
403
  const options = { size, safeSearch, aspect, targetSize, fit, position };
404
404
 
405
405
  const startTime = Date.now();
406
- const keywords = this.parseKeywords(query);
406
+ let keywords = this.parseKeywords(query);
407
407
 
408
408
  if (keywords.length === 0) {
409
409
  return {
@@ -412,6 +412,13 @@ export class Orchestrator {
412
412
  };
413
413
  }
414
414
 
415
+ // 限制最大关键词数量为 10 个,避免阻塞
416
+ const MAX_KEYWORDS = 10;
417
+ if (keywords.length > MAX_KEYWORDS) {
418
+ logger.warn(`Too many keywords (${keywords.length}), limiting to ${MAX_KEYWORDS}`);
419
+ keywords = keywords.slice(0, MAX_KEYWORDS);
420
+ }
421
+
415
422
  logger.info(`Starting task: mode=${mode}, keywords=${keywords.join(', ')}, count=${count}, source=${source}`);
416
423
 
417
424
  // 根据模式选择处理函数
@@ -419,12 +426,23 @@ export class Orchestrator {
419
426
  ? this.processKeywordLink.bind(this)
420
427
  : this.processKeywordDownload.bind(this);
421
428
 
422
- // 并发处理关键词
423
- const results = await Promise.all(
424
- keywords.map(keyword =>
425
- this.keywordLimit(() => processFunc(keyword, count, source, options))
426
- )
427
- );
429
+ // 串行处理关键词,避免阻塞事件循环
430
+ const results = [];
431
+ for (const keyword of keywords) {
432
+ try {
433
+ const result = await processFunc(keyword, count, source, options);
434
+ results.push(result);
435
+ // 让出事件循环,确保 MCP 通信不被阻塞
436
+ await new Promise(resolve => setImmediate(resolve));
437
+ } catch (error) {
438
+ results.push({
439
+ keyword,
440
+ success: false,
441
+ error: error.message,
442
+ duration: 0,
443
+ });
444
+ }
445
+ }
428
446
 
429
447
  // 汇总结果
430
448
  const successResults = results.filter(r => r.success);