@lmcc-dev/mult-fetch-mcp-server 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/README.md +8 -8
  2. package/README.zh.md +16 -8
  3. package/dist/i18n-test-report.json +8 -0
  4. package/dist/i18n-unused-keys-report.json +8 -0
  5. package/dist/src/client.js +70 -37
  6. package/dist/src/index.js +0 -0
  7. package/dist/src/lib/BrowserFetcher.js +12 -12
  8. package/dist/src/lib/NodeFetcher.js +22 -22
  9. package/dist/src/lib/fetch.js +40 -0
  10. package/dist/src/lib/fetchers/FetcherFactory.js +43 -0
  11. package/dist/src/lib/fetchers/browser/BrowserFetcher.js +381 -0
  12. package/dist/src/lib/fetchers/browser/BrowserInstance.js +180 -0
  13. package/dist/src/lib/fetchers/browser/PageOperations.js +168 -0
  14. package/dist/src/lib/fetchers/common/types.js +41 -0
  15. package/dist/src/lib/fetchers/common/utils.js +141 -0
  16. package/dist/src/lib/fetchers/index.js +62 -0
  17. package/dist/src/lib/fetchers/node/HttpClient.js +239 -0
  18. package/dist/src/lib/fetchers/node/NodeFetcher.js +180 -0
  19. package/dist/src/lib/i18n/index.js +105 -17
  20. package/dist/src/lib/i18n/keys/base.js +41 -0
  21. package/dist/src/lib/i18n/keys/browser.js +91 -0
  22. package/dist/src/lib/i18n/keys/client.js +68 -0
  23. package/dist/src/lib/i18n/keys/errors.js +49 -0
  24. package/dist/src/lib/i18n/keys/factory.js +46 -0
  25. package/dist/src/lib/i18n/keys/fetcher.js +87 -0
  26. package/dist/src/lib/i18n/keys/index.js +16 -0
  27. package/dist/src/lib/i18n/keys/node.js +69 -0
  28. package/dist/src/lib/i18n/keys/prompts.js +79 -0
  29. package/dist/src/lib/i18n/keys/resources.js +53 -0
  30. package/dist/src/lib/i18n/keys/server.js +67 -0
  31. package/dist/src/lib/i18n/keys/tools.js +39 -0
  32. package/dist/src/lib/i18n/keys.js +488 -119
  33. package/dist/src/lib/i18n/locales/en/browser.js +67 -57
  34. package/dist/src/lib/i18n/locales/en/client.js +57 -33
  35. package/dist/src/lib/i18n/locales/en/errors.js +36 -12
  36. package/dist/src/lib/i18n/locales/en/factory.js +38 -0
  37. package/dist/src/lib/i18n/locales/en/fetcher.js +8 -2
  38. package/dist/src/lib/i18n/locales/en/index.js +10 -8
  39. package/dist/src/lib/i18n/locales/en/node.js +57 -35
  40. package/dist/src/lib/i18n/locales/en/prompts.js +42 -65
  41. package/dist/src/lib/i18n/locales/en/resources.js +52 -55
  42. package/dist/src/lib/i18n/locales/en/server.js +49 -24
  43. package/dist/src/lib/i18n/locales/en/tools.js +27 -19
  44. package/dist/src/lib/i18n/locales/zh/browser.js +71 -62
  45. package/dist/src/lib/i18n/locales/zh/client.js +57 -32
  46. package/dist/src/lib/i18n/locales/zh/errors.js +36 -12
  47. package/dist/src/lib/i18n/locales/zh/factory.js +38 -0
  48. package/dist/src/lib/i18n/locales/zh/fetcher.js +36 -30
  49. package/dist/src/lib/i18n/locales/zh/index.js +10 -8
  50. package/dist/src/lib/i18n/locales/zh/node.js +61 -36
  51. package/dist/src/lib/i18n/locales/zh/prompts.js +35 -57
  52. package/dist/src/lib/i18n/locales/zh/resources.js +52 -55
  53. package/dist/src/lib/i18n/locales/zh/server.js +51 -25
  54. package/dist/src/lib/i18n/locales/zh/tools.js +27 -19
  55. package/dist/src/lib/i18n/logger.js +90 -19
  56. package/dist/src/lib/logger.js +135 -14
  57. package/dist/src/lib/server/browser.js +42 -10
  58. package/dist/src/lib/server/fetcher.js +87 -37
  59. package/dist/src/lib/server/index.js +2 -2
  60. package/dist/src/lib/server/prompts.js +1 -1
  61. package/dist/src/lib/server/resources.js +1 -1
  62. package/dist/src/lib/server/tools.js +46 -10
  63. package/dist/src/lib/utils/ErrorHandler.js +125 -0
  64. package/dist/src/mcp-server.js +0 -0
  65. package/dist/src/test-i18n.js +139 -0
  66. package/dist/tests/BrowserFetcher.test.js +0 -0
  67. package/dist/tests/NodeFetcher.test.js +0 -0
  68. package/dist/tests/client.test.js +42 -81
  69. package/dist/tests/fetch.test.js +70 -0
  70. package/dist/tests/fetchers/Fetcher.test.js +108 -0
  71. package/dist/tests/fetchers/browser/BrowserFetcher.test.js +195 -0
  72. package/dist/tests/fetchers/browser/BrowserInstance.test.js +163 -0
  73. package/dist/tests/fetchers/node/NodeFetcher.test.js +211 -0
  74. package/dist/tests/i18n-missing-keys.js +364 -0
  75. package/dist/tests/i18n-remove-unused-keys.js +236 -0
  76. package/dist/tests/i18n-test-report.json +2004 -0
  77. package/dist/tests/i18n-unused-keys.js +425 -0
  78. package/dist/tests/i18n.test.js +6 -7
  79. package/dist/tests/logger.test.js +14 -13
  80. package/dist/tests/mcp-server.test.js +7 -7
  81. package/dist/tests/server/browser.test.js +25 -21
  82. package/dist/tests/server/fetcher.test.js +58 -101
  83. package/dist/tests/server/index.test.js +30 -30
  84. package/dist/tests/server/prompts.test.js +36 -33
  85. package/dist/tests/server/resources.test.js +120 -84
  86. package/dist/tests/server/tools.test.js +7 -6
  87. package/dist/tests/setup.js +36 -0
  88. package/dist/tests/src/lib/i18n/index.js +108 -0
  89. package/dist/tests/src/lib/i18n/keys/base.js +47 -0
  90. package/dist/tests/src/lib/i18n/keys/browser.js +93 -0
  91. package/dist/tests/src/lib/i18n/keys/client.js +70 -0
  92. package/dist/tests/src/lib/i18n/keys/errors.js +34 -0
  93. package/dist/tests/src/lib/i18n/keys/fetcher.js +84 -0
  94. package/dist/tests/src/lib/i18n/keys/index.js +31 -0
  95. package/dist/tests/src/lib/i18n/keys/node.js +56 -0
  96. package/dist/tests/src/lib/i18n/keys/prompts.js +82 -0
  97. package/dist/tests/src/lib/i18n/keys/resources.js +50 -0
  98. package/dist/tests/src/lib/i18n/keys/server.js +64 -0
  99. package/dist/tests/src/lib/i18n/keys/tools.js +34 -0
  100. package/dist/tests/src/lib/i18n/locales/en/browser.js +88 -0
  101. package/dist/tests/src/lib/i18n/locales/en/client.js +66 -0
  102. package/dist/tests/src/lib/i18n/locales/en/errors.js +28 -0
  103. package/dist/tests/src/lib/i18n/locales/en/fetcher.js +71 -0
  104. package/dist/tests/src/lib/i18n/locales/en/index.js +29 -0
  105. package/dist/tests/src/lib/i18n/locales/en/node.js +51 -0
  106. package/dist/tests/src/lib/i18n/locales/en/prompts.js +52 -0
  107. package/dist/tests/src/lib/i18n/locales/en/resources.js +50 -0
  108. package/dist/tests/src/lib/i18n/locales/en/server.js +56 -0
  109. package/dist/tests/src/lib/i18n/locales/en/tools.js +28 -0
  110. package/dist/tests/src/lib/i18n/locales/zh/browser.js +87 -0
  111. package/dist/tests/src/lib/i18n/locales/zh/client.js +66 -0
  112. package/dist/tests/src/lib/i18n/locales/zh/errors.js +28 -0
  113. package/dist/tests/src/lib/i18n/locales/zh/fetcher.js +71 -0
  114. package/dist/tests/src/lib/i18n/locales/zh/index.js +29 -0
  115. package/dist/tests/src/lib/i18n/locales/zh/node.js +51 -0
  116. package/dist/tests/src/lib/i18n/locales/zh/prompts.js +53 -0
  117. package/dist/tests/src/lib/i18n/locales/zh/resources.js +50 -0
  118. package/dist/tests/src/lib/i18n/locales/zh/server.js +57 -0
  119. package/dist/tests/src/lib/i18n/locales/zh/tools.js +28 -0
  120. package/dist/tests/src/lib/i18n/logger.js +114 -0
  121. package/dist/tests/src/lib/logger.js +181 -0
  122. package/dist/tests/test-direct-client.js +0 -0
  123. package/dist/tests/test-i18n.js +530 -0
  124. package/dist/tests/test-mcp-methods.js +1 -1
  125. package/dist/tests/test-mcp.js +0 -0
  126. package/dist/tests/test-mini4k.js +0 -0
  127. package/dist/tests/tests/test-i18n.js +588 -0
  128. package/dist/tests/types.test.js +1 -1
  129. package/dist/tests/utils/ErrorHandler.test.js +144 -0
  130. package/dist/vitest.config.js +29 -0
  131. package/package.json +31 -27
  132. package/dist/src/cli-language.js +0 -78
  133. package/dist/src/client.d.ts +0 -6
  134. package/dist/src/client.js.map +0 -1
  135. package/dist/src/index.d.ts +0 -15
  136. package/dist/src/index.js.map +0 -1
  137. package/dist/src/lib/BrowserFetcher.d.ts +0 -175
  138. package/dist/src/lib/BrowserFetcher.js.map +0 -1
  139. package/dist/src/lib/NodeFetcher.d.ts +0 -92
  140. package/dist/src/lib/NodeFetcher.js.map +0 -1
  141. package/dist/src/lib/example.js +0 -93
  142. package/dist/src/lib/i18n/index.js.map +0 -1
  143. package/dist/src/lib/i18n/locales/en/browser.d.ts +0 -61
  144. package/dist/src/lib/i18n/locales/en/browser.js.map +0 -1
  145. package/dist/src/lib/i18n/locales/en/client.d.ts +0 -39
  146. package/dist/src/lib/i18n/locales/en/client.js.map +0 -1
  147. package/dist/src/lib/i18n/locales/en/errors.d.ts +0 -17
  148. package/dist/src/lib/i18n/locales/en/errors.js.map +0 -1
  149. package/dist/src/lib/i18n/locales/en/fetcher.d.ts +0 -66
  150. package/dist/src/lib/i18n/locales/en/fetcher.js.map +0 -1
  151. package/dist/src/lib/i18n/locales/en/index.d.ts +0 -344
  152. package/dist/src/lib/i18n/locales/en/index.js.map +0 -1
  153. package/dist/src/lib/i18n/locales/en/node.d.ts +0 -41
  154. package/dist/src/lib/i18n/locales/en/node.js.map +0 -1
  155. package/dist/src/lib/i18n/locales/en/prompts.d.ts +0 -65
  156. package/dist/src/lib/i18n/locales/en/prompts.js.map +0 -1
  157. package/dist/src/lib/i18n/locales/en/resources.d.ts +0 -41
  158. package/dist/src/lib/i18n/locales/en/resources.js.map +0 -1
  159. package/dist/src/lib/i18n/locales/en/server.d.ts +0 -30
  160. package/dist/src/lib/i18n/locales/en/server.js.map +0 -1
  161. package/dist/src/lib/i18n/locales/en/tools.d.ts +0 -24
  162. package/dist/src/lib/i18n/locales/en/tools.js.map +0 -1
  163. package/dist/src/lib/i18n/locales/zh/browser.d.ts +0 -61
  164. package/dist/src/lib/i18n/locales/zh/browser.js.map +0 -1
  165. package/dist/src/lib/i18n/locales/zh/client.d.ts +0 -38
  166. package/dist/src/lib/i18n/locales/zh/client.js.map +0 -1
  167. package/dist/src/lib/i18n/locales/zh/errors.d.ts +0 -17
  168. package/dist/src/lib/i18n/locales/zh/errors.js.map +0 -1
  169. package/dist/src/lib/i18n/locales/zh/fetcher.d.ts +0 -66
  170. package/dist/src/lib/i18n/locales/zh/fetcher.js.map +0 -1
  171. package/dist/src/lib/i18n/locales/zh/index.d.ts +0 -343
  172. package/dist/src/lib/i18n/locales/zh/index.js.map +0 -1
  173. package/dist/src/lib/i18n/locales/zh/node.d.ts +0 -41
  174. package/dist/src/lib/i18n/locales/zh/node.js.map +0 -1
  175. package/dist/src/lib/i18n/locales/zh/prompts.d.ts +0 -65
  176. package/dist/src/lib/i18n/locales/zh/prompts.js.map +0 -1
  177. package/dist/src/lib/i18n/locales/zh/resources.d.ts +0 -41
  178. package/dist/src/lib/i18n/locales/zh/resources.js.map +0 -1
  179. package/dist/src/lib/i18n/locales/zh/server.d.ts +0 -30
  180. package/dist/src/lib/i18n/locales/zh/server.js.map +0 -1
  181. package/dist/src/lib/i18n/locales/zh/tools.d.ts +0 -24
  182. package/dist/src/lib/i18n/locales/zh/tools.js.map +0 -1
  183. package/dist/src/lib/i18n/logger.d.ts +0 -38
  184. package/dist/src/lib/i18n/logger.js.map +0 -1
  185. package/dist/src/lib/logger.d.ts +0 -32
  186. package/dist/src/lib/logger.js.map +0 -1
  187. package/dist/src/lib/server/browser.d.ts +0 -34
  188. package/dist/src/lib/server/browser.js.map +0 -1
  189. package/dist/src/lib/server/fetcher.d.ts +0 -20
  190. package/dist/src/lib/server/fetcher.js.map +0 -1
  191. package/dist/src/lib/server/index.d.ts +0 -10
  192. package/dist/src/lib/server/index.js.map +0 -1
  193. package/dist/src/lib/server/logger.js +0 -23
  194. package/dist/src/lib/server/prompts.d.ts +0 -11
  195. package/dist/src/lib/server/prompts.js.map +0 -1
  196. package/dist/src/lib/server/resources.d.ts +0 -11
  197. package/dist/src/lib/server/resources.js.map +0 -1
  198. package/dist/src/lib/server/tools.d.ts +0 -12
  199. package/dist/src/lib/server/tools.js.map +0 -1
  200. package/dist/src/lib/server/types.d.ts +0 -71
  201. package/dist/src/lib/server/types.js.map +0 -1
  202. package/dist/src/lib/types.d.ts +0 -119
  203. package/dist/src/lib/types.js.map +0 -1
  204. package/dist/src/mcp-server.d.ts +0 -6
  205. package/dist/src/mcp-server.js.map +0 -1
  206. package/dist/tests/jest.setup.d.ts +0 -6
  207. package/dist/tests/jest.setup.js +0 -30
  208. package/dist/tests/jest.setup.js.map +0 -1
  209. package/dist/tests/test-direct-client.d.ts +0 -6
  210. package/dist/tests/test-direct-client.js.map +0 -1
  211. package/dist/tests/test-mcp-methods.d.ts +0 -6
  212. package/dist/tests/test-mcp-methods.js.map +0 -1
  213. package/dist/tests/test-mcp.d.ts +0 -6
  214. package/dist/tests/test-mcp.js.map +0 -1
  215. package/dist/tests/test-mini4k.d.ts +0 -6
  216. package/dist/tests/test-mini4k.js.map +0 -1
