@lmcc-dev/mult-fetch-mcp-server 1.2.1 → 1.3.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/README.md +80 -29
- package/README.zh.md +80 -29
- package/dist/i18n-test-report.json +2 -2
- package/dist/i18n-unused-keys-report.json +4 -4
- package/dist/src/client.js +522 -131
- package/dist/src/index.js +0 -0
- package/dist/src/lib/fetch.js +8 -0
- package/dist/src/lib/fetchers/browser/BrowserFetcher.js +139 -199
- package/dist/src/lib/fetchers/common/BaseFetcher.js +203 -0
- package/dist/src/lib/fetchers/common/types.js +4 -0
- package/dist/src/lib/fetchers/common/utils.js +1 -1
- package/dist/src/lib/fetchers/index.js +11 -0
- package/dist/src/lib/fetchers/node/NodeFetcher.js +91 -66
- package/dist/src/lib/i18n/keys/browser.js +40 -2
- package/dist/src/lib/i18n/keys/chunkManager.js +43 -0
- package/dist/src/lib/i18n/keys/client.js +53 -1
- package/dist/src/lib/i18n/keys/contentSize.js +20 -0
- package/dist/src/lib/i18n/keys/fetcher.js +88 -79
- package/dist/src/lib/i18n/keys/index.js +4 -0
- package/dist/src/lib/i18n/keys/node.js +7 -1
- package/dist/src/lib/i18n/keys/processor.js +30 -0
- package/dist/src/lib/i18n/keys/tools.js +5 -1
- package/dist/src/lib/i18n/keys/url.js +45 -0
- package/dist/src/lib/i18n/keys.js +25 -0
- package/dist/src/lib/i18n/locales/en/browser.js +52 -14
- package/dist/src/lib/i18n/locales/en/chunkManager.js +41 -0
- package/dist/src/lib/i18n/locales/en/client.js +62 -9
- package/dist/src/lib/i18n/locales/en/contentSize.js +17 -0
- package/dist/src/lib/i18n/locales/en/fetcher.js +50 -30
- package/dist/src/lib/i18n/locales/en/index.js +9 -1
- package/dist/src/lib/i18n/locales/en/node.js +26 -20
- package/dist/src/lib/i18n/locales/en/processor.js +28 -0
- package/dist/src/lib/i18n/locales/en/tools.js +5 -1
- package/dist/src/lib/i18n/locales/en/url.js +40 -0
- package/dist/src/lib/i18n/locales/en.js +51 -0
- package/dist/src/lib/i18n/locales/zh/browser.js +85 -47
- package/dist/src/lib/i18n/locales/zh/chunkManager.js +41 -0
- package/dist/src/lib/i18n/locales/zh/client.js +53 -1
- package/dist/src/lib/i18n/locales/zh/contentSize.js +17 -0
- package/dist/src/lib/i18n/locales/zh/fetcher.js +67 -47
- package/dist/src/lib/i18n/locales/zh/index.js +9 -1
- package/dist/src/lib/i18n/locales/zh/node.js +50 -44
- package/dist/src/lib/i18n/locales/zh/processor.js +28 -0
- package/dist/src/lib/i18n/locales/zh/tools.js +5 -1
- package/dist/src/lib/i18n/locales/zh/url.js +40 -0
- package/dist/src/lib/i18n/locales/zh.js +51 -0
- package/dist/src/lib/logger.js +11 -2
- package/dist/src/lib/server/fetcher.js +15 -5
- package/dist/src/lib/server/tools.js +202 -52
- package/dist/src/lib/types.js +5 -0
- package/dist/src/lib/utils/ChunkManager.js +177 -0
- package/dist/src/lib/utils/ContentProcessor.js +134 -0
- package/dist/src/lib/utils/ContentSizeManager.js +123 -0
- package/dist/src/lib/utils/TemplateUtils.js +71 -0
- package/dist/src/lib/utils/errors.js +2 -8
- package/dist/src/mcp-server.js +0 -0
- package/dist/src/test-i18n.js +0 -0
- package/dist/tests/BrowserFetcher.test.js +0 -0
- package/dist/tests/NodeFetcher.test.js +0 -0
- package/dist/tests/client.test.js +0 -0
- package/dist/tests/fetch.test.js +0 -0
- package/dist/tests/fetchers/plain-text.test.js +146 -0
- package/dist/tests/i18n-missing-keys.js +0 -0
- package/dist/tests/i18n-remove-unused-keys.js +0 -0
- package/dist/tests/i18n-unused-keys.js +0 -0
- package/dist/tests/i18n.test.js +0 -0
- package/dist/tests/logger.test.js +0 -0
- package/dist/tests/mcp-server.test.js +0 -0
- package/dist/tests/server/fetcher.test.js +69 -15
- package/dist/tests/server/tools.test.js +165 -16
- package/dist/tests/setup.js +0 -0
- package/dist/tests/test-direct-client.js +113 -5
- package/dist/tests/test-i18n.js +0 -0
- package/dist/tests/test-mcp-methods.js +67 -1
- package/dist/tests/test-mcp.js +83 -3
- package/dist/tests/test-mini4k.js +90 -3
- package/dist/tests/types.test.js +0 -0
- package/dist/tests/utils/ChunkManager.test.js +163 -0
- package/dist/tests/utils/ContentSizeManager.test.js +170 -0
- package/dist/vitest.config.js +0 -0
- package/package.json +28 -23
package/dist/src/client.js
CHANGED
|
@@ -10,9 +10,16 @@ import { fileURLToPath } from 'url';
|
|
|
10
10
|
import { log, COMPONENTS } from './lib/logger.js';
|
|
11
11
|
import { execSync } from 'child_process';
|
|
12
12
|
import { isAccessDeniedError, isNetworkError } from './lib/utils/errorDetection.js';
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import os from 'os';
|
|
15
|
+
import { ContentSizeManager } from './lib/utils/ContentSizeManager.js';
|
|
13
16
|
// 获取当前文件的目录路径 (Get the directory path of the current file)
|
|
14
17
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
18
|
const __dirname = path.dirname(__filename);
|
|
19
|
+
// 临时文件路径,用于存储分段信息 (Temporary file path for storing chunk information)
|
|
20
|
+
const CHUNK_INFO_FILE = path.join(os.tmpdir(), 'fetch-mcp-chunk-info.json');
|
|
21
|
+
// 添加最大分块数限制,默认为10 (Add maximum chunk limit, default is 10)
|
|
22
|
+
const DEFAULT_MAX_CHUNKS = 10;
|
|
16
23
|
/**
|
|
17
24
|
* 基于MCP的本地客户端 (MCP-based local client)
|
|
18
25
|
* 使用标准输入输出(Stdio)传输方式与服务端通信 (Communicates with the server using standard input/output (Stdio) transport)
|
|
@@ -69,151 +76,265 @@ function responseRequiresBrowser(response, debug = false) {
|
|
|
69
76
|
return false;
|
|
70
77
|
}
|
|
71
78
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
79
|
+
* 保存分段信息到临时文件 (Save chunk information to temporary file)
|
|
80
|
+
* @param result 获取结果 (Fetch result)
|
|
74
81
|
* @param params 请求参数 (Request parameters)
|
|
75
|
-
* @
|
|
82
|
+
* @param debug 是否为调试模式 (Whether in debug mode)
|
|
76
83
|
*/
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
env: {
|
|
88
|
-
...process.env, // 传递所有环境变量,包括MCP_LANG和DEBUG
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
// 创建客户端 (Create client)
|
|
92
|
-
const client = new Client({
|
|
93
|
-
name: "fetch-mcp-client",
|
|
94
|
-
version: "1.0.0"
|
|
95
|
-
});
|
|
96
|
-
// 连接到传输层 (Connect to transport layer)
|
|
97
|
-
await client.connect(transport);
|
|
98
|
-
try {
|
|
99
|
-
const { url, method = 'fetch_html' } = params;
|
|
100
|
-
log('client.fetchingUrl', debug, { url }, COMPONENTS.CLIENT);
|
|
101
|
-
// 如果启用了自动检测模式(默认启用) (If auto-detect mode is enabled (enabled by default))
|
|
102
|
-
if (params.autoDetectMode !== false) {
|
|
103
|
-
// 首先尝试使用标准模式 (First try using standard mode)
|
|
104
|
-
log('client.usingMode', debug, { mode: params.useBrowser ? 'browser' : 'standard', url }, COMPONENTS.CLIENT);
|
|
105
|
-
// 确保服务端也启用了自动检测模式
|
|
106
|
-
const paramsWithAutoDetect = {
|
|
107
|
-
...params,
|
|
108
|
-
autoDetectMode: true
|
|
84
|
+
function saveChunkInfo(result, params, debug = false) {
|
|
85
|
+
if (result.isChunked && result.hasMoreChunks) {
|
|
86
|
+
try {
|
|
87
|
+
const chunkInfo = {
|
|
88
|
+
method: params.method || '',
|
|
89
|
+
url: params.url,
|
|
90
|
+
chunkId: result.chunkId,
|
|
91
|
+
currentChunk: result.currentChunk,
|
|
92
|
+
totalChunks: result.totalChunks,
|
|
93
|
+
params: { ...params }
|
|
109
94
|
};
|
|
110
|
-
|
|
111
|
-
name: method,
|
|
112
|
-
arguments: paramsWithAutoDetect
|
|
113
|
-
});
|
|
114
|
-
// 在调试模式下打印响应结果的摘要 (Print response result summary in debug mode)
|
|
95
|
+
fs.writeFileSync(CHUNK_INFO_FILE, JSON.stringify(chunkInfo, null, 2));
|
|
115
96
|
if (debug) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
// 检查是否需要切换到浏览器模式 (Check if need to switch to browser mode)
|
|
124
|
-
if (result.isError && responseRequiresBrowser(result, debug) && !params.useBrowser) {
|
|
125
|
-
log('client.browserModeNeeded', debug, { url }, COMPONENTS.CLIENT);
|
|
126
|
-
log('client.retryingWithBrowser', debug, { url }, COMPONENTS.CLIENT);
|
|
127
|
-
// 切换到浏览器模式重试 (Switch to browser mode and retry)
|
|
128
|
-
const browserParams = {
|
|
129
|
-
...params,
|
|
130
|
-
useBrowser: true,
|
|
131
|
-
debug: true, // 确保在浏览器模式下启用调试
|
|
132
|
-
autoDetectMode: true // 确保自动检测模式也被传递
|
|
133
|
-
};
|
|
134
|
-
const browserResult = await client.callTool({
|
|
135
|
-
name: method,
|
|
136
|
-
arguments: browserParams
|
|
137
|
-
});
|
|
138
|
-
if (browserResult.isError) {
|
|
139
|
-
log('client.browserModeFetchFailed', debug, { url, error: browserResult.content[0].text }, COMPONENTS.CLIENT);
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
log('client.browserModeFetchSuccess', debug, { url }, COMPONENTS.CLIENT);
|
|
143
|
-
}
|
|
144
|
-
return browserResult;
|
|
97
|
+
log('client.chunkInfoSaved', debug, {
|
|
98
|
+
file: CHUNK_INFO_FILE,
|
|
99
|
+
current: result.currentChunk,
|
|
100
|
+
total: result.totalChunks
|
|
101
|
+
}, COMPONENTS.CLIENT);
|
|
145
102
|
}
|
|
146
|
-
|
|
147
|
-
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (debug) {
|
|
106
|
+
log('client.chunkInfoSaveError', debug, { error: String(error) }, COMPONENTS.CLIENT);
|
|
148
107
|
}
|
|
149
|
-
|
|
150
|
-
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 加载分段信息从临时文件 (Load chunk information from temporary file)
|
|
113
|
+
* @param debug 是否为调试模式 (Whether in debug mode)
|
|
114
|
+
* @returns 分段信息 (Chunk information)
|
|
115
|
+
*/
|
|
116
|
+
function loadChunkInfo(debug = false) {
|
|
117
|
+
try {
|
|
118
|
+
if (fs.existsSync(CHUNK_INFO_FILE)) {
|
|
119
|
+
const chunkInfoStr = fs.readFileSync(CHUNK_INFO_FILE, 'utf8');
|
|
120
|
+
const chunkInfo = JSON.parse(chunkInfoStr);
|
|
121
|
+
if (debug) {
|
|
122
|
+
log('client.chunkInfoLoaded', debug, {
|
|
123
|
+
file: CHUNK_INFO_FILE,
|
|
124
|
+
current: chunkInfo.currentChunk,
|
|
125
|
+
total: chunkInfo.totalChunks
|
|
126
|
+
}, COMPONENTS.CLIENT);
|
|
151
127
|
}
|
|
152
|
-
return
|
|
128
|
+
return chunkInfo;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (debug) {
|
|
133
|
+
log('client.chunkInfoLoadError', debug, { error: String(error) }, COMPONENTS.CLIENT);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 从响应内容中解析分段信息 (Parse chunk information from response content)
|
|
140
|
+
* @param result 响应结果 (Response result)
|
|
141
|
+
* @returns 包含分段信息的结果 (Result with chunk information)
|
|
142
|
+
*/
|
|
143
|
+
function parseChunkInfo(result) {
|
|
144
|
+
if (result.isError || !result.content || !result.content[0] || !result.content[0].text) {
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
const content = result.content[0].text;
|
|
148
|
+
const chunkIdMatch = content.match(/chunkId="([^"]+)"/);
|
|
149
|
+
// 更新正则表达式,识别新的基于字节的分块信息 (Update regex to identify new byte-based chunking info)
|
|
150
|
+
const byteInfoMatch = content.match(/(\d+) bytes retrieved \((\d+)% of total (\d+) bytes\)/);
|
|
151
|
+
const remainingBytesMatch = content.match(/(\d+) bytes remaining/);
|
|
152
|
+
// 存在chunkId但找不到分块信息,可能是老的格式 (ChunkId exists but no chunking info, might be old format)
|
|
153
|
+
const partMatch = content.match(/This is part (\d+) of (\d+)/);
|
|
154
|
+
if (!chunkIdMatch) {
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
const chunkId = chunkIdMatch[1];
|
|
158
|
+
let currentChunk = 0;
|
|
159
|
+
let totalChunks = 1;
|
|
160
|
+
let hasMoreChunks = false;
|
|
161
|
+
let fetchedBytes = 0;
|
|
162
|
+
let totalBytes = 0;
|
|
163
|
+
let remainingBytes = 0;
|
|
164
|
+
// 检查是否包含字节级分块信息 (Check if contains byte-level chunking info)
|
|
165
|
+
if (byteInfoMatch) {
|
|
166
|
+
fetchedBytes = parseInt(byteInfoMatch[1], 10);
|
|
167
|
+
const percentage = parseInt(byteInfoMatch[2], 10);
|
|
168
|
+
totalBytes = parseInt(byteInfoMatch[3], 10);
|
|
169
|
+
if (remainingBytesMatch) {
|
|
170
|
+
remainingBytes = parseInt(remainingBytesMatch[1], 10);
|
|
153
171
|
}
|
|
154
172
|
else {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
});
|
|
166
|
-
if (result.isError) {
|
|
167
|
-
log('client.fetchFailed', debug, { url, error: result.content[0].text }, COMPONENTS.CLIENT);
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
log('client.fetchSuccess', debug, { url }, COMPONENTS.CLIENT);
|
|
171
|
-
}
|
|
172
|
-
return result;
|
|
173
|
+
remainingBytes = totalBytes - fetchedBytes;
|
|
174
|
+
}
|
|
175
|
+
// 如果还有剩余字节,表示有更多分块 (If there are remaining bytes, indicates more chunks)
|
|
176
|
+
hasMoreChunks = remainingBytes > 0;
|
|
177
|
+
// 估算总分块数 (Estimate total chunks)
|
|
178
|
+
if (fetchedBytes > 0) {
|
|
179
|
+
// 估算总块数,基于已获取的字节数 (Estimate total chunks based on fetched bytes)
|
|
180
|
+
totalChunks = Math.ceil(totalBytes / fetchedBytes);
|
|
181
|
+
// 当前是第几块 (Current chunk number)
|
|
182
|
+
currentChunk = Math.floor(fetchedBytes / (totalBytes / totalChunks));
|
|
173
183
|
}
|
|
174
184
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
185
|
+
// 如果没有字节级信息,使用旧的部分匹配 (If no byte-level info, use old part matching)
|
|
186
|
+
else if (partMatch) {
|
|
187
|
+
currentChunk = parseInt(partMatch[1], 10) - 1; // 转换为0索引 (Convert to 0-based index)
|
|
188
|
+
totalChunks = parseInt(partMatch[2], 10);
|
|
189
|
+
hasMoreChunks = currentChunk < totalChunks - 1;
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
...result,
|
|
193
|
+
isChunked: hasMoreChunks,
|
|
194
|
+
hasMoreChunks,
|
|
195
|
+
chunkId,
|
|
196
|
+
currentChunk,
|
|
197
|
+
totalChunks,
|
|
198
|
+
fetchedBytes,
|
|
199
|
+
totalBytes,
|
|
200
|
+
remainingBytes
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* 智能获取函数 (Smart fetch function)
|
|
205
|
+
* 根据参数执行相应的获取操作 (Perform fetch operation based on parameters)
|
|
206
|
+
*/
|
|
207
|
+
async function smartFetch(params, client) {
|
|
208
|
+
// 不再从params中解构出debug,保留在rest中传递给MCP
|
|
209
|
+
const { method, ...rest } = params;
|
|
210
|
+
const debug = params.debug === true;
|
|
211
|
+
// 检查是否提供了方法 (Check if method is provided)
|
|
212
|
+
if (!method) {
|
|
213
|
+
throw new Error('Method is required');
|
|
214
|
+
}
|
|
215
|
+
// 检查是否提供了URL (Check if URL is provided)
|
|
216
|
+
if (method.includes('fetch_') && !rest.url) {
|
|
217
|
+
throw new Error('URL is required for fetch methods');
|
|
218
|
+
}
|
|
219
|
+
// 记录获取URL (Log fetch URL)
|
|
220
|
+
if (rest.url) {
|
|
221
|
+
log('client.fetchingUrl', debug, { url: rest.url }, COMPONENTS.CLIENT);
|
|
222
|
+
}
|
|
223
|
+
// 检查是否需要使用浏览器模式 (Check if browser mode is needed)
|
|
224
|
+
const useBrowser = rest.useBrowser === true;
|
|
225
|
+
// 记录使用的模式 (Log mode used)
|
|
226
|
+
if (useBrowser) {
|
|
227
|
+
log('client.usingBrowserMode', debug, {}, COMPONENTS.CLIENT);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
log('client.usingStandardMode', debug, {}, COMPONENTS.CLIENT);
|
|
231
|
+
}
|
|
232
|
+
// 在debug模式下输出发送给MCP的参数 (Output parameters sent to MCP in debug mode)
|
|
233
|
+
if (debug) {
|
|
234
|
+
log('client.sendingParameters', debug, { params: JSON.stringify(rest) }, COMPONENTS.CLIENT);
|
|
235
|
+
}
|
|
236
|
+
// 执行获取操作 (Perform fetch operation)
|
|
237
|
+
// 注意:rest中包含debug参数,会被传递给MCP (Note: rest contains debug parameter, which will be passed to MCP)
|
|
238
|
+
const result = await client.callTool({
|
|
239
|
+
name: method,
|
|
240
|
+
arguments: rest
|
|
241
|
+
});
|
|
242
|
+
// 检查是否需要使用浏览器模式 (Check if browser mode is needed)
|
|
243
|
+
if (responseRequiresBrowser(result) && !useBrowser && rest.autoDetectMode !== false) {
|
|
244
|
+
log('client.switchingToBrowserMode', debug, {}, COMPONENTS.CLIENT);
|
|
245
|
+
// 使用浏览器模式重新获取 (Retry with browser mode)
|
|
246
|
+
return smartFetch({
|
|
247
|
+
...params,
|
|
248
|
+
useBrowser: true
|
|
249
|
+
}, client);
|
|
250
|
+
}
|
|
251
|
+
// 记录获取成功 (Log fetch success)
|
|
252
|
+
if (!result.isError) {
|
|
253
|
+
log('client.fetchSuccessful', debug, {}, COMPONENTS.CLIENT);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
// 记录获取失败 (Log fetch failure)
|
|
257
|
+
log('client.fetchFailed', true, { error: result.content[0].text }, COMPONENTS.CLIENT);
|
|
179
258
|
}
|
|
259
|
+
return result;
|
|
180
260
|
}
|
|
181
261
|
/**
|
|
182
262
|
* 主函数 (Main function)
|
|
183
263
|
* 处理命令行参数并执行相应操作 (Process command line arguments and perform corresponding operations)
|
|
184
264
|
*/
|
|
185
265
|
async function main() {
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
266
|
+
// 检查是否请求所有分段内容 (Check if requesting all chunks)
|
|
267
|
+
const allChunksFlag = process.argv.includes('--all-chunks');
|
|
268
|
+
let method;
|
|
269
|
+
let params = {};
|
|
270
|
+
let paramsJson = "{}";
|
|
271
|
+
// 获取最大分块数限制 (Get maximum chunk limit)
|
|
272
|
+
let maxChunks = DEFAULT_MAX_CHUNKS;
|
|
273
|
+
const maxChunksArg = process.argv.find(arg => arg.startsWith('--max-chunks='));
|
|
274
|
+
if (maxChunksArg) {
|
|
275
|
+
const maxChunksValue = maxChunksArg.split('=')[1];
|
|
276
|
+
if (maxChunksValue && !isNaN(parseInt(maxChunksValue))) {
|
|
277
|
+
maxChunks = parseInt(maxChunksValue);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// 解析命令行参数 (Parse command line arguments)
|
|
281
|
+
if (process.argv.length < 3) {
|
|
282
|
+
log('client.usageInfo', true, { info: 'node src/client.js <method> <params_json> [--debug] [--all-chunks] [--max-chunks=N]' }, COMPONENTS.CLIENT);
|
|
283
|
+
log('client.exampleUsage', true, { example: 'node src/client.js fetch_html {"url":"https://example.com"}' }, COMPONENTS.CLIENT);
|
|
284
|
+
log('client.chunkUsageInfo', true, {}, COMPONENTS.CLIENT);
|
|
285
|
+
log('client.allChunksUsageInfo', true, {}, COMPONENTS.CLIENT);
|
|
286
|
+
log('client.maxChunksUsageInfo', true, { default: DEFAULT_MAX_CHUNKS }, COMPONENTS.CLIENT);
|
|
287
|
+
log('client.alternateUsageInfo', true, { example: 'node src/client.js fetch_html --url=https://example.com --startCursor=0' }, COMPONENTS.CLIENT);
|
|
190
288
|
process.exit(1);
|
|
191
289
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
290
|
+
method = process.argv[2];
|
|
291
|
+
// 检查参数格式:JSON 或 命令行参数 (Check parameter format: JSON or command line arguments)
|
|
292
|
+
const thirdArg = process.argv[3];
|
|
293
|
+
const isJsonParams = thirdArg && thirdArg.startsWith('{') && thirdArg.endsWith('}');
|
|
294
|
+
if (isJsonParams) {
|
|
295
|
+
// JSON 格式参数 (JSON format parameters)
|
|
296
|
+
paramsJson = thirdArg;
|
|
297
|
+
try {
|
|
298
|
+
params = JSON.parse(paramsJson);
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
log('client.invalidJson', true, { error: String(e) }, COMPONENTS.CLIENT);
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
197
304
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
//
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
305
|
+
else {
|
|
306
|
+
// 命令行参数格式 (Command line argument format)
|
|
307
|
+
// 收集所有 --key=value 格式的参数
|
|
308
|
+
const argParams = process.argv.slice(3).filter(arg => arg.startsWith('--') && arg.includes('='));
|
|
309
|
+
// 创建参数对象 (Create parameter object)
|
|
310
|
+
for (const arg of argParams) {
|
|
311
|
+
if (arg === '--debug' || arg === '--all-chunks' || arg.startsWith('--max-chunks=')) {
|
|
312
|
+
continue; // 这些是特殊标志,不是参数 (These are special flags, not parameters)
|
|
313
|
+
}
|
|
314
|
+
const [key, value] = arg.substring(2).split('=');
|
|
315
|
+
if (key && value !== undefined) {
|
|
316
|
+
// 尝试将可能的数字和布尔值转换为实际类型 (Try to convert possible numbers and booleans to actual types)
|
|
317
|
+
if (value === 'true') {
|
|
318
|
+
params[key] = true;
|
|
207
319
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
320
|
+
else if (value === 'false') {
|
|
321
|
+
params[key] = false;
|
|
322
|
+
}
|
|
323
|
+
else if (!isNaN(Number(value)) && value.trim() !== '') {
|
|
324
|
+
params[key] = Number(value);
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
params[key] = value;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// 转换为 JSON 字符串以便在日志等地方使用 (Convert to JSON string for use in logs, etc.)
|
|
332
|
+
paramsJson = JSON.stringify(params);
|
|
212
333
|
}
|
|
213
|
-
const debug = params.debug === true;
|
|
334
|
+
const debug = params.debug === true || process.argv.includes('--debug');
|
|
214
335
|
// 检查是否提供了代理参数 (Check if proxy parameter is provided)
|
|
215
336
|
const proxyArg = process.argv[4];
|
|
216
|
-
if (proxyArg) {
|
|
337
|
+
if (proxyArg && !proxyArg.startsWith('--')) {
|
|
217
338
|
if (proxyArg.startsWith('http://') || proxyArg.startsWith('https://')) {
|
|
218
339
|
log('client.usingCommandLineProxy', debug, { proxy: proxyArg }, COMPONENTS.CLIENT);
|
|
219
340
|
params.proxy = proxyArg;
|
|
@@ -283,33 +404,303 @@ async function main() {
|
|
|
283
404
|
log('client.proxySet', debug, { proxy: params.proxy }, COMPONENTS.CLIENT);
|
|
284
405
|
params.useSystemProxy = false;
|
|
285
406
|
}
|
|
407
|
+
// 创建服务器进程 (Create server process)
|
|
408
|
+
const serverPath = path.resolve(path.dirname(__dirname), 'index.js');
|
|
409
|
+
log('client.startingServer', debug, { path: serverPath }, COMPONENTS.CLIENT);
|
|
410
|
+
// 创建客户端传输层 (Create client transport layer)
|
|
411
|
+
const transport = new StdioClientTransport({
|
|
412
|
+
command: 'node',
|
|
413
|
+
args: [serverPath],
|
|
414
|
+
stderr: 'inherit',
|
|
415
|
+
env: {
|
|
416
|
+
...process.env, // 传递所有环境变量,包括MCP_LANG和DEBUG
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
// 创建客户端 (Create client)
|
|
420
|
+
const client = new Client({
|
|
421
|
+
name: "fetch-mcp-client",
|
|
422
|
+
version: "1.0.0"
|
|
423
|
+
});
|
|
424
|
+
// 连接到传输层 (Connect to transport layer)
|
|
425
|
+
await client.connect(transport);
|
|
286
426
|
try {
|
|
287
|
-
//
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
427
|
+
// 执行获取操作 (Perform fetch operation)
|
|
428
|
+
const initialParams = {
|
|
429
|
+
...params,
|
|
430
|
+
debug: debug,
|
|
431
|
+
method
|
|
432
|
+
};
|
|
433
|
+
// 获取第一段内容 (Fetch first chunk)
|
|
434
|
+
let result = await smartFetch(initialParams, client);
|
|
435
|
+
// 调试输出,查看响应结构 (Debug output, check response structure)
|
|
436
|
+
if (debug) {
|
|
437
|
+
log('client.responseStructure', debug, { structure: JSON.stringify(result, null, 2) }, COMPONENTS.CLIENT);
|
|
438
|
+
}
|
|
439
|
+
// 从响应内容中解析分段信息 (Parse chunk information from response content)
|
|
440
|
+
let chunkId;
|
|
441
|
+
let currentChunk = 0;
|
|
442
|
+
let totalChunks = 1;
|
|
443
|
+
let hasMoreChunks = false;
|
|
444
|
+
if (result.content && result.content[0] && result.content[0].text) {
|
|
445
|
+
const content = result.content[0].text;
|
|
446
|
+
// Update regex to match chunk information in system notes
|
|
447
|
+
const systemNoteMatch = content.match(/=== SYSTEM NOTE ===\s*([\s\S]*?)\s*={19}/);
|
|
448
|
+
const chunkIdMatch = systemNoteMatch ? systemNoteMatch[1].match(/chunkId(?:=|\s*[:=]\s*|\s*)?["']?([^"',\s]+)["']?/i) : null;
|
|
449
|
+
// 匹配字节级分块信息 (Match byte-level chunking information)
|
|
450
|
+
// 更新正则表达式来适应新的系统注释格式 (Update regex to adapt to new system note format)
|
|
451
|
+
const bytesRetrievedMatch = systemNoteMatch ?
|
|
452
|
+
systemNoteMatch[1].match(/(?:You'?(?:ve|have))?\s*retrieved\s*(\d+(?:,\d+)*)\s*bytes\s*\((\d+)%\s*of\s*(?:the\s*)?total\s*(\d+(?:,\d+)*)\s*bytes\)/) : null;
|
|
453
|
+
// 尝试兼容新旧两种格式 (Try to be compatible with both new and old formats)
|
|
454
|
+
const remainingBytesMatch = systemNoteMatch ?
|
|
455
|
+
systemNoteMatch[1].match(/(\d+(?:,\d+)*)\s*bytes\s*(?:are\s*)?remaining/) : null;
|
|
456
|
+
const moreRequestsMatch = systemNoteMatch ?
|
|
457
|
+
systemNoteMatch[1].match(/(?:a|ap)proximately\s*(\d+)\s*more\s*requests?\s*(?:are\s*)?needed/) : null;
|
|
458
|
+
// 旧版分块信息匹配 (Old version chunking information matching)
|
|
459
|
+
const partMatch = systemNoteMatch ?
|
|
460
|
+
systemNoteMatch[1].match(/This is part (\d+) of (\d+)/) : null;
|
|
461
|
+
if (chunkIdMatch) {
|
|
462
|
+
chunkId = chunkIdMatch[1];
|
|
463
|
+
// 判断是否有字节级分块信息 (Check if there's byte-level chunking info)
|
|
464
|
+
if (bytesRetrievedMatch) {
|
|
465
|
+
// 移除数字中的逗号 (Remove commas from numbers)
|
|
466
|
+
const fetchedBytesStr = bytesRetrievedMatch[1].replace(/,/g, '');
|
|
467
|
+
const totalBytesStr = bytesRetrievedMatch[3].replace(/,/g, '');
|
|
468
|
+
const fetchedBytes = parseInt(fetchedBytesStr, 10);
|
|
469
|
+
const percentage = parseInt(bytesRetrievedMatch[2], 10);
|
|
470
|
+
const totalBytes = parseInt(totalBytesStr, 10);
|
|
471
|
+
let remainingBytes = 0;
|
|
472
|
+
if (remainingBytesMatch) {
|
|
473
|
+
remainingBytes = parseInt(remainingBytesMatch[1].replace(/,/g, ''), 10);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
remainingBytes = totalBytes - fetchedBytes;
|
|
477
|
+
}
|
|
478
|
+
// 如果还有剩余字节,表示有更多分块 (If there are remaining bytes, indicates more chunks)
|
|
479
|
+
hasMoreChunks = remainingBytes > 0;
|
|
480
|
+
// 估算剩余的请求次数 (Estimate remaining requests)
|
|
481
|
+
let estimatedRemainingRequests = 0;
|
|
482
|
+
if (moreRequestsMatch) {
|
|
483
|
+
estimatedRemainingRequests = parseInt(moreRequestsMatch[1], 10);
|
|
484
|
+
}
|
|
485
|
+
else if (fetchedBytes > 0) {
|
|
486
|
+
// 基于当前获取的字节数估算 (Estimate based on current fetched bytes)
|
|
487
|
+
estimatedRemainingRequests = Math.ceil(remainingBytes / fetchedBytes);
|
|
488
|
+
}
|
|
489
|
+
// 估算总分块数 (Estimate total chunks)
|
|
490
|
+
totalChunks = 1 + estimatedRemainingRequests;
|
|
491
|
+
currentChunk = 0; // 当前是第一块 (Current is the first chunk)
|
|
492
|
+
// 将字节信息添加到结果中 (Add byte information to result)
|
|
493
|
+
result.fetchedBytes = fetchedBytes;
|
|
494
|
+
result.totalBytes = totalBytes;
|
|
495
|
+
result.remainingBytes = remainingBytes;
|
|
496
|
+
// 调试输出,查看解析后的分段信息 (Debug output, check parsed chunk information)
|
|
497
|
+
if (debug) {
|
|
498
|
+
log('client.parsedByteChunkInfo', debug, {
|
|
499
|
+
chunkId,
|
|
500
|
+
fetchedBytes,
|
|
501
|
+
totalBytes,
|
|
502
|
+
remainingBytes,
|
|
503
|
+
estimatedRemainingRequests
|
|
504
|
+
}, COMPONENTS.CLIENT);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
// 尝试匹配旧版的分块格式 (Try to match old version chunking format)
|
|
508
|
+
else if (partMatch) {
|
|
509
|
+
currentChunk = parseInt(partMatch[1], 10) - 1; // 转换为0索引 (Convert to 0-based index)
|
|
510
|
+
totalChunks = parseInt(partMatch[2], 10);
|
|
511
|
+
hasMoreChunks = currentChunk < totalChunks - 1;
|
|
512
|
+
// 调试输出,查看解析后的分段信息 (Debug output, check parsed chunk information)
|
|
513
|
+
if (debug) {
|
|
514
|
+
log('client.parsedChunkInfo', debug, {
|
|
515
|
+
chunkId,
|
|
516
|
+
currentChunk,
|
|
517
|
+
totalChunks,
|
|
518
|
+
hasMoreChunks
|
|
519
|
+
}, COMPONENTS.CLIENT);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
// 输出第一段内容 (Output first chunk)
|
|
525
|
+
console.log(JSON.stringify(result, null, 2));
|
|
526
|
+
// 如果启用了获取所有分段内容,并且结果包含分段信息 (If all chunks mode is enabled and result contains chunk information)
|
|
527
|
+
if (allChunksFlag && hasMoreChunks && chunkId) {
|
|
528
|
+
try {
|
|
529
|
+
// 确保totalChunks的计算正确 (Ensure totalChunks calculation is correct)
|
|
530
|
+
if (result.totalBytes && result.fetchedBytes) {
|
|
531
|
+
// 字节级分块:根据总字节数和已获取字节数计算总块数
|
|
532
|
+
// Byte-level chunking: calculate total chunks based on total bytes and fetched bytes
|
|
533
|
+
const contentSizeLimit = params.contentSizeLimit || ContentSizeManager.getDefaultSizeLimit();
|
|
534
|
+
const remainingBytes = result.totalBytes - result.fetchedBytes;
|
|
535
|
+
// 计算剩余需要的块数,加上已获取的第一个块
|
|
536
|
+
// Calculate remaining chunks needed, plus the already fetched first chunk
|
|
537
|
+
totalChunks = Math.ceil(remainingBytes / contentSizeLimit) + 1;
|
|
538
|
+
log('client.recalculatedTotalChunks', debug, {
|
|
539
|
+
totalBytes: result.totalBytes,
|
|
540
|
+
fetchedBytes: result.fetchedBytes,
|
|
541
|
+
remainingBytes: remainingBytes,
|
|
542
|
+
contentSizeLimit: contentSizeLimit,
|
|
543
|
+
totalChunks: totalChunks
|
|
544
|
+
}, COMPONENTS.CLIENT);
|
|
545
|
+
}
|
|
546
|
+
// 计算实际要获取的分块数量 (Calculate actual number of chunks to fetch)
|
|
547
|
+
const actualChunksToFetch = Math.min(totalChunks - 1, maxChunks);
|
|
548
|
+
log('client.fetchingAllChunks', debug, { total: totalChunks, fetching: actualChunksToFetch }, COMPONENTS.CLIENT);
|
|
549
|
+
if (actualChunksToFetch < totalChunks - 1) {
|
|
550
|
+
log('client.limitingChunks', debug, { limit: maxChunks, total: totalChunks }, COMPONENTS.CLIENT);
|
|
551
|
+
log('client.chunkLimitNotice', true, { total: totalChunks, fetching: maxChunks + 1 }, COMPONENTS.CLIENT);
|
|
552
|
+
log('client.chunkLimitHint', true, {}, COMPONENTS.CLIENT);
|
|
553
|
+
}
|
|
554
|
+
// 获取剩余的分段内容 (Fetch remaining chunks)
|
|
555
|
+
let nextStartCursor = result.fetchedBytes || 0; // 字节级分块的起始位置 (Starting position for byte-level chunking)
|
|
556
|
+
// 立即输出分块分隔符,表示第一个分块已完成 (Output chunk separator immediately to indicate first chunk is complete)
|
|
557
|
+
console.log('\n'); // 添加空行使输出更清晰 (Add empty line for clearer output)
|
|
558
|
+
log('client.chunkSeparator', true, {}, COMPONENTS.CLIENT);
|
|
559
|
+
log('client.firstChunkCompleted', true, { index: 1, total: totalChunks }, COMPONENTS.CLIENT);
|
|
560
|
+
for (let i = 1; i <= actualChunksToFetch; i++) {
|
|
561
|
+
const currentChunkNumber = i + 1; // 当前分块编号(从1开始)
|
|
562
|
+
// 输出正在获取的分块的信息 (Output information about the chunk being fetched)
|
|
563
|
+
log('client.fetchingChunk', debug, { index: i, total: totalChunks }, COMPONENTS.CLIENT);
|
|
564
|
+
log('client.fetchingChunkProgress', true, { current: currentChunkNumber, total: totalChunks }, COMPONENTS.CLIENT);
|
|
565
|
+
// 准备获取下一段内容的参数 (Prepare parameters for fetching next chunk)
|
|
566
|
+
const nextChunkParams = {
|
|
567
|
+
...params,
|
|
568
|
+
debug: debug,
|
|
569
|
+
method,
|
|
570
|
+
chunkId: chunkId,
|
|
571
|
+
startCursor: nextStartCursor, // 使用字节级起始位置 (Use byte-level starting position)
|
|
572
|
+
closeBrowser: false // 获取后续分段时强制设置为false,避免重复关闭浏览器
|
|
573
|
+
};
|
|
574
|
+
// 获取下一段内容 (Fetch next chunk)
|
|
575
|
+
const nextChunkResult = await smartFetch(nextChunkParams, client);
|
|
576
|
+
// 如果获取失败,输出错误信息并退出 (If fetch fails, output error message and exit)
|
|
577
|
+
if (nextChunkResult.isError) {
|
|
578
|
+
log('client.fetchChunkFailed', debug, { index: i, error: nextChunkResult.content[0].text }, COMPONENTS.CLIENT);
|
|
579
|
+
log('client.fetchChunkFailedError', true, { index: currentChunkNumber, error: nextChunkResult.content[0].text }, COMPONENTS.CLIENT);
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
// 更新下一段的起始位置 (Update starting position for next chunk)
|
|
583
|
+
if (nextChunkResult.fetchedBytes) {
|
|
584
|
+
// 使用返回的已获取字节数更新起始位置 (Use returned fetched bytes to update starting position)
|
|
585
|
+
nextStartCursor = result.fetchedBytes + nextChunkResult.fetchedBytes;
|
|
586
|
+
}
|
|
587
|
+
else if (nextChunkResult.content && nextChunkResult.content[0] && nextChunkResult.content[0].text) {
|
|
588
|
+
// 尝试从响应内容中解析起始位置 (Try to parse starting position from response content)
|
|
589
|
+
const content = nextChunkResult.content[0].text;
|
|
590
|
+
const systemNoteMatch = content.match(/=== SYSTEM NOTE ===\s*([\s\S]*?)\s*={19}/);
|
|
591
|
+
if (systemNoteMatch) {
|
|
592
|
+
const startCursorMatch = systemNoteMatch[1].match(/startCursor=(\d+)/);
|
|
593
|
+
if (startCursorMatch) {
|
|
594
|
+
nextStartCursor = parseInt(startCursorMatch[1], 10);
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
// 如果找不到明确的startCursor值,使用估算值 (If no explicit startCursor value found, use estimated value)
|
|
598
|
+
nextStartCursor += (params.contentSizeLimit || 1000);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
// 如果没有字节级信息,则递增索引 (If no byte-level info, increment index)
|
|
603
|
+
nextStartCursor += (params.contentSizeLimit || 1000);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
// 如果没有任何信息可用,则递增索引 (If no information available, increment index)
|
|
608
|
+
nextStartCursor += (params.contentSizeLimit || 1000);
|
|
609
|
+
}
|
|
610
|
+
// 立即输出分块完成的信息 (Output chunk completion information immediately)
|
|
611
|
+
log('client.chunkContent', true, { index: currentChunkNumber }, COMPONENTS.CLIENT);
|
|
612
|
+
// 立即输出当前分块内容 (Output current chunk content immediately)
|
|
613
|
+
console.log(JSON.stringify(nextChunkResult, null, 2));
|
|
614
|
+
// 输出分隔符,使每个分块的输出更清晰 (Output separator for clearer distinction between chunks)
|
|
615
|
+
console.log('\n'); // 添加空行使输出更清晰 (Add empty line for clearer output)
|
|
616
|
+
log('client.chunkSeparator', true, {}, COMPONENTS.CLIENT);
|
|
617
|
+
// 输出当前进度信息 (Output current progress information)
|
|
618
|
+
if (nextChunkResult.fetchedBytes && nextChunkResult.totalBytes) {
|
|
619
|
+
const totalFetchedBytes = (result.fetchedBytes || 0) + nextChunkResult.fetchedBytes;
|
|
620
|
+
const percent = Math.floor((totalFetchedBytes / nextChunkResult.totalBytes) * 100);
|
|
621
|
+
log('client.chunkProgress', true, {
|
|
622
|
+
current: currentChunkNumber,
|
|
623
|
+
total: totalChunks,
|
|
624
|
+
fetchedBytes: totalFetchedBytes,
|
|
625
|
+
totalBytes: nextChunkResult.totalBytes,
|
|
626
|
+
percent: percent
|
|
627
|
+
}, COMPONENTS.CLIENT);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
log('client.chunkCompleted', true, { current: currentChunkNumber, total: totalChunks }, COMPONENTS.CLIENT);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// 输出所有分段内容获取完成的信息 (Output all chunks fetched message)
|
|
634
|
+
const fetchedChunks = Math.min(totalChunks, maxChunks + 1);
|
|
635
|
+
log('client.allChunksFetched', debug, { fetched: fetchedChunks, total: totalChunks }, COMPONENTS.CLIENT);
|
|
636
|
+
if (fetchedChunks < totalChunks) {
|
|
637
|
+
log('client.partialChunksFetched', true, { fetched: fetchedChunks, total: totalChunks }, COMPONENTS.CLIENT);
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
log('client.completeChunksFetched', true, { total: totalChunks }, COMPONENTS.CLIENT);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
catch (error) {
|
|
644
|
+
// 处理获取分块内容过程中的错误 (Handle errors during chunk fetching)
|
|
645
|
+
log('client.fetchingChunksError', true, { error: String(error) }, COMPONENTS.CLIENT);
|
|
646
|
+
log('client.fetchingChunksErrorMessage', true, { error: String(error) }, COMPONENTS.CLIENT);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
// 如果结果包含分段信息,但没有启用获取所有分段内容 (If result contains chunk information but all chunks mode is not enabled)
|
|
650
|
+
else if (hasMoreChunks && chunkId) {
|
|
651
|
+
log('client.hasMoreChunks', debug, {
|
|
652
|
+
current: currentChunk,
|
|
653
|
+
total: totalChunks,
|
|
654
|
+
fetchedBytes: result.fetchedBytes,
|
|
655
|
+
totalBytes: result.totalBytes,
|
|
656
|
+
remainingBytes: result.remainingBytes
|
|
657
|
+
}, COMPONENTS.CLIENT);
|
|
658
|
+
// 提示用户如何获取所有分段内容 (Prompt user how to get all chunks)
|
|
659
|
+
const allChunksCommand = `node dist/src/client.js ${method} '${paramsJson}' ${debug ? '--debug' : ''} --all-chunks`;
|
|
660
|
+
const allChunksWithLimitCommand = `node dist/src/client.js ${method} '${paramsJson}' ${debug ? '--debug' : ''} --all-chunks --max-chunks=${maxChunks}`;
|
|
661
|
+
log('client.allChunksCommand', debug, { command: allChunksCommand }, COMPONENTS.CLIENT);
|
|
662
|
+
// 输出提示信息,告知用户有更多分段内容 (Output prompt message, inform user there are more chunks)
|
|
663
|
+
if (result.fetchedBytes && result.totalBytes) {
|
|
664
|
+
log('client.contentChunkedBytes', true, { fetched: result.fetchedBytes, total: result.totalBytes }, COMPONENTS.CLIENT);
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
log('client.contentChunkedCount', true, { current: currentChunk + 1, total: totalChunks }, COMPONENTS.CLIENT);
|
|
668
|
+
}
|
|
669
|
+
log('client.fetchAllChunksHint', true, { command: allChunksCommand }, COMPONENTS.CLIENT);
|
|
670
|
+
if (totalChunks > maxChunks + 1) {
|
|
671
|
+
log('client.fetchLimitedChunksHint', true, { limit: maxChunks + 1, command: allChunksWithLimitCommand }, COMPONENTS.CLIENT);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
292
674
|
}
|
|
293
675
|
catch (error) {
|
|
294
|
-
|
|
295
|
-
|
|
676
|
+
// 处理错误 (Handle error)
|
|
677
|
+
log('client.fatalError', true, { error: String(error) }, COMPONENTS.CLIENT);
|
|
678
|
+
// 返回标准错误结构体 (Return standard error structure)
|
|
296
679
|
const errorResult = {
|
|
297
680
|
isError: true,
|
|
298
681
|
content: [
|
|
299
682
|
{
|
|
300
683
|
type: "text",
|
|
301
|
-
text: `
|
|
684
|
+
text: `Fatal error: ${String(error)}`
|
|
302
685
|
}
|
|
303
686
|
]
|
|
304
687
|
};
|
|
305
|
-
|
|
306
|
-
|
|
688
|
+
console.log(JSON.stringify(errorResult, null, 2));
|
|
689
|
+
}
|
|
690
|
+
finally {
|
|
691
|
+
// 关闭客户端连接 (Close client connection)
|
|
692
|
+
log('client.serverClosed', debug, {}, COMPONENTS.CLIENT);
|
|
693
|
+
await client.close();
|
|
307
694
|
}
|
|
308
695
|
}
|
|
309
696
|
// 捕获未处理的异常 (Catch unhandled exceptions)
|
|
310
697
|
process.on('uncaughtException', (error) => {
|
|
698
|
+
console.error('Uncaught exception:', error);
|
|
311
699
|
log('client.fatalError', true, { error: error.toString() }, COMPONENTS.CLIENT);
|
|
312
700
|
process.exit(1);
|
|
313
701
|
});
|
|
314
702
|
// 执行主函数 (Execute main function)
|
|
315
|
-
main()
|
|
703
|
+
main().catch(error => {
|
|
704
|
+
console.error('Error in main function:', error);
|
|
705
|
+
process.exit(1);
|
|
706
|
+
});
|