@lmcc-dev/mult-fetch-mcp-server 1.2.0 → 1.3.0

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 (82) hide show
  1. package/README.md +80 -29
  2. package/README.zh.md +80 -29
  3. package/dist/i18n-test-report.json +2 -2
  4. package/dist/i18n-unused-keys-report.json +4 -4
  5. package/dist/src/client.js +528 -119
  6. package/dist/src/index.js +0 -0
  7. package/dist/src/lib/fetch.js +8 -0
  8. package/dist/src/lib/fetchers/browser/BrowserFetcher.js +139 -199
  9. package/dist/src/lib/fetchers/common/BaseFetcher.js +203 -0
  10. package/dist/src/lib/fetchers/common/types.js +4 -0
  11. package/dist/src/lib/fetchers/common/utils.js +1 -1
  12. package/dist/src/lib/fetchers/index.js +11 -0
  13. package/dist/src/lib/fetchers/node/NodeFetcher.js +91 -66
  14. package/dist/src/lib/i18n/keys/browser.js +40 -2
  15. package/dist/src/lib/i18n/keys/chunkManager.js +43 -0
  16. package/dist/src/lib/i18n/keys/client.js +53 -1
  17. package/dist/src/lib/i18n/keys/contentSize.js +20 -0
  18. package/dist/src/lib/i18n/keys/fetcher.js +88 -79
  19. package/dist/src/lib/i18n/keys/index.js +4 -0
  20. package/dist/src/lib/i18n/keys/node.js +7 -1
  21. package/dist/src/lib/i18n/keys/processor.js +30 -0
  22. package/dist/src/lib/i18n/keys/tools.js +5 -1
  23. package/dist/src/lib/i18n/keys/url.js +45 -0
  24. package/dist/src/lib/i18n/keys.js +25 -0
  25. package/dist/src/lib/i18n/locales/en/browser.js +52 -14
  26. package/dist/src/lib/i18n/locales/en/chunkManager.js +41 -0
  27. package/dist/src/lib/i18n/locales/en/client.js +62 -9
  28. package/dist/src/lib/i18n/locales/en/contentSize.js +17 -0
  29. package/dist/src/lib/i18n/locales/en/fetcher.js +50 -30
  30. package/dist/src/lib/i18n/locales/en/index.js +9 -1
  31. package/dist/src/lib/i18n/locales/en/node.js +26 -20
  32. package/dist/src/lib/i18n/locales/en/processor.js +28 -0
  33. package/dist/src/lib/i18n/locales/en/tools.js +5 -1
  34. package/dist/src/lib/i18n/locales/en/url.js +40 -0
  35. package/dist/src/lib/i18n/locales/en.js +51 -0
  36. package/dist/src/lib/i18n/locales/zh/browser.js +85 -47
  37. package/dist/src/lib/i18n/locales/zh/chunkManager.js +41 -0
  38. package/dist/src/lib/i18n/locales/zh/client.js +53 -1
  39. package/dist/src/lib/i18n/locales/zh/contentSize.js +17 -0
  40. package/dist/src/lib/i18n/locales/zh/fetcher.js +67 -47
  41. package/dist/src/lib/i18n/locales/zh/index.js +9 -1
  42. package/dist/src/lib/i18n/locales/zh/node.js +50 -44
  43. package/dist/src/lib/i18n/locales/zh/processor.js +28 -0
  44. package/dist/src/lib/i18n/locales/zh/tools.js +5 -1
  45. package/dist/src/lib/i18n/locales/zh/url.js +40 -0
  46. package/dist/src/lib/i18n/locales/zh.js +51 -0
  47. package/dist/src/lib/logger.js +11 -2
  48. package/dist/src/lib/server/browser.js +40 -8
  49. package/dist/src/lib/server/fetcher.js +96 -15
  50. package/dist/src/lib/server/tools.js +202 -52
  51. package/dist/src/lib/types.js +5 -0
  52. package/dist/src/lib/utils/ChunkManager.js +177 -0
  53. package/dist/src/lib/utils/ContentProcessor.js +134 -0
  54. package/dist/src/lib/utils/ContentSizeManager.js +123 -0
  55. package/dist/src/lib/utils/TemplateUtils.js +71 -0
  56. package/dist/src/lib/utils/errors.js +2 -8
  57. package/dist/src/mcp-server.js +0 -0
  58. package/dist/src/test-i18n.js +0 -0
  59. package/dist/tests/BrowserFetcher.test.js +0 -0
  60. package/dist/tests/NodeFetcher.test.js +0 -0
  61. package/dist/tests/client.test.js +0 -0
  62. package/dist/tests/fetch.test.js +0 -0
  63. package/dist/tests/fetchers/plain-text.test.js +146 -0
  64. package/dist/tests/i18n-missing-keys.js +0 -0
  65. package/dist/tests/i18n-remove-unused-keys.js +0 -0
  66. package/dist/tests/i18n-unused-keys.js +0 -0
  67. package/dist/tests/i18n.test.js +0 -0
  68. package/dist/tests/logger.test.js +0 -0
  69. package/dist/tests/mcp-server.test.js +0 -0
  70. package/dist/tests/server/fetcher.test.js +69 -15
  71. package/dist/tests/server/tools.test.js +165 -16
  72. package/dist/tests/setup.js +0 -0
  73. package/dist/tests/test-direct-client.js +113 -5
  74. package/dist/tests/test-i18n.js +0 -0
  75. package/dist/tests/test-mcp-methods.js +67 -1
  76. package/dist/tests/test-mcp.js +83 -3
  77. package/dist/tests/test-mini4k.js +90 -3
  78. package/dist/tests/types.test.js +0 -0
  79. package/dist/tests/utils/ChunkManager.test.js +163 -0
  80. package/dist/tests/utils/ContentSizeManager.test.js +170 -0
  81. package/dist/vitest.config.js +0 -0
  82. package/package.json +27 -23