@@ -3,24 +3,27 @@
3
3
  * Co-Author: AI Assistant (Claude)
4
4
  * Description: This code was collaboratively developed by Martin and AI Assistant.
5
5
  */
6
- import { BrowserFetcher } from '../../src/lib/BrowserFetcher.js';
6
+ import { BrowserInstance } from '../../src/lib/fetchers/browser/BrowserInstance.js';
7
+ import { vi, describe, test, expect, beforeEach } from 'vitest';
8
+ import * as logger from '../../src/lib/logger.js';
9
+ import * as browserModule from '../../src/lib/server/browser.js';
7
10
  // 模拟依赖
8
- jest.mock('../../src/lib/BrowserFetcher.js');
9
- jest.mock('../../src/lib/logger.js', () => {
11
+ vi.mock('../../src/lib/fetchers/browser/BrowserInstance.js');
12
+ vi.mock('../../src/lib/logger.js', () => {
10
13
  return {
11
- log: jest.fn(),
14
+ log: vi.fn(),
12
15
  COMPONENTS: {
13
16
  SERVER: 'server'
14
17
  }
15
18
  };
16
19
  });
17
20
  // 获取模拟的 log 函数和 COMPONENTS
18
- const { log, COMPONENTS } = require('../../src/lib/logger.js');
21
+ const { log, COMPONENTS } = logger;
19
22
  // 模拟 browser.js 模块
20
23
  let browserInitialized = false;
21
- jest.mock('../../src/lib/server/browser.js', () => {
24
+ vi.mock('../../src/lib/server/browser.js', () => {
22
25
  return {
23
- initializeBrowser: jest.fn(async (debug = false) => {
26
+ initializeBrowser: vi.fn(async (debug = false) => {
24
27
  if (!browserInitialized) {
25
28
  if (debug) {
26
29
  log('server.initializingBrowser', debug, {}, COMPONENTS.SERVER);
@@ -28,16 +31,16 @@ jest.mock('../../src/lib/server/browser.js', () => {
28
31
  browserInitialized = true;
29
32
  }
30
33
  }),
31
- closeBrowserInstance: jest.fn(async (debug = false) => {
34
+ closeBrowserInstance: vi.fn(async (debug = false) => {
32
35
  if (browserInitialized) {
33
36
  if (debug) {
34
37
  log('server.closingBrowser', debug, {}, COMPONENTS.SERVER);
35
38
  }
36
- await BrowserFetcher.closeBrowser(true);
39
+ await BrowserInstance.closeBrowser(debug);
37
40
  browserInitialized = false;
38
41
  }
39
42
  }),
40
- shouldSwitchToBrowser: jest.fn((error) => {
43
+ shouldSwitchToBrowser: vi.fn((error) => {
41
44
  if (error && error.message) {
42
45
  const errorMessage = error.message.toLowerCase();
43
46
  return errorMessage.includes('403') ||
@@ -48,26 +51,27 @@ jest.mock('../../src/lib/server/browser.js', () => {
48
51
  }
49
52
  return false;
50
53
  }),
51
- shouldUseBrowser: jest.fn((response, url) => {
54
+ shouldUseBrowser: vi.fn((response, url) => {
52
55
  if (response.isError) {
53
56
  const errorText = response.content[0].text.toLowerCase();
54
57
  return errorText.includes('403') ||
55
58
  errorText.includes('forbidden') ||
56
59
  errorText.includes('cloudflare') ||
57
- errorText.includes('javascript required');
60
+ errorText.includes('timeout') ||
61
+ errorText.includes('econnrefused');
58
62
  }
59
63
  return false;
60
64
  })
61
65
  };
62
66
  });
63
- // 导入被测试的函数
64
- const { initializeBrowser, closeBrowserInstance, shouldSwitchToBrowser, shouldUseBrowser } = require('../../src/lib/server/browser.js');
67
+ // 从模块中获取函数
68
+ const { initializeBrowser, closeBrowserInstance, shouldSwitchToBrowser, shouldUseBrowser } = browserModule;
65
69
  describe('浏览器管理函数测试 (Browser Management Functions Tests)', () => {
66
70
  beforeEach(() => {
67
71
  // 重置所有模拟
68
- jest.clearAllMocks();
72
+ vi.clearAllMocks();
69
73
  // 设置默认返回值
70
- BrowserFetcher.closeBrowser.mockResolvedValue(undefined);
74
+ BrowserInstance.closeBrowser.mockResolvedValue(undefined);
71
75
  // 重置 browserInitialized 变量
72
76
  browserInitialized = false;
73
77
  });
@@ -87,7 +91,7 @@ describe('浏览器管理函数测试 (Browser Management Functions Tests)', ()
87
91
  test('应该只初始化一次浏览器 (Should initialize browser only once)', async () => {
88
92
  // 调用被测试的函数两次
89
93
  await initializeBrowser(true);
90
- jest.clearAllMocks(); // 清除第一次调用的记录
94
+ vi.clearAllMocks(); // 清除第一次调用的记录
91
95
  await initializeBrowser(true);
92
96
  // 验证第二次调用时 log 函数没有被调用
93
97
  expect(log).not.toHaveBeenCalled();
@@ -99,8 +103,8 @@ describe('浏览器管理函数测试 (Browser Management Functions Tests)', ()
99
103
  await initializeBrowser();
100
104
  // 调用被测试的函数
101
105
  await closeBrowserInstance();
102
- // 验证 BrowserFetcher.closeBrowser 被调用
103
- expect(BrowserFetcher.closeBrowser).toHaveBeenCalledWith(true);
106
+ // 验证 BrowserInstance.closeBrowser 被调用
107
+ expect(BrowserInstance.closeBrowser).toHaveBeenCalled();
104
108
  });
105
109
  test('应该在调试模式下记录日志 (Should log in debug mode)', async () => {
106
110
  // 首先初始化浏览器
@@ -115,8 +119,8 @@ describe('浏览器管理函数测试 (Browser Management Functions Tests)', ()
115
119
  browserInitialized = false;
116
120
  // 调用被测试的函数
117
121
  await closeBrowserInstance();
118
- // 验证 BrowserFetcher.closeBrowser 没有被调用
119
- expect(BrowserFetcher.closeBrowser).not.toHaveBeenCalled();
122
+ // 验证 BrowserInstance.closeBrowser 没有被调用
123
+ expect(BrowserInstance.closeBrowser).not.toHaveBeenCalled();
120
124
  });
121
125
  });
122
126
  describe('shouldSwitchToBrowser 函数测试 (shouldSwitchToBrowser Function Tests)', () => {
@@ -4,15 +4,14 @@
4
4
  * Description: This code was collaboratively developed by Martin and AI Assistant.
5
5
  */
6
6
  import { fetchWithAutoDetect } from '../../src/lib/server/fetcher.js';
7
- import { BrowserFetcher } from '../../src/lib/BrowserFetcher.js';
8
- import { NodeFetcher } from '../../src/lib/NodeFetcher.js';
7
+ import { Fetcher } from '../../src/lib/fetch.js';
9
8
  import { initializeBrowser, closeBrowserInstance, shouldSwitchToBrowser } from '../../src/lib/server/browser.js';
9
+ import { vi, describe, test, expect, beforeEach } from 'vitest';
10
10
  // 模拟依赖
11
- jest.mock('../../src/lib/BrowserFetcher.js');
12
- jest.mock('../../src/lib/NodeFetcher.js');
13
- jest.mock('../../src/lib/server/browser.js');
14
- jest.mock('../../src/lib/logger.js', () => ({
15
- log: jest.fn(),
11
+ vi.mock('../../src/lib/fetch.js');
12
+ vi.mock('../../src/lib/server/browser.js');
13
+ vi.mock('../../src/lib/logger.js', () => ({
14
+ log: vi.fn(),
16
15
  COMPONENTS: {
17
16
  SERVER: 'server'
18
17
  }
@@ -20,153 +19,135 @@ jest.mock('../../src/lib/logger.js', () => ({
20
19
  describe('fetchWithAutoDetect 函数测试 (fetchWithAutoDetect Function Tests)', () => {
21
20
  beforeEach(() => {
22
21
  // 重置所有模拟
23
- jest.clearAllMocks();
22
+ vi.clearAllMocks();
24
23
  // 设置默认返回值
25
- NodeFetcher.html.mockResolvedValue({ html: '<html>Test</html>' });
26
- NodeFetcher.json.mockResolvedValue({ json: { test: 'data' } });
27
- NodeFetcher.txt.mockResolvedValue({ text: 'Test text' });
28
- NodeFetcher.markdown.mockResolvedValue({ markdown: '# Test' });
29
- BrowserFetcher.html.mockResolvedValue({
30
- content: [{ type: 'text', text: '<html>Browser Test</html>' }],
24
+ Fetcher.html.mockResolvedValue({
25
+ content: [{ type: 'text', text: '<html>Test</html>' }],
31
26
  isError: false
32
27
  });
33
- BrowserFetcher.json.mockResolvedValue({
34
- content: [{ type: 'text', text: '{"test":"browser data"}' }],
28
+ Fetcher.json.mockResolvedValue({
29
+ content: [{ type: 'text', text: '{"test":"data"}' }],
35
30
  isError: false
36
31
  });
37
- BrowserFetcher.txt.mockResolvedValue({
38
- content: [{ type: 'text', text: 'Browser test text' }],
32
+ Fetcher.txt.mockResolvedValue({
33
+ content: [{ type: 'text', text: 'Test text' }],
39
34
  isError: false
40
35
  });
41
- BrowserFetcher.markdown.mockResolvedValue({
42
- content: [{ type: 'text', text: '# Browser Test' }],
36
+ Fetcher.markdown.mockResolvedValue({
37
+ content: [{ type: 'text', text: '# Test' }],
43
38
  isError: false
44
39
  });
45
40
  initializeBrowser.mockResolvedValue(undefined);
46
41
  closeBrowserInstance.mockResolvedValue(undefined);
47
42
  shouldSwitchToBrowser.mockReturnValue(false);
48
43
  });
49
- test('应该使用 NodeFetcher 获取 HTML (Should use NodeFetcher to fetch HTML)', async () => {
44
+ test('应该使用 Fetcher 获取 HTML (Should use Fetcher to fetch HTML)', async () => {
50
45
  const params = { url: 'https://example.com' };
51
46
  const result = await fetchWithAutoDetect(params, 'html');
52
- expect(NodeFetcher.html).toHaveBeenCalledWith(params);
53
- expect(BrowserFetcher.html).not.toHaveBeenCalled();
47
+ expect(Fetcher.html).toHaveBeenCalledWith(params);
54
48
  expect(result).toEqual({
55
49
  content: [{ type: 'text', text: '<html>Test</html>' }],
56
50
  isError: false
57
51
  });
58
52
  });
59
- test('应该使用 NodeFetcher 获取 JSON (Should use NodeFetcher to fetch JSON)', async () => {
53
+ test('应该使用 Fetcher 获取 JSON (Should use Fetcher to fetch JSON)', async () => {
60
54
  const params = { url: 'https://example.com/data.json' };
61
55
  const result = await fetchWithAutoDetect(params, 'json');
62
- expect(NodeFetcher.json).toHaveBeenCalledWith(params);
63
- expect(BrowserFetcher.json).not.toHaveBeenCalled();
56
+ expect(Fetcher.json).toHaveBeenCalledWith(params);
64
57
  expect(result).toEqual({
65
- content: [{ type: 'text', text: JSON.stringify({ test: 'data' }, null, 2) }],
58
+ content: [{ type: 'text', text: '{"test":"data"}' }],
66
59
  isError: false
67
60
  });
68
61
  });
69
- test('应该使用 NodeFetcher 获取文本 (Should use NodeFetcher to fetch text)', async () => {
62
+ test('应该使用 Fetcher 获取文本 (Should use Fetcher to fetch text)', async () => {
70
63
  const params = { url: 'https://example.com/text.txt' };
71
64
  const result = await fetchWithAutoDetect(params, 'txt');
72
- expect(NodeFetcher.txt).toHaveBeenCalledWith(params);
73
- expect(BrowserFetcher.txt).not.toHaveBeenCalled();
65
+ expect(Fetcher.txt).toHaveBeenCalledWith(params);
74
66
  expect(result).toEqual({
75
67
  content: [{ type: 'text', text: 'Test text' }],
76
68
  isError: false
77
69
  });
78
70
  });
79
- test('应该使用 NodeFetcher 获取 Markdown (Should use NodeFetcher to fetch Markdown)', async () => {
71
+ test('应该使用 Fetcher 获取 Markdown (Should use Fetcher to fetch Markdown)', async () => {
80
72
  const params = { url: 'https://example.com/readme.md' };
81
73
  const result = await fetchWithAutoDetect(params, 'markdown');
82
- expect(NodeFetcher.markdown).toHaveBeenCalledWith(params);
83
- expect(BrowserFetcher.markdown).not.toHaveBeenCalled();
74
+ expect(Fetcher.markdown).toHaveBeenCalledWith(params);
84
75
  expect(result).toEqual({
85
76
  content: [{ type: 'text', text: '# Test' }],
86
77
  isError: false
87
78
  });
88
79
  });
89
- test('应该使用 BrowserFetcher 获取 HTML (Should use BrowserFetcher to fetch HTML)', async () => {
80
+ test('应该在浏览器模式下使用 Fetcher 获取 HTML (Should use Fetcher to fetch HTML in browser mode)', async () => {
90
81
  const params = { url: 'https://example.com', useBrowser: true };
82
+ // 设置浏览器模式下的返回值
83
+ Fetcher.html.mockResolvedValue({
84
+ content: [{ type: 'text', text: '<html>Browser Test</html>' }],
85
+ isError: false
86
+ });
91
87
  const result = await fetchWithAutoDetect(params, 'html');
92
88
  expect(initializeBrowser).toHaveBeenCalled();
93
- expect(NodeFetcher.html).not.toHaveBeenCalled();
94
- expect(BrowserFetcher.html).toHaveBeenCalledWith({ ...params, debug: false });
89
+ expect(Fetcher.html).toHaveBeenCalledWith({ ...params, debug: false });
95
90
  expect(result).toEqual({
96
91
  content: [{ type: 'text', text: '<html>Browser Test</html>' }],
97
92
  isError: false
98
93
  });
99
94
  });
100
- test('应该使用 BrowserFetcher 获取 JSON (Should use BrowserFetcher to fetch JSON)', async () => {
95
+ test('应该在浏览器模式下使用 Fetcher 获取 JSON (Should use Fetcher to fetch JSON in browser mode)', async () => {
101
96
  const params = { url: 'https://example.com/data.json', useBrowser: true };
102
- const result = await fetchWithAutoDetect(params, 'json');
103
- expect(initializeBrowser).toHaveBeenCalled();
104
- expect(NodeFetcher.json).not.toHaveBeenCalled();
105
- expect(BrowserFetcher.json).toHaveBeenCalledWith({ ...params, debug: false });
106
- expect(result).toEqual({
97
+ // 设置浏览器模式下的返回值
98
+ Fetcher.json.mockResolvedValue({
107
99
  content: [{ type: 'text', text: '{"test":"browser data"}' }],
108
100
  isError: false
109
101
  });
110
- });
111
- test('应该使用 BrowserFetcher 获取文本 (Should use BrowserFetcher to fetch text)', async () => {
112
- const params = { url: 'https://example.com/text.txt', useBrowser: true };
113
- const result = await fetchWithAutoDetect(params, 'txt');
102
+ const result = await fetchWithAutoDetect(params, 'json');
114
103
  expect(initializeBrowser).toHaveBeenCalled();
115
- expect(NodeFetcher.txt).not.toHaveBeenCalled();
116
- expect(BrowserFetcher.txt).toHaveBeenCalledWith({ ...params, debug: false });
104
+ expect(Fetcher.json).toHaveBeenCalledWith({ ...params, debug: false });
117
105
  expect(result).toEqual({
118
- content: [{ type: 'text', text: 'Browser test text' }],
106
+ content: [{ type: 'text', text: '{"test":"browser data"}' }],
119
107
  isError: false
120
108
  });
121
109
  });
122
- test('应该使用 BrowserFetcher 获取 Markdown (Should use BrowserFetcher to fetch Markdown)', async () => {
123
- const params = { url: 'https://example.com/readme.md', useBrowser: true };
124
- const result = await fetchWithAutoDetect(params, 'markdown');
125
- expect(initializeBrowser).toHaveBeenCalled();
126
- expect(NodeFetcher.markdown).not.toHaveBeenCalled();
127
- expect(BrowserFetcher.markdown).toHaveBeenCalledWith({ ...params, debug: false });
128
- expect(result).toEqual({
129
- content: [{ type: 'text', text: '# Browser Test' }],
110
+ test('应该在 Fetcher 失败时自动切换到浏览器模式 (Should automatically switch to browser mode when Fetcher fails)', async () => {
111
+ const params = { url: 'https://example.com' };
112
+ // 设置 Fetcher 第一次调用抛出错误,第二次调用成功
113
+ Fetcher.html
114
+ .mockRejectedValueOnce(new Error('Fetch failed'))
115
+ .mockResolvedValueOnce({
116
+ content: [{ type: 'text', text: '<html>Browser Test</html>' }],
130
117
  isError: false
131
118
  });
132
- });
133
- test('应该在 NodeFetcher 失败时自动切换到 BrowserFetcher (Should automatically switch to BrowserFetcher when NodeFetcher fails)', async () => {
134
- const params = { url: 'https://example.com' };
135
- // 设置 NodeFetcher 抛出错误
136
- NodeFetcher.html.mockRejectedValue(new Error('Fetch failed'));
137
119
  // 设置 shouldSwitchToBrowser 返回 true
138
120
  shouldSwitchToBrowser.mockReturnValue(true);
139
121
  const result = await fetchWithAutoDetect(params, 'html');
140
- expect(NodeFetcher.html).toHaveBeenCalledWith(params);
122
+ expect(Fetcher.html).toHaveBeenCalledTimes(2);
123
+ expect(Fetcher.html).toHaveBeenNthCalledWith(1, params);
124
+ expect(Fetcher.html).toHaveBeenNthCalledWith(2, { ...params, useBrowser: true, debug: false });
141
125
  expect(shouldSwitchToBrowser).toHaveBeenCalled();
142
126
  expect(initializeBrowser).toHaveBeenCalled();
143
- expect(BrowserFetcher.html).toHaveBeenCalledWith({ ...params, useBrowser: true, debug: false });
144
127
  expect(result).toEqual({
145
128
  content: [{ type: 'text', text: '<html>Browser Test</html>' }],
146
129
  isError: false
147
130
  });
148
131
  });
149
- test('应该在 NodeFetcher 失败且不需要切换时抛出原始错误 (Should throw original error when NodeFetcher fails and no switch is needed)', async () => {
132
+ test('应该在 Fetcher 失败且不需要切换时抛出原始错误 (Should throw original error when Fetcher fails and no switch is needed)', async () => {
150
133
  const params = { url: 'https://example.com' };
151
- // 设置 NodeFetcher 抛出错误
134
+ // 设置 Fetcher 抛出错误
152
135
  const originalError = new Error('Fetch failed');
153
- NodeFetcher.html.mockRejectedValue(originalError);
136
+ Fetcher.html.mockRejectedValue(originalError);
154
137
  // 设置 shouldSwitchToBrowser 返回 false
155
138
  shouldSwitchToBrowser.mockReturnValue(false);
156
139
  await expect(fetchWithAutoDetect(params, 'html')).rejects.toThrow(originalError);
157
- expect(NodeFetcher.html).toHaveBeenCalledWith(params);
140
+ expect(Fetcher.html).toHaveBeenCalledWith(params);
158
141
  expect(shouldSwitchToBrowser).toHaveBeenCalled();
159
- expect(BrowserFetcher.html).not.toHaveBeenCalled();
160
142
  });
161
143
  test('应该在 autoDetectMode 为 false 时不自动切换 (Should not automatically switch when autoDetectMode is false)', async () => {
162
144
  const params = { url: 'https://example.com', autoDetectMode: false };
163
- // 设置 NodeFetcher 抛出错误
145
+ // 设置 Fetcher 抛出错误
164
146
  const originalError = new Error('Fetch failed');
165
- NodeFetcher.html.mockRejectedValue(originalError);
147
+ Fetcher.html.mockRejectedValue(originalError);
166
148
  await expect(fetchWithAutoDetect(params, 'html')).rejects.toThrow(originalError);
167
- expect(NodeFetcher.html).toHaveBeenCalledWith(params);
149
+ expect(Fetcher.html).toHaveBeenCalledWith(params);
168
150
  expect(shouldSwitchToBrowser).not.toHaveBeenCalled();
169
- expect(BrowserFetcher.html).not.toHaveBeenCalled();
170
151
  });
171
152
  test('应该在 closeBrowser 为 true 时关闭浏览器 (Should close browser when closeBrowser is true)', async () => {
172
153
  const params = { url: 'https://example.com', useBrowser: true, closeBrowser: true };
@@ -176,41 +157,17 @@ describe('fetchWithAutoDetect 函数测试 (fetchWithAutoDetect Function Tests)'
176
157
  // 手动调用 closeBrowserInstance 函数,模拟外部调用
177
158
  await closeBrowserInstance(false);
178
159
  expect(initializeBrowser).toHaveBeenCalled();
179
- expect(BrowserFetcher.html).toHaveBeenCalled();
160
+ expect(Fetcher.html).toHaveBeenCalled();
180
161
  expect(closeBrowserInstance).toHaveBeenCalled();
181
162
  });
182
163
  test('应该在出错且 closeBrowser 为 true 时关闭浏览器 (Should close browser when error occurs and closeBrowser is true)', async () => {
183
164
  const params = { url: 'https://example.com', useBrowser: true, closeBrowser: true };
184
- // 设置 BrowserFetcher 抛出错误
165
+ // 设置 Fetcher 抛出错误
185
166
  const originalError = new Error('Browser fetch failed');
186
- BrowserFetcher.html.mockRejectedValue(originalError);
167
+ Fetcher.html.mockRejectedValue(originalError);
187
168
  await expect(fetchWithAutoDetect(params, 'html')).rejects.toThrow(originalError);
188
169
  expect(initializeBrowser).toHaveBeenCalled();
189
- expect(BrowserFetcher.html).toHaveBeenCalled();
170
+ expect(Fetcher.html).toHaveBeenCalled();
190
171
  expect(closeBrowserInstance).toHaveBeenCalled();
191
172
  });
192
- test('应该处理 JSON 对象和字符串 (Should handle JSON objects and strings)', async () => {
193
- // 测试 JSON 对象
194
- NodeFetcher.json.mockResolvedValue({ json: { test: 'data' } });
195
- let result = await fetchWithAutoDetect({ url: 'https://example.com/data.json' }, 'json');
196
- expect(result).toEqual({
197
- content: [{ type: 'text', text: JSON.stringify({ test: 'data' }, null, 2) }],
198
- isError: false
199
- });
200
- // 测试 JSON 字符串
201
- NodeFetcher.json.mockResolvedValue({ json: '{"test":"string data"}' });
202
- result = await fetchWithAutoDetect({ url: 'https://example.com/data.json' }, 'json');
203
- expect(result).toEqual({
204
- content: [{ type: 'text', text: '{"test":"string data"}' }],
205
- isError: false
206
- });
207
- });
208
- test('应该返回不支持的内容类型错误 (Should return unsupported content type error)', async () => {
209
- // @ts-ignore - 故意传入不支持的类型
210
- const result = await fetchWithAutoDetect({ url: 'https://example.com' }, 'unsupported');
211
- expect(result).toEqual({
212
- content: [{ type: 'text', text: 'Unsupported content type: unsupported' }],
213
- isError: true
214
- });
215
- });
216
173
  });
@@ -3,32 +3,33 @@
3
3
  * Co-Author: AI Assistant (Claude)
4
4
  * Description: This code was collaboratively developed by Martin and AI Assistant.
5
5
  */
6
+ import { vi, describe, test, expect, beforeEach, afterEach } from 'vitest';
6
7
  // 模拟依赖
7
- jest.mock('@modelcontextprotocol/sdk/server/index.js', () => ({
8
- Server: jest.fn().mockImplementation(() => ({
9
- connect: jest.fn().mockResolvedValue(undefined),
10
- registerTool: jest.fn(),
11
- registerResource: jest.fn(),
12
- registerPrompt: jest.fn()
8
+ vi.mock('@modelcontextprotocol/sdk/server/index.js', () => ({
9
+ Server: vi.fn().mockImplementation(() => ({
10
+ connect: vi.fn().mockResolvedValue(undefined),
11
+ registerTool: vi.fn(),
12
+ registerResource: vi.fn(),
13
+ registerPrompt: vi.fn()
13
14
  }))
14
15
  }));
15
- jest.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({
16
- StdioServerTransport: jest.fn().mockImplementation(() => ({}))
16
+ vi.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({
17
+ StdioServerTransport: vi.fn().mockImplementation(() => ({}))
17
18
  }));
18
- jest.mock('../../src/lib/server/tools.js', () => ({
19
- registerTools: jest.fn()
19
+ vi.mock('../../src/lib/server/tools.js', () => ({
20
+ registerTools: vi.fn()
20
21
  }));
21
- jest.mock('../../src/lib/server/resources.js', () => ({
22
- registerResources: jest.fn()
22
+ vi.mock('../../src/lib/server/resources.js', () => ({
23
+ registerResources: vi.fn()
23
24
  }));
24
- jest.mock('../../src/lib/server/prompts.js', () => ({
25
- registerPrompts: jest.fn()
25
+ vi.mock('../../src/lib/server/prompts.js', () => ({
26
+ registerPrompts: vi.fn()
26
27
  }));
27
- jest.mock('../../src/lib/server/browser.js', () => ({
28
- closeBrowserInstance: jest.fn().mockResolvedValue(undefined)
28
+ vi.mock('../../src/lib/server/browser.js', () => ({
29
+ closeBrowserInstance: vi.fn().mockResolvedValue(undefined)
29
30
  }));
30
- jest.mock('../../src/lib/logger.js', () => ({
31
- log: jest.fn(),
31
+ vi.mock('../../src/lib/logger.js', () => ({
32
+ log: vi.fn(),
32
33
  COMPONENTS: {
33
34
  SERVER: 'SERVER'
34
35
  }
@@ -42,15 +43,15 @@ describe('MCP服务器信号处理测试 (MCP Server Signal Handling Tests)', ()
42
43
  const originalProcessOn = process.on;
43
44
  const originalProcessExit = process.exit;
44
45
  // 存储信号处理函数
45
- let exitHandler;
46
- let sigintHandler;
47
- let sigtermHandler;
48
- let uncaughtExceptionHandler;
49
- beforeEach(() => {
46
+ let exitHandler = null;
47
+ let sigintHandler = null;
48
+ let sigtermHandler = null;
49
+ let uncaughtExceptionHandler = null;
50
+ beforeEach(async () => {
50
51
  // 重置所有模拟
51
- jest.clearAllMocks();
52
+ vi.clearAllMocks();
52
53
  // 模拟process.on
53
- process.on = jest.fn().mockImplementation((event, handler) => {
54
+ process.on = vi.fn().mockImplementation((event, handler) => {
54
55
  // 存储处理函数以便测试
55
56
  if (event === 'exit') {
56
57
  exitHandler = handler;
@@ -67,12 +68,11 @@ describe('MCP服务器信号处理测试 (MCP Server Signal Handling Tests)', ()
67
68
  return process;
68
69
  });
69
70
  // 模拟process.exit
70
- process.exit = jest.fn();
71
+ process.exit = vi.fn();
71
72
  // 导入startServer函数
72
- // 注意:我们需要在每个测试中重新导入,以确保process.on的模拟生效
73
- jest.isolateModules(() => {
74
- require('../../src/lib/server/index.js').startServer();
75
- });
73
+ // 使用动态导入而不是isolateModules
74
+ const serverModule = await import('../../src/lib/server/index.js');
75
+ serverModule.startServer();
76
76
  });
77
77
  afterEach(() => {
78
78
  // 恢复原始的process.on和process.exit
@@ -4,9 +4,11 @@
4
4
  * Description: This code was collaboratively developed by Martin and AI Assistant.
5
5
  */
6
6
  import { registerPrompts } from "../../src/lib/server/prompts.js";
7
- import { ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
+ import { ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
8
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
9
+ import * as logger from '../../src/lib/logger.js';
8
10
  // 模拟i18n模块
9
- jest.mock('../../src/lib/i18n/index.js', () => ({
11
+ vi.mock('../../src/lib/i18n/index.js', () => ({
10
12
  t: (key) => {
11
13
  const translations = {
12
14
  'prompts.fetchWebsite.description': '获取网站内容',
@@ -44,56 +46,57 @@ jest.mock('../../src/lib/i18n/index.js', () => ({
44
46
  }
45
47
  }));
46
48
  // 模拟logger模块
47
- jest.mock('../../src/lib/logger.js', () => ({
48
- log: jest.fn(),
49
+ vi.mock('../../src/lib/logger.js', () => ({
50
+ log: vi.fn(),
49
51
  COMPONENTS: {
50
52
  PROMPTS: 'prompts'
51
53
  }
52
54
  }));
53
- describe('prompts模块测试', () => {
54
- let server;
55
+ describe('提示模块测试 (Prompts Module Tests)', () => {
56
+ let mockServer;
57
+ let mockSetRequestHandler;
55
58
  let listPromptsHandler;
56
59
  let getPromptHandler;
57
60
  beforeEach(() => {
58
- // 创建模拟Server实例
59
- server = {
60
- setRequestHandler: jest.fn((schema, handler) => {
61
- if (schema === ListPromptsRequestSchema) {
62
- listPromptsHandler = handler;
63
- }
64
- else if (schema === GetPromptRequestSchema) {
65
- getPromptHandler = handler;
66
- }
67
- })
61
+ // 重置所有模拟
62
+ vi.clearAllMocks();
63
+ // 创建模拟的 Server 实例
64
+ mockSetRequestHandler = vi.fn((schema, handler) => {
65
+ if (schema === ListPromptsRequestSchema) {
66
+ listPromptsHandler = handler;
67
+ }
68
+ else if (schema === GetPromptRequestSchema) {
69
+ getPromptHandler = handler;
70
+ }
71
+ });
72
+ mockServer = {
73
+ setRequestHandler: mockSetRequestHandler
68
74
  };
69
75
  // 注册提示处理程序
70
- registerPrompts(server);
76
+ registerPrompts(mockServer);
71
77
  });
72
78
  describe('registerPrompts', () => {
73
79
  it('应该注册ListPromptsRequestSchema和GetPromptRequestSchema处理程序', () => {
74
- expect(server.setRequestHandler).toHaveBeenCalledTimes(2);
75
- expect(server.setRequestHandler).toHaveBeenCalledWith(ListPromptsRequestSchema, expect.any(Function));
76
- expect(server.setRequestHandler).toHaveBeenCalledWith(GetPromptRequestSchema, expect.any(Function));
80
+ expect(mockServer.setRequestHandler).toHaveBeenCalledTimes(2);
81
+ expect(mockServer.setRequestHandler).toHaveBeenCalledWith(ListPromptsRequestSchema, expect.any(Function));
82
+ expect(mockServer.setRequestHandler).toHaveBeenCalledWith(GetPromptRequestSchema, expect.any(Function));
77
83
  });
78
84
  });
79
85
  describe('ListPromptsRequestSchema处理程序', () => {
80
86
  it('应该返回所有可用的提示', async () => {
81
- const request = { params: {} };
82
- const response = await listPromptsHandler(request);
83
- expect(response).toHaveProperty('prompts');
84
- expect(Array.isArray(response.prompts)).toBe(true);
85
- expect(response.prompts.length).toBe(3);
86
- // 验证返回的提示
87
- const promptNames = response.prompts.map((p) => p.name);
88
- expect(promptNames).toContain('mult-fetch-mcp:prompt:fetch-website');
89
- expect(promptNames).toContain('mult-fetch-mcp:prompt:extract-content');
90
- expect(promptNames).toContain('mult-fetch-mcp:prompt:debug-fetch');
87
+ const result = await listPromptsHandler({});
88
+ expect(result).toHaveProperty('prompts');
89
+ expect(Array.isArray(result.prompts)).toBe(true);
90
+ expect(result.prompts.length).toBeGreaterThan(0);
91
+ // 验证提示格式
92
+ const prompt = result.prompts[0];
93
+ expect(prompt).toHaveProperty('name');
94
+ expect(prompt).toHaveProperty('description');
91
95
  });
92
96
  it('应该处理debug参数', async () => {
93
- const request = { params: { debug: true } };
94
- await listPromptsHandler(request);
97
+ const result = await listPromptsHandler({ params: { debug: true } });
95
98
  // 验证日志调用
96
- expect(require('../../src/lib/logger.js').log).toHaveBeenCalledWith('prompts.list.request', true, { params: { debug: true } }, 'prompts');
99
+ expect(logger.log).toHaveBeenCalledWith('prompts.list.request', true, expect.any(Object), 'prompts');
97
100
  });
98
101
  });
99
102
  describe('GetPromptRequestSchema处理程序', () => {