@liangshanli/mcp-server-project-standards 1.0.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.
@@ -0,0 +1,514 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ // Get API debug config file path
5
+ const getApiConfigPath = () => {
6
+ const configDir = process.env.CONFIG_DIR || './.setting';
7
+ return path.join(configDir, 'api.json');
8
+ };
9
+
10
+ // Load API debug config
11
+ const loadApiConfig = () => {
12
+ const apiConfigPath = getApiConfigPath();
13
+ try {
14
+ if (fs.existsSync(apiConfigPath)) {
15
+ const configData = fs.readFileSync(apiConfigPath, 'utf8');
16
+ return JSON.parse(configData);
17
+ }
18
+ } catch (err) {
19
+ console.error('Failed to read API config file:', err.message);
20
+ }
21
+ return {
22
+ baseUrl: '',
23
+ headers: {
24
+ 'Content-Type': 'application/json',
25
+ 'Accept': 'application/json'
26
+ },
27
+ list: []
28
+ };
29
+ };
30
+
31
+ // Save API debug config
32
+ const saveApiConfig = (apiConfig) => {
33
+ const configDir = process.env.CONFIG_DIR || './.setting';
34
+ const apiConfigPath = path.join(configDir, 'api.json');
35
+
36
+ try {
37
+ if (!fs.existsSync(configDir)) {
38
+ fs.mkdirSync(configDir, { recursive: true });
39
+ }
40
+ fs.writeFileSync(apiConfigPath, JSON.stringify(apiConfig, null, 2), 'utf8');
41
+ return true;
42
+ } catch (err) {
43
+ console.error('Failed to save API config file:', err.message);
44
+ return false;
45
+ }
46
+ };
47
+
48
+ // Detect content type based on body content
49
+ const detectContentType = (body) => {
50
+ if (typeof body !== 'string') {
51
+ return 'application/json';
52
+ }
53
+
54
+ const trimmedBody = body.trim();
55
+
56
+ // Check for XML
57
+ if (trimmedBody.startsWith('<') && trimmedBody.endsWith('>')) {
58
+ return 'application/xml';
59
+ }
60
+
61
+ // Check for HTML
62
+ if (trimmedBody.includes('<html') || trimmedBody.includes('<!DOCTYPE html')) {
63
+ return 'text/html';
64
+ }
65
+
66
+ // Check for JSON
67
+ try {
68
+ JSON.parse(trimmedBody);
69
+ return 'application/json';
70
+ } catch {
71
+ // Not JSON, continue checking
72
+ }
73
+
74
+ // Check for URL encoded format
75
+ if (trimmedBody.includes('=') && trimmedBody.includes('&')) {
76
+ return 'application/x-www-form-urlencoded';
77
+ }
78
+
79
+ // Check for plain text patterns
80
+ if (trimmedBody.includes('\n') || trimmedBody.includes('\r')) {
81
+ return 'text/plain';
82
+ }
83
+
84
+ // Default to text/plain
85
+ return 'text/plain';
86
+ };
87
+
88
+ /**
89
+ * API 调试工具 - 执行 API 请求和调试
90
+ * @param {Object} params - 参数
91
+ * @param {string} params.action - 操作类型 ('get', 'set', 'delete', 'execute', 'updateBaseUrl', 'updateHeaders', 'deleteHeader', 'search')
92
+ * @param {Object} params.config - API 调试配置
93
+ * @param {string} params.config.baseUrl - API 基础 URL
94
+ * @param {Object} params.config.headers - 公共请求头
95
+ * @param {Array} params.config.list - API 接口列表
96
+ * @param {string} params.index - 要执行的接口索引(执行时必需)
97
+ * @param {string} params.baseUrl - 新的基础 URL(updateBaseUrl 时必需)
98
+ * @param {Object} params.headers - 新的公共请求头(updateHeaders 时必需)
99
+ * @param {string} params.headerName - 要删除的请求头名称(deleteHeader 时必需)
100
+ * @param {string} params.keyword - 搜索关键词(search 时必需)
101
+ * @param {Object} config - 服务器配置
102
+ * @param {Function} saveConfig - 保存配置函数
103
+ * @returns {Object} API 调试结果
104
+ */
105
+ async function api_debug(params, config, saveConfig) {
106
+ const { action, config: apiConfig, index } = params || {};
107
+
108
+ if (!action) {
109
+ throw new Error('Missing action parameter. Must be "get", "set", "delete", "execute", "updateBaseUrl", "updateHeaders", "deleteHeader", or "search"');
110
+ }
111
+
112
+ if (action === 'get') {
113
+ try {
114
+ // 从 api.json 文件中获取配置
115
+ const apiDebugConfig = loadApiConfig();
116
+ return apiDebugConfig;
117
+ } catch (err) {
118
+ throw new Error(`Failed to get API debug config: ${err.message}`);
119
+ }
120
+ } else if (action === 'set') {
121
+ if (!apiConfig) {
122
+ throw new Error('Missing config parameter for set action');
123
+ }
124
+
125
+ try {
126
+ // 加载现有配置
127
+ const existingConfig = loadApiConfig();
128
+
129
+ // 合并配置,保持 baseUrl 和 headers
130
+ const mergedConfig = {
131
+ baseUrl: apiConfig.baseUrl || existingConfig.baseUrl || '',
132
+ headers: { ...existingConfig.headers, ...apiConfig.headers },
133
+ list: []
134
+ };
135
+
136
+ // 去重处理:相同的 URL 只保留一份
137
+ const urlMap = new Map();
138
+
139
+ // 先添加现有列表中的项目
140
+ if (existingConfig.list && Array.isArray(existingConfig.list)) {
141
+ existingConfig.list.forEach(item => {
142
+ if (item.url) {
143
+ urlMap.set(item.url, item);
144
+ }
145
+ });
146
+ }
147
+
148
+ // 再添加新配置中的项目(会覆盖相同 URL 的项目)
149
+ if (apiConfig.list && Array.isArray(apiConfig.list)) {
150
+ apiConfig.list.forEach(item => {
151
+ if (item.url) {
152
+ urlMap.set(item.url, item);
153
+ }
154
+ });
155
+ }
156
+
157
+ // 转换为数组
158
+ mergedConfig.list = Array.from(urlMap.values());
159
+
160
+ // 保存到 api.json 文件
161
+ const saved = saveApiConfig(mergedConfig);
162
+ if (!saved) {
163
+ throw new Error('Failed to save API configuration');
164
+ }
165
+
166
+ return {
167
+ success: true,
168
+ message: `Successfully updated API debug config. Total APIs: ${mergedConfig.list.length}`,
169
+ totalApis: mergedConfig.list.length,
170
+ timestamp: new Date().toISOString()
171
+ };
172
+ } catch (err) {
173
+ throw new Error(`Failed to update API debug config: ${err.message}`);
174
+ }
175
+ } else if (action === 'delete') {
176
+ if (!index && index !== 0) {
177
+ throw new Error('Missing index parameter for delete action');
178
+ }
179
+
180
+ try {
181
+ // 加载当前配置
182
+ const apiDebugConfig = loadApiConfig();
183
+
184
+ if (!apiDebugConfig.list) {
185
+ apiDebugConfig.list = [];
186
+ }
187
+
188
+ // 删除指定索引的接口
189
+ if (index >= 0 && index < apiDebugConfig.list.length) {
190
+ const deletedItem = apiDebugConfig.list.splice(index, 1)[0];
191
+
192
+ // 保存配置
193
+ const saved = saveApiConfig(apiDebugConfig);
194
+ if (!saved) {
195
+ throw new Error('Failed to save API configuration');
196
+ }
197
+
198
+ return {
199
+ success: true,
200
+ message: `Successfully deleted API at index ${index}`,
201
+ deletedItem: deletedItem,
202
+ timestamp: new Date().toISOString()
203
+ };
204
+ } else {
205
+ throw new Error(`Invalid index: ${index}. Must be between 0 and ${apiDebugConfig.list.length - 1}`);
206
+ }
207
+ } catch (err) {
208
+ throw new Error(`Failed to delete API: ${err.message}`);
209
+ }
210
+ } else if (action === 'execute') {
211
+ if (!index && index !== 0) {
212
+ throw new Error('Missing index parameter for execute action');
213
+ }
214
+
215
+ try {
216
+ // 加载当前配置
217
+ const apiDebugConfig = loadApiConfig();
218
+
219
+ if (!apiDebugConfig.list || !Array.isArray(apiDebugConfig.list)) {
220
+ throw new Error('API list not found or invalid');
221
+ }
222
+
223
+ if (index < 0 || index >= apiDebugConfig.list.length) {
224
+ throw new Error(`Invalid index: ${index}. Must be between 0 and ${apiDebugConfig.list.length - 1}`);
225
+ }
226
+
227
+ const apiItem = apiDebugConfig.list[index];
228
+ const baseUrl = apiDebugConfig.baseUrl || '';
229
+ const commonHeaders = apiDebugConfig.headers || {};
230
+
231
+ // 构建完整 URL
232
+ const fullUrl = baseUrl + apiItem.url;
233
+
234
+ // 合并请求头
235
+ const headers = {
236
+ ...commonHeaders,
237
+ ...apiItem.header
238
+ };
239
+
240
+ // 处理请求体
241
+ let body = null;
242
+ if (apiItem.body && (apiItem.method === 'POST' || apiItem.method === 'PUT' || apiItem.method === 'PATCH')) {
243
+ if (typeof apiItem.body === 'string') {
244
+ // 检查是否指定了 Content-Type
245
+ if (apiItem.contentType) {
246
+ headers['Content-Type'] = apiItem.contentType;
247
+ body = apiItem.body;
248
+ } else {
249
+ // 自动判断 Content-Type
250
+ const detectedType = detectContentType(apiItem.body);
251
+ headers['Content-Type'] = detectedType;
252
+ body = apiItem.body;
253
+ }
254
+ } else if (typeof apiItem.body === 'object') {
255
+ // 检查是否指定了 Content-Type
256
+ if (apiItem.contentType) {
257
+ if (apiItem.contentType === 'application/x-www-form-urlencoded') {
258
+ // 转换为 URL 编码格式
259
+ const formData = new URLSearchParams();
260
+ Object.entries(apiItem.body).forEach(([key, value]) => {
261
+ if (value !== null && value !== undefined) {
262
+ formData.append(key, value);
263
+ }
264
+ });
265
+ headers['Content-Type'] = 'application/x-www-form-urlencoded';
266
+ body = formData.toString();
267
+ } else {
268
+ // 其他指定类型,直接序列化
269
+ headers['Content-Type'] = apiItem.contentType;
270
+ body = JSON.stringify(apiItem.body);
271
+ }
272
+ } else {
273
+ // 自动判断:对象默认使用 JSON 格式
274
+ headers['Content-Type'] = 'application/json';
275
+ body = JSON.stringify(apiItem.body);
276
+ }
277
+ }
278
+ }
279
+
280
+ // 处理查询参数
281
+ let queryString = '';
282
+ if (apiItem.query && Object.keys(apiItem.query).length > 0) {
283
+ const queryParams = new URLSearchParams();
284
+ Object.entries(apiItem.query).forEach(([key, value]) => {
285
+ if (value !== null && value !== undefined) {
286
+ queryParams.append(key, value);
287
+ }
288
+ });
289
+ queryString = queryParams.toString();
290
+ }
291
+
292
+ const finalUrl = queryString ? `${fullUrl}?${queryString}` : fullUrl;
293
+
294
+ let response;
295
+ let responseData;
296
+ let error = null;
297
+ let success = true;
298
+
299
+ try {
300
+ // 执行请求
301
+ response = await fetch(finalUrl, {
302
+ method: apiItem.method || 'GET',
303
+ headers: headers,
304
+ body: body
305
+ });
306
+
307
+ // 获取响应数据
308
+ const contentType = response.headers.get('content-type');
309
+
310
+ if (contentType && contentType.includes('application/json')) {
311
+ responseData = await response.json();
312
+ } else {
313
+ responseData = await response.text();
314
+ }
315
+
316
+ // 记录成功信息
317
+ apiDebugConfig.list[index].data = responseData;
318
+ apiDebugConfig.list[index].status = response.status;
319
+ apiDebugConfig.list[index].statusText = response.statusText;
320
+ apiDebugConfig.list[index].responseHeaders = Object.fromEntries(response.headers.entries());
321
+ apiDebugConfig.list[index].lastExecuted = new Date().toISOString();
322
+ apiDebugConfig.list[index].success = true;
323
+ apiDebugConfig.list[index].error = null;
324
+
325
+ } catch (fetchError) {
326
+ // 记录失败信息
327
+ error = fetchError.message;
328
+ success = false;
329
+
330
+ apiDebugConfig.list[index].data = null;
331
+ apiDebugConfig.list[index].status = null;
332
+ apiDebugConfig.list[index].statusText = null;
333
+ apiDebugConfig.list[index].responseHeaders = null;
334
+ apiDebugConfig.list[index].lastExecuted = new Date().toISOString();
335
+ apiDebugConfig.list[index].success = false;
336
+ apiDebugConfig.list[index].error = error;
337
+ }
338
+
339
+ // 去重处理:相同的 URL 只保留一份(保留最新的执行结果)
340
+ const urlMap = new Map();
341
+ apiDebugConfig.list.forEach(item => {
342
+ if (item.url) {
343
+ urlMap.set(item.url, item);
344
+ }
345
+ });
346
+ apiDebugConfig.list = Array.from(urlMap.values());
347
+
348
+ // 无论成功还是失败,都保存配置
349
+ saveApiConfig(apiDebugConfig);
350
+
351
+ if (success) {
352
+ return {
353
+ success: true,
354
+ message: `Successfully executed API: ${apiItem.description || apiItem.url}`,
355
+ request: {
356
+ url: finalUrl,
357
+ method: apiItem.method || 'GET',
358
+ headers: headers,
359
+ body: body
360
+ },
361
+ response: {
362
+ status: response.status,
363
+ statusText: response.statusText,
364
+ headers: Object.fromEntries(response.headers.entries()),
365
+ data: responseData
366
+ },
367
+ timestamp: new Date().toISOString()
368
+ };
369
+ } else {
370
+ return {
371
+ success: false,
372
+ message: `Failed to execute API: ${apiItem.description || apiItem.url}`,
373
+ request: {
374
+ url: finalUrl,
375
+ method: apiItem.method || 'GET',
376
+ headers: headers,
377
+ body: body
378
+ },
379
+ error: error,
380
+ timestamp: new Date().toISOString()
381
+ };
382
+ }
383
+ } catch (err) {
384
+ // 如果是在保存配置时出错,也要尝试记录错误信息
385
+ try {
386
+ const apiDebugConfig = loadApiConfig();
387
+ if (apiDebugConfig.list && apiDebugConfig.list[index]) {
388
+ apiDebugConfig.list[index].lastExecuted = new Date().toISOString();
389
+ apiDebugConfig.list[index].success = false;
390
+ apiDebugConfig.list[index].error = err.message;
391
+ saveApiConfig(apiDebugConfig);
392
+ }
393
+ } catch (saveErr) {
394
+ // 如果连保存都失败了,记录到控制台
395
+ console.error('Failed to save error information:', saveErr.message);
396
+ }
397
+
398
+ throw new Error(`Failed to execute API: ${err.message}`);
399
+ }
400
+ } else if (action === 'updateBaseUrl') {
401
+ const { baseUrl } = params;
402
+ if (!baseUrl) {
403
+ throw new Error('Missing baseUrl parameter for updateBaseUrl action');
404
+ }
405
+
406
+ try {
407
+ const apiDebugConfig = loadApiConfig();
408
+ apiDebugConfig.baseUrl = baseUrl;
409
+ const saved = saveApiConfig(apiDebugConfig);
410
+ if (!saved) {
411
+ throw new Error('Failed to save API configuration');
412
+ }
413
+
414
+ return {
415
+ success: true,
416
+ message: `Successfully updated base URL to: ${baseUrl}`,
417
+ baseUrl: baseUrl,
418
+ timestamp: new Date().toISOString()
419
+ };
420
+ } catch (err) {
421
+ throw new Error(`Failed to update base URL: ${err.message}`);
422
+ }
423
+ } else if (action === 'updateHeaders') {
424
+ const { headers } = params;
425
+ if (!headers || typeof headers !== 'object') {
426
+ throw new Error('Missing or invalid headers parameter for updateHeaders action');
427
+ }
428
+
429
+ try {
430
+ const apiDebugConfig = loadApiConfig();
431
+ apiDebugConfig.headers = { ...apiDebugConfig.headers, ...headers };
432
+ const saved = saveApiConfig(apiDebugConfig);
433
+ if (!saved) {
434
+ throw new Error('Failed to save API configuration');
435
+ }
436
+
437
+ return {
438
+ success: true,
439
+ message: 'Successfully updated headers',
440
+ headers: apiDebugConfig.headers,
441
+ timestamp: new Date().toISOString()
442
+ };
443
+ } catch (err) {
444
+ throw new Error(`Failed to update headers: ${err.message}`);
445
+ }
446
+ } else if (action === 'deleteHeader') {
447
+ const { headerName } = params;
448
+ if (!headerName) {
449
+ throw new Error('Missing headerName parameter for deleteHeader action');
450
+ }
451
+
452
+ try {
453
+ const apiDebugConfig = loadApiConfig();
454
+ if (apiDebugConfig.headers && apiDebugConfig.headers[headerName]) {
455
+ delete apiDebugConfig.headers[headerName];
456
+ const saved = saveApiConfig(apiDebugConfig);
457
+ if (!saved) {
458
+ throw new Error('Failed to save API configuration');
459
+ }
460
+
461
+ return {
462
+ success: true,
463
+ message: `Successfully deleted header: ${headerName}`,
464
+ headers: apiDebugConfig.headers,
465
+ timestamp: new Date().toISOString()
466
+ };
467
+ } else {
468
+ return {
469
+ success: false,
470
+ message: `Header '${headerName}' not found`,
471
+ headers: apiDebugConfig.headers,
472
+ timestamp: new Date().toISOString()
473
+ };
474
+ }
475
+ } catch (err) {
476
+ throw new Error(`Failed to delete header: ${err.message}`);
477
+ }
478
+ } else if (action === 'search') {
479
+ const { keyword } = params;
480
+ if (!keyword) {
481
+ throw new Error('Missing keyword parameter for search action');
482
+ }
483
+
484
+ try {
485
+ const apiDebugConfig = loadApiConfig();
486
+ let searchResults = [];
487
+
488
+ if (apiDebugConfig.list && Array.isArray(apiDebugConfig.list)) {
489
+ searchResults = apiDebugConfig.list.filter((item) => {
490
+ return item.url.toLowerCase().includes(keyword.toLowerCase()) || item.description.toLowerCase().includes(keyword.toLowerCase());
491
+ });
492
+ }
493
+
494
+ return {
495
+ success: true,
496
+ message: `Found ${searchResults.length} matching API(s) for keyword: "${keyword}"`,
497
+ keyword: keyword,
498
+ results: searchResults,
499
+ totalCount: searchResults.length,
500
+ timestamp: new Date().toISOString()
501
+
502
+
503
+ }
504
+
505
+ } catch (err) {
506
+ throw new Error(`Failed to search APIs: ${err.message}`);
507
+ }
508
+ } else {
509
+ throw new Error('Invalid action. Must be "get", "set", "delete", "execute", "updateBaseUrl", "updateHeaders", "deleteHeader", or "search"');
510
+ }
511
+ }
512
+
513
+
514
+ module.exports = api_debug;
@@ -0,0 +1,54 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Create a custom tool with custom handler code
6
+ * @param {Object} params - Parameters
7
+ * @param {string} params.toolName - Name of the custom tool
8
+ * @param {string} params.description - Description of the custom tool
9
+ * @param {Object} params.inputSchema - JSON schema for input parameters
10
+ * @param {string} params.handlerCode - JavaScript code for the tool handler
11
+ * @param {Object} config - Server configuration
12
+ * @param {Function} saveConfig - Function to save configuration
13
+ * @returns {Object} Result of tool creation
14
+ */
15
+ async function create_custom_tool(params, config, saveConfig) {
16
+ const { toolName, description, inputSchema, handlerCode } = params || {};
17
+
18
+ if (!toolName || !description || !inputSchema || !handlerCode) {
19
+ throw new Error('Missing required parameters: toolName, description, inputSchema, handlerCode');
20
+ }
21
+
22
+ try {
23
+ const customTool = {
24
+ name: toolName,
25
+ description: description,
26
+ inputSchema: inputSchema,
27
+ handlerCode: handlerCode,
28
+ createdAt: new Date().toISOString(),
29
+ id: Date.now().toString()
30
+ };
31
+
32
+ // Add to config
33
+ if (!config.customTools) {
34
+ config.customTools = [];
35
+ }
36
+ config.customTools.push(customTool);
37
+
38
+ // Save config
39
+ const saved = saveConfig(config);
40
+ if (!saved) {
41
+ throw new Error('Failed to save configuration');
42
+ }
43
+
44
+ return {
45
+ success: true,
46
+ tool: customTool,
47
+ message: `Custom tool '${toolName}' created successfully`
48
+ };
49
+ } catch (err) {
50
+ throw new Error(`Failed to create custom tool: ${err.message}`);
51
+ }
52
+ }
53
+
54
+ module.exports = create_custom_tool;