@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,284 @@
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 { Client } from '@modelcontextprotocol/sdk/client/index.js';
7
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { log, COMPONENTS } from './lib/logger.js';
11
+ import { execSync } from 'child_process';
12
+ // 获取当前文件的目录路径 (Get the directory path of the current file)
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+ /**
16
+ * 基于MCP的本地客户端 (MCP-based local client)
17
+ * 使用标准输入输出(Stdio)传输方式与服务端通信 (Communicates with the server using standard input/output (Stdio) transport)
18
+ */
19
+ /**
20
+ * 检查响应是否需要浏览器模式 (Check if response requires browser mode)
21
+ * 根据错误信息判断是否需要切换到浏览器模式 (Determine if need to switch to browser mode based on error message)
22
+ * @param response 响应对象 (Response object)
23
+ * @param debug 是否为调试模式 (Whether in debug mode)
24
+ * @returns 是否需要浏览器模式 (Whether browser mode is needed)
25
+ */
26
+ function responseRequiresBrowser(response, debug = false) {
27
+ if (response.isError) {
28
+ const errorText = response.content[0].text.toLowerCase();
29
+ if (debug) {
30
+ log('fetcher.fetchError', debug, { url: '', error: response.content[0].text }, COMPONENTS.CLIENT);
31
+ // 尝试从错误信息中提取HTTP状态码 (Try to extract HTTP status code from error message)
32
+ const statusCodeMatch = response.content[0].text.match(/(\b[45]\d\d\b)/);
33
+ if (statusCodeMatch) {
34
+ log('client.statusCodeDetected', debug, { code: statusCodeMatch[0] }, COMPONENTS.CLIENT);
35
+ }
36
+ // 尝试解析更详细的错误原因
37
+ if (errorText.includes('403') || errorText.includes('forbidden')) {
38
+ log('errors.forbidden', debug, {}, COMPONENTS.CLIENT);
39
+ }
40
+ else if (errorText.includes('cloudflare')) {
41
+ log('errors.cloudflareProtection', debug, {}, COMPONENTS.CLIENT);
42
+ }
43
+ else if (errorText.includes('captcha')) {
44
+ log('errors.captchaRequired', debug, {}, COMPONENTS.CLIENT);
45
+ }
46
+ else if (errorText.includes('timeout')) {
47
+ log('errors.timeout', debug, { timeout: '' }, COMPONENTS.CLIENT);
48
+ }
49
+ else if (errorText.includes('socket') || errorText.includes('econnrefused')) {
50
+ log('errors.connectionProblem', debug, {}, COMPONENTS.CLIENT);
51
+ }
52
+ }
53
+ return errorText.includes('403') ||
54
+ errorText.includes('forbidden') ||
55
+ errorText.includes('access denied') ||
56
+ errorText.includes('cloudflare') ||
57
+ errorText.includes('captcha') ||
58
+ errorText.includes('javascript required') ||
59
+ errorText.includes('timeout') ||
60
+ errorText.includes('connect timeout') ||
61
+ errorText.includes('socket') ||
62
+ errorText.includes('econnrefused') ||
63
+ errorText.includes('und_err_connect_timeout') ||
64
+ errorText.includes('fetch failed');
65
+ }
66
+ return false;
67
+ }
68
+ /**
69
+ * 智能获取函数,根据需要自动切换模式 (Smart fetch function, automatically switches modes as needed)
70
+ * 支持在标准模式和浏览器模式之间自动切换 (Supports automatic switching between standard mode and browser mode)
71
+ * @param params 请求参数 (Request parameters)
72
+ * @returns 获取结果 (Fetch result)
73
+ */
74
+ async function smartFetch(params) {
75
+ const debug = params.debug === true;
76
+ // 创建服务器进程 (Create server process)
77
+ const serverPath = path.resolve(path.dirname(__dirname), 'index.js');
78
+ log('client.startingServer', debug, { path: serverPath }, COMPONENTS.CLIENT);
79
+ // 创建客户端传输层 (Create client transport layer)
80
+ const transport = new StdioClientTransport({
81
+ command: 'node',
82
+ args: [serverPath],
83
+ stderr: 'inherit',
84
+ env: {
85
+ ...process.env, // 传递所有环境变量,包括MCP_LANG和DEBUG
86
+ }
87
+ });
88
+ // 创建客户端 (Create client)
89
+ const client = new Client({
90
+ name: "fetch-mcp-client",
91
+ version: "1.0.0"
92
+ });
93
+ // 连接到传输层 (Connect to transport layer)
94
+ await client.connect(transport);
95
+ try {
96
+ const { url, method = 'fetch_html' } = params;
97
+ log('client.fetchingUrl', debug, { url }, COMPONENTS.CLIENT);
98
+ // 如果启用了自动检测模式(默认启用) (If auto-detect mode is enabled (enabled by default))
99
+ if (params.autoDetectMode !== false) {
100
+ // 首先尝试使用标准模式 (First try using standard mode)
101
+ log('client.usingMode', debug, { mode: params.useBrowser ? 'browser' : 'standard', url }, COMPONENTS.CLIENT);
102
+ const result = await client.callTool({
103
+ name: method,
104
+ arguments: params
105
+ });
106
+ // 在调试模式下打印响应结果的摘要 (Print response result summary in debug mode)
107
+ if (debug) {
108
+ if (result.isError) {
109
+ log('client.fetchFailed', debug, { error: result.content[0].text.substring(0, 150) + (result.content[0].text.length > 150 ? '...' : '') }, COMPONENTS.CLIENT);
110
+ }
111
+ else {
112
+ const contentLength = result.content[0].text.length;
113
+ log('client.fetchSuccess', debug, { length: contentLength }, COMPONENTS.CLIENT);
114
+ }
115
+ }
116
+ // 如果失败并且响应表明需要浏览器模式,且当前不是浏览器模式 (If failed and response indicates browser mode is needed, and current mode is not browser mode)
117
+ if (!params.useBrowser && responseRequiresBrowser(result, debug)) {
118
+ log('client.browserModeNeeded', debug, { url }, COMPONENTS.CLIENT);
119
+ // 切换到浏览器模式重试 (Switch to browser mode and retry)
120
+ params.useBrowser = true;
121
+ // 设置关闭浏览器选项,确保资源被释放 (Set close browser option to ensure resources are released)
122
+ params.closeBrowser = true;
123
+ log('client.retryingWithBrowser', debug, { url }, COMPONENTS.CLIENT);
124
+ const browserResult = await client.callTool({
125
+ name: method,
126
+ arguments: params
127
+ });
128
+ // 在调试模式下打印浏览器模式的响应结果摘要 (Print browser mode response result summary in debug mode)
129
+ if (debug) {
130
+ if (browserResult.isError) {
131
+ log('client.browserModeFetchFailed', debug, { error: browserResult.content[0].text.substring(0, 150) + (browserResult.content[0].text.length > 150 ? '...' : '') }, COMPONENTS.CLIENT);
132
+ }
133
+ else {
134
+ const contentLength = browserResult.content[0].text.length;
135
+ log('client.browserModeFetchSuccess', debug, { length: contentLength }, COMPONENTS.CLIENT);
136
+ }
137
+ }
138
+ return browserResult;
139
+ }
140
+ return result;
141
+ }
142
+ else {
143
+ // 不启用自动检测,直接使用指定模式
144
+ log('client.usingMode', debug, { mode: params.useBrowser ? 'browser' : 'standard', url }, COMPONENTS.CLIENT);
145
+ // 如果使用浏览器模式,设置关闭浏览器选项
146
+ if (params.useBrowser) {
147
+ params.closeBrowser = true;
148
+ }
149
+ const result = await client.callTool({
150
+ name: method,
151
+ arguments: params
152
+ });
153
+ // 在调试模式下打印响应结果的摘要
154
+ if (debug) {
155
+ if (result.isError) {
156
+ log('client.fetchFailed', debug, { error: result.content[0].text.substring(0, 150) + (result.content[0].text.length > 150 ? '...' : '') }, COMPONENTS.CLIENT);
157
+ }
158
+ else {
159
+ const contentLength = result.content[0].text.length;
160
+ log('client.fetchSuccess', debug, { length: contentLength }, COMPONENTS.CLIENT);
161
+ }
162
+ }
163
+ return result;
164
+ }
165
+ }
166
+ finally {
167
+ // 关闭客户端连接 (Close client connection)
168
+ await client.close();
169
+ log('client.serverClosed', debug, {}, COMPONENTS.CLIENT);
170
+ }
171
+ }
172
+ /**
173
+ * 主函数 (Main function)
174
+ * 处理命令行参数并执行相应操作 (Process command line arguments and perform corresponding operations)
175
+ */
176
+ async function main() {
177
+ // 检查命令行参数 (Check command line arguments)
178
+ if (process.argv.length < 4) {
179
+ log('client.usageInfo', true, {}, COMPONENTS.CLIENT);
180
+ log('client.exampleUsage', true, {}, COMPONENTS.CLIENT);
181
+ process.exit(1);
182
+ }
183
+ const method = process.argv[2];
184
+ let paramsJson = process.argv[3];
185
+ let params;
186
+ try {
187
+ params = JSON.parse(paramsJson);
188
+ }
189
+ catch (error) {
190
+ log('client.invalidJson', true, {}, COMPONENTS.CLIENT);
191
+ process.exit(1);
192
+ }
193
+ const debug = params.debug === true;
194
+ // 检查是否提供了代理参数 (Check if proxy parameter is provided)
195
+ const proxyArg = process.argv[4];
196
+ if (proxyArg) {
197
+ if (proxyArg.startsWith('http://') || proxyArg.startsWith('https://')) {
198
+ log('client.usingCommandLineProxy', debug, { proxy: proxyArg }, COMPONENTS.CLIENT);
199
+ params.proxy = proxyArg;
200
+ }
201
+ else {
202
+ log('client.invalidProxyFormat', debug, { proxy: proxyArg }, COMPONENTS.CLIENT);
203
+ }
204
+ }
205
+ // 如果没有设置代理但启用了系统代理检测 (If no proxy is set but system proxy detection is enabled)
206
+ if (!params.proxy && params.useSystemProxy !== false) {
207
+ // 尝试从环境变量获取代理 (Try to get proxy from environment variables)
208
+ const envProxy = process.env.HTTP_PROXY || process.env.http_proxy ||
209
+ process.env.HTTPS_PROXY || process.env.https_proxy;
210
+ if (envProxy) {
211
+ log('client.usingEnvProxy', debug, { proxy: envProxy }, COMPONENTS.CLIENT);
212
+ params.proxy = envProxy;
213
+ }
214
+ else {
215
+ // 尝试使用系统命令获取环境变量 (Try to get environment variables using system commands)
216
+ try {
217
+ let proxyUrl;
218
+ const platform = process.platform;
219
+ if (platform === 'win32') {
220
+ // Windows系统 - 使用set命令 (Windows - use set command)
221
+ try {
222
+ const setOutput = execSync('set http_proxy & set https_proxy & set HTTP_PROXY & set HTTPS_PROXY').toString();
223
+ const proxyMatch = setOutput.match(/(?:http_proxy|https_proxy|HTTP_PROXY|HTTPS_PROXY)=(https?:\/\/[^=\r\n]+)/i);
224
+ if (proxyMatch && proxyMatch[1]) {
225
+ proxyUrl = proxyMatch[1].trim();
226
+ }
227
+ }
228
+ catch (error) {
229
+ // 忽略错误 (Ignore errors)
230
+ }
231
+ }
232
+ else {
233
+ // Unix系统 (macOS/Linux) - 使用env命令 (Unix systems - use env command)
234
+ try {
235
+ const envOutput = execSync('env | grep -i proxy').toString();
236
+ const proxyMatch = envOutput.match(/(?:http_proxy|https_proxy|HTTP_PROXY|HTTPS_PROXY)=(https?:\/\/[^=\n]+)/i);
237
+ if (proxyMatch && proxyMatch[1]) {
238
+ proxyUrl = proxyMatch[1].trim();
239
+ }
240
+ }
241
+ catch (error) {
242
+ // 忽略错误 (Ignore errors)
243
+ }
244
+ }
245
+ if (proxyUrl) {
246
+ log('client.usingSystemProxy', debug, { proxy: proxyUrl }, COMPONENTS.CLIENT);
247
+ params.proxy = proxyUrl;
248
+ }
249
+ else {
250
+ log('client.noSystemProxy', debug, {}, COMPONENTS.CLIENT);
251
+ }
252
+ }
253
+ catch (error) {
254
+ // 忽略错误 (Ignore errors)
255
+ }
256
+ }
257
+ }
258
+ else if (params.useSystemProxy === false) {
259
+ log('client.systemProxyDisabled', debug, {}, COMPONENTS.CLIENT);
260
+ }
261
+ // 如果已经设置了代理,禁用系统代理自动检测 (If proxy is already set, disable system proxy auto-detection)
262
+ if (params.proxy) {
263
+ log('client.proxySet', debug, { proxy: params.proxy }, COMPONENTS.CLIENT);
264
+ params.useSystemProxy = false;
265
+ }
266
+ try {
267
+ // 执行请求 (Execute request)
268
+ const result = await smartFetch({ ...params, method });
269
+ // 使用process.stdout.write输出结果,这是实际的结果输出,不是日志
270
+ // (Use process.stdout.write to output the result, this is the actual result output, not a log)
271
+ process.stdout.write(JSON.stringify(result, null, 2));
272
+ }
273
+ catch (error) {
274
+ log('client.requestFailed', debug, { error: error.message }, COMPONENTS.CLIENT);
275
+ process.exit(1);
276
+ }
277
+ }
278
+ // 捕获未处理的异常 (Catch unhandled exceptions)
279
+ process.on('uncaughtException', (error) => {
280
+ log('client.fatalError', true, { error: error.toString() }, COMPONENTS.CLIENT);
281
+ process.exit(1);
282
+ });
283
+ // 执行主函数 (Execute main function)
284
+ main();
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Author: Martin <lmccc.dev@gmail.com>
4
+ * Co-Author: AI Assistant (Claude)
5
+ * Description: This code was collaboratively developed by Martin and AI Assistant.
6
+ *
7
+ * MCP 服务器启动入口 (MCP Server Entry Point)
8
+ *
9
+ * 这个文件是 MCP 服务器的主入口点,它导入并启动 mcp-server.ts 中定义的服务器。
10
+ * (This file is the main entry point of the MCP server, it imports and starts the server defined in mcp-server.ts.)
11
+ *
12
+ * 使用方法 (Usage):
13
+ * node dist/index.js
14
+ */
15
+ // 导入日志模块 (Import logger module)
16
+ import { log, COMPONENTS } from './lib/logger.js';
17
+ // 导入服务器模块 (Import server module)
18
+ import './mcp-server.js';
19
+ // 服务器在 mcp-server.js 中已经启动,这里不需要额外的代码
20
+ // (The server is already started in mcp-server.js, no additional code is needed here)
21
+ log('server.entry.loaded', true, {}, COMPONENTS.SERVER);