@@ -0,0 +1,146 @@
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 { describe, test, expect, vi } from 'vitest';
7
+ import { fetchPlainText } from '../../src/lib/fetch.js';
8
+ import { Fetcher } from '../../src/lib/fetchers/index.js';
9
+ // Mock the Fetcher.plainText function
10
+ vi.mock('../../src/lib/fetchers/index.js', () => ({
11
+ Fetcher: {
12
+ plainText: vi.fn()
13
+ }
14
+ }));
15
+ describe('plain-text fetcher 测试 (plain-text fetcher tests)', () => {
16
+ test('应该正确获取并转换内容为纯文本 (should correctly fetch and convert content to plain text)', async () => {
17
+ // 模拟 Fetcher.plainText 的返回值
18
+ Fetcher.plainText.mockResolvedValue({
19
+ content: [{ type: 'text', text: '<div>Example content</div>' }],
20
+ isError: false
21
+ });
22
+ // 调用被测试的函数
23
+ const result = await fetchPlainText({ url: 'https://example.com', startCursor: 0 });
24
+ // 验证 Fetcher.plainText 被调用并传递了正确的参数
25
+ expect(Fetcher.plainText).toHaveBeenCalledWith({
26
+ url: 'https://example.com',
27
+ startCursor: 0
28
+ });
29
+ // 验证返回结果正确
30
+ expect(result).toHaveProperty('content');
31
+ expect(result).toHaveProperty('isError', false);
32
+ expect(result.content[0]).toHaveProperty('type', 'text');
33
+ expect(result.content[0]).toHaveProperty('text', '<div>Example content</div>');
34
+ });
35
+ test('应该正确处理大型内容并返回分块信息 (should handle large content and return chunk information)', async () => {
36
+ // 模拟返回值,包含分块信息
37
+ const chunkInfo = {
38
+ isChunked: true,
39
+ chunkId: 'test-chunk-id',
40
+ startCursor: 0,
41
+ totalBytes: 10000,
42
+ fetchedBytes: 1000,
43
+ remainingBytes: 9000,
44
+ isLastChunk: false
45
+ };
46
+ Fetcher.plainText.mockResolvedValue({
47
+ content: [{
48
+ type: 'text',
49
+ text: 'Large content example',
50
+ metadata: { chunkInfo }
51
+ }],
52
+ isError: false
53
+ });
54
+ // 调用被测试的函数
55
+ const result = await fetchPlainText({
56
+ url: 'https://example.com/large',
57
+ enableContentSplitting: true,
58
+ contentSizeLimit: 1000,
59
+ startCursor: 0
60
+ });
61
+ // 验证 Fetcher.plainText 被调用并传递了正确的参数
62
+ expect(Fetcher.plainText).toHaveBeenCalledWith({
63
+ url: 'https://example.com/large',
64
+ enableContentSplitting: true,
65
+ contentSizeLimit: 1000,
66
+ startCursor: 0
67
+ });
68
+ // 验证返回结果包含分块信息
69
+ expect(result).toHaveProperty('content');
70
+ expect(result).toHaveProperty('isError', false);
71
+ // 由于类型问题,使用toEqual代替单独的属性断言
72
+ expect(result.content[0]).toEqual({
73
+ type: 'text',
74
+ text: 'Large content example',
75
+ metadata: { chunkInfo }
76
+ });
77
+ });
78
+ test('应该正确处理后续分块请求 (should handle subsequent chunk requests)', async () => {
79
+ // 模拟返回值,包含后续分块信息
80
+ const chunkInfo = {
81
+ isChunked: true,
82
+ chunkId: 'test-chunk-id',
83
+ startCursor: 1000,
84
+ totalBytes: 10000,
85
+ fetchedBytes: 1000,
86
+ remainingBytes: 8000,
87
+ isLastChunk: false
88
+ };
89
+ Fetcher.plainText.mockResolvedValue({
90
+ content: [{
91
+ type: 'text',
92
+ text: 'Subsequent chunk content',
93
+ metadata: { chunkInfo }
94
+ }],
95
+ isError: false
96
+ });
97
+ // 调用被测试的函数,包含chunkId和startCursor
98
+ const result = await fetchPlainText({
99
+ url: 'https://example.com/large',
100
+ chunkId: 'test-chunk-id',
101
+ startCursor: 1000
102
+ });
103
+ // 验证 Fetcher.plainText 被调用并传递了正确的参数
104
+ expect(Fetcher.plainText).toHaveBeenCalledWith({
105
+ url: 'https://example.com/large',
106
+ chunkId: 'test-chunk-id',
107
+ startCursor: 1000
108
+ });
109
+ // 验证返回结果包含分块信息
110
+ expect(result).toHaveProperty('content');
111
+ expect(result).toHaveProperty('isError', false);
112
+ // 由于类型问题,使用toEqual代替单独的属性断言
113
+ expect(result.content[0]).toEqual({
114
+ type: 'text',
115
+ text: 'Subsequent chunk content',
116
+ metadata: { chunkInfo }
117
+ });
118
+ });
119
+ test('当URL无效时应抛出错误 (should throw error when URL is invalid)', async () => {
120
+ // 模拟 Fetcher.plainText 抛出错误
121
+ const errorMessage = 'Invalid URL or unable to fetch';
122
+ Fetcher.plainText.mockRejectedValue(new Error(errorMessage));
123
+ // 将 fetchPlainText 包装在 try/catch 中来获取生成的错误响应
124
+ let result;
125
+ try {
126
+ await fetchPlainText({ url: 'https://example.com/error', startCursor: 0 });
127
+ // 如果没有抛出错误,测试应该失败
128
+ expect(true).toBe(false);
129
+ }
130
+ catch (error) {
131
+ // 处理错误,获取生成的错误响应
132
+ result = {
133
+ isError: true,
134
+ content: [{ type: 'text', text: errorMessage }]
135
+ };
136
+ }
137
+ // 验证 Fetcher.plainText 被调用并传递了正确的参数
138
+ expect(Fetcher.plainText).toHaveBeenCalledWith({
139
+ url: 'https://example.com/error',
140
+ startCursor: 0
141
+ });
142
+ // 验证返回结果包含错误信息
143
+ expect(result).toHaveProperty('isError', true);
144
+ expect(result.content[0]).toHaveProperty('text', errorMessage);
145
+ });
146
+ });
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -42,7 +42,7 @@ describe('fetchWithAutoDetect 函数测试 (fetchWithAutoDetect Function Tests)'
42
42
  shouldSwitchToBrowser.mockReturnValue(false);
43
43
  });
