@lmcc-dev/mult-fetch-mcp-server 1.0.3 → 1.1.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 (113) hide show
  1. package/README.md +3 -1
  2. package/README.zh.md +3 -2
  3. package/dist/src/client.d.ts +6 -0
  4. package/dist/src/client.js +26 -28
  5. package/dist/src/client.js.map +1 -0
  6. package/dist/src/index.d.ts +15 -0
  7. package/dist/src/index.js.map +1 -0
  8. package/dist/src/lib/BrowserFetcher.d.ts +175 -0
  9. package/dist/src/lib/BrowserFetcher.js.map +1 -0
  10. package/dist/src/lib/NodeFetcher.d.ts +92 -0
  11. package/dist/src/lib/NodeFetcher.js +143 -84
  12. package/dist/src/lib/NodeFetcher.js.map +1 -0
  13. package/dist/src/lib/i18n/index.js.map +1 -0
  14. package/dist/src/lib/i18n/keys.js +135 -0
  15. package/dist/src/lib/i18n/locales/en/browser.d.ts +61 -0
  16. package/dist/src/lib/i18n/locales/en/browser.js +68 -54
  17. package/dist/src/lib/i18n/locales/en/browser.js.map +1 -0
  18. package/dist/src/lib/i18n/locales/en/client.d.ts +39 -0
  19. package/dist/src/lib/i18n/locales/en/client.js.map +1 -0
  20. package/dist/src/lib/i18n/locales/en/errors.d.ts +17 -0
  21. package/dist/src/lib/i18n/locales/en/errors.js.map +1 -0
  22. package/dist/src/lib/i18n/locales/en/fetcher.d.ts +66 -0
  23. package/dist/src/lib/i18n/locales/en/fetcher.js +60 -59
  24. package/dist/src/lib/i18n/locales/en/fetcher.js.map +1 -0
  25. package/dist/src/lib/i18n/locales/en/index.d.ts +344 -0
  26. package/dist/src/lib/i18n/locales/en/index.js +1 -1
  27. package/dist/src/lib/i18n/locales/en/index.js.map +1 -0
  28. package/dist/src/lib/i18n/locales/en/node.d.ts +41 -0
  29. package/dist/src/lib/i18n/locales/en/node.js.map +1 -0
  30. package/dist/src/lib/i18n/locales/en/prompts.d.ts +65 -0
  31. package/dist/src/lib/i18n/locales/en/prompts.js.map +1 -0
  32. package/dist/src/lib/i18n/locales/en/resources.d.ts +41 -0
  33. package/dist/src/lib/i18n/locales/en/resources.js +30 -15
  34. package/dist/src/lib/i18n/locales/en/resources.js.map +1 -0
  35. package/dist/src/lib/i18n/locales/en/server.d.ts +30 -0
  36. package/dist/src/lib/i18n/locales/en/server.js.map +1 -0
  37. package/dist/src/lib/i18n/locales/en/tools.d.ts +24 -0
  38. package/dist/src/lib/i18n/locales/en/tools.js.map +1 -0
  39. package/dist/src/lib/i18n/locales/zh/browser.d.ts +61 -0
  40. package/dist/src/lib/i18n/locales/zh/browser.js +68 -54
  41. package/dist/src/lib/i18n/locales/zh/browser.js.map +1 -0
  42. package/dist/src/lib/i18n/locales/zh/client.d.ts +38 -0
  43. package/dist/src/lib/i18n/locales/zh/client.js.map +1 -0
  44. package/dist/src/lib/i18n/locales/zh/errors.d.ts +17 -0
  45. package/dist/src/lib/i18n/locales/zh/errors.js.map +1 -0
  46. package/dist/src/lib/i18n/locales/zh/fetcher.d.ts +66 -0
  47. package/dist/src/lib/i18n/locales/zh/fetcher.js +60 -59
  48. package/dist/src/lib/i18n/locales/zh/fetcher.js.map +1 -0
  49. package/dist/src/lib/i18n/locales/zh/index.d.ts +343 -0
  50. package/dist/src/lib/i18n/locales/zh/index.js +1 -1
  51. package/dist/src/lib/i18n/locales/zh/index.js.map +1 -0
  52. package/dist/src/lib/i18n/locales/zh/node.d.ts +41 -0
  53. package/dist/src/lib/i18n/locales/zh/node.js.map +1 -0
  54. package/dist/src/lib/i18n/locales/zh/prompts.d.ts +65 -0
  55. package/dist/src/lib/i18n/locales/zh/prompts.js.map +1 -0
  56. package/dist/src/lib/i18n/locales/zh/resources.d.ts +41 -0
  57. package/dist/src/lib/i18n/locales/zh/resources.js +28 -13
  58. package/dist/src/lib/i18n/locales/zh/resources.js.map +1 -0
  59. package/dist/src/lib/i18n/locales/zh/server.d.ts +30 -0
  60. package/dist/src/lib/i18n/locales/zh/server.js.map +1 -0
  61. package/dist/src/lib/i18n/locales/zh/tools.d.ts +24 -0
  62. package/dist/src/lib/i18n/locales/zh/tools.js.map +1 -0
  63. package/dist/src/lib/i18n/logger.d.ts +38 -0
  64. package/dist/src/lib/i18n/logger.js.map +1 -0
  65. package/dist/src/lib/logger.d.ts +32 -0
  66. package/dist/src/lib/logger.js.map +1 -0
  67. package/dist/src/lib/server/browser.d.ts +34 -0
  68. package/dist/src/lib/server/browser.js +10 -16
  69. package/dist/src/lib/server/browser.js.map +1 -0
  70. package/dist/src/lib/server/fetcher.d.ts +20 -0
  71. package/dist/src/lib/server/fetcher.js.map +1 -0
  72. package/dist/src/lib/server/index.d.ts +10 -0
  73. package/dist/src/lib/server/index.js.map +1 -0
  74. package/dist/src/lib/server/prompts.d.ts +11 -0
  75. package/dist/src/lib/server/prompts.js.map +1 -0
  76. package/dist/src/lib/server/resources.d.ts +11 -0
  77. package/dist/src/lib/server/resources.js.map +1 -0
  78. package/dist/src/lib/server/tools.d.ts +12 -0
  79. package/dist/src/lib/server/tools.js +369 -309
  80. package/dist/src/lib/server/tools.js.map +1 -0
  81. package/dist/src/lib/server/types.d.ts +71 -0
  82. package/dist/src/lib/server/types.js.map +1 -0
  83. package/dist/src/lib/types.d.ts +119 -0
  84. package/dist/src/lib/types.js.map +1 -0
  85. package/dist/src/lib/utils/errorDetection.js +41 -0
  86. package/dist/src/lib/utils/errors.js +279 -0
  87. package/dist/src/mcp-server.d.ts +6 -0
  88. package/dist/src/mcp-server.js.map +1 -0
  89. package/dist/tests/BrowserFetcher.test.js +951 -0
  90. package/dist/tests/NodeFetcher.test.js +263 -0
  91. package/dist/tests/client.test.js +250 -0
  92. package/dist/tests/i18n.test.js +179 -0
  93. package/dist/tests/jest.setup.d.ts +6 -0
  94. package/dist/tests/jest.setup.js +30 -0
  95. package/dist/tests/jest.setup.js.map +1 -0
  96. package/dist/tests/logger.test.js +99 -0
  97. package/dist/tests/mcp-server.test.js +113 -0
  98. package/dist/tests/server/browser.test.js +197 -0
  99. package/dist/tests/server/fetcher.test.js +216 -0
  100. package/dist/tests/server/index.test.js +122 -0
  101. package/dist/tests/server/prompts.test.js +243 -0
  102. package/dist/tests/server/resources.test.js +359 -0
  103. package/dist/tests/server/tools.test.js +222 -0
  104. package/dist/tests/test-direct-client.d.ts +6 -0
  105. package/dist/tests/test-direct-client.js.map +1 -0
  106. package/dist/tests/test-mcp-methods.d.ts +6 -0
  107. package/dist/tests/test-mcp-methods.js.map +1 -0
  108. package/dist/tests/test-mcp.d.ts +6 -0
  109. package/dist/tests/test-mcp.js.map +1 -0
  110. package/dist/tests/test-mini4k.d.ts +6 -0
  111. package/dist/tests/test-mini4k.js.map +1 -0
  112. package/dist/tests/types.test.js +57 -0
  113. package/package.json +11 -2
