smart-image-scraper-mcp 2.12.2 → 2.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-image-scraper-mcp",
3
- "version": "2.12.2",
3
+ "version": "2.12.4",
4
4
  "description": "全网智能图片抓取 MCP 服务器 - 支持 Bing/Google 图片搜索、验证和下载",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -33,7 +33,7 @@ function ensureSaveRoot(savePath) {
33
33
  fs.unlinkSync(testFile);
34
34
  return savePath;
35
35
  } catch (error) {
36
- console.error(`[CONFIG] Cannot write to SAVE_ROOT: ${savePath}, using default`);
36
+ process.stderr.write(`[CONFIG] Cannot write to SAVE_ROOT: ${savePath}, using default\n`);
37
37
  const defaultPath = path.join(__dirname, '../../images');
38
38
  if (!fs.existsSync(defaultPath)) {
39
39
  fs.mkdirSync(defaultPath, { recursive: true });
package/src/index.js CHANGED
@@ -229,7 +229,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
229
229
  } catch (error) {
230
230
  clearTimeout(mcpTimeoutId);
231
231
  orchestrator.cleanup();
232
- console.error(`[MCP Error] ${error.message}`);
232
+ process.stderr.write(`[MCP Error] ${error.message}\n`);
233
233
  return {
234
234
  content: [{
235
235
  type: 'text',
@@ -242,16 +242,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
242
242
 
243
243
  // 启动服务器(主流做法:最简启动,使用 stderr 输出日志避免干扰 stdio 通信)
244
244
  async function main() {
245
- console.error(`[MCP] Starting Smart Image Scraper v${packageJson.version}`);
246
- console.error(`[MCP] Save root: ${config.SAVE_ROOT}`);
245
+ process.stderr.write(`[MCP] Starting Smart Image Scraper v${packageJson.version}\n`);
246
+ process.stderr.write(`[MCP] Save root: ${config.SAVE_ROOT}\n`);
247
247
 
248
248
  const transport = new StdioServerTransport();
249
249
  await server.connect(transport);
250
250
 
251
- console.error('[MCP] Server is running');
251
+ process.stderr.write('[MCP] Server is running\n');
252
252
  }
253
253
 
254
254
  main().catch((error) => {
255
- console.error(`[MCP] Startup error: ${error.message}`);
255
+ process.stderr.write(`[MCP] Startup error: ${error.message}\n`);
256
256
  process.exit(1);
257
257
  });
@@ -43,6 +43,10 @@ class Logger {
43
43
  this.logFile = options.logFile || process.env.LOG_FILE;
44
44
  this.maxLogSize = options.maxLogSize || 10 * 1024 * 1024; // 10MB
45
45
 
46
+ // stderr 限流计数器(每秒窗口)
47
+ this._stderrWindowStart = 0;
48
+ this._stderrWindowCount = 0;
49
+
46
50
  // 初始化文件日志
47
51
  if (this.logFile) {
48
52
  this._initFileLogging();
@@ -127,17 +131,26 @@ class Logger {
127
131
  }
128
132
 
129
133
  /**
130
- * 写入日志
134
+ * 写入日志(带限流保护)
131
135
  */
132
136
  _write(level, message, data) {
133
137
  if (level < this.level) return;
134
138
 
135
139
  const formatted = this._format(level, message, data);
136
140
 
137
- // 输出到 stderr(使用 process.stderr.write 避免 console.error 阻塞 MCP stdio)
138
- // 仅输出 WARN 及以上级别到 stderr,减少 IO 压力
141
+ // 输出到 stderr(带限流:每秒最多 20 条,防止高频调用时 stderr 缓冲区堆积阻塞事件循环)
139
142
  if (level >= LogLevel.WARN) {
140
- process.stderr.write(formatted + '\n');
143
+ const now = Date.now();
144
+ if (now - this._stderrWindowStart > 1000) {
145
+ // 新的1秒窗口
146
+ this._stderrWindowStart = now;
147
+ this._stderrWindowCount = 0;
148
+ }
149
+ if (this._stderrWindowCount < 20) {
150
+ this._stderrWindowCount++;
151
+ process.stderr.write(formatted + '\n');
152
+ }
153
+ // 超出限流的日志静默丢弃,不阻塞
141
154
  }
142
155
 
143
156
  // 输出到文件(异步写入,避免阻塞事件循环)
@@ -169,10 +169,11 @@ export class FileManager {
169
169
 
170
170
  response = await httpClient.get(url, {
171
171
  responseType: 'stream',
172
- timeout: 20000, // 连接超时20
172
+ timeout: 10000, // 连接超时10
173
173
  maxContentLength: 50 * 1024 * 1024, // 最大50MB
174
174
  maxBodyLength: 50 * 1024 * 1024,
175
175
  headers: downloadHeaders,
176
+ signal: AbortSignal.timeout(12000), // 12秒硬超时,防止连接挂起
176
177
  });
177
178
 
178
179
  if (response.status !== 200) {
@@ -214,7 +215,7 @@ export class FileManager {
214
215
  let downloadedBytes = 0;
215
216
  const maxBytes = 50 * 1024 * 1024;
216
217
 
217
- // 下载超时保护(30秒)
218
+ // 下载超时保护(15秒)
218
219
  downloadTimeout = setTimeout(() => {
219
220
  if (!resolved) {
220
221
  resolved = true;
@@ -223,7 +224,7 @@ export class FileManager {
223
224
  this._cleanupFile(filePath);
224
225
  resolve({ success: false, url, error: 'Download timeout' });
225
226
  }
226
- }, 30000);
227
+ }, 15000);
227
228
 
228
229
  response.data.on('data', (chunk) => {
229
230
  downloadedBytes += chunk.length;