@lmcc-dev/mult-fetch-mcp-server 1.0.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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +512 -0
  3. package/README.zh.md +510 -0
  4. package/dist/index.js +21 -0
  5. package/dist/src/cli-language.js +78 -0
  6. package/dist/src/client.js +284 -0
  7. package/dist/src/index.js +21 -0
  8. package/dist/src/lib/BrowserFetcher.js +753 -0
  9. package/dist/src/lib/NodeFetcher.js +428 -0
  10. package/dist/src/lib/example.js +93 -0
  11. package/dist/src/lib/i18n/index.js +36 -0
  12. package/dist/src/lib/i18n/locales/en/browser.js +53 -0
  13. package/dist/src/lib/i18n/locales/en/client.js +40 -0
  14. package/dist/src/lib/i18n/locales/en/errors.js +18 -0
  15. package/dist/src/lib/i18n/locales/en/fetcher.js +67 -0
  16. package/dist/src/lib/i18n/locales/en/index.js +26 -0
  17. package/dist/src/lib/i18n/locales/en/node.js +42 -0
  18. package/dist/src/lib/i18n/locales/en/prompts.js +72 -0
  19. package/dist/src/lib/i18n/locales/en/resources.js +47 -0
  20. package/dist/src/lib/i18n/locales/en/server.js +32 -0
  21. package/dist/src/lib/i18n/locales/en/tools.js +25 -0
  22. package/dist/src/lib/i18n/locales/zh/browser.js +53 -0
  23. package/dist/src/lib/i18n/locales/zh/client.js +39 -0
  24. package/dist/src/lib/i18n/locales/zh/errors.js +18 -0
  25. package/dist/src/lib/i18n/locales/zh/fetcher.js +67 -0
  26. package/dist/src/lib/i18n/locales/zh/index.js +26 -0
  27. package/dist/src/lib/i18n/locales/zh/node.js +42 -0
  28. package/dist/src/lib/i18n/locales/zh/prompts.js +72 -0
  29. package/dist/src/lib/i18n/locales/zh/resources.js +47 -0
  30. package/dist/src/lib/i18n/locales/zh/server.js +32 -0
  31. package/dist/src/lib/i18n/locales/zh/tools.js +25 -0
  32. package/dist/src/lib/i18n/logger.js +57 -0
  33. package/dist/src/lib/logger.js +126 -0
  34. package/dist/src/lib/server/browser.js +86 -0
  35. package/dist/src/lib/server/fetcher.js +130 -0
  36. package/dist/src/lib/server/index.js +73 -0
  37. package/dist/src/lib/server/logger.js +23 -0
  38. package/dist/src/lib/server/prompts.js +207 -0
  39. package/dist/src/lib/server/resources.js +171 -0
  40. package/dist/src/lib/server/tools.js +346 -0
  41. package/dist/src/lib/server/types.js +6 -0
  42. package/dist/src/lib/types.js +35 -0
  43. package/dist/src/mcp-server.js +22 -0
  44. package/dist/tests/test-direct-client.js +129 -0
  45. package/dist/tests/test-mcp-methods.js +114 -0
  46. package/dist/tests/test-mcp.js +145 -0
  47. package/dist/tests/test-mini4k.js +147 -0
  48. package/images/example_en.png +0 -0
  49. package/images/example_zh.png +0 -0
  50. package/package.json +80 -0
