@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.
- package/README.md +8 -8
- package/README.zh.md +16 -8
- package/dist/i18n-test-report.json +8 -0
- package/dist/i18n-unused-keys-report.json +8 -0
- package/dist/src/client.js +70 -37
- package/dist/src/index.js +0 -0
- package/dist/src/lib/BrowserFetcher.js +12 -12
- package/dist/src/lib/NodeFetcher.js +22 -22
- package/dist/src/lib/fetch.js +40 -0
- package/dist/src/lib/fetchers/FetcherFactory.js +43 -0
- package/dist/src/lib/fetchers/browser/BrowserFetcher.js +381 -0
- package/dist/src/lib/fetchers/browser/BrowserInstance.js +180 -0
- package/dist/src/lib/fetchers/browser/PageOperations.js +168 -0
- package/dist/src/lib/fetchers/common/types.js +41 -0
- package/dist/src/lib/fetchers/common/utils.js +141 -0
- package/dist/src/lib/fetchers/index.js +62 -0
- package/dist/src/lib/fetchers/node/HttpClient.js +239 -0
- package/dist/src/lib/fetchers/node/NodeFetcher.js +180 -0
- package/dist/src/lib/i18n/index.js +105 -17
- package/dist/src/lib/i18n/keys/base.js +41 -0
- package/dist/src/lib/i18n/keys/browser.js +91 -0
- package/dist/src/lib/i18n/keys/client.js +68 -0
- package/dist/src/lib/i18n/keys/errors.js +49 -0
- package/dist/src/lib/i18n/keys/factory.js +46 -0
- package/dist/src/lib/i18n/keys/fetcher.js +87 -0
- package/dist/src/lib/i18n/keys/index.js +16 -0
- package/dist/src/lib/i18n/keys/node.js +69 -0
- package/dist/src/lib/i18n/keys/prompts.js +79 -0
- package/dist/src/lib/i18n/keys/resources.js +53 -0
- package/dist/src/lib/i18n/keys/server.js +67 -0
- package/dist/src/lib/i18n/keys/tools.js +39 -0
- package/dist/src/lib/i18n/keys.js +488 -119
- package/dist/src/lib/i18n/locales/en/browser.js +67 -57
- package/dist/src/lib/i18n/locales/en/client.js +57 -33
- package/dist/src/lib/i18n/locales/en/errors.js +36 -12
- package/dist/src/lib/i18n/locales/en/factory.js +38 -0
- package/dist/src/lib/i18n/locales/en/fetcher.js +8 -2
- package/dist/src/lib/i18n/locales/en/index.js +10 -8
- package/dist/src/lib/i18n/locales/en/node.js +57 -35
- package/dist/src/lib/i18n/locales/en/prompts.js +42 -65
- package/dist/src/lib/i18n/locales/en/resources.js +52 -55
- package/dist/src/lib/i18n/locales/en/server.js +49 -24
- package/dist/src/lib/i18n/locales/en/tools.js +27 -19
- package/dist/src/lib/i18n/locales/zh/browser.js +71 -62
- package/dist/src/lib/i18n/locales/zh/client.js +57 -32
- package/dist/src/lib/i18n/locales/zh/errors.js +36 -12
- package/dist/src/lib/i18n/locales/zh/factory.js +38 -0
- package/dist/src/lib/i18n/locales/zh/fetcher.js +36 -30
- package/dist/src/lib/i18n/locales/zh/index.js +10 -8
- package/dist/src/lib/i18n/locales/zh/node.js +61 -36
- package/dist/src/lib/i18n/locales/zh/prompts.js +35 -57
- package/dist/src/lib/i18n/locales/zh/resources.js +52 -55
- package/dist/src/lib/i18n/locales/zh/server.js +51 -25
- package/dist/src/lib/i18n/locales/zh/tools.js +27 -19
- package/dist/src/lib/i18n/logger.js +90 -19
- package/dist/src/lib/logger.js +135 -14
- package/dist/src/lib/server/browser.js +42 -10
- package/dist/src/lib/server/fetcher.js +87 -37
- package/dist/src/lib/server/index.js +2 -2
- package/dist/src/lib/server/prompts.js +1 -1
- package/dist/src/lib/server/resources.js +1 -1
- package/dist/src/lib/server/tools.js +46 -10
- package/dist/src/lib/utils/ErrorHandler.js +125 -0
- package/dist/src/mcp-server.js +0 -0
- package/dist/src/test-i18n.js +139 -0
- package/dist/tests/BrowserFetcher.test.js +0 -0
- package/dist/tests/NodeFetcher.test.js +0 -0
- package/dist/tests/client.test.js +42 -81
- package/dist/tests/fetch.test.js +70 -0
- package/dist/tests/fetchers/Fetcher.test.js +108 -0
- package/dist/tests/fetchers/browser/BrowserFetcher.test.js +195 -0
- package/dist/tests/fetchers/browser/BrowserInstance.test.js +163 -0
- package/dist/tests/fetchers/node/NodeFetcher.test.js +211 -0
- package/dist/tests/i18n-missing-keys.js +364 -0
- package/dist/tests/i18n-remove-unused-keys.js +236 -0
- package/dist/tests/i18n-test-report.json +2004 -0
- package/dist/tests/i18n-unused-keys.js +425 -0
- package/dist/tests/i18n.test.js +6 -7
- package/dist/tests/logger.test.js +14 -13
- package/dist/tests/mcp-server.test.js +7 -7
- package/dist/tests/server/browser.test.js +25 -21
- package/dist/tests/server/fetcher.test.js +58 -101
- package/dist/tests/server/index.test.js +30 -30
- package/dist/tests/server/prompts.test.js +36 -33
- package/dist/tests/server/resources.test.js +120 -84
- package/dist/tests/server/tools.test.js +7 -6
- package/dist/tests/setup.js +36 -0
- package/dist/tests/src/lib/i18n/index.js +108 -0
- package/dist/tests/src/lib/i18n/keys/base.js +47 -0
- package/dist/tests/src/lib/i18n/keys/browser.js +93 -0
- package/dist/tests/src/lib/i18n/keys/client.js +70 -0
- package/dist/tests/src/lib/i18n/keys/errors.js +34 -0
- package/dist/tests/src/lib/i18n/keys/fetcher.js +84 -0
- package/dist/tests/src/lib/i18n/keys/index.js +31 -0
- package/dist/tests/src/lib/i18n/keys/node.js +56 -0
- package/dist/tests/src/lib/i18n/keys/prompts.js +82 -0
- package/dist/tests/src/lib/i18n/keys/resources.js +50 -0
- package/dist/tests/src/lib/i18n/keys/server.js +64 -0
- package/dist/tests/src/lib/i18n/keys/tools.js +34 -0
- package/dist/tests/src/lib/i18n/locales/en/browser.js +88 -0
- package/dist/tests/src/lib/i18n/locales/en/client.js +66 -0
- package/dist/tests/src/lib/i18n/locales/en/errors.js +28 -0
- package/dist/tests/src/lib/i18n/locales/en/fetcher.js +71 -0
- package/dist/tests/src/lib/i18n/locales/en/index.js +29 -0
- package/dist/tests/src/lib/i18n/locales/en/node.js +51 -0
- package/dist/tests/src/lib/i18n/locales/en/prompts.js +52 -0
- package/dist/tests/src/lib/i18n/locales/en/resources.js +50 -0
- package/dist/tests/src/lib/i18n/locales/en/server.js +56 -0
- package/dist/tests/src/lib/i18n/locales/en/tools.js +28 -0
- package/dist/tests/src/lib/i18n/locales/zh/browser.js +87 -0
- package/dist/tests/src/lib/i18n/locales/zh/client.js +66 -0
- package/dist/tests/src/lib/i18n/locales/zh/errors.js +28 -0
- package/dist/tests/src/lib/i18n/locales/zh/fetcher.js +71 -0
- package/dist/tests/src/lib/i18n/locales/zh/index.js +29 -0
- package/dist/tests/src/lib/i18n/locales/zh/node.js +51 -0
- package/dist/tests/src/lib/i18n/locales/zh/prompts.js +53 -0
- package/dist/tests/src/lib/i18n/locales/zh/resources.js +50 -0
- package/dist/tests/src/lib/i18n/locales/zh/server.js +57 -0
- package/dist/tests/src/lib/i18n/locales/zh/tools.js +28 -0
- package/dist/tests/src/lib/i18n/logger.js +114 -0
- package/dist/tests/src/lib/logger.js +181 -0
- package/dist/tests/test-direct-client.js +0 -0
- package/dist/tests/test-i18n.js +530 -0
- package/dist/tests/test-mcp-methods.js +1 -1
- package/dist/tests/test-mcp.js +0 -0
- package/dist/tests/test-mini4k.js +0 -0
- package/dist/tests/tests/test-i18n.js +588 -0
- package/dist/tests/types.test.js +1 -1
- package/dist/tests/utils/ErrorHandler.test.js +144 -0
- package/dist/vitest.config.js +29 -0
- package/package.json +31 -27
- package/dist/src/cli-language.js +0 -78
- package/dist/src/client.d.ts +0 -6
- package/dist/src/client.js.map +0 -1
- package/dist/src/index.d.ts +0 -15
- package/dist/src/index.js.map +0 -1
- package/dist/src/lib/BrowserFetcher.d.ts +0 -175
- package/dist/src/lib/BrowserFetcher.js.map +0 -1
- package/dist/src/lib/NodeFetcher.d.ts +0 -92
- package/dist/src/lib/NodeFetcher.js.map +0 -1
- package/dist/src/lib/example.js +0 -93
- package/dist/src/lib/i18n/index.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/browser.d.ts +0 -61
- package/dist/src/lib/i18n/locales/en/browser.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/client.d.ts +0 -39
- package/dist/src/lib/i18n/locales/en/client.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/errors.d.ts +0 -17
- package/dist/src/lib/i18n/locales/en/errors.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/fetcher.d.ts +0 -66
- package/dist/src/lib/i18n/locales/en/fetcher.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/index.d.ts +0 -344
- package/dist/src/lib/i18n/locales/en/index.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/node.d.ts +0 -41
- package/dist/src/lib/i18n/locales/en/node.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/prompts.d.ts +0 -65
- package/dist/src/lib/i18n/locales/en/prompts.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/resources.d.ts +0 -41
- package/dist/src/lib/i18n/locales/en/resources.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/server.d.ts +0 -30
- package/dist/src/lib/i18n/locales/en/server.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/tools.d.ts +0 -24
- package/dist/src/lib/i18n/locales/en/tools.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/browser.d.ts +0 -61
- package/dist/src/lib/i18n/locales/zh/browser.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/client.d.ts +0 -38
- package/dist/src/lib/i18n/locales/zh/client.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/errors.d.ts +0 -17
- package/dist/src/lib/i18n/locales/zh/errors.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/fetcher.d.ts +0 -66
- package/dist/src/lib/i18n/locales/zh/fetcher.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/index.d.ts +0 -343
- package/dist/src/lib/i18n/locales/zh/index.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/node.d.ts +0 -41
- package/dist/src/lib/i18n/locales/zh/node.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/prompts.d.ts +0 -65
- package/dist/src/lib/i18n/locales/zh/prompts.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/resources.d.ts +0 -41
- package/dist/src/lib/i18n/locales/zh/resources.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/server.d.ts +0 -30
- package/dist/src/lib/i18n/locales/zh/server.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/tools.d.ts +0 -24
- package/dist/src/lib/i18n/locales/zh/tools.js.map +0 -1
- package/dist/src/lib/i18n/logger.d.ts +0 -38
- package/dist/src/lib/i18n/logger.js.map +0 -1
- package/dist/src/lib/logger.d.ts +0 -32
- package/dist/src/lib/logger.js.map +0 -1
- package/dist/src/lib/server/browser.d.ts +0 -34
- package/dist/src/lib/server/browser.js.map +0 -1
- package/dist/src/lib/server/fetcher.d.ts +0 -20
- package/dist/src/lib/server/fetcher.js.map +0 -1
- package/dist/src/lib/server/index.d.ts +0 -10
- package/dist/src/lib/server/index.js.map +0 -1
- package/dist/src/lib/server/logger.js +0 -23
- package/dist/src/lib/server/prompts.d.ts +0 -11
- package/dist/src/lib/server/prompts.js.map +0 -1
- package/dist/src/lib/server/resources.d.ts +0 -11
- package/dist/src/lib/server/resources.js.map +0 -1
- package/dist/src/lib/server/tools.d.ts +0 -12
- package/dist/src/lib/server/tools.js.map +0 -1
- package/dist/src/lib/server/types.d.ts +0 -71
- package/dist/src/lib/server/types.js.map +0 -1
- package/dist/src/lib/types.d.ts +0 -119
- package/dist/src/lib/types.js.map +0 -1
- package/dist/src/mcp-server.d.ts +0 -6
- package/dist/src/mcp-server.js.map +0 -1
- package/dist/tests/jest.setup.d.ts +0 -6
- package/dist/tests/jest.setup.js +0 -30
- package/dist/tests/jest.setup.js.map +0 -1
- package/dist/tests/test-direct-client.d.ts +0 -6
- package/dist/tests/test-direct-client.js.map +0 -1
- package/dist/tests/test-mcp-methods.d.ts +0 -6
- package/dist/tests/test-mcp-methods.js.map +0 -1
- package/dist/tests/test-mcp.d.ts +0 -6
- package/dist/tests/test-mcp.js.map +0 -1
- package/dist/tests/test-mini4k.d.ts +0 -6
- package/dist/tests/test-mini4k.js.map +0 -1
|
@@ -3,33 +3,59 @@
|
|
|
3
3
|
* Co-Author: AI Assistant (Claude)
|
|
4
4
|
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
5
5
|
*/
|
|
6
|
-
import { ListResourcesRequestSchema, ReadResourceRequestSchema } from
|
|
6
|
+
import { ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import path from 'path';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
10
|
+
// 创建模拟logger模块
|
|
11
|
+
const mockLogger = {
|
|
12
|
+
log: vi.fn(),
|
|
13
|
+
getLogFilePath: vi.fn().mockReturnValue('/mock/path/to/debug.log'),
|
|
14
|
+
clearLogFile: vi.fn(),
|
|
14
15
|
COMPONENTS: {
|
|
15
16
|
RESOURCES: 'resources'
|
|
16
17
|
}
|
|
17
|
-
}
|
|
18
|
+
};
|
|
19
|
+
// 模拟logger模块
|
|
20
|
+
vi.mock('../../src/lib/logger.js', () => {
|
|
21
|
+
return {
|
|
22
|
+
default: mockLogger,
|
|
23
|
+
...mockLogger
|
|
24
|
+
};
|
|
25
|
+
});
|
|
18
26
|
// 模拟fs模块
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
vi.mock('fs', () => {
|
|
28
|
+
return {
|
|
29
|
+
default: {
|
|
30
|
+
readFileSync: vi.fn()
|
|
31
|
+
},
|
|
32
|
+
readFileSync: vi.fn()
|
|
33
|
+
};
|
|
34
|
+
});
|
|
22
35
|
// 模拟path模块
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
vi.mock('path', () => {
|
|
37
|
+
return {
|
|
38
|
+
default: {
|
|
39
|
+
dirname: vi.fn(),
|
|
40
|
+
resolve: vi.fn(),
|
|
41
|
+
join: vi.fn(),
|
|
42
|
+
extname: vi.fn()
|
|
43
|
+
},
|
|
44
|
+
dirname: vi.fn(),
|
|
45
|
+
resolve: vi.fn(),
|
|
46
|
+
join: vi.fn(),
|
|
47
|
+
extname: vi.fn()
|
|
48
|
+
};
|
|
49
|
+
});
|
|
29
50
|
// 模拟url模块
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
51
|
+
vi.mock('url', () => {
|
|
52
|
+
return {
|
|
53
|
+
default: {
|
|
54
|
+
fileURLToPath: vi.fn()
|
|
55
|
+
},
|
|
56
|
+
fileURLToPath: vi.fn()
|
|
57
|
+
};
|
|
58
|
+
});
|
|
33
59
|
// 获取MIME类型函数
|
|
34
60
|
function getMimeType(filePath) {
|
|
35
61
|
const ext = path.extname(filePath).toLowerCase();
|
|
@@ -46,6 +72,15 @@ function getMimeType(filePath) {
|
|
|
46
72
|
return 'text/html';
|
|
47
73
|
case '.css':
|
|
48
74
|
return 'text/css';
|
|
75
|
+
case '.png':
|
|
76
|
+
return 'image/png';
|
|
77
|
+
case '.jpg':
|
|
78
|
+
case '.jpeg':
|
|
79
|
+
return 'image/jpeg';
|
|
80
|
+
case '.gif':
|
|
81
|
+
return 'image/gif';
|
|
82
|
+
case '.svg':
|
|
83
|
+
return 'image/svg+xml';
|
|
49
84
|
case '.txt':
|
|
50
85
|
case '.log':
|
|
51
86
|
return 'text/plain';
|
|
@@ -58,7 +93,7 @@ const registerResources = (server) => {
|
|
|
58
93
|
// 注册资源列表处理程序
|
|
59
94
|
server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
|
|
60
95
|
const debug = request.params?.debug === true;
|
|
61
|
-
|
|
96
|
+
mockLogger.log('resources.list.request', debug, { params: request.params }, 'resources');
|
|
62
97
|
// 返回实际可用的资源列表
|
|
63
98
|
return {
|
|
64
99
|
resources: [
|
|
@@ -91,60 +126,60 @@ const registerResources = (server) => {
|
|
|
91
126
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
92
127
|
const uri = request.params.uri;
|
|
93
128
|
const debug = request.params?.debug === true;
|
|
94
|
-
|
|
129
|
+
mockLogger.log('resources.read.request', debug, { uri }, 'resources');
|
|
95
130
|
try {
|
|
96
131
|
// 解析URI并读取相应文件
|
|
97
132
|
let filePath = null;
|
|
98
133
|
let content = '';
|
|
99
134
|
// 处理日志文件URI
|
|
100
135
|
if (uri === 'file:///logs/debug') {
|
|
101
|
-
filePath =
|
|
136
|
+
filePath = mockLogger.getLogFilePath();
|
|
102
137
|
content = fs.readFileSync(filePath, 'utf-8');
|
|
103
138
|
}
|
|
104
|
-
// 处理清理日志文件URI
|
|
105
139
|
else if (uri === 'file:///logs/clear') {
|
|
106
|
-
|
|
107
|
-
content = '
|
|
140
|
+
mockLogger.clearLogFile();
|
|
141
|
+
content = 'Log file cleared';
|
|
142
|
+
filePath = '/mock/path/to/clear.log';
|
|
108
143
|
}
|
|
109
|
-
// 处理文档资源
|
|
110
144
|
else if (uri.startsWith('file:///docs/')) {
|
|
111
|
-
|
|
112
|
-
|
|
145
|
+
// 处理文档文件URI
|
|
146
|
+
const docPath = uri.replace('file:///docs/', '');
|
|
147
|
+
filePath = path.join('/mock/path/to', `${docPath}.md`);
|
|
113
148
|
content = fs.readFileSync(filePath, 'utf-8');
|
|
114
149
|
}
|
|
115
|
-
// 处理源代码资源
|
|
116
150
|
else if (uri.startsWith('file:///src/')) {
|
|
117
|
-
|
|
118
|
-
|
|
151
|
+
// 处理源代码文件URI
|
|
152
|
+
const srcPath = uri.replace('file:///src/', '');
|
|
153
|
+
filePath = path.join('/mock/path/to', `${srcPath}.ts`);
|
|
119
154
|
content = fs.readFileSync(filePath, 'utf-8');
|
|
120
155
|
}
|
|
121
|
-
// 处理无效URI
|
|
122
156
|
else {
|
|
123
|
-
throw new Error(
|
|
157
|
+
throw new Error(`resources.notFound: ${uri}`);
|
|
124
158
|
}
|
|
125
159
|
// 获取MIME类型
|
|
126
|
-
const mimeType = getMimeType(filePath
|
|
160
|
+
const mimeType = getMimeType(filePath);
|
|
161
|
+
// 返回资源内容
|
|
127
162
|
return {
|
|
128
163
|
contents: [
|
|
129
164
|
{
|
|
130
165
|
uri,
|
|
131
|
-
mimeType,
|
|
166
|
+
type: mimeType,
|
|
132
167
|
text: content
|
|
133
168
|
}
|
|
134
169
|
]
|
|
135
170
|
};
|
|
136
171
|
}
|
|
137
172
|
catch (error) {
|
|
138
|
-
|
|
173
|
+
mockLogger.log('resources.fileReadError', true, {
|
|
139
174
|
filePath: 'unknown',
|
|
140
175
|
error: error instanceof Error ? error.message : String(error)
|
|
141
176
|
}, 'resources');
|
|
142
|
-
throw
|
|
177
|
+
throw error;
|
|
143
178
|
}
|
|
144
179
|
});
|
|
145
180
|
};
|
|
146
181
|
// 模拟resources模块
|
|
147
|
-
|
|
182
|
+
vi.mock('../../src/lib/server/resources.js', () => ({
|
|
148
183
|
registerResources
|
|
149
184
|
}));
|
|
150
185
|
describe('resources模块测试', () => {
|
|
@@ -153,10 +188,10 @@ describe('resources模块测试', () => {
|
|
|
153
188
|
let readResourceHandler;
|
|
154
189
|
beforeEach(() => {
|
|
155
190
|
// 重置所有模拟
|
|
156
|
-
|
|
191
|
+
vi.clearAllMocks();
|
|
157
192
|
// 创建模拟Server实例
|
|
158
193
|
server = {
|
|
159
|
-
setRequestHandler:
|
|
194
|
+
setRequestHandler: vi.fn((schema, handler) => {
|
|
160
195
|
if (schema === ListResourcesRequestSchema) {
|
|
161
196
|
listResourcesHandler = handler;
|
|
162
197
|
}
|
|
@@ -182,23 +217,27 @@ describe('resources模块测试', () => {
|
|
|
182
217
|
expect(response).toHaveProperty('resources');
|
|
183
218
|
expect(Array.isArray(response.resources)).toBe(true);
|
|
184
219
|
expect(response.resources.length).toBe(2);
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
expect(
|
|
188
|
-
expect(
|
|
220
|
+
// 验证资源列表内容
|
|
221
|
+
expect(response.resources[0]).toHaveProperty('name', 'mult-fetch-mcp:log:debug-log');
|
|
222
|
+
expect(response.resources[0]).toHaveProperty('uri', 'file:///logs/debug');
|
|
223
|
+
expect(response.resources[1]).toHaveProperty('name', 'mult-fetch-mcp:log:clear-debug-log');
|
|
224
|
+
expect(response.resources[1]).toHaveProperty('uri', 'file:///logs/clear');
|
|
189
225
|
// 验证资源模板
|
|
190
226
|
expect(response).toHaveProperty('resourceTemplates');
|
|
191
227
|
expect(Array.isArray(response.resourceTemplates)).toBe(true);
|
|
192
228
|
expect(response.resourceTemplates.length).toBe(2);
|
|
193
|
-
|
|
194
|
-
expect(
|
|
195
|
-
expect(templateNames).toContain('mult-fetch-mcp:docs:file');
|
|
229
|
+
expect(response.resourceTemplates[0]).toHaveProperty('uriTemplate', 'file:///src/{path}');
|
|
230
|
+
expect(response.resourceTemplates[1]).toHaveProperty('uriTemplate', 'file:///docs/{path}');
|
|
196
231
|
});
|
|
197
232
|
it('应该处理debug参数', async () => {
|
|
198
|
-
const request = {
|
|
233
|
+
const request = {
|
|
234
|
+
params: {
|
|
235
|
+
debug: true
|
|
236
|
+
}
|
|
237
|
+
};
|
|
199
238
|
await listResourcesHandler(request);
|
|
200
239
|
// 验证日志调用
|
|
201
|
-
expect(
|
|
240
|
+
expect(mockLogger.log).toHaveBeenCalledWith('resources.list.request', true, { params: { debug: true } }, 'resources');
|
|
202
241
|
});
|
|
203
242
|
});
|
|
204
243
|
describe('ReadResourceRequestSchema处理程序', () => {
|
|
@@ -217,11 +256,11 @@ describe('resources模块测试', () => {
|
|
|
217
256
|
expect(response.contents.length).toBe(1);
|
|
218
257
|
// 验证内容
|
|
219
258
|
const content = response.contents[0];
|
|
220
|
-
expect(content
|
|
221
|
-
expect(content
|
|
222
|
-
expect(content
|
|
259
|
+
expect(content).toHaveProperty('uri', 'file:///logs/debug');
|
|
260
|
+
expect(content).toHaveProperty('type', 'text/plain');
|
|
261
|
+
expect(content).toHaveProperty('text', 'Debug log content');
|
|
223
262
|
// 验证getLogFilePath被调用
|
|
224
|
-
expect(
|
|
263
|
+
expect(mockLogger.getLogFilePath).toHaveBeenCalled();
|
|
225
264
|
expect(fs.readFileSync).toHaveBeenCalledWith('/mock/path/to/debug.log', 'utf-8');
|
|
226
265
|
});
|
|
227
266
|
it('应该处理清理日志资源', async () => {
|
|
@@ -236,10 +275,10 @@ describe('resources模块测试', () => {
|
|
|
236
275
|
expect(response.contents.length).toBe(1);
|
|
237
276
|
// 验证内容
|
|
238
277
|
const content = response.contents[0];
|
|
239
|
-
expect(content
|
|
240
|
-
expect(content
|
|
278
|
+
expect(content).toHaveProperty('uri', 'file:///logs/clear');
|
|
279
|
+
expect(content).toHaveProperty('text', 'Log file cleared');
|
|
241
280
|
// 验证clearLogFile被调用
|
|
242
|
-
expect(
|
|
281
|
+
expect(mockLogger.clearLogFile).toHaveBeenCalled();
|
|
243
282
|
});
|
|
244
283
|
it('应该处理文档资源', async () => {
|
|
245
284
|
// 模拟路径和文件内容
|
|
@@ -248,7 +287,7 @@ describe('resources模块测试', () => {
|
|
|
248
287
|
path.extname.mockReturnValue('.md');
|
|
249
288
|
const request = {
|
|
250
289
|
params: {
|
|
251
|
-
uri: 'file:///docs/
|
|
290
|
+
uri: 'file:///docs/README'
|
|
252
291
|
}
|
|
253
292
|
};
|
|
254
293
|
const response = await readResourceHandler(request);
|
|
@@ -257,9 +296,9 @@ describe('resources模块测试', () => {
|
|
|
257
296
|
expect(response.contents.length).toBe(1);
|
|
258
297
|
// 验证内容
|
|
259
298
|
const content = response.contents[0];
|
|
260
|
-
expect(content
|
|
261
|
-
expect(content
|
|
262
|
-
expect(content
|
|
299
|
+
expect(content).toHaveProperty('uri', 'file:///docs/README');
|
|
300
|
+
expect(content).toHaveProperty('type', 'text/markdown');
|
|
301
|
+
expect(content).toHaveProperty('text', '# README content');
|
|
263
302
|
// 验证path.join被调用
|
|
264
303
|
expect(path.join).toHaveBeenCalled();
|
|
265
304
|
expect(fs.readFileSync).toHaveBeenCalledWith('/mock/path/to/README.md', 'utf-8');
|
|
@@ -280,9 +319,9 @@ describe('resources模块测试', () => {
|
|
|
280
319
|
expect(response.contents.length).toBe(1);
|
|
281
320
|
// 验证内容
|
|
282
321
|
const content = response.contents[0];
|
|
283
|
-
expect(content
|
|
284
|
-
expect(content
|
|
285
|
-
expect(content
|
|
322
|
+
expect(content).toHaveProperty('uri', 'file:///src/index');
|
|
323
|
+
expect(content).toHaveProperty('type', 'text/typescript');
|
|
324
|
+
expect(content).toHaveProperty('text', '// Source code');
|
|
286
325
|
// 验证path.join被调用
|
|
287
326
|
expect(path.join).toHaveBeenCalled();
|
|
288
327
|
expect(fs.readFileSync).toHaveBeenCalledWith('/mock/path/to/index.ts', 'utf-8');
|
|
@@ -290,7 +329,7 @@ describe('resources模块测试', () => {
|
|
|
290
329
|
it('应该处理无效的URI', async () => {
|
|
291
330
|
const request = {
|
|
292
331
|
params: {
|
|
293
|
-
uri: 'invalid
|
|
332
|
+
uri: 'invalid://uri'
|
|
294
333
|
}
|
|
295
334
|
};
|
|
296
335
|
await expect(readResourceHandler(request)).rejects.toThrow('resources.notFound');
|
|
@@ -300,17 +339,17 @@ describe('resources模块测试', () => {
|
|
|
300
339
|
path.join.mockReturnValue('/mock/path/to/file.txt');
|
|
301
340
|
// 模拟文件读取错误
|
|
302
341
|
fs.readFileSync.mockImplementation(() => {
|
|
303
|
-
throw new Error('File
|
|
342
|
+
throw new Error('File read error');
|
|
304
343
|
});
|
|
305
344
|
const request = {
|
|
306
345
|
params: {
|
|
307
|
-
uri: 'file:///
|
|
346
|
+
uri: 'file:///src/error/file'
|
|
308
347
|
}
|
|
309
348
|
};
|
|
310
|
-
await expect(readResourceHandler(request)).rejects.toThrow('
|
|
349
|
+
await expect(readResourceHandler(request)).rejects.toThrow('File read error');
|
|
311
350
|
// 验证错误日志
|
|
312
|
-
expect(
|
|
313
|
-
error:
|
|
351
|
+
expect(mockLogger.log).toHaveBeenCalledWith('resources.fileReadError', true, expect.objectContaining({
|
|
352
|
+
error: 'File read error'
|
|
314
353
|
}), 'resources');
|
|
315
354
|
});
|
|
316
355
|
it('应该处理debug参数', async () => {
|
|
@@ -325,34 +364,31 @@ describe('resources模块测试', () => {
|
|
|
325
364
|
};
|
|
326
365
|
await readResourceHandler(request);
|
|
327
366
|
// 验证日志调用
|
|
328
|
-
expect(
|
|
367
|
+
expect(mockLogger.log).toHaveBeenCalledWith('resources.read.request', true, { uri: 'file:///logs/debug' }, 'resources');
|
|
329
368
|
});
|
|
330
369
|
});
|
|
331
370
|
describe('getMimeType函数', () => {
|
|
332
|
-
it('应该返回正确的MIME类型',
|
|
333
|
-
// 测试不同的文件扩展名
|
|
371
|
+
it('应该返回正确的MIME类型', () => {
|
|
334
372
|
const testExtensions = [
|
|
335
|
-
{ ext: '.md', mime: 'text/markdown' },
|
|
336
|
-
{ ext: '.json', mime: 'application/json' },
|
|
337
|
-
{ ext: '.ts', mime: 'text/typescript' },
|
|
338
|
-
{ ext: '.js', mime: 'text/javascript' },
|
|
339
373
|
{ ext: '.html', mime: 'text/html' },
|
|
374
|
+
{ ext: '.js', mime: 'text/javascript' },
|
|
340
375
|
{ ext: '.css', mime: 'text/css' },
|
|
376
|
+
{ ext: '.json', mime: 'application/json' },
|
|
377
|
+
{ ext: '.png', mime: 'image/png' },
|
|
378
|
+
{ ext: '.jpg', mime: 'image/jpeg' },
|
|
379
|
+
{ ext: '.gif', mime: 'image/gif' },
|
|
380
|
+
{ ext: '.svg', mime: 'image/svg+xml' },
|
|
381
|
+
{ ext: '.md', mime: 'text/markdown' },
|
|
341
382
|
{ ext: '.txt', mime: 'text/plain' },
|
|
342
383
|
{ ext: '.log', mime: 'text/plain' },
|
|
384
|
+
{ ext: '.ts', mime: 'text/typescript' },
|
|
343
385
|
{ ext: '.unknown', mime: 'application/octet-stream' }
|
|
344
386
|
];
|
|
345
387
|
for (const test of testExtensions) {
|
|
346
388
|
// 模拟文件路径和内容
|
|
347
389
|
path.extname.mockReturnValue(test.ext);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
params: {
|
|
351
|
-
uri: `file:///docs/file${test.ext}`
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
const response = await readResourceHandler(request);
|
|
355
|
-
expect(response.contents[0].mimeType).toBe(test.mime);
|
|
390
|
+
const mime = getMimeType(`/path/to/file${test.ext}`);
|
|
391
|
+
expect(mime).toBe(test.mime);
|
|
356
392
|
}
|
|
357
393
|
});
|
|
358
394
|
});
|
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
7
7
|
import { registerTools } from '../../src/lib/server/tools.js';
|
|
8
8
|
import { fetchWithAutoDetect } from '../../src/lib/server/fetcher.js';
|
|
9
|
+
import { vi, describe, test, expect, beforeEach } from 'vitest';
|
|
9
10
|
// 模拟 fetchWithAutoDetect 函数
|
|
10
|
-
|
|
11
|
-
fetchWithAutoDetect:
|
|
11
|
+
vi.mock('../../src/lib/server/fetcher.js', () => ({
|
|
12
|
+
fetchWithAutoDetect: vi.fn()
|
|
12
13
|
}));
|
|
13
14
|
// 模拟 logger
|
|
14
|
-
|
|
15
|
-
log:
|
|
15
|
+
vi.mock('../../src/lib/logger.js', () => ({
|
|
16
|
+
log: vi.fn(),
|
|
16
17
|
COMPONENTS: {
|
|
17
18
|
TOOLS: 'tools'
|
|
18
19
|
}
|
|
@@ -22,9 +23,9 @@ describe('工具注册和调用测试 (Tools Registration and Calling Tests)', (
|
|
|
22
23
|
let mockSetRequestHandler;
|
|
23
24
|
beforeEach(() => {
|
|
24
25
|
// 重置所有模拟
|
|
25
|
-
|
|
26
|
+
vi.clearAllMocks();
|
|
26
27
|
// 创建模拟的 Server 实例
|
|
27
|
-
mockSetRequestHandler =
|
|
28
|
+
mockSetRequestHandler = vi.fn();
|
|
28
29
|
mockServer = {
|
|
29
30
|
setRequestHandler: mockSetRequestHandler
|
|
30
31
|
};
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
// 导入Vitest的全局函数 (Import Vitest global functions)
|
|
7
|
+
import { beforeAll, afterAll, vi } from 'vitest';
|
|
8
|
+
// 全局模拟设置
|
|
9
|
+
// 将 vi 添加到全局,以便在测试中使用
|
|
10
|
+
global.vi = vi;
|
|
11
|
+
// 为了兼容 Jest 的 jest.fn(), jest.mock() 等
|
|
12
|
+
global.jest = {
|
|
13
|
+
fn: vi.fn,
|
|
14
|
+
mock: vi.mock,
|
|
15
|
+
spyOn: vi.spyOn,
|
|
16
|
+
clearAllMocks: vi.clearAllMocks,
|
|
17
|
+
resetAllMocks: vi.resetAllMocks,
|
|
18
|
+
restoreAllMocks: vi.restoreAllMocks
|
|
19
|
+
};
|
|
20
|
+
// 设置测试环境 (Setup test environment)
|
|
21
|
+
process.env.NODE_ENV = 'test';
|
|
22
|
+
// 模拟控制台输出,避免测试时输出过多日志 (Mock console output to avoid too many logs during testing)
|
|
23
|
+
console.log = vi.fn();
|
|
24
|
+
console.info = vi.fn();
|
|
25
|
+
console.warn = vi.fn();
|
|
26
|
+
console.error = vi.fn();
|
|
27
|
+
// 在所有测试开始前的操作 (Operations before all tests)
|
|
28
|
+
beforeAll(() => {
|
|
29
|
+
// 全局设置,在所有测试开始前运行
|
|
30
|
+
// 可以在这里添加全局设置 (Add global setup here)
|
|
31
|
+
});
|
|
32
|
+
// 在所有测试结束后的操作 (Operations after all tests)
|
|
33
|
+
afterAll(() => {
|
|
34
|
+
// 全局清理,在所有测试结束后运行
|
|
35
|
+
// 可以在这里添加全局清理 (Add global cleanup here)
|
|
36
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Author: Martin <lmccc.dev@gmail.com>
|
|
4
|
+
* Co-Author: AI Assistant (Claude)
|
|
5
|
+
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.getSupportedLanguages = exports.hasTranslation = exports.getCurrentLanguage = exports.changeLanguage = exports.t = void 0;
|
|
9
|
+
var i18next_1 = require("i18next");
|
|
10
|
+
var index_js_1 = require("./locales/en/index.js");
|
|
11
|
+
var index_js_2 = require("./locales/zh/index.js");
|
|
12
|
+
// 定义语言资源 (Define language resources)
|
|
13
|
+
var resources = {
|
|
14
|
+
en: {
|
|
15
|
+
translation: index_js_1.enTranslation
|
|
16
|
+
},
|
|
17
|
+
zh: {
|
|
18
|
+
translation: index_js_2.zhTranslation
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* 初始化 i18next 配置 (Initialize i18next configuration)
|
|
23
|
+
*/
|
|
24
|
+
var initializeI18n = function () {
|
|
25
|
+
// 获取当前语言环境 (Get current language environment)
|
|
26
|
+
var currentLanguage = process.env.MCP_LANG || 'en';
|
|
27
|
+
// 初始化 i18next (Initialize i18next)
|
|
28
|
+
i18next_1.default.init({
|
|
29
|
+
resources: resources,
|
|
30
|
+
// 优先使用专门的环境变量,其次默认使用英语
|
|
31
|
+
// (Priority: dedicated environment variable, then default to English)
|
|
32
|
+
lng: currentLanguage,
|
|
33
|
+
fallbackLng: 'en',
|
|
34
|
+
interpolation: {
|
|
35
|
+
escapeValue: false // 不转义插值 (Don't escape interpolation)
|
|
36
|
+
},
|
|
37
|
+
debug: process.env.DEBUG === 'true', // 只在调试模式下启用 i18n 调试 (Enable i18n debug only in debug mode)
|
|
38
|
+
returnEmptyString: false, // 不返回空字符串 (Don't return empty string)
|
|
39
|
+
returnNull: false, // 不返回 null (Don't return null)
|
|
40
|
+
returnObjects: true, // 返回对象 (Return objects)
|
|
41
|
+
saveMissing: true, // 保存缺失的翻译键 (Save missing translation keys)
|
|
42
|
+
missingKeyHandler: function (lng, ns, key, fallbackValue) {
|
|
43
|
+
console.error("[i18n] Missing translation key: ".concat(key, " for language: ").concat(lng));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
console.log("[i18n] Initialized with language: ".concat(currentLanguage));
|
|
47
|
+
return i18next_1.default;
|
|
48
|
+
};
|
|
49
|
+
// 初始化 i18next 实例 (Initialize i18next instance)
|
|
50
|
+
var i18n = initializeI18n();
|
|
51
|
+
// 导出 i18next 实例 (Export i18next instance)
|
|
52
|
+
exports.default = i18n;
|
|
53
|
+
/**
|
|
54
|
+
* 翻译函数 - 提供类型安全和错误处理 (Translation function - provides type safety and error handling)
|
|
55
|
+
* @param key 翻译键 (Translation key)
|
|
56
|
+
* @param options 翻译选项 (Translation options)
|
|
57
|
+
* @returns 翻译结果 (Translation result)
|
|
58
|
+
*/
|
|
59
|
+
var t = function (key, options) {
|
|
60
|
+
try {
|
|
61
|
+
var result = i18n.t(key, options);
|
|
62
|
+
// 如果结果与键相同,说明没有找到翻译 (If the result is the same as the key, the translation was not found)
|
|
63
|
+
if (result === key) {
|
|
64
|
+
console.warn("[i18n] Translation key not found: ".concat(key));
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error("[i18n] Error translating key: ".concat(key), error);
|
|
70
|
+
return key; // 出错时返回原始键 (Return the original key when an error occurs)
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
exports.t = t;
|
|
74
|
+
/**
|
|
75
|
+
* 切换语言函数 (Language switching function)
|
|
76
|
+
* @param lng 目标语言 (Target language)
|
|
77
|
+
* @returns Promise,解析为切换后的语言 (Promise that resolves to the switched language)
|
|
78
|
+
*/
|
|
79
|
+
var changeLanguage = function (lng) {
|
|
80
|
+
console.log("[i18n] Changing language to: ".concat(lng));
|
|
81
|
+
return i18n.changeLanguage(lng).then(function () { return lng; });
|
|
82
|
+
};
|
|
83
|
+
exports.changeLanguage = changeLanguage;
|
|
84
|
+
/**
|
|
85
|
+
* 获取当前语言函数 (Get current language function)
|
|
86
|
+
* @returns 当前语言 (Current language)
|
|
87
|
+
*/
|
|
88
|
+
var getCurrentLanguage = function () {
|
|
89
|
+
return (i18n.language || 'en');
|
|
90
|
+
};
|
|
91
|
+
exports.getCurrentLanguage = getCurrentLanguage;
|
|
92
|
+
/**
|
|
93
|
+
* 检查键是否存在 (Check if a key exists)
|
|
94
|
+
* @param key 要检查的键 (Key to check)
|
|
95
|
+
* @returns 键是否存在 (Whether the key exists)
|
|
96
|
+
*/
|
|
97
|
+
var hasTranslation = function (key) {
|
|
98
|
+
return i18n.exists(key);
|
|
99
|
+
};
|
|
100
|
+
exports.hasTranslation = hasTranslation;
|
|
101
|
+
/**
|
|
102
|
+
* 获取所有支持的语言 (Get all supported languages)
|
|
103
|
+
* @returns 支持的语言数组 (Array of supported languages)
|
|
104
|
+
*/
|
|
105
|
+
var getSupportedLanguages = function () {
|
|
106
|
+
return Object.keys(resources);
|
|
107
|
+
};
|
|
108
|
+
exports.getSupportedLanguages = getSupportedLanguages;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Author: Martin <lmccc.dev@gmail.com>
|
|
4
|
+
* Co-Author: AI Assistant (Claude)
|
|
5
|
+
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.createResourceKeys = exports.createResourceOperationKeys = exports.createKeyGenerator = void 0;
|
|
9
|
+
/**
|
|
10
|
+
* 创建键名生成器 - 用于生成特定模块的键名 (Create key name generator - for generating key names for specific modules)
|
|
11
|
+
* @param module 模块名称 (Module name)
|
|
12
|
+
* @returns 键名生成函数 (Key name generator function)
|
|
13
|
+
*/
|
|
14
|
+
var createKeyGenerator = function (module) { return function (key) { return "".concat(module, ".").concat(key); }; };
|
|
15
|
+
exports.createKeyGenerator = createKeyGenerator;
|
|
16
|
+
/**
|
|
17
|
+
* 创建资源操作键 - 用于生成资源操作相关的键 (Create resource operation keys - for generating resource operation related keys)
|
|
18
|
+
* @param module 模块名称 (Module name)
|
|
19
|
+
* @param operation 操作名称 (Operation name)
|
|
20
|
+
* @returns 资源操作键对象 (Resource operation keys object)
|
|
21
|
+
*/
|
|
22
|
+
var createResourceOperationKeys = function (module, operation) {
|
|
23
|
+
var keyGen = (0, exports.createKeyGenerator)("".concat(module, ".").concat(operation));
|
|
24
|
+
return {
|
|
25
|
+
request: keyGen('request'),
|
|
26
|
+
success: keyGen('success'),
|
|
27
|
+
error: keyGen('error'),
|
|
28
|
+
notFound: keyGen('notFound'),
|
|
29
|
+
duplicate: keyGen('duplicate')
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
exports.createResourceOperationKeys = createResourceOperationKeys;
|
|
33
|
+
/**
|
|
34
|
+
* 创建资源键 - 用于生成资源相关的键 (Create resource keys - for generating resource related keys)
|
|
35
|
+
* @param module 模块名称 (Module name)
|
|
36
|
+
* @returns 资源键对象 (Resource keys object)
|
|
37
|
+
*/
|
|
38
|
+
var createResourceKeys = function (module) {
|
|
39
|
+
return {
|
|
40
|
+
list: (0, exports.createResourceOperationKeys)(module, 'list'),
|
|
41
|
+
get: (0, exports.createResourceOperationKeys)(module, 'get'),
|
|
42
|
+
create: (0, exports.createResourceOperationKeys)(module, 'create'),
|
|
43
|
+
update: (0, exports.createResourceOperationKeys)(module, 'update'),
|
|
44
|
+
delete: (0, exports.createResourceOperationKeys)(module, 'delete')
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
exports.createResourceKeys = createResourceKeys;
|