@lmcc-dev/mult-fetch-mcp-server 1.0.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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +512 -0
  3. package/README.zh.md +510 -0
  4. package/dist/index.js +21 -0
  5. package/dist/src/cli-language.js +78 -0
  6. package/dist/src/client.js +284 -0
  7. package/dist/src/index.js +21 -0
  8. package/dist/src/lib/BrowserFetcher.js +753 -0
  9. package/dist/src/lib/NodeFetcher.js +428 -0
  10. package/dist/src/lib/example.js +93 -0
  11. package/dist/src/lib/i18n/index.js +36 -0
  12. package/dist/src/lib/i18n/locales/en/browser.js +53 -0
  13. package/dist/src/lib/i18n/locales/en/client.js +40 -0
  14. package/dist/src/lib/i18n/locales/en/errors.js +18 -0
  15. package/dist/src/lib/i18n/locales/en/fetcher.js +67 -0
  16. package/dist/src/lib/i18n/locales/en/index.js +26 -0
  17. package/dist/src/lib/i18n/locales/en/node.js +42 -0
  18. package/dist/src/lib/i18n/locales/en/prompts.js +72 -0
  19. package/dist/src/lib/i18n/locales/en/resources.js +47 -0
  20. package/dist/src/lib/i18n/locales/en/server.js +32 -0
  21. package/dist/src/lib/i18n/locales/en/tools.js +25 -0
  22. package/dist/src/lib/i18n/locales/zh/browser.js +53 -0
  23. package/dist/src/lib/i18n/locales/zh/client.js +39 -0
  24. package/dist/src/lib/i18n/locales/zh/errors.js +18 -0
  25. package/dist/src/lib/i18n/locales/zh/fetcher.js +67 -0
  26. package/dist/src/lib/i18n/locales/zh/index.js +26 -0
  27. package/dist/src/lib/i18n/locales/zh/node.js +42 -0
  28. package/dist/src/lib/i18n/locales/zh/prompts.js +72 -0
  29. package/dist/src/lib/i18n/locales/zh/resources.js +47 -0
  30. package/dist/src/lib/i18n/locales/zh/server.js +32 -0
  31. package/dist/src/lib/i18n/locales/zh/tools.js +25 -0
  32. package/dist/src/lib/i18n/logger.js +57 -0
  33. package/dist/src/lib/logger.js +126 -0
  34. package/dist/src/lib/server/browser.js +86 -0
  35. package/dist/src/lib/server/fetcher.js +130 -0
  36. package/dist/src/lib/server/index.js +73 -0
  37. package/dist/src/lib/server/logger.js +23 -0
  38. package/dist/src/lib/server/prompts.js +207 -0
  39. package/dist/src/lib/server/resources.js +171 -0
  40. package/dist/src/lib/server/tools.js +346 -0
  41. package/dist/src/lib/server/types.js +6 -0
  42. package/dist/src/lib/types.js +35 -0
  43. package/dist/src/mcp-server.js +22 -0
  44. package/dist/tests/test-direct-client.js +129 -0
  45. package/dist/tests/test-mcp-methods.js +114 -0
  46. package/dist/tests/test-mcp.js +145 -0
  47. package/dist/tests/test-mini4k.js +147 -0
  48. package/images/example_en.png +0 -0
  49. package/images/example_zh.png +0 -0
  50. package/package.json +80 -0