@@ -0,0 +1,428 @@
1
+ /**
2
+ * Author: Martin <lmccc.dev@gmail.com>
3
+ * Co-Author: AI Assistant (Claude)
4
+ * Description: This code was collaboratively developed by Martin and AI Assistant.
5
+ */
6
+ import TurndownService from "turndown";
7
+ import fetch from "node-fetch";
8
+ import { HttpProxyAgent } from "http-proxy-agent";
9
+ import { HttpsProxyAgent } from "https-proxy-agent";
10
+ import { execSync } from 'child_process';
11
+ import { log, COMPONENTS } from './logger.js';
12
+ export class NodeFetcher {
13
+ /**
14
+ * 常用浏览器的User-Agent列表 (List of common browser User-Agents)
15
+ * 用于模拟不同浏览器的请求 (Used to simulate requests from different browsers)
16
+ */
17
+ static userAgents = [
18
+ // Chrome
19
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
20
+ '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',
21
+ // Firefox
22
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0',
23
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:89.0) Gecko/20100101 Firefox/89.0',
24
+ // Safari
25
+ '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',
26
+ // Edge
27
+ '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'
28
+ ];
29
+ /**
30
+ * 获取随机User-Agent (Get random User-Agent)
31
+ * @returns 随机的User-Agent字符串 (Random User-Agent string)
32
+ */
33
+ static getRandomUserAgent() {
34
+ const index = Math.floor(Math.random() * this.userAgents.length);
35
+ return this.userAgents[index];
36
+ }
37
+ /**
38
+ * 随机延迟函数 (Random delay function)
39
+ * 模拟人类行为,避免被检测为机器人 (Simulate human behavior, avoid being detected as a bot)
40
+ * @param minMs 最小延迟毫秒数 (Minimum delay in milliseconds)
41
+ * @param maxMs 最大延迟毫秒数 (Maximum delay in milliseconds)
42
+ * @returns Promise对象 (Promise object)
43
+ */
44
+ static async randomDelay(minMs = 500, maxMs = 3000) {
45
+ const delay = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
46
+ return new Promise(resolve => setTimeout(resolve, delay));
47
+ }
48
+ /**
49
+ * 获取系统代理设置 (Get system proxy settings)
50
+ * @param useSystemProxy 是否使用系统代理 (Whether to use system proxy)
51
+ * @returns 代理URL或undefined (Proxy URL or undefined)
52
+ */
53
+ static getSystemProxy(useSystemProxy = true) {
54
+ if (!useSystemProxy) {
55
+ return undefined;
56
+ }
57
+ // 检查环境变量 (Check environment variables)
58
+ log('fetcher.checkingProxyEnv', true, {}, COMPONENTS.NODE_FETCH);
59
+ const envVars = ['https_proxy', 'HTTPS_PROXY', 'http_proxy', 'HTTP_PROXY'];
60
+ for (const envVar of envVars) {
61
+ log('fetcher.envVarValue', true, {
62
+ envVar,
63
+ value: process.env[envVar]
64
+ }, COMPONENTS.NODE_FETCH);
65
+ if (process.env[envVar]) {
66
+ const proxyUrl = process.env[envVar];
67
+ log('fetcher.foundSystemProxy', true, { proxy: proxyUrl }, COMPONENTS.NODE_FETCH);
68
+ return proxyUrl;
69
+ }
70
+ }
71
+ // 尝试使用系统命令获取环境变量 (Try to get environment variables using system commands)
72
+ try {
73
+ const platform = process.platform;
74
+ let proxyUrl;
75
+ log('fetcher.checkingSystemEnvVars', true, { platform }, COMPONENTS.NODE_FETCH);
76
+ if (platform === 'win32') {
77
+ // Windows系统 - 使用set命令 (Windows - use set command)
78
+ try {
79
+ // 使用set命令获取代理环境变量 (Use set command to get proxy environment variables)
80
+ const setOutput = execSync('set http_proxy & set https_proxy & set HTTP_PROXY & set HTTPS_PROXY').toString();
81
+ log('fetcher.windowsEnvVars', true, { output: setOutput.trim() }, COMPONENTS.NODE_FETCH);
82
+ // 解析输出找到代理设置 (Parse output to find proxy settings)
83
+ const proxyMatch = setOutput.match(/(?:http_proxy|https_proxy|HTTP_PROXY|HTTPS_PROXY)=(https?:\/\/[^=\r\n]+)/i);
84
+ if (proxyMatch && proxyMatch[1]) {
85
+ proxyUrl = proxyMatch[1].trim();
86
+ log('fetcher.foundWindowsEnvProxy', true, { proxy: proxyUrl }, COMPONENTS.NODE_FETCH);
87
+ }
88
+ }
89
+ catch (winError) {
90
+ log('fetcher.errorGettingWindowsEnvVars', true, { error: String(winError) }, COMPONENTS.NODE_FETCH);
91
+ }
92
+ }
93
+ else {
94
+ // Unix系统 (macOS/Linux) - 使用export或env命令 (Unix systems - use export or env command)
95
+ try {
96
+ // 使用env命令获取所有环境变量 (Use env command to get all environment variables)
97
+ const envOutput = execSync('env').toString();
98
+ log('fetcher.unixEnvVars', true, { output: envOutput.length > 200 ? envOutput.substring(0, 200) + '...' : envOutput }, COMPONENTS.NODE_FETCH);
99
+ // 解析输出找到代理设置 (Parse output to find proxy settings)
100
+ const proxyMatch = envOutput.match(/(?:http_proxy|https_proxy|HTTP_PROXY|HTTPS_PROXY)=(https?:\/\/[^=\n]+)/i);
101
+ if (proxyMatch && proxyMatch[1]) {
102
+ proxyUrl = proxyMatch[1].trim();
103
+ log('fetcher.foundUnixEnvProxy', true, { proxy: proxyUrl }, COMPONENTS.NODE_FETCH);
104
+ }
105
+ }
106
+ catch (unixError) {
107
+ log('fetcher.errorGettingUnixEnvVars', true, { error: String(unixError) }, COMPONENTS.NODE_FETCH);
108
+ }
109
+ }
110
+ if (proxyUrl) {
111
+ return proxyUrl;
112
+ }
113
+ // 如果没有找到代理,记录日志 (If no proxy is found, log a message)
114
+ log('fetcher.noSystemProxyFound', true, {}, COMPONENTS.NODE_FETCH);
115
+ }
116
+ catch (error) {
117
+ log('fetcher.errorGettingSystemEnvVars', true, { error: String(error) }, COMPONENTS.NODE_FETCH);
118
+ }
119
+ // 检查NO_PROXY环境变量 (Check NO_PROXY environment variable)
120
+ const noProxy = process.env.NO_PROXY || process.env.no_proxy;
121
+ if (noProxy) {
122
+ log('fetcher.foundNoProxy', true, { noProxy }, COMPONENTS.NODE_FETCH);
123
+ }
124
+ return undefined;
125
+ }
126
+ /**
127
+ * 执行带重定向处理的HTTP请求 (Perform HTTP request with redirect handling)
128
+ * @param requestPayload 请求参数 (Request parameters)
129
+ * @returns 响应数据 (Response data)
130
+ */
131
+ static async _fetchWithRedirects({ url, headers = {}, proxy, noDelay, timeout = 30000, // 默认30秒超时
132
+ maxRedirects = 10, // 最大重定向次数
133
+ useSystemProxy = true, // 是否使用系统代理
134
+ debug = false, // 是否启用调试模式
135
+ }) {
136
+ log('node.fetchingUrl', debug, { url }, COMPONENTS.NODE_FETCH);
137
+ // 处理代理设置 (Handle proxy settings)
138
+ const systemProxy = this.getSystemProxy(useSystemProxy);
139
+ const finalProxy = proxy || systemProxy;
140
+ if (finalProxy) {
141
+ log('node.usingProxy', debug, { proxy: finalProxy }, COMPONENTS.NODE_FETCH);
142
+ }
143
+ // 初始化重定向计数器 (Initialize redirect counter)
144
+ let redirectCount = 0;
145
+ let currentUrl = url;
146
+ // 记录请求开始时间 (Record request start time)
147
+ const fetchStart = Date.now();
148
+ // 创建AbortController用于超时控制 (Create AbortController for timeout control)
149
+ const controller = new AbortController();
150
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
151
+ try {
152
+ // 处理重定向循环 (Handle redirect loop)
153
+ while (redirectCount < maxRedirects) {
154
+ log('node.fetchingUrl', debug, { url: currentUrl, redirect: redirectCount }, COMPONENTS.NODE_FETCH);
155
+ // 如果启用了随机延迟且不是第一个请求,则添加延迟 (Add delay if random delay is enabled and not the first request)
156
+ if (!noDelay && redirectCount > 0) {
157
+ await this.randomDelay();
158
+ }
159
+ // 准备请求头 (Prepare request headers)
160
+ const requestHeaders = {
161
+ ...headers
162
+ };
163
+ // 添加随机User-Agent (Add random User-Agent)
164
+ if (!requestHeaders['User-Agent']) {
165
+ const userAgent = this.getRandomUserAgent();
166
+ requestHeaders['User-Agent'] = userAgent;
167
+ log('node.usingUserAgent', debug, { userAgent }, COMPONENTS.NODE_FETCH);
168
+ }
169
+ // 准备请求选项 (Prepare request options)
170
+ const fetchOptions = {
171
+ method: 'GET',
172
+ headers: requestHeaders,
173
+ timeout,
174
+ signal: controller.signal,
175
+ };
176
+ // 设置代理 (Set proxy)
177
+ let agent = undefined;
178
+ if (finalProxy) {
179
+ if (currentUrl.startsWith('https://')) {
180
+ agent = new HttpsProxyAgent(finalProxy);
181
+ log('node.usingHttpsProxy', debug, {}, COMPONENTS.NODE_FETCH);
182
+ }
183
+ else {
184
+ agent = new HttpProxyAgent(finalProxy);
185
+ log('node.usingHttpProxy', debug, {}, COMPONENTS.NODE_FETCH);
186
+ }
187
+ fetchOptions.agent = agent;
188
+ }
189
+ // 记录请求详情 (Log request details)
190
+ log('node.requestDetails', debug, {
191
+ url: currentUrl,
192
+ method: fetchOptions.method || 'GET',
193
+ headers: requestHeaders,
194
+ proxy: finalProxy,
195
+ timeout,
196
+ }, COMPONENTS.NODE_FETCH);
197
+ // 执行请求 (Execute request)
198
+ const response = await fetch(currentUrl, fetchOptions);
199
+ // 记录响应状态 (Log response status)
200
+ log('node.responseStatus', debug, {
201
+ status: response.status,
202
+ statusText: response.statusText,
203
+ headers: Object.fromEntries(response.headers.entries()),
204
+ }, COMPONENTS.NODE_FETCH);
205
+ // 处理重定向 (Handle redirects)
206
+ if (response.status >= 300 && response.status < 400 && response.headers.has('location')) {
207
+ redirectCount++;
208
+ // 获取重定向URL (Get redirect URL)
209
+ const location = response.headers.get('location');
210
+ log('node.redirectingTo', debug, { location }, COMPONENTS.NODE_FETCH);
211
+ // 构建完整的重定向URL (Build complete redirect URL)
212
+ let redirectUrl = location;
213
+ if (location.startsWith('/')) {
214
+ const urlObj = new URL(currentUrl);
215
+ redirectUrl = `${urlObj.protocol}//${urlObj.host}${location}`;
216
+ }
217
+ else if (!location.startsWith('http')) {
218
+ redirectUrl = new URL(location, currentUrl).toString();
219
+ }
220
+ log('node.constructedFullRedirectUrl', debug, { redirectUrl }, COMPONENTS.NODE_FETCH);
221
+ // 更新当前URL为重定向URL (Update current URL to redirect URL)
222
+ currentUrl = redirectUrl;
223
+ continue;
224
+ }
225
+ // 如果响应成功,返回响应 (If response is successful, return response)
226
+ if (response.ok) {
227
+ log('node.requestSuccess', debug, {}, COMPONENTS.NODE_FETCH);
228
+ return response;
229
+ }
230
+ else {
231
+ // 处理错误响应 (Handle error response)
232
+ log('node.errorResponse', debug, { status: response.status, statusText: response.statusText }, COMPONENTS.NODE_FETCH);
233
+ // 尝试读取错误响应体 (Try to read error response body)
234
+ let errorText = '';
235
+ try {
236
+ errorText = await response.text();
237
+ log('node.errorResponseBody', debug, { body: errorText.substring(0, 200) + (errorText.length > 200 ? '...' : '') }, COMPONENTS.NODE_FETCH);
238
+ }
239
+ catch (textError) {
240
+ log('node.errorReadingBody', debug, { error: String(textError) }, COMPONENTS.NODE_FETCH);
241
+ }
242
+ // 创建错误对象 (Create error object)
243
+ const error = new Error(`HTTP Error ${response.status}: ${response.statusText}`);
244
+ error.status = response.status;
245
+ error.statusText = response.statusText;
246
+ error.body = errorText;
247
+ throw error;
248
+ }
249
+ }
250
+ // 如果达到最大重定向次数,抛出错误 (If maximum redirects reached, throw error)
251
+ throw new Error(`Too many redirects (${maxRedirects})`);
252
+ }
253
+ catch (error) {
254
+ // 清除超时定时器 (Clear timeout timer)
255
+ clearTimeout(timeoutId);
256
+ // 记录错误 (Log error)
257
+ log('node.fetchError', debug, { error: String(error) }, COMPONENTS.NODE_FETCH);
258
+ // 处理超时错误 (Handle timeout error)
259
+ if (error.name === 'AbortError') {
260
+ log('node.requestAborted', debug, { duration: Date.now() - fetchStart }, COMPONENTS.NODE_FETCH);
261
+ const timeoutError = new Error(`Request timeout after ${timeout}ms`);
262
+ timeoutError.code = 'ETIMEDOUT';
263
+ timeoutError.timeout = timeout;
264
+ throw timeoutError;
265
+ }
266
+ // 处理网络错误 (Handle network error)
267
+ if (error.code) {
268
+ log('node.networkError', debug, { code: error.code || error.name }, COMPONENTS.NODE_FETCH);
269
+ const networkError = new Error(`Network error: ${error.code || error.message}`);
270
+ networkError.code = error.code;
271
+ networkError.originalError = error;
272
+ throw networkError;
273
+ }
274
+ // 处理重定向错误 (Handle redirect error)
275
+ if (redirectCount >= maxRedirects) {
276
+ log('node.tooManyRedirects', debug, { redirects: maxRedirects }, COMPONENTS.NODE_FETCH);
277
+ const redirectError = new Error(`Too many redirects (${maxRedirects})`);
278
+ redirectError.code = 'EMAXREDIRECTS';
279
+ redirectError.redirects = redirectCount;
280
+ throw redirectError;
281
+ }
282
+ // 重新抛出其他错误 (Rethrow other errors)
283
+ throw error;
284
+ }
285
+ finally {
286
+ // 清除超时定时器 (Clear timeout timer)
287
+ clearTimeout(timeoutId);
288
+ }
289
+ }
290
+ /**
291
+ * 获取HTML内容 (Get HTML content)
292
+ * @param requestPayload 请求参数 (Request parameters)
293
+ * @returns HTML内容 (HTML content)
294
+ */
295
+ static async html(requestPayload) {
296
+ const { debug = false } = requestPayload;
297
+ log('node.startingHtmlFetch', debug, {}, COMPONENTS.NODE_FETCH);
298
+ try {
299
+ // 执行请求 (Execute request)
300
+ const response = await this._fetchWithRedirects(requestPayload);
301
+ // 读取响应文本 (Read response text)
302
+ log('node.readingText', debug, {}, COMPONENTS.NODE_FETCH);
303
+ const html = await response.text();
304
+ log('node.htmlContentLength', debug, { length: html.length }, COMPONENTS.NODE_FETCH);
305
+ // 返回HTML内容 (Return HTML content)
306
+ return {
307
+ html,
308
+ url: response.url,
309
+ status: response.status,
310
+ headers: Object.fromEntries(response.headers.entries()),
311
+ };
312
+ }
313
+ catch (error) {
314
+ // 处理错误 (Handle error)
315
+ log('node.htmlFetchError', debug, { error: error instanceof Error ? error.message : String(error) }, COMPONENTS.NODE_FETCH);
316
+ // 重新抛出错误 (Rethrow error)
317
+ throw error;
318
+ }
319
+ }
320
+ /**
321
+ * 获取JSON内容 (Get JSON content)
322
+ * @param requestPayload 请求参数 (Request parameters)
323
+ * @returns JSON内容 (JSON content)
324
+ */
325
+ static async json(requestPayload) {
326
+ const { debug = false } = requestPayload;
327
+ log('node.startingJsonFetch', debug, {}, COMPONENTS.NODE_FETCH);
328
+ try {
329
+ // 执行请求 (Execute request)
330
+ const response = await this._fetchWithRedirects(requestPayload);
331
+ // 读取响应文本 (Read response text)
332
+ const text = await response.text();
333
+ // 解析JSON (Parse JSON)
334
+ log('node.parsingJson', debug, {}, COMPONENTS.NODE_FETCH);
335
+ let json;
336
+ try {
337
+ json = JSON.parse(text);
338
+ log('node.jsonParsed', debug, {}, COMPONENTS.NODE_FETCH);
339
+ }
340
+ catch (parseError) {
341
+ // 处理JSON解析错误 (Handle JSON parse error)
342
+ const error = new Error(`Invalid JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
343
+ error.text = text;
344
+ error.originalError = parseError;
345
+ log('node.jsonParseError', debug, { error: String(parseError) }, COMPONENTS.NODE_FETCH);
346
+ throw error;
347
+ }
348
+ // 返回JSON内容 (Return JSON content)
349
+ return {
350
+ json,
351
+ text,
352
+ url: response.url,
353
+ status: response.status,
354
+ headers: Object.fromEntries(response.headers.entries()),
355
+ };
356
+ }
357
+ catch (error) {
358
+ // 处理错误 (Handle error)
359
+ log('node.jsonFetchError', debug, { error: error instanceof Error ? error.message : String(error) }, COMPONENTS.NODE_FETCH);
360
+ // 重新抛出错误 (Rethrow error)
361
+ throw error;
362
+ }
363
+ }
364
+ /**
365
+ * 获取纯文本内容 (Get plain text content)
366
+ * @param requestPayload 请求参数 (Request parameters)
367
+ * @returns 纯文本内容 (Plain text content)
368
+ */
369
+ static async txt(requestPayload) {
370
+ const { debug = false } = requestPayload;
371
+ log('fetcher.startingTxtFetch', debug, {}, COMPONENTS.NODE_FETCH);
372
+ try {
373
+ // 执行请求 (Execute request)
374
+ const response = await this._fetchWithRedirects(requestPayload);
375
+ // 读取响应文本 (Read response text)
376
+ log('fetcher.readingText', debug, {}, COMPONENTS.NODE_FETCH);
377
+ const text = await response.text();
378
+ log('fetcher.textContentLength', debug, { length: text.length }, COMPONENTS.NODE_FETCH);
379
+ // 返回纯文本内容 (Return plain text content)
380
+ return {
381
+ text,
382
+ url: response.url,
383
+ status: response.status,
384
+ headers: Object.fromEntries(response.headers.entries()),
385
+ };
386
+ }
387
+ catch (error) {
388
+ // 重新抛出错误 (Rethrow error)
389
+ throw error;
390
+ }
391
+ }
392
+ /**
393
+ * 获取Markdown内容 (Get Markdown content)
394
+ * @param requestPayload 请求参数 (Request parameters)
395
+ * @returns Markdown内容 (Markdown content)
396
+ */
397
+ static async markdown(requestPayload) {
398
+ const { debug = false } = requestPayload;
399
+ log('fetcher.startingMarkdownFetch', debug, {}, COMPONENTS.NODE_FETCH);
400
+ try {
401
+ // 执行请求 (Execute request)
402
+ const response = await this._fetchWithRedirects(requestPayload);
403
+ // 读取响应文本 (Read response text)
404
+ log('fetcher.readingText', debug, {}, COMPONENTS.NODE_FETCH);
405
+ const html = await response.text();
406
+ log('fetcher.htmlContentLength', debug, { length: html.length }, COMPONENTS.NODE_FETCH);
407
+ // 创建Turndown服务 (Create Turndown service)
408
+ log('fetcher.creatingTurndown', debug, {}, COMPONENTS.NODE_FETCH);
409
+ const turndownService = new TurndownService();
410
+ // 将HTML转换为Markdown (Convert HTML to Markdown)
411
+ log('fetcher.convertingToMarkdown', debug, {}, COMPONENTS.NODE_FETCH);
412
+ const markdown = turndownService.turndown(html);
413
+ log('fetcher.markdownContentLength', debug, { length: markdown.length }, COMPONENTS.NODE_FETCH);
414
+ // 返回Markdown内容 (Return Markdown content)
415
+ return {
416
+ markdown,
417
+ html,
418
+ url: response.url,
419
+ status: response.status,
420
+ headers: Object.fromEntries(response.headers.entries()),
421
+ };
422
+ }
423
+ catch (error) {
424
+ // 重新抛出错误 (Rethrow error)
425
+ throw error;
426
+ }
427
+ }
428
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Author: Martin <lmccc.dev@gmail.com>
3
+ * Co-Author: AI Assistant (Claude)
4
+ * Description: This code was collaboratively developed by Martin and AI Assistant.
5
+ */
6
+ /**
7
+ * 示例工具类,展示双语注释风格 (Example utility class, demonstrating bilingual comment style)
8
+ * 包含各种实用函数和示例 (Contains various utility functions and examples)
9
+ */
10
+ export class ExampleUtils {
11
+ /**
12
+ * 缓存存储 (Cache storage)
13
+ * 用于存储临时数据 (Used to store temporary data)
14
+ */
15
+ static cache = new Map();
16
+ /**
17
+ * 获取用户信息 (Get user information)
18
+ * @param userId 用户唯一标识 (Unique identifier of the user)
19
+ * @param includeDetails 是否包含详细信息 (Whether to include detailed information)
20
+ * @returns 用户详细信息 (Detailed user information)
21
+ */
22
+ static async getUserInfo(userId, includeDetails = false) {
23
+ // 检查缓存中是否存在用户信息 (Check if user information exists in cache)
24
+ const cacheKey = `user_${userId}_${includeDetails ? 'detailed' : 'basic'}`;
25
+ if (this.cache.has(cacheKey)) {
26
+ return this.cache.get(cacheKey);
27
+ }
28
+ // 从数据库中查询用户 (Fetch user from the database)
29
+ const user = await this.fetchUserFromDB(userId);
30
+ // 如果需要详细信息,则获取额外数据 (If detailed information is needed, get additional data)
31
+ if (includeDetails && user) {
32
+ // 获取用户的额外信息 (Get additional information of the user)
33
+ user.details = await this.fetchUserDetails(userId);
34
+ }
35
+ // 将结果存入缓存 (Store the result in cache)
36
+ this.cache.set(cacheKey, user);
37
+ return user;
38
+ }
39
+ /**
40
+ * 从数据库获取用户基本信息 (Fetch basic user information from database)
41
+ * @param userId 用户ID (User ID)
42
+ * @returns 用户基本信息 (Basic user information)
43
+ */
44
+ static async fetchUserFromDB(userId) {
45
+ try {
46
+ // 这里是模拟的数据库查询 (This is a simulated database query)
47
+ console.log(`从数据库获取用户 ${userId} 的信息 (Fetching information of user ${userId} from database)`);
48
+ // 模拟网络延迟 (Simulate network delay)
49
+ await new Promise(resolve => setTimeout(resolve, 100));
50
+ // 返回模拟数据 (Return simulated data)
51
+ return {
52
+ id: userId,
53
+ name: `User ${userId}`,
54
+ email: `user${userId}@example.com`,
55
+ createdAt: new Date().toISOString()
56
+ };
57
+ }
58
+ catch (error) {
59
+ // 记录错误并返回null (Log error and return null)
60
+ console.error(`获取用户信息失败: ${error.message} (Failed to get user information: ${error.message})`);
61
+ return null;
62
+ }
63
+ }
64
+ /**
65
+ * 获取用户详细信息 (Get detailed user information)
66
+ * @param userId 用户ID (User ID)
67
+ * @returns 用户详细信息 (Detailed user information)
68
+ */
69
+ static async fetchUserDetails(userId) {
70
+ // 模拟获取用户详细信息 (Simulate getting detailed user information)
71
+ console.log(`获取用户 ${userId} 的详细信息 (Getting detailed information of user ${userId})`);
72
+ // 模拟网络延迟 (Simulate network delay)
73
+ await new Promise(resolve => setTimeout(resolve, 150));
74
+ // 返回模拟的详细信息 (Return simulated detailed information)
75
+ return {
76
+ address: '123 Example St, City, Country',
77
+ phone: '+1234567890',
78
+ preferences: {
79
+ theme: 'dark',
80
+ notifications: true
81
+ }
82
+ };
83
+ }
84
+ /**
85
+ * 清除缓存 (Clear cache)
86
+ * 删除所有缓存的数据 (Delete all cached data)
87
+ */
88
+ static clearCache() {
89
+ // 清空缓存映射 (Empty the cache map)
90
+ this.cache.clear();
91
+ console.log('缓存已清空 (Cache has been cleared)');
92
+ }
93
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Author: Martin <lmccc.dev@gmail.com>
3
+ * Co-Author: AI Assistant (Claude)
4
+ * Description: This code was collaboratively developed by Martin and AI Assistant.
5
+ */
6
+ import i18next from 'i18next';
7
+ import { enTranslation } from './locales/en/index.js';
8
+ import { zhTranslation } from './locales/zh/index.js';
9
+ // 定义语言资源 (Define language resources)
10
+ const resources = {
11
+ en: {
12
+ translation: enTranslation
13
+ },
14
+ zh: {
15
+ translation: zhTranslation
16
+ }
17
+ };
18
+ // 初始化 i18next (Initialize i18next)
19
+ i18next.init({
20
+ resources,
21
+ // 优先使用专门的环境变量,其次默认使用英语
22
+ // (Priority: dedicated environment variable, then default to English)
23
+ lng: process.env.MCP_LANG || 'en',
24
+ fallbackLng: 'en',
25
+ interpolation: {
26
+ escapeValue: false // 不转义插值 (Don't escape interpolation)
27
+ }
28
+ });
29
+ // 导出 i18next 实例 (Export i18next instance)
30
+ export default i18next;
31
+ // 导出一个简便的翻译函数 (Export a convenient translation function)
32
+ export const t = (key, options) => i18next.t(key, options);
33
+ // 导出语言切换函数 (Export language switching function)
34
+ export const changeLanguage = (lng) => i18next.changeLanguage(lng);
35
+ // 导出获取当前语言函数 (Export get current language function)
36
+ export const getCurrentLanguage = () => i18next.language;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Author: Martin <lmccc.dev@gmail.com>
3
+ * Co-Author: AI Assistant (Claude)
4
+ * Description: This code was collaboratively developed by Martin and AI Assistant.
5
+ */
6
+ // 浏览器相关消息 (Browser related messages)
7
+ export const browser = {
8
+ closing: "Closing browser...",
9
+ closed: "Browser closed",
10
+ starting: "Starting new browser instance",
11
+ startingFailed: "Browser startup failed: {{error}}",
12
+ waiting: "Browser is starting, waiting...",
13
+ startupSuccess: "Browser started successfully",
14
+ navigating: "Navigating to URL...",
15
+ waitingForSelector: "Waiting for selector: {{selector}}",
16
+ waitingForTimeout: "Waiting for timeout: {{timeout}}",
17
+ scrolling: "Auto-scrolling page...",
18
+ scrollCompleted: "Auto-scroll completed",
19
+ gettingContent: "Getting page content...",
20
+ savingCookies: "Saving cookies...",
21
+ contentLength: "Content length: {{length}}",
22
+ contentTruncated: "Content too large, truncating...",
23
+ pageClosed: "Page closed",
24
+ fetchError: "Error in browser fetch: {{error}}",
25
+ highMemory: "High memory usage detected, attempting to free resources...",
26
+ closingDueToMemory: "Closing browser due to high memory usage...",
27
+ closingError: "Error closing browser: {{error}}",
28
+ forcingGC: "Forcing garbage collection...",
29
+ memoryCheckError: "Error checking memory usage: {{error}}",
30
+ usingCookies: "Using stored cookies for domain: {{domain}}",
31
+ usingProxy: "Using proxy: {{proxy}}",
32
+ checkingCloudflare: "Checking for Cloudflare protection...",
33
+ cloudflareDetected: "Cloudflare protection detected, attempting to bypass...",
34
+ simulatingHuman: "Simulating human behavior...",
35
+ simulatingHumanError: "Error simulating human behavior: {{error}}",
36
+ stillOnCloudflare: "Still on Cloudflare protection page, trying to refresh...",
37
+ bypassFailed: "Failed to bypass Cloudflare protection",
38
+ cloudflareError: "Error handling Cloudflare protection: {{error}}",
39
+ continuingWithoutBypass: "Unable to bypass Cloudflare protection, continuing to try to get content...",
40
+ fetchingWithRetry: "Fetching HTML with browser (attempt {{attempt}}/{{maxAttempts}}): {{url}}",
41
+ memoryUsage: "Memory usage - Heap: {{heapUsed}}MB/{{heapTotal}}MB, RSS: {{rss}}MB",
42
+ memoryTooHigh: "Memory usage too high - Heap: {{heapUsed}}MB/{{heapTotal}}MB, RSS: {{rss}}MB",
43
+ unableToBypassCloudflare: "Unable to bypass Cloudflare protection, continuing to try to get content...",
44
+ contentTooLarge: "Content too large, truncating...",
45
+ failedToParseJSON: "Failed to parse JSON: {{error}}",
46
+ startingBrowserFetchForMarkdown: "Starting browser fetch for Markdown: {{url}}",
47
+ errorInBrowserFetchForMarkdown: "Error in browser fetch for Markdown: {{error}}",
48
+ fetchRequest: "Browser fetch request: {{url}}",
49
+ usingStoredCookies: "Using stored cookies for domain: {{domain}}",
50
+ closingInstance: "Closing browser instance",
51
+ fetchErrorWithAttempt: "Error in browser fetch (attempt {{attempt}}/{{maxAttempts}}): {{error}}",
52
+ retryingAfterDelay: "Retrying after {{delayMs}}ms delay..."
53
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Author: Martin <lmccc.dev@gmail.com>
3
+ * Co-Author: AI Assistant (Claude)
4
+ * Description: This code was collaboratively developed by Martin and AI Assistant.
5
+ */
6
+ // 客户端相关消息 (Client related messages)
7
+ export const client = {
8
+ connecting: "Connecting to MCP server...",
9
+ connected: "Connected to MCP server",
10
+ disconnecting: "Disconnecting from MCP server...",
11
+ disconnected: "Disconnected from MCP server",
12
+ error: "Client error: {{error}}",
13
+ callTool: "Calling tool: {{tool}}",
14
+ callToolSuccess: "Tool call successful: {{tool}}",
15
+ callToolError: "Tool call failed: {{tool}}, error: {{error}}",
16
+ statusCodeDetected: "HTTP status code detected: {{code}}",
17
+ usageInfo: "Usage: node client.js <method> <params_json> [proxy]",
18
+ exampleUsage: "Example: node client.js fetch_html '{\"url\":\"https://example.com\",\"debug\":true}'",
19
+ invalidJson: "Invalid JSON parameters",
20
+ usingCommandLineProxy: "Using command line proxy: {{proxy}}",
21
+ invalidProxyFormat: "Invalid proxy format: {{proxy}}",
22
+ usingEnvProxy: "Using environment variable proxy: {{proxy}}",
23
+ usingShellProxy: "Using shell proxy: {{proxy}}",
24
+ noShellProxy: "No shell proxy found",
25
+ systemProxyDisabled: "System proxy detection disabled",
26
+ usingSystemProxy: "Using system proxy: {{proxy}}",
27
+ noSystemProxy: "No system proxy found",
28
+ requestFailed: "Request failed: {{error}}",
29
+ fatalError: "Fatal error: {{error}}",
30
+ startingServer: "Starting server at path: {{path}}",
31
+ fetchingUrl: "Fetching URL: {{url}}",
32
+ usingMode: "Using {{mode}} mode for URL: {{url}}",
33
+ fetchFailed: "Fetch failed: {{error}}",
34
+ fetchSuccess: "Fetch successful, content length: {{length}} bytes",
35
+ browserModeNeeded: "Browser mode needed for URL: {{url}}",
36
+ retryingWithBrowser: "Retrying with browser mode for URL: {{url}}",
37
+ browserModeFetchFailed: "Browser mode fetch failed: {{error}}",
38
+ browserModeFetchSuccess: "Browser mode fetch successful, content length: {{length}} bytes",
39
+ serverClosed: "Server closed"
40
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Author: Martin <lmccc.dev@gmail.com>
3
+ * Co-Author: AI Assistant (Claude)
4
+ * Description: This code was collaboratively developed by Martin and AI Assistant.
5
+ */
6
+ // 通用错误消息 (Common error messages)
7
+ export const errors = {
8
+ missingUrl: "Missing required parameter: url",
9
+ invalidUrl: "Invalid URL: {{url}}",
10
+ timeout: "Request timed out after {{timeout}}ms",
11
+ networkError: "Network error: {{error}}",
12
+ browserError: "Browser error: {{error}}",
13
+ unexpectedError: "Unexpected error: {{error}}",
14
+ forbidden: "Reason: Access forbidden (403 Forbidden)",
15
+ cloudflareProtection: "Reason: Cloudflare protection",
16
+ captchaRequired: "Reason: CAPTCHA required",
17
+ connectionProblem: "Reason: Connection problem"
18
+ };