@lmcc-dev/mult-fetch-mcp-server 1.0.2 → 1.0.3

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
@@ -12,6 +12,8 @@
12
12
  [![npm downloads](https://img.shields.io/badge/downloads-coming%20soon-lightgrey)](https://www.npmjs.com/package/@lmcc-dev/mult-fetch-mcp-server)
13
13
  [![GitHub last commit](https://img.shields.io/github/last-commit/lmcc-dev/mult-fetch-mcp-server)](https://github.com/lmcc-dev/mult-fetch-mcp-server/commits/main)
14
14
  [![GitHub contributors](https://img.shields.io/github/contributors/lmcc-dev/mult-fetch-mcp-server)](https://github.com/lmcc-dev/mult-fetch-mcp-server/graphs/contributors)
15
+ [![smithery badge](https://smithery.ai/badge/@lmcc-dev/mult-fetch-mcp-server)](https://smithery.ai/server/@lmcc-dev/mult-fetch-mcp-server)
16
+
15
17
 
16
18
  <!-- Future badges to consider:
17
19
  [![codecov](https://codecov.io/gh/lmcc-dev/mult-fetch-mcp-server/branch/main/graph/badge.svg)](https://codecov.io/gh/lmcc-dev/mult-fetch-mcp-server)
@@ -76,6 +78,14 @@ This project implements the Standard Input/Output (Stdio) transport method.
76
78
 
77
79
  ## Installation
78
80
 
81
+ ### Installing via Smithery
82
+
83
+ To install Mult Fetch MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@lmcc-dev/mult-fetch-mcp-server):
84
+
85
+ ```bash
86
+ npx -y @smithery/cli install @lmcc-dev/mult-fetch-mcp-server --client claude
87
+ ```
88
+
79
89
  ### Local Installation
80
90
 
81
91
  ```bash
@@ -509,4 +519,4 @@ MIT
509
519
 
510
520
  ---
511
521
 
512
- Updated by lmcc-dev
522
+ Updated by lmcc-dev
@@ -46,6 +46,30 @@ export class BrowserFetcher {
46
46
  * 内存检查间隔(毫秒) (Memory check interval in milliseconds)
47
47
  */
48
48
  static memoryCheckInterval = 60000;
49
+ /**
50
+ * 常用浏览器的User-Agent列表 (List of common browser User-Agents)
51
+ * 用于模拟不同浏览器的请求 (Used to simulate requests from different browsers)
52
+ */
53
+ static userAgents = [
54
+ // Chrome
55
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
56
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
57
+ // Firefox
58
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0',
59
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:89.0) Gecko/20100101 Firefox/89.0',
60
+ // Safari
61
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15',
62
+ // Edge
63
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.59'
64
+ ];
65
+ /**
66
+ * 获取随机User-Agent (Get random User-Agent)
67
+ * @returns 随机的User-Agent字符串 (Random User-Agent string)
68
+ */
69
+ static getRandomUserAgent() {
70
+ const index = Math.floor(Math.random() * this.userAgents.length);
71
+ return this.userAgents[index];
72
+ }
49
73
  /**
50
74
  * 检查内存使用情况 (Check memory usage)
51
75
  * 定期监控浏览器内存使用,必要时关闭浏览器释放资源 (Periodically monitor browser memory usage, close browser to release resources when necessary)
@@ -156,88 +180,98 @@ export class BrowserFetcher {
156
180
  }
157
181
  return undefined;
158
182
  }
159
- // 获取或启动浏览器实例
160
- static async getBrowser({ proxy, debug = false, useSystemProxy = true }) {
161
- // 如果浏览器正在启动,等待启动完成
162
- if (this.browserStarting) {
163
- log('browser.waiting', debug, {}, COMPONENTS.BROWSER_FETCH);
164
- return this.browserStartPromise;
165
- }
166
- // 如果浏览器已经启动,直接返回
183
+ /**
184
+ * 获取浏览器实例 (Get browser instance)
185
+ * 如果浏览器未启动,则启动浏览器 (Start browser if not started)
186
+ * @param debug 是否启用调试输出 (Whether to enable debug output)
187
+ * @returns 浏览器实例 (Browser instance)
188
+ */
189
+ static async getBrowser(debug = false) {
190
+ // 检查内存使用情况 (Check memory usage)
191
+ this.checkMemoryUsage(debug);
192
+ // 如果浏览器已经存在,直接返回 (If browser already exists, return directly)
167
193
  if (this.browser) {
168
- log('browser.starting', debug, { debug }, COMPONENTS.BROWSER_FETCH);
194
+ log('browser.reusingExistingBrowser', debug, {}, COMPONENTS.BROWSER_FETCH);
169
195
  return this.browser;
170
196
  }
197
+ // 如果浏览器正在启动中,等待启动完成 (If browser is starting, wait for startup to complete)
198
+ if (this.browserStarting && this.browserStartPromise) {
199
+ log('browser.waitingForBrowserStart', debug, {}, COMPONENTS.BROWSER_FETCH);
200
+ return this.browserStartPromise;
201
+ }
202
+ // 标记浏览器正在启动 (Mark browser as starting)
171
203
  this.browserStarting = true;
172
- this.browserStartPromise = (async () => {
204
+ // 创建启动Promise (Create startup Promise)
205
+ this.browserStartPromise = new Promise(async (resolve, reject) => {
173
206
  try {
174
- // 获取代理设置 - 优先使用用户指定的代理,其次使用系统代理
175
- const proxyServer = proxy || (useSystemProxy ? this.getSystemProxy(useSystemProxy, debug) : undefined);
176
- // 详细记录代理设置情况
177
- if (proxy) {
178
- log('fetcher.usingSpecifiedProxy', debug, { proxy }, COMPONENTS.BROWSER_FETCH);
179
- }
180
- else if (useSystemProxy) {
181
- log('fetcher.attemptingToUseSystemProxy', debug, {}, COMPONENTS.BROWSER_FETCH);
182
- }
183
- else {
184
- log('fetcher.notUsingProxy', debug, {}, COMPONENTS.BROWSER_FETCH);
185
- }
186
- log('fetcher.finalProxyUsed', debug, { proxy: proxyServer || t('fetcher.none') }, COMPONENTS.BROWSER_FETCH);
187
- // 启动浏览器
188
- const args = [
189
- '--no-sandbox',
190
- '--disable-setuid-sandbox',
191
- '--disable-dev-shm-usage',
192
- '--disable-accelerated-2d-canvas',
193
- '--no-first-run',
194
- '--no-zygote',
195
- '--single-process',
196
- '--disable-gpu',
197
- '--disable-infobars',
198
- '--window-position=0,0',
199
- '--ignore-certifcate-errors',
200
- '--ignore-certifcate-errors-spki-list',
201
- '--disable-features=site-per-process',
202
- '--disable-web-security',
203
- '--disable-features=IsolateOrigins,site-per-process',
204
- '--allow-running-insecure-content',
205
- '--disable-blink-features=AutomationControlled',
206
- ];
207
- // 如果有代理,添加代理参数
208
- if (proxyServer) {
209
- args.push(`--proxy-server=${proxyServer}`);
210
- log('browser.usingProxy', debug, { proxy: proxyServer }, COMPONENTS.BROWSER_FETCH);
211
- }
212
- // 启动浏览器
213
- // @ts-ignore - 忽略puppeteer-extra的类型错误
214
- const browser = await puppeteerExtra.launch({
207
+ log('browser.startingBrowser', debug, {}, COMPONENTS.BROWSER_FETCH);
208
+ // 准备浏览器启动参数 (Prepare browser startup parameters)
209
+ const launchOptions = {
215
210
  headless: 'new',
216
- args,
211
+ args: [
212
+ '--no-sandbox',
213
+ '--disable-setuid-sandbox',
214
+ '--disable-dev-shm-usage',
215
+ '--disable-accelerated-2d-canvas',
216
+ '--no-first-run',
217
+ '--no-zygote',
218
+ '--disable-gpu',
219
+ '--disable-infobars',
220
+ '--window-position=0,0',
221
+ '--ignore-certificate-errors',
222
+ '--ignore-certificate-errors-spki-list',
223
+ '--disable-extensions',
224
+ '--disable-default-apps',
225
+ '--enable-features=NetworkService',
226
+ '--disable-features=IsolateOrigins,site-per-process',
227
+ '--disable-web-security',
228
+ '--disable-site-isolation-trials',
229
+ '--disable-features=BlockInsecurePrivateNetworkRequests',
230
+ '--disable-features=IsolateOrigins',
231
+ '--disable-features=site-per-process',
232
+ '--disable-blink-features=AutomationControlled',
233
+ '--user-agent=' + this.getRandomUserAgent()
234
+ ],
217
235
  ignoreHTTPSErrors: true,
218
236
  defaultViewport: {
219
237
  width: 1920,
220
238
  height: 1080
221
239
  }
222
- });
223
- this.browser = browser;
224
- log('browser.startupSuccess', debug, {}, COMPONENTS.BROWSER_FETCH);
225
- // 设置浏览器关闭事件
226
- browser.on('disconnected', () => {
240
+ };
241
+ // 检查是否有环境变量指定的Chrome路径 (Check if there is a Chrome path specified by environment variables)
242
+ const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH;
243
+ if (executablePath) {
244
+ log('browser.usingCustomChromePath', debug, { path: executablePath }, COMPONENTS.BROWSER_FETCH);
245
+ launchOptions.executablePath = executablePath;
246
+ launchOptions.channel = undefined;
247
+ }
248
+ // 启动浏览器 (Launch browser)
249
+ // @ts-ignore - 忽略puppeteer-extra的类型错误
250
+ this.browser = await puppeteerExtra.launch(launchOptions);
251
+ log('browser.browserStarted', debug, {}, COMPONENTS.BROWSER_FETCH);
252
+ // 设置浏览器关闭事件处理 (Set browser close event handling)
253
+ this.browser.on('disconnected', () => {
254
+ log('browser.browserDisconnected', debug, {}, COMPONENTS.BROWSER_FETCH);
227
255
  this.browser = null;
228
- log('browser.closed', debug, {}, COMPONENTS.BROWSER_FETCH);
256
+ this.browserStarting = false;
257
+ this.browserStartPromise = null;
229
258
  });
230
- return browser;
259
+ // 重置标志 (Reset flags)
260
+ this.browserStarting = false;
261
+ // 解析Promise (Resolve Promise)
262
+ resolve(this.browser);
231
263
  }
232
264
  catch (error) {
265
+ // 记录错误 (Log error)
266
+ log('browser.browserStartError', true, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
267
+ // 重置标志 (Reset flags)
233
268
  this.browser = null;
234
- log('browser.startingFailed', debug, { error: String(error) }, COMPONENTS.BROWSER_FETCH);
235
- throw error;
236
- }
237
- finally {
238
269
  this.browserStarting = false;
270
+ this.browserStartPromise = null;
271
+ // 拒绝Promise (Reject Promise)
272
+ reject(error);
239
273
  }
240
- })();
274
+ });
241
275
  return this.browserStartPromise;
242
276
  }
243
277
  // 关闭浏览器
@@ -426,7 +460,7 @@ export class BrowserFetcher {
426
460
  };
427
461
  }
428
462
  // 获取浏览器实例
429
- const browser = await this.getBrowser({ proxy: effectiveProxy, debug, useSystemProxy });
463
+ const browser = await this.getBrowser(debug);
430
464
  // 创建新页面
431
465
  const page = await browser.newPage();
432
466
  // 设置页面超时
@@ -339,10 +339,15 @@ export class NodeFetcher {
339
339
  }
340
340
  catch (parseError) {
341
341
  // 处理JSON解析错误 (Handle JSON parse error)
342
- const error = new Error(`Invalid JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
342
+ const textPreview = text.length > 100 ? `${text.substring(0, 100)}...` : text;
343
+ const error = new Error(`Invalid JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}. Text preview: "${textPreview}", length: ${text.length}`);
343
344
  error.text = text;
344
345
  error.originalError = parseError;
345
- log('node.jsonParseError', debug, { error: String(parseError) }, COMPONENTS.NODE_FETCH);
346
+ log('fetcher.jsonParseError', debug, {
347
+ error: String(parseError),
348
+ textPreview,
349
+ textLength: text.length
350
+ }, COMPONENTS.NODE_FETCH);
346
351
  throw error;
347
352
  }
348
353
  // 返回JSON内容 (Return JSON content)
@@ -49,5 +49,14 @@ export const browser = {
49
49
  usingStoredCookies: "Using stored cookies for domain: {{domain}}",
50
50
  closingInstance: "Closing browser instance",
51
51
  fetchErrorWithAttempt: "Error in browser fetch (attempt {{attempt}}/{{maxAttempts}}): {{error}}",
52
- retryingAfterDelay: "Retrying after {{delayMs}}ms delay..."
52
+ retryingAfterDelay: "Retrying after {{delayMs}}ms delay...",
53
+ browserStartupSuccess: "Browser started successfully",
54
+ browserStartupFailed: "Browser startup failed: {{error}}",
55
+ usingCustomChromePath: "Using custom Chrome path: {{path}}",
56
+ browserDisconnected: "Browser disconnected",
57
+ waitingForBrowserStart: "Waiting for browser to start",
58
+ reusingExistingBrowser: "Reusing existing browser instance",
59
+ startingBrowser: "Starting browser",
60
+ browserStarted: "Browser started successfully",
61
+ browserStartError: "Browser startup error: {{error}}"
53
62
  };
@@ -39,7 +39,7 @@ export const fetcher = {
39
39
  startingJsonFetch: "Starting JSON fetch",
40
40
  parsingJson: "Parsing JSON",
41
41
  jsonParsed: "JSON parsed successfully",
42
- jsonParseError: "JSON parse error: {{error}}",
42
+ jsonParseError: "JSON parse error: {{error}}. Text preview: \"{{textPreview}}\", length: {{textLength}} bytes",
43
43
  startingTxtFetch: "Starting text fetch",
44
44
  textContentLength: "Text content length: {{length}} bytes",
45
45
  startingMarkdownFetch: "Starting Markdown fetch",
@@ -49,5 +49,14 @@ export const browser = {
49
49
  usingStoredCookies: "使用存储的域名Cookie: {{domain}}",
50
50
  closingInstance: "关闭浏览器实例",
51
51
  fetchErrorWithAttempt: "浏览器获取错误(尝试 {{attempt}}/{{maxAttempts}}): {{error}}",
52
- retryingAfterDelay: "延迟 {{delayMs}}ms 后重试..."
52
+ retryingAfterDelay: "延迟 {{delayMs}}ms 后重试...",
53
+ browserStartupSuccess: "浏览器启动成功",
54
+ browserStartupFailed: "浏览器启动失败: {{error}}",
55
+ usingCustomChromePath: "使用自定义Chrome路径: {{path}}",
56
+ browserDisconnected: "浏览器已断开连接",
57
+ waitingForBrowserStart: "等待浏览器启动",
58
+ reusingExistingBrowser: "复用现有浏览器实例",
59
+ startingBrowser: "正在启动浏览器",
60
+ browserStarted: "浏览器启动成功",
61
+ browserStartError: "浏览器启动错误: {{error}}"
53
62
  };
@@ -39,7 +39,7 @@ export const fetcher = {
39
39
  startingJsonFetch: "开始JSON获取",
40
40
  parsingJson: "解析JSON",
41
41
  jsonParsed: "JSON解析成功",
42
- jsonParseError: "JSON解析错误: {{error}}",
42
+ jsonParseError: "JSON解析错误: {{error}}。文本预览: \"{{textPreview}}\", 长度: {{textLength}} 字节",
43
43
  startingTxtFetch: "开始文本获取",
44
44
  textContentLength: "文本内容长度: {{length}} 字节",
45
45
  startingMarkdownFetch: "开始Markdown获取",
@@ -8,7 +8,7 @@ import { log, COMPONENTS } from '../logger.js';
8
8
  import { t } from '../i18n/index.js';
9
9
  // 定义提示模板 (Define prompt templates)
10
10
  const PROMPTS = {
11
- "fetch-website": {
11
+ "mult-fetch-mcp:prompt:fetch-website": {
12
12
  name: "mult-fetch-mcp:prompt:fetch-website",
13
13
  description: t('prompts.fetchWebsite.description'),
14
14
  arguments: [
@@ -29,7 +29,7 @@ const PROMPTS = {
29
29
  }
30
30
  ]
31
31
  },
32
- "extract-content": {
32
+ "mult-fetch-mcp:prompt:extract-content": {
33
33
  name: "mult-fetch-mcp:prompt:extract-content",
34
34
  description: t('prompts.extractContent.description'),
35
35
  arguments: [
@@ -50,7 +50,7 @@ const PROMPTS = {
50
50
  }
51
51
  ]
52
52
  },
53
- "debug-fetch": {
53
+ "mult-fetch-mcp:prompt:debug-fetch": {
54
54
  name: "mult-fetch-mcp:prompt:debug-fetch",
55
55
  description: t('prompts.debugFetch.description'),
56
56
  arguments: [
@@ -99,7 +99,7 @@ export function registerPrompts(server) {
99
99
  }
100
100
  }
101
101
  // 根据提示名称和参数生成消息
102
- if (promptName === "fetch-website") {
102
+ if (promptName === "mult-fetch-mcp:prompt:fetch-website") {
103
103
  const url = args.url;
104
104
  const format = args.format || "html";
105
105
  let useBrowser = false;
@@ -152,7 +152,7 @@ export function registerPrompts(server) {
152
152
  ]
153
153
  };
154
154
  }
155
- else if (promptName === "extract-content") {
155
+ else if (promptName === "mult-fetch-mcp:prompt:extract-content") {
156
156
  const url = args.url;
157
157
  const selector = args.selector || "main";
158
158
  const dataType = args.dataType || "text";
@@ -171,7 +171,7 @@ export function registerPrompts(server) {
171
171
  ]
172
172
  };
173
173
  }
174
- else if (promptName === "debug-fetch") {
174
+ else if (promptName === "mult-fetch-mcp:prompt:debug-fetch") {
175
175
  const url = args.url;
176
176
  const error = args.error;
177
177
  return {
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@lmcc-dev/mult-fetch-mcp-server",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "An MCP protocol-based web content fetching tool that supports multiple modes and formats, can be integrated with AI assistants like Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
- "mult-fetch-mcp-server": "./dist/index.js"
8
+ "mult-fetch-mcp-server": "dist/index.js"
9
9
  },
10
10
  "license": "MIT",
11
11
  "author": "Martin (@lmcc.dev)",
@@ -31,7 +31,7 @@
31
31
  "client": "node dist/src/client.js",
32
32
  "server": "node dist/index.js",
33
33
  "prepublishOnly": "npm run build",
34
- "publish": "npm publish --access public"
34
+ "release": "npm publish --access public"
35
35
  },
36
36
  "keywords": [
37
37
  "mcp",