@@ -0,0 +1,207 @@
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 { ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
+ import { log, COMPONENTS } from '../logger.js';
8
+ import { t } from '../i18n/index.js';
9
+ // 定义提示模板 (Define prompt templates)
10
+ const PROMPTS = {
11
+ "fetch-website": {
12
+ name: "mult-fetch-mcp:prompt:fetch-website",
13
+ description: t('prompts.fetchWebsite.description'),
14
+ arguments: [
15
+ {
16
+ name: "url",
17
+ description: t('prompts.url.description'),
18
+ required: true
19
+ },
20
+ {
21
+ name: "format",
22
+ description: t('prompts.format.description'),
23
+ required: false
24
+ },
25
+ {
26
+ name: "useBrowser",
27
+ description: t('prompts.useBrowser.description'),
28
+ required: false
29
+ }
30
+ ]
31
+ },
32
+ "extract-content": {
33
+ name: "mult-fetch-mcp:prompt:extract-content",
34
+ description: t('prompts.extractContent.description'),
35
+ arguments: [
36
+ {
37
+ name: "url",
38
+ description: t('prompts.url.description'),
39
+ required: true
40
+ },
41
+ {
42
+ name: "selector",
43
+ description: t('prompts.selector.description'),
44
+ required: false
45
+ },
46
+ {
47
+ name: "dataType",
48
+ description: t('prompts.dataType.description'),
49
+ required: false
50
+ }
51
+ ]
52
+ },
53
+ "debug-fetch": {
54
+ name: "mult-fetch-mcp:prompt:debug-fetch",
55
+ description: t('prompts.debugFetch.description'),
56
+ arguments: [
57
+ {
58
+ name: "url",
59
+ description: t('prompts.url.description'),
60
+ required: true
61
+ },
62
+ {
63
+ name: "error",
64
+ description: t('prompts.error.description'),
65
+ required: true
66
+ }
67
+ ]
68
+ }
69
+ };
70
+ /**
71
+ * 注册提示相关处理程序到服务器 (Register prompt handlers to server)
72
+ * @param server MCP服务器实例 (MCP server instance)
73
+ */
74
+ export function registerPrompts(server) {
75
+ // 注册提示列表处理程序
76
+ server.setRequestHandler(ListPromptsRequestSchema, async (request) => {
77
+ const debug = request.params?.debug === true;
78
+ log('prompts.list.request', debug, { params: request.params }, COMPONENTS.PROMPTS);
79
+ return {
80
+ prompts: Object.values(PROMPTS)
81
+ };
82
+ });
83
+ // 注册获取提示处理程序
84
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
85
+ const promptName = request.params.name;
86
+ const args = request.params.arguments || {};
87
+ const debug = typeof args.debug === 'boolean' ? args.debug : false;
88
+ log('prompts.get.request', debug, { promptName, args }, COMPONENTS.PROMPTS);
89
+ // 检查提示是否存在
90
+ if (!PROMPTS[promptName]) {
91
+ throw new Error(t('prompts.notFound') + `: ${promptName}`);
92
+ }
93
+ // 检查必需参数
94
+ const prompt = PROMPTS[promptName];
95
+ const requiredArgs = prompt.arguments?.filter(arg => arg.required) || [];
96
+ for (const arg of requiredArgs) {
97
+ if (args[arg.name] === undefined) {
98
+ throw new Error(t('prompts.missingRequiredArg') + `: ${arg.name}`);
99
+ }
100
+ }
101
+ // 根据提示名称和参数生成消息
102
+ if (promptName === "fetch-website") {
103
+ const url = args.url;
104
+ const format = args.format || "html";
105
+ let useBrowser = false;
106
+ if (args.useBrowser !== undefined) {
107
+ if (typeof args.useBrowser === 'boolean') {
108
+ useBrowser = args.useBrowser;
109
+ }
110
+ else if (typeof args.useBrowser === 'string') {
111
+ const useBrowserStr = args.useBrowser.toLowerCase().trim();
112
+ useBrowser = useBrowserStr === 'true' || useBrowserStr === 'yes' || useBrowserStr === '是' || useBrowserStr === '1';
113
+ }
114
+ else if (typeof args.useBrowser === 'number') {
115
+ useBrowser = args.useBrowser !== 0;
116
+ }
117
+ }
118
+ log('prompts.useBrowserValue', debug, { original: args.useBrowser, parsed: useBrowser }, COMPONENTS.PROMPTS);
119
+ let toolName = "fetch_html";
120
+ if (format === "json")
121
+ toolName = "fetch_json";
122
+ if (format === "text")
123
+ toolName = "fetch_txt";
124
+ if (format === "markdown")
125
+ toolName = "fetch_markdown";
126
+ return {
127
+ description: t('prompts.fetchWebsite.result'),
128
+ messages: [
129
+ {
130
+ role: "user",
131
+ content: {
132
+ type: "text",
133
+ text: t('prompts.fetchWebsite.message')
134
+ }
135
+ },
136
+ {
137
+ role: "assistant",
138
+ content: {
139
+ type: "text",
140
+ text: t('prompts.fetchWebsite.response')
141
+ }
142
+ },
143
+ {
144
+ role: "user",
145
+ content: {
146
+ type: "text",
147
+ text: `${t('prompts.fetchWebsite.instruction')}: ${url}\n\n` +
148
+ `${t('prompts.fetchWebsite.formatInstruction')}: ${format}\n\n` +
149
+ `${t('prompts.fetchWebsite.browserInstruction')}: ${useBrowser ? t('prompts.yes') : t('prompts.no')}`
150
+ }
151
+ }
152
+ ]
153
+ };
154
+ }
155
+ else if (promptName === "extract-content") {
156
+ const url = args.url;
157
+ const selector = args.selector || "main";
158
+ const dataType = args.dataType || "text";
159
+ return {
160
+ description: t('prompts.extractContent.result'),
161
+ messages: [
162
+ {
163
+ role: "user",
164
+ content: {
165
+ type: "text",
166
+ text: `${t('prompts.extractContent.message')}: ${url}\n\n` +
167
+ `${t('prompts.extractContent.selectorInstruction')}: ${selector}\n\n` +
168
+ `${t('prompts.extractContent.dataTypeInstruction')}: ${dataType}`
169
+ }
170
+ }
171
+ ]
172
+ };
173
+ }
174
+ else if (promptName === "debug-fetch") {
175
+ const url = args.url;
176
+ const error = args.error;
177
+ return {
178
+ description: t('prompts.debugFetch.result'),
179
+ messages: [
180
+ {
181
+ role: "user",
182
+ content: {
183
+ type: "text",
184
+ text: `${t('prompts.debugFetch.message')}: ${url}\n\n` +
185
+ `${t('prompts.debugFetch.errorDetails')}: ${error}\n\n` +
186
+ `${t('prompts.debugFetch.instruction')}`
187
+ }
188
+ }
189
+ ]
190
+ };
191
+ }
192
+ // 默认情况下返回一个通用消息
193
+ return {
194
+ description: t('prompts.generic.result'),
195
+ messages: [
196
+ {
197
+ role: "user",
198
+ content: {
199
+ type: "text",
200
+ text: `${t('prompts.generic.message')}: ${promptName}\n\n` +
201
+ `${t('prompts.generic.args')}: ${JSON.stringify(args, null, 2)}`
202
+ }
203
+ }
204
+ ]
205
+ };
206
+ });
207
+ }
@@ -0,0 +1,171 @@
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 { ListResourcesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
+ import { log, getLogFilePath, clearLogFile, COMPONENTS } from '../logger.js';
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ // 获取项目根目录路径 (Get project root directory path)
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+ const projectRoot = path.resolve(__dirname, '../../..');
15
+ /**
16
+ * 注册资源相关处理程序到服务器 (Register resource handlers to server)
17
+ * @param server MCP服务器实例 (MCP server instance)
18
+ */
19
+ export function registerResources(server) {
20
+ // 注册资源列表处理程序
21
+ server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
22
+ const debug = request.params?.debug === true;
23
+ log('resources.list.request', debug, { params: request.params }, COMPONENTS.RESOURCES);
24
+ // 返回实际可用的资源列表
25
+ return {
26
+ resources: [
27
+ // 项目文档资源 (Project documentation resources)
28
+ {
29
+ name: 'mult-fetch-mcp:log:debug-log',
30
+ uri: 'file:///logs/debug',
31
+ description: 'Debug log file containing all debug messages from the server'
32
+ },
33
+ // 清理日志资源 (Clear log resource)
34
+ {
35
+ name: 'mult-fetch-mcp:log:clear-debug-log',
36
+ uri: 'file:///logs/clear',
37
+ description: 'Clear the debug log file'
38
+ }
39
+ ],
40
+ resourceTemplates: [
41
+ // 源代码文件模板 (Source code file template)
42
+ {
43
+ uriTemplate: 'file:///src/{path}',
44
+ description: 'Source code files in the project',
45
+ name: 'mult-fetch-mcp:src:file'
46
+ },
47
+ // 文档文件模板 (Documentation file template)
48
+ {
49
+ uriTemplate: 'file:///docs/{path}',
50
+ description: 'Documentation files in the project',
51
+ name: 'mult-fetch-mcp:docs:file'
52
+ }
53
+ ]
54
+ };
55
+ });
56
+ // 注册资源读取处理程序
57
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
58
+ const uri = request.params.uri;
59
+ const debug = request.params?.debug === true;
60
+ log('resources.read.request', debug, { uri }, COMPONENTS.RESOURCES);
61
+ try {
62
+ // 解析URI并读取相应文件
63
+ const filePath = parseResourceUri(uri);
64
+ if (!filePath) {
65
+ throw new Error(`resources.invalidUri`);
66
+ }
67
+ const content = await readFileContent(filePath);
68
+ const mimeType = getMimeType(filePath);
69
+ return {
70
+ contents: [
71
+ {
72
+ uri,
73
+ mimeType,
74
+ text: content
75
+ }
76
+ ]
77
+ };
78
+ }
79
+ catch (error) {
80
+ log('resources.readError', debug, { uri, error: error instanceof Error ? error.message : String(error) }, COMPONENTS.RESOURCES);
81
+ throw new Error(`resources.notFound: ${uri}`);
82
+ }
83
+ });
84
+ }
85
+ /**
86
+ * 解析资源URI为文件路径 (Parse resource URI to file path)
87
+ * @param uri 资源URI (Resource URI)
88
+ * @returns 文件路径或null (File path or null)
89
+ */
90
+ function parseResourceUri(uri) {
91
+ // 处理日志文件URI
92
+ if (uri === 'file:///logs/debug') {
93
+ return getLogFilePath();
94
+ }
95
+ // 处理清理日志文件URI
96
+ if (uri === 'file:///logs/clear') {
97
+ return 'CLEAR_LOG'; // 特殊标记,表示清理日志文件
98
+ }
99
+ // 处理预定义的URI模式
100
+ if (uri.startsWith('file:///docs/')) {
101
+ const resourceName = uri.substring('file:///docs/'.length);
102
+ if (resourceName === 'readme') {
103
+ return path.join(projectRoot, 'README.md');
104
+ }
105
+ else if (resourceName === 'package') {
106
+ return path.join(projectRoot, 'package.json');
107
+ }
108
+ else {
109
+ return path.join(projectRoot, resourceName);
110
+ }
111
+ }
112
+ else if (uri.startsWith('file:///src/')) {
113
+ const resourceName = uri.substring('file:///src/'.length);
114
+ if (resourceName === 'index') {
115
+ return path.join(projectRoot, 'index.ts');
116
+ }
117
+ else if (resourceName === 'client') {
118
+ return path.join(projectRoot, 'src', 'client.ts');
119
+ }
120
+ else {
121
+ return path.join(projectRoot, 'src', resourceName);
122
+ }
123
+ }
124
+ return null;
125
+ }
126
+ /**
127
+ * 读取文件内容 (Read file content)
128
+ * @param filePath 文件路径 (File path)
129
+ * @returns 文件内容 (File content)
130
+ */
131
+ async function readFileContent(filePath) {
132
+ try {
133
+ // 处理清理日志文件的特殊标记
134
+ if (filePath === 'CLEAR_LOG') {
135
+ clearLogFile();
136
+ return '日志文件已清理 (Log file has been cleared)';
137
+ }
138
+ return fs.readFileSync(filePath, 'utf-8');
139
+ }
140
+ catch (error) {
141
+ log('resources.fileReadError', true, { filePath, error: error instanceof Error ? error.message : String(error) }, COMPONENTS.RESOURCES);
142
+ throw new Error('resources.fileReadError');
143
+ }
144
+ }
145
+ /**
146
+ * 根据文件路径获取MIME类型 (Get MIME type based on file path)
147
+ * @param filePath 文件路径 (File path)
148
+ * @returns MIME类型 (MIME type)
149
+ */
150
+ function getMimeType(filePath) {
151
+ const ext = path.extname(filePath).toLowerCase();
152
+ switch (ext) {
153
+ case '.md':
154
+ return 'text/markdown';
155
+ case '.json':
156
+ return 'application/json';
157
+ case '.ts':
158
+ return 'text/typescript';
159
+ case '.js':
160
+ return 'text/javascript';
161
+ case '.html':
162
+ return 'text/html';
163
+ case '.css':
164
+ return 'text/css';
165
+ case '.txt':
166
+ case '.log':
167
+ return 'text/plain';
168
+ default:
169
+ return 'application/octet-stream';
170
+ }
171
+ }