@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.
- package/LICENSE +21 -0
- package/README.md +512 -0
- package/README.zh.md +510 -0
- package/dist/index.js +21 -0
- package/dist/src/cli-language.js +78 -0
- package/dist/src/client.js +284 -0
- package/dist/src/index.js +21 -0
- package/dist/src/lib/BrowserFetcher.js +753 -0
- package/dist/src/lib/NodeFetcher.js +428 -0
- package/dist/src/lib/example.js +93 -0
- package/dist/src/lib/i18n/index.js +36 -0
- package/dist/src/lib/i18n/locales/en/browser.js +53 -0
- package/dist/src/lib/i18n/locales/en/client.js +40 -0
- package/dist/src/lib/i18n/locales/en/errors.js +18 -0
- package/dist/src/lib/i18n/locales/en/fetcher.js +67 -0
- package/dist/src/lib/i18n/locales/en/index.js +26 -0
- package/dist/src/lib/i18n/locales/en/node.js +42 -0
- package/dist/src/lib/i18n/locales/en/prompts.js +72 -0
- package/dist/src/lib/i18n/locales/en/resources.js +47 -0
- package/dist/src/lib/i18n/locales/en/server.js +32 -0
- package/dist/src/lib/i18n/locales/en/tools.js +25 -0
- package/dist/src/lib/i18n/locales/zh/browser.js +53 -0
- package/dist/src/lib/i18n/locales/zh/client.js +39 -0
- package/dist/src/lib/i18n/locales/zh/errors.js +18 -0
- package/dist/src/lib/i18n/locales/zh/fetcher.js +67 -0
- package/dist/src/lib/i18n/locales/zh/index.js +26 -0
- package/dist/src/lib/i18n/locales/zh/node.js +42 -0
- package/dist/src/lib/i18n/locales/zh/prompts.js +72 -0
- package/dist/src/lib/i18n/locales/zh/resources.js +47 -0
- package/dist/src/lib/i18n/locales/zh/server.js +32 -0
- package/dist/src/lib/i18n/locales/zh/tools.js +25 -0
- package/dist/src/lib/i18n/logger.js +57 -0
- package/dist/src/lib/logger.js +126 -0
- package/dist/src/lib/server/browser.js +86 -0
- package/dist/src/lib/server/fetcher.js +130 -0
- package/dist/src/lib/server/index.js +73 -0
- package/dist/src/lib/server/logger.js +23 -0
- package/dist/src/lib/server/prompts.js +207 -0
- package/dist/src/lib/server/resources.js +171 -0
- package/dist/src/lib/server/tools.js +346 -0
- package/dist/src/lib/server/types.js +6 -0
- package/dist/src/lib/types.js +35 -0
- package/dist/src/mcp-server.js +22 -0
- package/dist/tests/test-direct-client.js +129 -0
- package/dist/tests/test-mcp-methods.js +114 -0
- package/dist/tests/test-mcp.js +145 -0
- package/dist/tests/test-mini4k.js +147 -0
- package/images/example_en.png +0 -0
- package/images/example_zh.png +0 -0
- package/package.json +80 -0
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
export default {
|
|
7
|
+
// 资源列表错误 (Resource list errors)
|
|
8
|
+
list: {
|
|
9
|
+
request: "收到资源列表请求: {params}"
|
|
10
|
+
},
|
|
11
|
+
// 资源读取错误 (Resource read errors)
|
|
12
|
+
read: {
|
|
13
|
+
request: "收到资源读取请求: {uri}",
|
|
14
|
+
error: "读取资源时出错: {uri}, {error}"
|
|
15
|
+
},
|
|
16
|
+
// 文件读取错误 (File read errors)
|
|
17
|
+
fileReadError: "读取文件时出错: {filePath}, {error}",
|
|
18
|
+
// 无效的URI (Invalid URI)
|
|
19
|
+
invalidUri: "无效的资源URI",
|
|
20
|
+
// 资源未找到 (Resource not found)
|
|
21
|
+
notFound: "资源未找到",
|
|
22
|
+
// 资源描述 (Resource descriptions)
|
|
23
|
+
readme: {
|
|
24
|
+
description: "项目自述文件,包含项目概述、安装和使用说明"
|
|
25
|
+
},
|
|
26
|
+
package: {
|
|
27
|
+
description: "项目包配置文件,包含依赖和脚本定义"
|
|
28
|
+
},
|
|
29
|
+
index: {
|
|
30
|
+
description: "项目入口文件,包含服务器启动逻辑"
|
|
31
|
+
},
|
|
32
|
+
client: {
|
|
33
|
+
description: "MCP客户端实现,用于与服务器通信"
|
|
34
|
+
},
|
|
35
|
+
// 资源模板描述 (Resource template descriptions)
|
|
36
|
+
sourceFile: {
|
|
37
|
+
name: "源代码文件",
|
|
38
|
+
description: "访问项目源代码文件"
|
|
39
|
+
},
|
|
40
|
+
docFile: {
|
|
41
|
+
name: "文档文件",
|
|
42
|
+
description: "访问项目文档文件"
|
|
43
|
+
},
|
|
44
|
+
filename: {
|
|
45
|
+
description: "文件名,包括扩展名"
|
|
46
|
+
}
|
|
47
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
// 服务器相关消息 (Server related messages)
|
|
7
|
+
export const server = {
|
|
8
|
+
starting: "MCP 服务器启动中...",
|
|
9
|
+
connecting: "正在连接到 MCP 传输: {{transport}}",
|
|
10
|
+
started: "MCP 服务器已启动",
|
|
11
|
+
stopping: "MCP 服务器停止中...",
|
|
12
|
+
stopped: "MCP 服务器已停止",
|
|
13
|
+
error: "MCP 服务器错误: {{error}}",
|
|
14
|
+
receivedRequest: "收到工具请求: {{tool}}",
|
|
15
|
+
processingRequest: "正在处理工具请求: {{tool}}",
|
|
16
|
+
requestCompleted: "工具请求完成: {{tool}}",
|
|
17
|
+
requestFailed: "工具请求失败: {{tool}}, 错误: {{error}}",
|
|
18
|
+
receivedInterruptSignal: "收到中断信号,正在关闭服务器...",
|
|
19
|
+
receivedTerminateSignal: "收到终止信号,正在关闭服务器...",
|
|
20
|
+
uncaughtException: "未捕获的异常: {{error}}",
|
|
21
|
+
debug: "{{message}}",
|
|
22
|
+
// 新增的翻译键 (New translation keys)
|
|
23
|
+
initializingBrowser: "初始化浏览器实例...",
|
|
24
|
+
closingBrowser: "关闭浏览器实例...",
|
|
25
|
+
receivedToolRequest: "收到{{name}}请求: {{url}}",
|
|
26
|
+
processingToolRequestError: "处理{{name}}请求时出错: {{error}}",
|
|
27
|
+
usingBrowserMode: "使用浏览器模式获取 {{type}}: {{url}}",
|
|
28
|
+
usingAutoDetectMode: "使用自动检测模式获取 {{type}}: {{url}}",
|
|
29
|
+
switchingToBrowserMode: "标准模式失败,切换到浏览器模式: {{url}}",
|
|
30
|
+
fetchError: "获取 {{type}} 时出错: {{error}}",
|
|
31
|
+
connectionError: "连接到传输时出错: {{error}}"
|
|
32
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
// 工具相关消息 (Tool related messages)
|
|
7
|
+
export const tools = {
|
|
8
|
+
fetchHtml: {
|
|
9
|
+
description: "获取网站并以 HTML 格式返回内容",
|
|
10
|
+
error: "获取 HTML 失败: {{error}}"
|
|
11
|
+
},
|
|
12
|
+
fetchJson: {
|
|
13
|
+
description: "从 URL 获取 JSON 文件",
|
|
14
|
+
error: "获取 JSON 失败: {{error}}",
|
|
15
|
+
invalidJson: "URL 返回的 JSON 无效: {{url}}"
|
|
16
|
+
},
|
|
17
|
+
fetchTxt: {
|
|
18
|
+
description: "获取网站,以纯文本格式返回内容(无 HTML)",
|
|
19
|
+
error: "获取文本失败: {{error}}"
|
|
20
|
+
},
|
|
21
|
+
fetchMarkdown: {
|
|
22
|
+
description: "获取网站并以 Markdown 格式返回内容",
|
|
23
|
+
error: "获取 Markdown 失败: {{error}}"
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
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 { t } from './index.js';
|
|
7
|
+
/**
|
|
8
|
+
* 创建一个带前缀的日志记录器 (Create a logger with prefix)
|
|
9
|
+
* @param prefix 日志前缀 (Log prefix)
|
|
10
|
+
* @returns 日志记录器对象 (Logger object)
|
|
11
|
+
*/
|
|
12
|
+
export function createLogger(prefix) {
|
|
13
|
+
return {
|
|
14
|
+
/**
|
|
15
|
+
* 输出信息日志 (Output info log)
|
|
16
|
+
* @param key 翻译键 (Translation key)
|
|
17
|
+
* @param params 翻译参数 (Translation parameters)
|
|
18
|
+
* @param forceDebug 是否强制输出信息日志 (Whether to force output info logs)
|
|
19
|
+
*/
|
|
20
|
+
info: (key, params, forceDebug) => {
|
|
21
|
+
// 只有在环境变量DEBUG为true或forceDebug为true时才输出信息日志
|
|
22
|
+
if (process.env.DEBUG === 'true' || forceDebug === true) {
|
|
23
|
+
console.error(`[${prefix}] ${t(key, params)}`);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
/**
|
|
27
|
+
* 输出错误日志 (Output error log)
|
|
28
|
+
* @param key 翻译键 (Translation key)
|
|
29
|
+
* @param params 翻译参数 (Translation parameters)
|
|
30
|
+
*/
|
|
31
|
+
error: (key, params) => {
|
|
32
|
+
// 错误日志总是输出,不受debug控制
|
|
33
|
+
console.error(`[${prefix}] ${t(key, params)}`);
|
|
34
|
+
},
|
|
35
|
+
/**
|
|
36
|
+
* 输出警告日志 (Output warning log)
|
|
37
|
+
* @param key 翻译键 (Translation key)
|
|
38
|
+
* @param params 翻译参数 (Translation parameters)
|
|
39
|
+
*/
|
|
40
|
+
warn: (key, params) => {
|
|
41
|
+
// 警告日志总是输出,不受debug控制
|
|
42
|
+
console.error(`[${prefix}] ${t(key, params)}`);
|
|
43
|
+
},
|
|
44
|
+
/**
|
|
45
|
+
* 输出调试日志 (Output debug log)
|
|
46
|
+
* @param key 翻译键 (Translation key)
|
|
47
|
+
* @param params 翻译参数 (Translation parameters)
|
|
48
|
+
* @param forceDebug 是否强制输出调试日志 (Whether to force output debug logs)
|
|
49
|
+
*/
|
|
50
|
+
debug: (key, params, forceDebug) => {
|
|
51
|
+
// 只有在forceDebug=true时才输出调试日志
|
|
52
|
+
if (forceDebug === true) {
|
|
53
|
+
console.error(`[${prefix}] ${t(key, params)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
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 { createLogger } from './i18n/logger.js';
|
|
7
|
+
import { t } from './i18n/index.js';
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import os from 'os';
|
|
11
|
+
// 组件名称常量 (Component name constants)
|
|
12
|
+
export const COMPONENTS = {
|
|
13
|
+
SERVER: 'MCP-SERVER',
|
|
14
|
+
NODE_FETCH: 'NODE-FETCH',
|
|
15
|
+
BROWSER_FETCH: 'BROWSER-FETCH',
|
|
16
|
+
CLIENT: 'CLIENT',
|
|
17
|
+
RESOURCES: 'MCP-RESOURCES',
|
|
18
|
+
PROMPTS: 'MCP-PROMPTS',
|
|
19
|
+
TOOLS: 'MCP-TOOLS'
|
|
20
|
+
};
|
|
21
|
+
// 日志文件路径 (Log file path)
|
|
22
|
+
const LOG_DIR = path.join(os.homedir(), '.mult-fetch-mcp-server');
|
|
23
|
+
const LOG_FILE = path.join(LOG_DIR, 'debug.log');
|
|
24
|
+
// 确保日志目录存在 (Ensure log directory exists)
|
|
25
|
+
try {
|
|
26
|
+
if (!fs.existsSync(LOG_DIR)) {
|
|
27
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
console.error(`[${COMPONENTS.SERVER}] Failed to create log directory: ${error}`);
|
|
32
|
+
}
|
|
33
|
+
// 组件日志记录器缓存 (Component logger cache)
|
|
34
|
+
const loggerCache = {};
|
|
35
|
+
/**
|
|
36
|
+
* 获取指定组件的日志记录器 (Get logger for specified component)
|
|
37
|
+
* @param component 组件名称 (Component name)
|
|
38
|
+
* @returns 日志记录器 (Logger)
|
|
39
|
+
*/
|
|
40
|
+
function getLogger(component) {
|
|
41
|
+
if (!loggerCache[component]) {
|
|
42
|
+
loggerCache[component] = createLogger(component);
|
|
43
|
+
}
|
|
44
|
+
return loggerCache[component];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 获取格式化的本地时间 (Get formatted local time)
|
|
48
|
+
* @returns 格式化的本地时间字符串 (Formatted local time string)
|
|
49
|
+
*/
|
|
50
|
+
function getFormattedLocalTime() {
|
|
51
|
+
const now = new Date();
|
|
52
|
+
// 获取本地时间的年、月、日、时、分、秒、毫秒
|
|
53
|
+
const year = now.getFullYear();
|
|
54
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
55
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
56
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
57
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
58
|
+
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
59
|
+
const milliseconds = String(now.getMilliseconds()).padStart(3, '0');
|
|
60
|
+
// 格式化为 YYYY-MM-DD HH:MM:SS.mmm
|
|
61
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 将日志写入文件 (Write log to file)
|
|
65
|
+
* @param component 组件名称 (Component name)
|
|
66
|
+
* @param message 日志消息 (Log message)
|
|
67
|
+
*/
|
|
68
|
+
function writeToLogFile(component, message) {
|
|
69
|
+
try {
|
|
70
|
+
const timestamp = getFormattedLocalTime();
|
|
71
|
+
const logEntry = `${timestamp} [${component}] ${message}\n`;
|
|
72
|
+
fs.appendFileSync(LOG_FILE, logEntry);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.error(`[${COMPONENTS.SERVER}] Failed to write to log file: ${error}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 清除日志文件 (Clear log file)
|
|
80
|
+
*/
|
|
81
|
+
export function clearLogFile() {
|
|
82
|
+
try {
|
|
83
|
+
fs.writeFileSync(LOG_FILE, '');
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error(`[${COMPONENTS.SERVER}] Failed to clear log file: ${error}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 获取日志文件路径 (Get log file path)
|
|
91
|
+
* @returns 日志文件的完整路径 (Full path to the log file)
|
|
92
|
+
*/
|
|
93
|
+
export function getLogFilePath() {
|
|
94
|
+
return LOG_FILE;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 统一日志函数 (Unified log function)
|
|
98
|
+
* 输出调试信息到标准错误流和日志文件 (Output debug information to standard error stream and log file)
|
|
99
|
+
* @param key 翻译键或消息 (Translation key or message)
|
|
100
|
+
* @param debug 是否为调试模式 (Whether in debug mode)
|
|
101
|
+
* @param options 翻译选项 (Translation options)
|
|
102
|
+
* @param component 组件名称 (Component name)
|
|
103
|
+
*/
|
|
104
|
+
export function log(key, debug = false, options, component = COMPONENTS.SERVER) {
|
|
105
|
+
// 只有在明确设置 debug 为 true 时才输出日志 (Only output logs when debug is explicitly set to true)
|
|
106
|
+
if (!debug) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// 获取组件的日志记录器 (Get logger for component)
|
|
110
|
+
const logger = getLogger(component);
|
|
111
|
+
// 输出到控制台 (Output to console)
|
|
112
|
+
logger.debug(key, options, debug);
|
|
113
|
+
// 翻译消息 (Translate message)
|
|
114
|
+
let translatedMessage;
|
|
115
|
+
try {
|
|
116
|
+
const translated = t(key, options);
|
|
117
|
+
// 确保 translatedMessage 是字符串 (Ensure translatedMessage is a string)
|
|
118
|
+
translatedMessage = typeof translated === 'string' ? translated : JSON.stringify(translated);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
// 如果翻译失败,使用原始消息 (If translation fails, use original message)
|
|
122
|
+
translatedMessage = typeof key === 'string' ? key : JSON.stringify(key);
|
|
123
|
+
}
|
|
124
|
+
// 写入日志文件 (Write to log file)
|
|
125
|
+
writeToLogFile(component, translatedMessage);
|
|
126
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
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 { BrowserFetcher } from '../BrowserFetcher.js';
|
|
7
|
+
import { log, COMPONENTS } from '../logger.js';
|
|
8
|
+
// 全局浏览器实例 (Global browser instance)
|
|
9
|
+
let browserInitialized = false;
|
|
10
|
+
/**
|
|
11
|
+
* 初始化浏览器实例 (Initialize browser instance)
|
|
12
|
+
* 确保浏览器实例已准备好用于网页获取 (Ensure browser instance is ready for webpage fetching)
|
|
13
|
+
* @param debug 是否为调试模式 (Whether in debug mode)
|
|
14
|
+
* @returns Promise对象 (Promise object)
|
|
15
|
+
*/
|
|
16
|
+
export async function initializeBrowser(debug = false) {
|
|
17
|
+
if (!browserInitialized) {
|
|
18
|
+
if (debug) {
|
|
19
|
+
log('server.initializingBrowser', debug, {}, COMPONENTS.SERVER);
|
|
20
|
+
}
|
|
21
|
+
// BrowserFetcher 是静态类,不需要实例化 (BrowserFetcher is a static class, no need to instantiate)
|
|
22
|
+
browserInitialized = true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 关闭浏览器实例 (Close browser instance)
|
|
27
|
+
* 释放浏览器资源 (Release browser resources)
|
|
28
|
+
* @param debug 是否为调试模式 (Whether in debug mode)
|
|
29
|
+
* @returns Promise对象 (Promise object)
|
|
30
|
+
*/
|
|
31
|
+
export async function closeBrowserInstance(debug = false) {
|
|
32
|
+
if (browserInitialized) {
|
|
33
|
+
if (debug) {
|
|
34
|
+
log('server.closingBrowser', debug, {}, COMPONENTS.SERVER);
|
|
35
|
+
}
|
|
36
|
+
await BrowserFetcher.closeBrowser(true);
|
|
37
|
+
browserInitialized = false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 检查是否应该切换到浏览器模式 (Check if should switch to browser mode)
|
|
42
|
+
* 根据错误信息判断是否需要浏览器模式 (Determine if browser mode is needed based on error message)
|
|
43
|
+
* @param error 错误对象 (Error object)
|
|
44
|
+
* @returns 是否应该切换到浏览器模式 (Whether to switch to browser mode)
|
|
45
|
+
*/
|
|
46
|
+
export function shouldSwitchToBrowser(error) {
|
|
47
|
+
// 检查错误是否表明需要浏览器模式 (Check if error indicates browser mode is needed)
|
|
48
|
+
if (error && error.message) {
|
|
49
|
+
const errorMessage = error.message.toLowerCase();
|
|
50
|
+
return errorMessage.includes('403') ||
|
|
51
|
+
errorMessage.includes('forbidden') ||
|
|
52
|
+
errorMessage.includes('access denied') ||
|
|
53
|
+
errorMessage.includes('cloudflare') ||
|
|
54
|
+
errorMessage.includes('captcha') ||
|
|
55
|
+
errorMessage.includes('javascript required') ||
|
|
56
|
+
errorMessage.includes('timeout') ||
|
|
57
|
+
errorMessage.includes('connect timeout') ||
|
|
58
|
+
errorMessage.includes('socket') ||
|
|
59
|
+
errorMessage.includes('econnrefused') ||
|
|
60
|
+
errorMessage.includes('und_err_connect_timeout') ||
|
|
61
|
+
errorMessage.includes('fetch failed');
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 检查是否应该使用浏览器模式 (Check if browser mode should be used)
|
|
67
|
+
* 根据响应内容判断是否需要浏览器模式 (Determine if browser mode is needed based on response content)
|
|
68
|
+
* @param response 响应对象 (Response object)
|
|
69
|
+
* @param url 请求URL (Request URL)
|
|
70
|
+
* @returns 是否应该使用浏览器模式 (Whether to use browser mode)
|
|
71
|
+
*/
|
|
72
|
+
export function shouldUseBrowser(response, url) {
|
|
73
|
+
// 检查响应是否表明需要浏览器模式 (Check if response indicates browser mode is needed)
|
|
74
|
+
if (response.isError) {
|
|
75
|
+
const errorText = response.content[0].text.toLowerCase();
|
|
76
|
+
return errorText.includes('403') ||
|
|
77
|
+
errorText.includes('forbidden') ||
|
|
78
|
+
errorText.includes('access denied') ||
|
|
79
|
+
errorText.includes('cloudflare') ||
|
|
80
|
+
errorText.includes('captcha') ||
|
|
81
|
+
errorText.includes('javascript required') ||
|
|
82
|
+
errorText.includes('browser') ||
|
|
83
|
+
errorText.includes('cookie');
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
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 { BrowserFetcher } from '../BrowserFetcher.js';
|
|
7
|
+
import { NodeFetcher } from '../NodeFetcher.js';
|
|
8
|
+
import { log, COMPONENTS } from '../logger.js';
|
|
9
|
+
import { initializeBrowser, closeBrowserInstance, shouldSwitchToBrowser } from './browser.js';
|
|
10
|
+
/**
|
|
11
|
+
* 辅助函数:根据类型和参数自动选择合适的获取方法 (Helper function: automatically select appropriate fetching method based on type and parameters)
|
|
12
|
+
* 支持自动在标准模式和浏览器模式之间切换 (Supports automatic switching between standard mode and browser mode)
|
|
13
|
+
* @param params 请求参数 (Request parameters)
|
|
14
|
+
* @param type 内容类型 (Content type)
|
|
15
|
+
* @returns 获取结果 (Fetch result)
|
|
16
|
+
*/
|
|
17
|
+
export async function fetchWithAutoDetect(params, type) {
|
|
18
|
+
const useBrowser = params.useBrowser === true;
|
|
19
|
+
const autoDetectMode = params.autoDetectMode !== false; // 除非明确设置为 false,否则默认为 true (Unless explicitly set to false, default to true)
|
|
20
|
+
const closeBrowserAfterFetch = params.closeBrowser === true; // 是否在获取后关闭浏览器 (Whether to close the browser after fetching)
|
|
21
|
+
// 严格从调用参数中获取debug值
|
|
22
|
+
const debug = params.debug === true;
|
|
23
|
+
try {
|
|
24
|
+
if (useBrowser) {
|
|
25
|
+
// 如果使用浏览器模式,确保浏览器已初始化 (If using browser mode, ensure browser is initialized)
|
|
26
|
+
await initializeBrowser(debug);
|
|
27
|
+
if (debug) {
|
|
28
|
+
log('server.usingBrowserMode', debug, { type, url: params.url }, COMPONENTS.SERVER);
|
|
29
|
+
}
|
|
30
|
+
// 确保params中包含debug参数 (Ensure params contains debug parameter)
|
|
31
|
+
params.debug = debug;
|
|
32
|
+
// 根据类型选择合适的浏览器获取方法 (Choose appropriate browser fetching method based on type)
|
|
33
|
+
switch (type) {
|
|
34
|
+
case 'html':
|
|
35
|
+
return await BrowserFetcher.html(params);
|
|
36
|
+
case 'json':
|
|
37
|
+
return await BrowserFetcher.json(params);
|
|
38
|
+
case 'txt':
|
|
39
|
+
return await BrowserFetcher.txt(params);
|
|
40
|
+
case 'markdown':
|
|
41
|
+
return await BrowserFetcher.markdown(params);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// 使用标准模式
|
|
46
|
+
if (debug) {
|
|
47
|
+
log('server.usingAutoDetectMode', debug, { type, url: params.url }, COMPONENTS.SERVER);
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
// 根据类型选择合适的标准获取方法 (Choose appropriate standard fetching method based on type)
|
|
51
|
+
switch (type) {
|
|
52
|
+
case 'html': {
|
|
53
|
+
const result = await NodeFetcher.html(params);
|
|
54
|
+
// 转换为统一格式 (Convert to unified format)
|
|
55
|
+
return {
|
|
56
|
+
content: [{ type: 'text', text: result.html }],
|
|
57
|
+
isError: false
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
case 'json': {
|
|
61
|
+
const result = await NodeFetcher.json(params);
|
|
62
|
+
// 转换为统一格式 (Convert to unified format)
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: 'text', text: typeof result.json === 'string' ? result.json : JSON.stringify(result.json, null, 2) }],
|
|
65
|
+
isError: false
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
case 'txt': {
|
|
69
|
+
const result = await NodeFetcher.txt(params);
|
|
70
|
+
// 转换为统一格式 (Convert to unified format)
|
|
71
|
+
return {
|
|
72
|
+
content: [{ type: 'text', text: result.text }],
|
|
73
|
+
isError: false
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
case 'markdown': {
|
|
77
|
+
const result = await NodeFetcher.markdown(params);
|
|
78
|
+
// 转换为统一格式 (Convert to unified format)
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: 'text', text: result.markdown }],
|
|
81
|
+
isError: false
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
// 如果标准模式失败且启用了自动检测
|
|
88
|
+
if (autoDetectMode && shouldSwitchToBrowser(error)) {
|
|
89
|
+
if (debug) {
|
|
90
|
+
log('server.switchingToBrowserMode', debug, { url: params.url }, COMPONENTS.SERVER);
|
|
91
|
+
}
|
|
92
|
+
// 确保浏览器已初始化
|
|
93
|
+
await initializeBrowser(debug);
|
|
94
|
+
// 设置浏览器模式参数
|
|
95
|
+
params.useBrowser = true;
|
|
96
|
+
params.debug = debug;
|
|
97
|
+
// 根据类型选择合适的浏览器获取方法
|
|
98
|
+
switch (type) {
|
|
99
|
+
case 'html':
|
|
100
|
+
return await BrowserFetcher.html(params);
|
|
101
|
+
case 'json':
|
|
102
|
+
return await BrowserFetcher.json(params);
|
|
103
|
+
case 'txt':
|
|
104
|
+
return await BrowserFetcher.txt(params);
|
|
105
|
+
case 'markdown':
|
|
106
|
+
return await BrowserFetcher.markdown(params);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// 如果不需要切换到浏览器模式,则抛出原始错误
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
// 处理所有错误
|
|
116
|
+
if (debug) {
|
|
117
|
+
log('server.fetchError', debug, { type, error: error instanceof Error ? error.message : String(error) }, COMPONENTS.SERVER);
|
|
118
|
+
}
|
|
119
|
+
// 如果出错且启用了关闭浏览器选项,确保浏览器被关闭
|
|
120
|
+
if (closeBrowserAfterFetch) {
|
|
121
|
+
await closeBrowserInstance(debug);
|
|
122
|
+
}
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
// 如果没有匹配的类型,返回错误
|
|
126
|
+
return {
|
|
127
|
+
content: [{ type: 'text', text: `Unsupported content type: ${type}` }],
|
|
128
|
+
isError: true
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
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 { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { log, COMPONENTS } from '../logger.js';
|
|
9
|
+
import { closeBrowserInstance } from './browser.js';
|
|
10
|
+
import { registerTools } from './tools.js';
|
|
11
|
+
import { registerResources } from './resources.js';
|
|
12
|
+
import { registerPrompts } from './prompts.js';
|
|
13
|
+
// 创建MCP服务器
|
|
14
|
+
const server = new Server({
|
|
15
|
+
name: "mult-fetch-mcp-server",
|
|
16
|
+
version: "1.0.0",
|
|
17
|
+
}, {
|
|
18
|
+
capabilities: {
|
|
19
|
+
resources: {
|
|
20
|
+
listChanged: true
|
|
21
|
+
},
|
|
22
|
+
tools: {},
|
|
23
|
+
prompts: {
|
|
24
|
+
listChanged: true
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* 启动服务器 (Start the server)
|
|
30
|
+
* 初始化MCP服务器并设置信号处理 (Initialize MCP server and set up signal handling)
|
|
31
|
+
*/
|
|
32
|
+
export async function startServer() {
|
|
33
|
+
// 注册工具
|
|
34
|
+
registerTools(server);
|
|
35
|
+
// 注册资源
|
|
36
|
+
registerResources(server);
|
|
37
|
+
// 注册提示
|
|
38
|
+
registerPrompts(server);
|
|
39
|
+
log('server.starting', true, {}, COMPONENTS.SERVER);
|
|
40
|
+
// 初始化 StdioServerTransport,不传递任何参数
|
|
41
|
+
const transport = new StdioServerTransport();
|
|
42
|
+
// 设置进程退出时的清理逻辑 (Set up cleanup logic when process exits)
|
|
43
|
+
process.on('exit', () => {
|
|
44
|
+
log('server.stopping', true, {}, COMPONENTS.SERVER);
|
|
45
|
+
// 同步清理逻辑 (Synchronous cleanup logic)
|
|
46
|
+
});
|
|
47
|
+
process.on('SIGINT', async () => {
|
|
48
|
+
log('server.receivedInterruptSignal', true, {}, COMPONENTS.SERVER);
|
|
49
|
+
await closeBrowserInstance(false);
|
|
50
|
+
process.exit(0);
|
|
51
|
+
});
|
|
52
|
+
process.on('SIGTERM', async () => {
|
|
53
|
+
log('server.receivedTerminateSignal', true, {}, COMPONENTS.SERVER);
|
|
54
|
+
await closeBrowserInstance(false);
|
|
55
|
+
process.exit(0);
|
|
56
|
+
});
|
|
57
|
+
// 处理未捕获的异常
|
|
58
|
+
process.on('uncaughtException', async (error) => {
|
|
59
|
+
log('server.uncaughtException', true, { error: error.message }, COMPONENTS.SERVER);
|
|
60
|
+
await closeBrowserInstance(false);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
});
|
|
63
|
+
try {
|
|
64
|
+
log('server.connecting', true, { transport: 'stdio' }, COMPONENTS.SERVER);
|
|
65
|
+
await server.connect(transport);
|
|
66
|
+
log('server.started', true, {}, COMPONENTS.SERVER);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
log('server.connectionError', true, { error: error instanceof Error ? error.message : String(error) }, COMPONENTS.SERVER);
|
|
70
|
+
await closeBrowserInstance(false);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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 { createLogger } from '../i18n/logger.js';
|
|
7
|
+
// 创建服务器日志记录器 (Create server logger)
|
|
8
|
+
const logger = createLogger('MCP-SERVER');
|
|
9
|
+
/**
|
|
10
|
+
* 日志函数 (Log function)
|
|
11
|
+
* 输出调试信息到标准错误流 (Output debug information to standard error stream)
|
|
12
|
+
* @param key 翻译键或消息 (Translation key or message)
|
|
13
|
+
* @param debug 是否为调试模式 (Whether in debug mode)
|
|
14
|
+
* @param options 翻译选项 (Translation options)
|
|
15
|
+
*/
|
|
16
|
+
export function log(key, debug = false, options) {
|
|
17
|
+
// 只有在明确设置 debug 为 true 时才输出日志 (Only output logs when debug is explicitly set to true)
|
|
18
|
+
if (!debug) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// 所有日志消息都使用翻译键 (All log messages use translation keys)
|
|
22
|
+
logger.debug(key, options, debug);
|
|
23
|
+
}
|