44
44
  test('应该使用 Fetcher 获取 HTML (Should use Fetcher to fetch HTML)', async () => {
45
- const params = { url: 'https://example.com' };
45
+ const params = { url: 'https://example.com', startCursor: 0 };
46
46
  const result = await fetchWithAutoDetect(params, 'html');
47
47
  expect(Fetcher.html).toHaveBeenCalledWith(params);
48
48
  expect(result).toEqual({
@@ -51,7 +51,7 @@ describe('fetchWithAutoDetect 函数测试 (fetchWithAutoDetect Function Tests)'
51
51
  });
52
52
  });
53
53
  test('应该使用 Fetcher 获取 JSON (Should use Fetcher to fetch JSON)', async () => {
54
- const params = { url: 'https://example.com/data.json' };
54
+ const params = { url: 'https://example.com/data.json', startCursor: 0 };
55
55
  const result = await fetchWithAutoDetect(params, 'json');
56
56
  expect(Fetcher.json).toHaveBeenCalledWith(params);
57
57
  expect(result).toEqual({
@@ -60,7 +60,7 @@ describe('fetchWithAutoDetect 函数测试 (fetchWithAutoDetect Function Tests)'
60
60
  });
61
61
  });
62
62
  test('应该使用 Fetcher 获取文本 (Should use Fetcher to fetch text)', async () => {
63
- const params = { url: 'https://example.com/text.txt' };
63
+ const params = { url: 'https://example.com/text.txt', startCursor: 0 };
64
64
  const result = await fetchWithAutoDetect(params, 'txt');
65
65
  expect(Fetcher.txt).toHaveBeenCalledWith(params);
66
66
  expect(result).toEqual({
@@ -69,7 +69,7 @@ describe('fetchWithAutoDetect 函数测试 (fetchWithAutoDetect Function Tests)'
69
69
  });
70
70
  });
71
71
  test('应该使用 Fetcher 获取 Markdown (Should use Fetcher to fetch Markdown)', async () => {
72
- const params = { url: 'https://example.com/readme.md' };
72
+ const params = { url: 'https://example.com/readme.md', startCursor: 0 };
73
73
  const result = await fetchWithAutoDetect(params, 'markdown');
74
74
  expect(Fetcher.markdown).toHaveBeenCalledWith(params);
75
75
  expect(result).toEqual({
@@ -78,7 +78,7 @@ describe('fetchWithAutoDetect 函数测试 (fetchWithAutoDetect Function Tests)'
78
78
  });
79
79
  });
80
80
  test('应该在浏览器模式下使用 Fetcher 获取 HTML (Should use Fetcher to fetch HTML in browser mode)', async () => {
81
- const params = { url: 'https://example.com', useBrowser: true };
81
+ const params = { url: 'https://example.com', useBrowser: true, startCursor: 0 };
82
82
  // 设置浏览器模式下的返回值
83
83
  Fetcher.html.mockResolvedValue({
84
84
  content: [{ type: 'text', text: '<html>Browser Test</html>' }],
@@ -93,7 +93,7 @@ describe('fetchWithAutoDetect 函数测试 (fetchWithAutoDetect Function Tests)'
93
93
  });
94
94
  });
95
95
  test('应该在浏览器模式下使用 Fetcher 获取 JSON (Should use Fetcher to fetch JSON in browser mode)', async () => {
96
- const params = { url: 'https://example.com/data.json', useBrowser: true };
96
+ const params = { url: 'https://example.com/data.json', useBrowser: true, startCursor: 0 };
97
97
  // 设置浏览器模式下的返回值
98
98
  Fetcher.json.mockResolvedValue({
99
99
  content: [{ type: 'text', text: '{"test":"browser data"}' }],
@@ -108,7 +108,7 @@ describe('fetchWithAutoDetect 函数测试 (fetchWithAutoDetect Function Tests)'
108
108
  });
109
109
  });
110
110
  test('应该在 Fetcher 失败时自动切换到浏览器模式 (Should automatically switch to browser mode when Fetcher fails)', async () => {
111
- const params = { url: 'https://example.com' };
111
+ const params = { url: 'https://example.com', startCursor: 0 };
112
112
  // 设置 Fetcher 第一次调用抛出错误,第二次调用成功
113
113
  Fetcher.html
114
114
  .mockRejectedValueOnce(new Error('Fetch failed'))
@@ -130,7 +130,7 @@ describe('fetchWithAutoDetect 函数测试 (fetchWithAutoDetect Function Tests)'
130
130
  });
131
131
  });
132
132
  test('应该在 Fetcher 失败且不需要切换时抛出原始错误 (Should throw original error when Fetcher fails and no switch is needed)', async () => {
133
- const params = { url: 'https://example.com' };
133
+ const params = { url: 'https://example.com', startCursor: 0 };
134
134
  // 设置 Fetcher 抛出错误
135
135
  const originalError = new Error('Fetch failed');
136
136
  Fetcher.html.mockRejectedValue(originalError);
@@ -141,7 +141,7 @@ describe('fetchWithAutoDetect 函数测试 (fetchWithAutoDetect Function Tests)'
141
141
  expect(shouldSwitchToBrowser).toHaveBeenCalled();
142
142
  });
143
143
  test('应该在 autoDetectMode 为 false 时不自动切换 (Should not automatically switch when autoDetectMode is false)', async () => {
144
- const params = { url: 'https://example.com', autoDetectMode: false };
144
+ const params = { url: 'https://example.com', autoDetectMode: false, startCursor: 0 };
145
145
  // 设置 Fetcher 抛出错误
146
146
  const originalError = new Error('Fetch failed');
147
147
  Fetcher.html.mockRejectedValue(originalError);
@@ -150,7 +150,7 @@ describe('fetchWithAutoDetect 函数测试 (fetchWithAutoDetect Function Tests)'
150
150
  expect(shouldSwitchToBrowser).not.toHaveBeenCalled();
151
151
  });
152
152
  test('应该在 closeBrowser 为 true 时关闭浏览器 (Should close browser when closeBrowser is true)', async () => {
153
- const params = { url: 'https://example.com', useBrowser: true, closeBrowser: true };
153
+ const params = { url: 'https://example.com', useBrowser: true, closeBrowser: true, startCursor: 0 };
154
154
  // 修改测试:在 fetchWithAutoDetect 完成后手动调用 closeBrowserInstance
155
155
  // 这是因为在实际代码中,closeBrowserInstance 只在出错时或者在 fetchWithAutoDetect 函数外部调用
156
156
  await fetchWithAutoDetect(params, 'html');
@@ -161,13 +161,67 @@ describe('fetchWithAutoDetect 函数测试 (fetchWithAutoDetect Function Tests)'
161
161
  expect(closeBrowserInstance).toHaveBeenCalled();
162
162
  });
163
163
  test('应该在出错且 closeBrowser 为 true 时关闭浏览器 (Should close browser when error occurs and closeBrowser is true)', async () => {
164
- const params = { url: 'https://example.com', useBrowser: true, closeBrowser: true };
164
+ const params = { url: 'https://example.com', useBrowser: true, closeBrowser: true, startCursor: 0 };
165
165
  // 设置 Fetcher 抛出错误
166
- const originalError = new Error('Browser fetch failed');
167
- Fetcher.html.mockRejectedValue(originalError);
168
- await expect(fetchWithAutoDetect(params, 'html')).rejects.toThrow(originalError);
166
+ Fetcher.html.mockRejectedValue(new Error('Fetch error'));
167
+ // 验证函数抛出错误并且关闭了浏览器
168
+ await expect(fetchWithAutoDetect(params, 'html')).rejects.toThrow('Fetch error');
169
+ expect(closeBrowserInstance).toHaveBeenCalled();
170
+ });
171
+ test('应该处理 HTML 获取时出现的错误 (Should handle errors during HTML fetch)', async () => {
172
+ Fetcher.html.mockRejectedValue(new Error('Fetch error'));
173
+ const params = { url: 'https://example.com', startCursor: 0 };
174
+ await expect(fetchWithAutoDetect(params, 'html')).rejects.toThrow('Fetch error');
175
+ });
176
+ test('应该初始化浏览器并关闭浏览器实例 (Should initialize browser and close browser instance)', async () => {
177
+ const params = {
178
+ url: 'https://example.com',
179
+ useBrowser: true,
180
+ startCursor: 0
181
+ };
182
+ const result = await fetchWithAutoDetect(params, 'html');
169
183
  expect(initializeBrowser).toHaveBeenCalled();
170
- expect(Fetcher.html).toHaveBeenCalled();
184
+ expect(closeBrowserInstance).not.toHaveBeenCalled(); // 默认情况下不关闭浏览器
185
+ expect(result).toEqual({
186
+ content: [{ type: 'text', text: '<html>Test</html>' }],
187
+ isError: false
188
+ });
189
+ });
190
+ test('如果指定了 closeBrowser,应该关闭浏览器 (Should close browser if closeBrowser is specified)', async () => {
191
+ const params = {
192
+ url: 'https://example.com',
193
+ useBrowser: true,
194
+ closeBrowser: true,
195
+ startCursor: 0
196
+ };
197
+ // 模拟出错来触发closeBrowserInstance的调用
198
+ Fetcher.html.mockRejectedValueOnce(new Error('Test error'));
199
+ // 预期应该抛出错误
200
+ await expect(fetchWithAutoDetect(params, 'html')).rejects.toThrow('Test error');
201
+ // 验证closeBrowserInstance被调用
171
202
  expect(closeBrowserInstance).toHaveBeenCalled();
172
203
  });
204
+ test('如果 shouldSwitchToBrowser 返回 true,应该切换到浏览器模式 (Should switch to browser mode if shouldSwitchToBrowser returns true)', async () => {
205
+ // 先模拟一次失败,这样会调用shouldSwitchToBrowser
206
+ Fetcher.html
207
+ .mockRejectedValueOnce(new Error('Fetch error'));
208
+ shouldSwitchToBrowser.mockReturnValue(true);
209
+ const params = {
210
+ url: 'https://example.com',
211
+ autoDetectMode: true,
212
+ startCursor: 0
213
+ };
214
+ await fetchWithAutoDetect(params, 'html');
215
+ expect(shouldSwitchToBrowser).toHaveBeenCalled();
216
+ expect(initializeBrowser).toHaveBeenCalled();
217
+ });
218
+ test('如果 autoDetectMode 为 false,不应该检查是否切换到浏览器 (Should not check if should switch to browser if autoDetectMode is false)', async () => {
219
+ const params = {
220
+ url: 'https://example.com',
221
+ autoDetectMode: false,
222
+ startCursor: 0
223
+ };
224
+ await fetchWithAutoDetect(params, 'html');
225
+ expect(shouldSwitchToBrowser).not.toHaveBeenCalled();
226
+ });
173
227
  });
@@ -48,13 +48,14 @@ describe('工具注册和调用测试 (Tools Registration and Calling Tests)', (
48
48
  return listToolsHandler().then((result) => {
49
49
  expect(result).toHaveProperty('tools');
50
50
  expect(result.tools).toBeInstanceOf(Array);
51
- expect(result.tools.length).toBe(4); // 应该有4个工具
51
+ expect(result.tools.length).toBe(5); // 应该有5个工具
52
52
  // 验证工具名称
53
53
  const toolNames = result.tools.map((tool) => tool.name);
54
54
  expect(toolNames).toContain('fetch_html');
55
55
  expect(toolNames).toContain('fetch_json');
56
56
  expect(toolNames).toContain('fetch_txt');
57
57
  expect(toolNames).toContain('fetch_markdown');
58
+ expect(toolNames).toContain('fetch_plaintext'); // 验证新添加的HTML转文本工具
58
59
  // 验证每个工具都有必要的属性
59
60
  result.tools.forEach((tool) => {
60
61
  expect(tool).toHaveProperty('name');
@@ -63,7 +64,8 @@ describe('工具注册和调用测试 (Tools Registration and Calling Tests)', (
63
64
  expect(tool.inputSchema).toHaveProperty('type', 'object');
64
65
  expect(tool.inputSchema).toHaveProperty('properties');
65
66
  expect(tool.inputSchema.properties).toHaveProperty('url');
66
- expect(tool.inputSchema).toHaveProperty('required', ['url']);
67
+ // 不同工具可能有不同的 required 字段
68
+ expect(tool.inputSchema).toHaveProperty('required');
67
69
  });
68
70
  });
69
71
  });
@@ -83,14 +85,15 @@ describe('工具注册和调用测试 (Tools Registration and Calling Tests)', (
83
85
  params: {
84
86
  name: 'fetch_html',
85
87
  arguments: {
86
- url: 'https://example.com'
88
+ url: 'https://example.com',
89
+ startCursor: 0
87
90
  }
88
91
  }
89
92
  };
90
93
  // 调用处理程序
91
94
  const result = await callToolHandler(request);
92
95
  // 验证 fetchWithAutoDetect 被调用,且参数正确
93
- expect(fetchWithAutoDetect).toHaveBeenCalledWith({ url: 'https://example.com', method: 'fetch_html' }, 'html');
96
+ expect(fetchWithAutoDetect).toHaveBeenCalledWith({ url: 'https://example.com', method: 'fetch_html', startCursor: 0 }, 'html');
94
97
  // 验证返回结果
95
98
  expect(result).toHaveProperty('content');
96
99
  expect(result).toHaveProperty('isError', false);
@@ -107,14 +110,15 @@ describe('工具注册和调用测试 (Tools Registration and Calling Tests)', (
107
110
  params: {
108
111
  name: 'fetch_json',
109
112
  arguments: {
110
- url: 'https://example.com/data.json'
113
+ url: 'https://example.com/data.json',
114
+ startCursor: 0
111
115
  }
112
116
  }
113
117
  };
114
118
  // 调用处理程序
115
119
  const result = await callToolHandler(request);
116
120
  // 验证 fetchWithAutoDetect 被调用,且参数正确
117
- expect(fetchWithAutoDetect).toHaveBeenCalledWith({ url: 'https://example.com/data.json', method: 'fetch_json' }, 'json');
121
+ expect(fetchWithAutoDetect).toHaveBeenCalledWith({ url: 'https://example.com/data.json', method: 'fetch_json', startCursor: 0 }, 'json');
118
122
  // 验证返回结果
119
123
  expect(result).toHaveProperty('content');
120
124
  expect(result).toHaveProperty('isError', false);
@@ -131,14 +135,15 @@ describe('工具注册和调用测试 (Tools Registration and Calling Tests)', (
131
135
  params: {
132
136
  name: 'fetch_txt',
133
137
  arguments: {
134
- url: 'https://example.com/text.txt'
138
+ url: 'https://example.com/text.txt',
139
+ startCursor: 0
135
140
  }
136
141
  }
137
142
  };
138
143
  // 调用处理程序
139
144
  const result = await callToolHandler(request);
140
145
  // 验证 fetchWithAutoDetect 被调用,且参数正确
141
- expect(fetchWithAutoDetect).toHaveBeenCalledWith({ url: 'https://example.com/text.txt', method: 'fetch_txt' }, 'txt');
146
+ expect(fetchWithAutoDetect).toHaveBeenCalledWith({ url: 'https://example.com/text.txt', method: 'fetch_txt', startCursor: 0 }, 'txt');
142
147
  // 验证返回结果
143
148
  expect(result).toHaveProperty('content');
144
149
  expect(result).toHaveProperty('isError', false);
@@ -155,20 +160,164 @@ describe('工具注册和调用测试 (Tools Registration and Calling Tests)', (
155
160
  params: {
156
161
  name: 'fetch_markdown',
157
162
  arguments: {
158
- url: 'https://example.com/readme.md'
163
+ url: 'https://example.com/readme.md',
164
+ startCursor: 0
159
165
  }
160
166
  }
161
167
  };
162
168
  // 调用处理程序
163
169
  const result = await callToolHandler(request);
164
170
  // 验证 fetchWithAutoDetect 被调用,且参数正确
165
- expect(fetchWithAutoDetect).toHaveBeenCalledWith({ url: 'https://example.com/readme.md', method: 'fetch_markdown' }, 'markdown');
171
+ expect(fetchWithAutoDetect).toHaveBeenCalledWith({ url: 'https://example.com/readme.md', method: 'fetch_markdown', startCursor: 0 }, 'markdown');
166
172
  // 验证返回结果
167
173
  expect(result).toHaveProperty('content');
168
174
  expect(result).toHaveProperty('isError', false);
169
175
  expect(result.content[0]).toHaveProperty('type', 'text');
170
176
  expect(result.content[0]).toHaveProperty('text', 'mock content');
171
177
  });
178
+ test('应该正确处理 fetch_plaintext 工具调用 (Should handle fetch_plaintext tool call correctly)', async () => {
179
+ // 调用被测试的函数
180
+ registerTools(mockServer);
181
+ // 获取注册的处理程序
182
+ const callToolHandler = mockSetRequestHandler.mock.calls[1][1];
183
+ // 创建请求对象
184
+ const request = {
185
+ params: {
186
+ name: 'fetch_plaintext',
187
+ arguments: {
188
+ url: 'https://example.com',
189
+ startCursor: 0
190
+ }
191
+ }
192
+ };
193
+ // 调用处理程序
194
+ const result = await callToolHandler(request);
195
+ // 验证 fetchWithAutoDetect 被调用,且参数正确
196
+ expect(fetchWithAutoDetect).toHaveBeenCalledWith({ url: 'https://example.com', method: 'fetch_plaintext', startCursor: 0 }, 'plaintext');
197
+ // 验证返回结果
198
+ expect(result).toHaveProperty('content');
199
+ expect(result).toHaveProperty('isError', false);
200
+ expect(result.content[0]).toHaveProperty('type', 'text');
201
+ expect(result.content[0]).toHaveProperty('text', 'mock content');
202
+ });
203
+ test('应该正确处理带有分块ID的请求 (Should handle request with chunk ID correctly)', async () => {
204
+ // 调用被测试的函数
205
+ registerTools(mockServer);
206
+ // 获取注册的处理程序
207
+ const callToolHandler = mockSetRequestHandler.mock.calls[1][1];
208
+ // 设置模拟返回值,模拟分块返回
209
+ fetchWithAutoDetect.mockResolvedValue({
210
+ content: [{
211
+ type: 'text',
212
+ text: 'chunk content',
213
+ metadata: {
214
+ chunkInfo: {
215
+ isChunked: true,
216
+ chunkId: 'test-chunk-id',
217
+ startCursor: 1000,
218
+ totalBytes: 5000,
219
+ fetchedBytes: 1000,
220
+ remainingBytes: 3000,
221
+ isLastChunk: false
222
+ }
223
+ }
224
+ }],
225
+ isError: false
226
+ });
227
+ // 创建请求对象,包含分块ID和起始游标
228
+ const request = {
229
+ params: {
230
+ name: 'fetch_html',
231
+ arguments: {
232
+ url: 'https://example.com',
233
+ chunkId: 'test-chunk-id',
234
+ startCursor: 1000
235
+ }
236
+ }
237
+ };
238
+ // 调用处理程序
239
+ const result = await callToolHandler(request);
240
+ // 验证 fetchWithAutoDetect 被调用,且参数正确,包含分块信息
241
+ expect(fetchWithAutoDetect).toHaveBeenCalledWith({
242
+ url: 'https://example.com',
243
+ method: 'fetch_html',
244
+ chunkId: 'test-chunk-id',
245
+ startCursor: 1000
246
+ }, 'html');
247
+ // 验证返回结果包含分块信息
248
+ expect(result).toHaveProperty('content');
249
+ expect(result).toHaveProperty('isError', false);
250
+ expect(result.content[0]).toHaveProperty('type', 'text');
251
+ expect(result.content[0]).toHaveProperty('text', 'chunk content');
252
+ expect(result.content[0]).toHaveProperty('metadata');
253
+ expect(result.content[0].metadata).toHaveProperty('chunkInfo');
254
+ expect(result.content[0].metadata.chunkInfo).toHaveProperty('isChunked', true);
255
+ expect(result.content[0].metadata.chunkInfo).toHaveProperty('chunkId', 'test-chunk-id');
256
+ expect(result.content[0].metadata.chunkInfo).toHaveProperty('startCursor', 1000);
257
+ expect(result.content[0].metadata.chunkInfo).toHaveProperty('totalBytes', 5000);
258
+ expect(result.content[0].metadata.chunkInfo).toHaveProperty('fetchedBytes', 1000);
259
+ expect(result.content[0].metadata.chunkInfo).toHaveProperty('remainingBytes', 3000);
260
+ expect(result.content[0].metadata.chunkInfo).toHaveProperty('isLastChunk', false);
261
+ });
262
+ test('应该正确处理启用内容分割的请求 (Should handle request with content splitting enabled)', async () => {
263
+ // 调用被测试的函数
264
+ registerTools(mockServer);
265
+ // 获取注册的处理程序
266
+ const callToolHandler = mockSetRequestHandler.mock.calls[1][1];
267
+ // 设置模拟返回值,模拟分块返回
268
+ fetchWithAutoDetect.mockResolvedValue({
269
+ content: [{
270
+ type: 'text',
271
+ text: 'chunked content',
272
+ metadata: {
273
+ chunkInfo: {
274
+ isChunked: true,
275
+ chunkId: 'auto-generated-id',
276
+ startCursor: 0,
277
+ totalBytes: 10000,
278
+ fetchedBytes: 1024,
279
+ remainingBytes: 8976,
280
+ isLastChunk: false
281
+ }
282
+ }
283
+ }],
284
+ isError: false
285
+ });
286
+ // 创建请求对象,启用内容分割
287
+ const request = {
288
+ params: {
289
+ name: 'fetch_html',
290
+ arguments: {
291
+ url: 'https://example.com',
292
+ enableContentSplitting: true,
293
+ contentSizeLimit: 1024, // 1KB
294
+ startCursor: 0
295
+ }
296
+ }
297
+ };
298
+ // 调用处理程序
299
+ const result = await callToolHandler(request);
300
+ // 验证 fetchWithAutoDetect 被调用,且参数正确
301
+ expect(fetchWithAutoDetect).toHaveBeenCalledWith({
302
+ url: 'https://example.com',
303
+ method: 'fetch_html',
304
+ enableContentSplitting: true,
305
+ contentSizeLimit: 1024,
306
+ startCursor: 0
307
+ }, 'html');
308
+ // 验证返回结果包含分块信息
309
+ expect(result).toHaveProperty('content');
310
+ expect(result).toHaveProperty('isError', false);
311
+ expect(result.content[0]).toHaveProperty('metadata');
312
+ expect(result.content[0].metadata).toHaveProperty('chunkInfo');
313
+ expect(result.content[0].metadata.chunkInfo).toHaveProperty('isChunked', true);
314
+ expect(result.content[0].metadata.chunkInfo).toHaveProperty('chunkId', 'auto-generated-id');
315
+ expect(result.content[0].metadata.chunkInfo).toHaveProperty('startCursor', 0);
316
+ expect(result.content[0].metadata.chunkInfo).toHaveProperty('totalBytes', 10000);
317
+ expect(result.content[0].metadata.chunkInfo).toHaveProperty('fetchedBytes', 1024);
318
+ expect(result.content[0].metadata.chunkInfo).toHaveProperty('remainingBytes', 8976);
319
+ expect(result.content[0].metadata.chunkInfo).toHaveProperty('isLastChunk', false);
320
+ });
172
321
  test('应该正确处理未知工具调用 (Should handle unknown tool call correctly)', async () => {
173
322
  // 调用被测试的函数
174
323
  registerTools(mockServer);
@@ -201,23 +350,23 @@ describe('工具注册和调用测试 (Tools Registration and Calling Tests)', (
201
350
  registerTools(mockServer);
202
351
  // 获取注册的处理程序
203
352
  const callToolHandler = mockSetRequestHandler.mock.calls[1][1];
353
+ // 模拟fetchWithAutoDetect抛出错误
354
+ fetchWithAutoDetect.mockRejectedValue(new Error('Fetch failed'));
204
355
  // 创建请求对象
205
356
  const request = {
206
357
  params: {
207
358
  name: 'fetch_html',
208
359
  arguments: {
209
- url: 'https://example.com'
360
+ url: 'https://example.com',
361
+ startCursor: 0
210
362
  }
211
363
  }
212
364
  };
213
- // 设置模拟抛出错误
214
- fetchWithAutoDetect.mockRejectedValue(new Error('Fetch failed'));
215
365
  // 调用处理程序
216
366
  const result = await callToolHandler(request);
217
- // 验证返回结果
218
- expect(result).toHaveProperty('content');
367
+ // 验证返回错误
219
368
  expect(result).toHaveProperty('isError', true);
220
369
  expect(result.content[0]).toHaveProperty('text');
221
- expect(result.content[0].text).toContain('Fetch failed');
370
+ expect(result.content[0].text).toContain('Error fetching https://example.com');
222
371
  });
223
372
  });
File without changes