@@ -0,0 +1,122 @@
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
+ // 模拟依赖
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()
13
+ }))
14
+ }));
15
+ jest.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({
16
+ StdioServerTransport: jest.fn().mockImplementation(() => ({}))
17
+ }));
18
+ jest.mock('../../src/lib/server/tools.js', () => ({
19
+ registerTools: jest.fn()
20
+ }));
21
+ jest.mock('../../src/lib/server/resources.js', () => ({
22
+ registerResources: jest.fn()
23
+ }));
24
+ jest.mock('../../src/lib/server/prompts.js', () => ({
25
+ registerPrompts: jest.fn()
26
+ }));
27
+ jest.mock('../../src/lib/server/browser.js', () => ({
28
+ closeBrowserInstance: jest.fn().mockResolvedValue(undefined)
29
+ }));
30
+ jest.mock('../../src/lib/logger.js', () => ({
31
+ log: jest.fn(),
32
+ COMPONENTS: {
33
+ SERVER: 'SERVER'
34
+ }
35
+ }));
36
+ // 导入被测试的模块和依赖
37
+ import { closeBrowserInstance } from '../../src/lib/server/browser.js';
38
+ import { log } from '../../src/lib/logger.js';
39
+ // 测试信号处理函数
40
+ describe('MCP服务器信号处理测试 (MCP Server Signal Handling Tests)', () => {
41
+ // 保存原始的process.on和process.exit
42
+ const originalProcessOn = process.on;
43
+ const originalProcessExit = process.exit;
44
+ // 存储信号处理函数
45
+ let exitHandler;
46
+ let sigintHandler;
47
+ let sigtermHandler;
48
+ let uncaughtExceptionHandler;
49
+ beforeEach(() => {
50
+ // 重置所有模拟
51
+ jest.clearAllMocks();
52
+ // 模拟process.on
53
+ process.on = jest.fn().mockImplementation((event, handler) => {
54
+ // 存储处理函数以便测试
55
+ if (event === 'exit') {
56
+ exitHandler = handler;
57
+ }
58
+ else if (event === 'SIGINT') {
59
+ sigintHandler = handler;
60
+ }
61
+ else if (event === 'SIGTERM') {
62
+ sigtermHandler = handler;
63
+ }
64
+ else if (event === 'uncaughtException') {
65
+ uncaughtExceptionHandler = handler;
66
+ }
67
+ return process;
68
+ });
69
+ // 模拟process.exit
70
+ process.exit = jest.fn();
71
+ // 导入startServer函数
72
+ // 注意:我们需要在每个测试中重新导入,以确保process.on的模拟生效
73
+ jest.isolateModules(() => {
74
+ require('../../src/lib/server/index.js').startServer();
75
+ });
76
+ });
77
+ afterEach(() => {
78
+ // 恢复原始的process.on和process.exit
79
+ process.on = originalProcessOn;
80
+ process.exit = originalProcessExit;
81
+ });
82
+ test('应该处理SIGINT信号 (Should handle SIGINT signal)', async () => {
83
+ // 确保处理函数已定义
84
+ expect(sigintHandler).toBeDefined();
85
+ // 调用SIGINT处理函数
86
+ await sigintHandler();
87
+ // 验证日志被记录
88
+ expect(log).toHaveBeenCalledWith('server.receivedInterruptSignal', true, {}, expect.anything());
89
+ // 验证closeBrowserInstance被调用
90
+ expect(closeBrowserInstance).toHaveBeenCalledWith(false);
91
+ // 验证process.exit被调用
92
+ expect(process.exit).toHaveBeenCalledWith(0);
93
+ });
94
+ test('应该处理SIGTERM信号 (Should handle SIGTERM signal)', async () => {
95
+ // 确保处理函数已定义
96
+ expect(sigtermHandler).toBeDefined();
97
+ // 调用SIGTERM处理函数
98
+ await sigtermHandler();
99
+ // 验证日志被记录
100
+ expect(log).toHaveBeenCalledWith('server.receivedTerminateSignal', true, {}, expect.anything());
101
+ // 验证closeBrowserInstance被调用
102
+ expect(closeBrowserInstance).toHaveBeenCalledWith(false);
103
+ // 验证process.exit被调用
104
+ expect(process.exit).toHaveBeenCalledWith(0);
105
+ });
106
+ test('应该处理未捕获的异常 (Should handle uncaught exception)', async () => {
107
+ // 确保处理函数已定义
108
+ expect(uncaughtExceptionHandler).toBeDefined();
109
+ // 创建测试错误
110
+ const testError = new Error('Uncaught error');
111
+ // 调用uncaughtException处理函数
112
+ await uncaughtExceptionHandler(testError);
113
+ // 验证日志被记录
114
+ expect(log).toHaveBeenCalledWith('server.uncaughtException', true, {
115
+ error: 'Uncaught error'
116
+ }, expect.anything());
117
+ // 验证closeBrowserInstance被调用
118
+ expect(closeBrowserInstance).toHaveBeenCalledWith(false);
119
+ // 验证process.exit被调用
120
+ expect(process.exit).toHaveBeenCalledWith(1);
121
+ });
122
+ });
@@ -0,0 +1,243 @@
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 { registerPrompts } from "../../src/lib/server/prompts.js";
7
+ import { ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";
8
+ // 模拟i18n模块
9
+ jest.mock('../../src/lib/i18n/index.js', () => ({
10
+ t: (key) => {
11
+ const translations = {
12
+ 'prompts.fetchWebsite.description': '获取网站内容',
13
+ 'prompts.url.description': '网站URL',
14
+ 'prompts.format.description': '返回格式',
15
+ 'prompts.useBrowser.description': '是否使用浏览器',
16
+ 'prompts.selector.description': '选择器',
17
+ 'prompts.dataType.description': '数据类型',
18
+ 'prompts.error.description': '错误信息',
19
+ 'prompts.notFound': '提示未找到',
20
+ 'prompts.missingRequiredArg': '缺少必需参数',
21
+ 'prompts.fetchWebsite.result': '获取网站结果',
22
+ 'prompts.fetchWebsite.message': '我需要获取网站内容',
23
+ 'prompts.fetchWebsite.response': '我可以帮你获取网站内容',
24
+ 'prompts.fetchWebsite.instruction': '请获取以下网站',
25
+ 'prompts.fetchWebsite.formatInstruction': '返回格式',
26
+ 'prompts.fetchWebsite.browserInstruction': '使用浏览器',
27
+ 'prompts.yes': '是',
28
+ 'prompts.no': '否',
29
+ 'prompts.extractContent.description': '提取网站内容',
30
+ 'prompts.extractContent.result': '提取内容结果',
31
+ 'prompts.extractContent.message': '请提取以下网站内容',
32
+ 'prompts.extractContent.selectorInstruction': '使用选择器',
33
+ 'prompts.extractContent.dataTypeInstruction': '数据类型',
34
+ 'prompts.debugFetch.description': '调试获取网站内容',
35
+ 'prompts.debugFetch.result': '调试结果',
36
+ 'prompts.debugFetch.message': '调试获取网站内容',
37
+ 'prompts.debugFetch.errorDetails': '错误详情',
38
+ 'prompts.debugFetch.instruction': '请提供调试建议',
39
+ 'prompts.generic.result': '通用结果',
40
+ 'prompts.generic.message': '提示',
41
+ 'prompts.generic.args': '参数'
42
+ };
43
+ return translations[key] || key;
44
+ }
45
+ }));
46
+ // 模拟logger模块
47
+ jest.mock('../../src/lib/logger.js', () => ({
48
+ log: jest.fn(),
49
+ COMPONENTS: {
50
+ PROMPTS: 'prompts'
51
+ }
52
+ }));
53
+ describe('prompts模块测试', () => {
54
+ let server;
55
+ let listPromptsHandler;
56
+ let getPromptHandler;
57
+ 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
+ })
68
+ };
69
+ // 注册提示处理程序
70
+ registerPrompts(server);
71
+ });
72
+ describe('registerPrompts', () => {
73
+ 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));
77
+ });
78
+ });
79
+ describe('ListPromptsRequestSchema处理程序', () => {
80
+ 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');
91
+ });
92
+ it('应该处理debug参数', async () => {
93
+ const request = { params: { debug: true } };
94
+ await listPromptsHandler(request);
95
+ // 验证日志调用
96
+ expect(require('../../src/lib/logger.js').log).toHaveBeenCalledWith('prompts.list.request', true, { params: { debug: true } }, 'prompts');
97
+ });
98
+ });
99
+ describe('GetPromptRequestSchema处理程序', () => {
100
+ it('应该处理fetch-website提示', async () => {
101
+ const request = {
102
+ params: {
103
+ name: 'mult-fetch-mcp:prompt:fetch-website',
104
+ arguments: {
105
+ url: 'https://example.com',
106
+ format: 'html',
107
+ useBrowser: false
108
+ }
109
+ }
110
+ };
111
+ const response = await getPromptHandler(request);
112
+ expect(response).toHaveProperty('description');
113
+ expect(response).toHaveProperty('messages');
114
+ expect(Array.isArray(response.messages)).toBe(true);
115
+ expect(response.messages.length).toBe(3);
116
+ // 验证消息内容
117
+ const userMessage = response.messages[2];
118
+ expect(userMessage.role).toBe('user');
119
+ expect(userMessage.content.type).toBe('text');
120
+ expect(userMessage.content.text).toContain('https://example.com');
121
+ expect(userMessage.content.text).toContain('html');
122
+ expect(userMessage.content.text).toContain('否');
123
+ });
124
+ it('应该处理extract-content提示', async () => {
125
+ const request = {
126
+ params: {
127
+ name: 'mult-fetch-mcp:prompt:extract-content',
128
+ arguments: {
129
+ url: 'https://example.com',
130
+ selector: 'main',
131
+ dataType: 'text'
132
+ }
133
+ }
134
+ };
135
+ const response = await getPromptHandler(request);
136
+ expect(response).toHaveProperty('description');
137
+ expect(response).toHaveProperty('messages');
138
+ expect(Array.isArray(response.messages)).toBe(true);
139
+ expect(response.messages.length).toBe(1);
140
+ // 验证消息内容
141
+ const userMessage = response.messages[0];
142
+ expect(userMessage.role).toBe('user');
143
+ expect(userMessage.content.type).toBe('text');
144
+ expect(userMessage.content.text).toContain('https://example.com');
145
+ expect(userMessage.content.text).toContain('main');
146
+ expect(userMessage.content.text).toContain('text');
147
+ });
148
+ it('应该处理debug-fetch提示', async () => {
149
+ const request = {
150
+ params: {
151
+ name: 'mult-fetch-mcp:prompt:debug-fetch',
152
+ arguments: {
153
+ url: 'https://example.com',
154
+ error: '404 Not Found'
155
+ }
156
+ }
157
+ };
158
+ const response = await getPromptHandler(request);
159
+ expect(response).toHaveProperty('description');
160
+ expect(response).toHaveProperty('messages');
161
+ expect(Array.isArray(response.messages)).toBe(true);
162
+ expect(response.messages.length).toBe(1);
163
+ // 验证消息内容
164
+ const userMessage = response.messages[0];
165
+ expect(userMessage.role).toBe('user');
166
+ expect(userMessage.content.type).toBe('text');
167
+ expect(userMessage.content.text).toContain('https://example.com');
168
+ expect(userMessage.content.text).toContain('404 Not Found');
169
+ });
170
+ it('应该处理未知提示', async () => {
171
+ const request = {
172
+ params: {
173
+ name: 'unknown-prompt',
174
+ arguments: {}
175
+ }
176
+ };
177
+ await expect(getPromptHandler(request)).rejects.toThrow('提示未找到');
178
+ });
179
+ it('应该检查必需参数', async () => {
180
+ const request = {
181
+ params: {
182
+ name: 'mult-fetch-mcp:prompt:fetch-website',
183
+ arguments: {
184
+ // 缺少必需的url参数
185
+ format: 'html'
186
+ }
187
+ }
188
+ };
189
+ await expect(getPromptHandler(request)).rejects.toThrow('缺少必需参数');
190
+ });
191
+ it('应该处理不同类型的useBrowser参数', async () => {
192
+ // 测试字符串类型的useBrowser
193
+ const request1 = {
194
+ params: {
195
+ name: 'mult-fetch-mcp:prompt:fetch-website',
196
+ arguments: {
197
+ url: 'https://example.com',
198
+ useBrowser: 'true'
199
+ }
200
+ }
201
+ };
202
+ const response1 = await getPromptHandler(request1);
203
+ expect(response1.messages[2].content.text).toContain('是');
204
+ // 测试数字类型的useBrowser
205
+ const request2 = {
206
+ params: {
207
+ name: 'mult-fetch-mcp:prompt:fetch-website',
208
+ arguments: {
209
+ url: 'https://example.com',
210
+ useBrowser: 1
211
+ }
212
+ }
213
+ };
214
+ const response2 = await getPromptHandler(request2);
215
+ expect(response2.messages[2].content.text).toContain('是');
216
+ // 测试零值
217
+ const request3 = {
218
+ params: {
219
+ name: 'mult-fetch-mcp:prompt:fetch-website',
220
+ arguments: {
221
+ url: 'https://example.com',
222
+ useBrowser: 0
223
+ }
224
+ }
225
+ };
226
+ const response3 = await getPromptHandler(request3);
227
+ expect(response3.messages[2].content.text).toContain('否');
228
+ });
229
+ it('应该使用默认格式', async () => {
230
+ const request = {
231
+ params: {
232
+ name: 'mult-fetch-mcp:prompt:fetch-website',
233
+ arguments: {
234
+ url: 'https://example.com'
235
+ // 未指定format,应使用默认值html
236
+ }
237
+ }
238
+ };
239
+ const response = await getPromptHandler(request);
240
+ expect(response.messages[2].content.text).toContain('html');
241
+ });
242
+ });
243
+ });