@liangshanli/mcp-server-project-standards 1.2.1 → 2.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.
- package/README.md +15 -0
- package/bin/cli.js +1 -1
- package/package.json +8 -4
- package/src/server-final.js +297 -268
- package/src/utils/api_common.js +137 -0
- package/src/utils/api_config.js +217 -0
- package/src/utils/api_debug.js +130 -604
- package/src/utils/api_login.js +165 -0
|
@@ -0,0 +1,137 @@
|
|
|
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
|
+
// Get allowed methods from environment variable
|
|
49
|
+
const getAllowedMethods = () => {
|
|
50
|
+
const allowedMethods = process.env.API_DEBUG_ALLOWED_METHODS || 'GET';
|
|
51
|
+
return allowedMethods.split(',').map(method => method.trim().toUpperCase());
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Get login URL from environment variable
|
|
55
|
+
const getLoginUrl = () => {
|
|
56
|
+
return process.env.API_DEBUG_LOGIN_URL || '/api/login';
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Get login method from environment variable
|
|
60
|
+
const getLoginMethod = () => {
|
|
61
|
+
return (process.env.API_DEBUG_LOGIN_METHOD || 'POST').toUpperCase();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Get login body from environment variable
|
|
65
|
+
const getLoginBody = () => {
|
|
66
|
+
const envBody = process.env.API_DEBUG_LOGIN_BODY || '{"username":"","password":""}';
|
|
67
|
+
|
|
68
|
+
// 如果是字符串格式,直接返回
|
|
69
|
+
if (typeof envBody === 'string' && !envBody.startsWith('{')) {
|
|
70
|
+
return envBody;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 如果是JSON格式,解析后返回
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(envBody);
|
|
76
|
+
} catch {
|
|
77
|
+
// 如果解析失败,返回原始字符串
|
|
78
|
+
return envBody;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Get login description from environment variable
|
|
83
|
+
const getLoginDescription = () => {
|
|
84
|
+
return process.env.API_DEBUG_LOGIN_DESCRIPTION || 'Save returned token to common headers in debug tool, field name Authorization, field value Bearer token';
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Detect content type based on body content
|
|
88
|
+
const detectContentType = (body) => {
|
|
89
|
+
if (typeof body !== 'string') {
|
|
90
|
+
return 'application/json';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const trimmedBody = body.trim();
|
|
94
|
+
|
|
95
|
+
// Check for XML
|
|
96
|
+
if (trimmedBody.startsWith('<') && trimmedBody.endsWith('>')) {
|
|
97
|
+
return 'application/xml';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check for HTML
|
|
101
|
+
if (trimmedBody.includes('<html') || trimmedBody.includes('<!DOCTYPE html')) {
|
|
102
|
+
return 'text/html';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check for JSON
|
|
106
|
+
try {
|
|
107
|
+
JSON.parse(trimmedBody);
|
|
108
|
+
return 'application/json';
|
|
109
|
+
} catch {
|
|
110
|
+
// Not JSON, continue checking
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check for URL encoded format
|
|
114
|
+
if (trimmedBody.includes('=') && trimmedBody.includes('&')) {
|
|
115
|
+
return 'application/x-www-form-urlencoded';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check for plain text patterns
|
|
119
|
+
if (trimmedBody.includes('\n') || trimmedBody.includes('\r')) {
|
|
120
|
+
return 'text/plain';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Default to text/plain
|
|
124
|
+
return 'text/plain';
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
getApiConfigPath,
|
|
129
|
+
loadApiConfig,
|
|
130
|
+
saveApiConfig,
|
|
131
|
+
getAllowedMethods,
|
|
132
|
+
getLoginUrl,
|
|
133
|
+
getLoginMethod,
|
|
134
|
+
getLoginBody,
|
|
135
|
+
getLoginDescription,
|
|
136
|
+
detectContentType
|
|
137
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const { loadApiConfig, saveApiConfig } = require('./api_common');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* API 配置工具 - 专门处理API配置管理
|
|
5
|
+
* @param {Object} params - 参数
|
|
6
|
+
* @param {string} params.action - 操作类型 ('get', 'set', 'updateBaseUrl', 'updateHeaders', 'deleteHeader', 'search', 'list')
|
|
7
|
+
* @param {Object} params.config - API配置(set 时必需)
|
|
8
|
+
* @param {string} params.baseUrl - 基础URL(updateBaseUrl 时必需)
|
|
9
|
+
* @param {Object} params.headers - 请求头(updateHeaders 时必需)
|
|
10
|
+
* @param {string} params.headerName - 要删除的请求头名称(deleteHeader 时必需)
|
|
11
|
+
* @param {string} params.keyword - 搜索关键词(search 时必需)
|
|
12
|
+
* @param {Object} config - 服务器配置
|
|
13
|
+
* @param {Function} saveConfig - 保存配置函数
|
|
14
|
+
* @returns {Object} API配置结果
|
|
15
|
+
*/
|
|
16
|
+
async function api_config(params, config, saveConfig) {
|
|
17
|
+
const { action, config: apiConfig, baseUrl, headers, headerName, keyword } = params || {};
|
|
18
|
+
|
|
19
|
+
if (!action) {
|
|
20
|
+
throw new Error('Missing action parameter. Must be "get", "set", "updateBaseUrl", "updateHeaders", "deleteHeader", "search", or "list"');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (action === 'get') {
|
|
24
|
+
try {
|
|
25
|
+
const apiDebugConfig = loadApiConfig();
|
|
26
|
+
return {
|
|
27
|
+
success: true,
|
|
28
|
+
config: apiDebugConfig,
|
|
29
|
+
timestamp: new Date().toISOString()
|
|
30
|
+
};
|
|
31
|
+
} catch (err) {
|
|
32
|
+
throw new Error(`Failed to get API config: ${err.message}`);
|
|
33
|
+
}
|
|
34
|
+
} else if (action === 'set') {
|
|
35
|
+
if (!apiConfig || typeof apiConfig !== 'object') {
|
|
36
|
+
throw new Error('Missing or invalid config parameter for set action');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// 加载现有配置
|
|
41
|
+
const existingConfig = loadApiConfig();
|
|
42
|
+
|
|
43
|
+
// 合并配置
|
|
44
|
+
const mergedConfig = {
|
|
45
|
+
baseUrl: apiConfig.baseUrl !== undefined ? apiConfig.baseUrl : existingConfig.baseUrl || '',
|
|
46
|
+
headers: { ...existingConfig.headers, ...apiConfig.headers },
|
|
47
|
+
list: []
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// 去重处理:相同的 URL 只保留一份
|
|
51
|
+
const urlMap = new Map();
|
|
52
|
+
|
|
53
|
+
// 先添加现有列表中的项目
|
|
54
|
+
if (existingConfig.list && Array.isArray(existingConfig.list)) {
|
|
55
|
+
existingConfig.list.forEach(item => {
|
|
56
|
+
if (item.url) {
|
|
57
|
+
urlMap.set(item.url, item);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 再添加新配置中的项目(会覆盖相同 URL 的项目)
|
|
63
|
+
if (apiConfig.list && Array.isArray(apiConfig.list)) {
|
|
64
|
+
apiConfig.list.forEach(item => {
|
|
65
|
+
if (item.url) {
|
|
66
|
+
urlMap.set(item.url, item);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 转换为数组
|
|
72
|
+
mergedConfig.list = Array.from(urlMap.values());
|
|
73
|
+
|
|
74
|
+
// 保存配置
|
|
75
|
+
const saved = saveApiConfig(mergedConfig);
|
|
76
|
+
if (!saved) {
|
|
77
|
+
throw new Error('Failed to save API configuration');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
success: true,
|
|
82
|
+
message: `Successfully updated API config. Total APIs: ${mergedConfig.list.length}`,
|
|
83
|
+
config: mergedConfig,
|
|
84
|
+
totalApis: mergedConfig.list.length,
|
|
85
|
+
timestamp: new Date().toISOString()
|
|
86
|
+
};
|
|
87
|
+
} catch (err) {
|
|
88
|
+
throw new Error(`Failed to update API config: ${err.message}`);
|
|
89
|
+
}
|
|
90
|
+
} else if (action === 'updateBaseUrl') {
|
|
91
|
+
if (!baseUrl) {
|
|
92
|
+
throw new Error('Missing baseUrl parameter for updateBaseUrl action');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const apiDebugConfig = loadApiConfig();
|
|
97
|
+
apiDebugConfig.baseUrl = baseUrl;
|
|
98
|
+
const saved = saveApiConfig(apiDebugConfig);
|
|
99
|
+
if (!saved) {
|
|
100
|
+
throw new Error('Failed to save API configuration');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
message: `Successfully updated base URL to: ${baseUrl}`,
|
|
106
|
+
baseUrl: baseUrl,
|
|
107
|
+
timestamp: new Date().toISOString()
|
|
108
|
+
};
|
|
109
|
+
} catch (err) {
|
|
110
|
+
throw new Error(`Failed to update base URL: ${err.message}`);
|
|
111
|
+
}
|
|
112
|
+
} else if (action === 'updateHeaders') {
|
|
113
|
+
if (!headers || typeof headers !== 'object') {
|
|
114
|
+
throw new Error('Missing or invalid headers parameter for updateHeaders action');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const apiDebugConfig = loadApiConfig();
|
|
119
|
+
apiDebugConfig.headers = { ...apiDebugConfig.headers, ...headers };
|
|
120
|
+
const saved = saveApiConfig(apiDebugConfig);
|
|
121
|
+
if (!saved) {
|
|
122
|
+
throw new Error('Failed to save API configuration');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
message: 'Successfully updated headers',
|
|
128
|
+
headers: apiDebugConfig.headers,
|
|
129
|
+
timestamp: new Date().toISOString()
|
|
130
|
+
};
|
|
131
|
+
} catch (err) {
|
|
132
|
+
throw new Error(`Failed to update headers: ${err.message}`);
|
|
133
|
+
}
|
|
134
|
+
} else if (action === 'deleteHeader') {
|
|
135
|
+
if (!headerName) {
|
|
136
|
+
throw new Error('Missing headerName parameter for deleteHeader action');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const apiDebugConfig = loadApiConfig();
|
|
141
|
+
if (apiDebugConfig.headers && apiDebugConfig.headers[headerName]) {
|
|
142
|
+
delete apiDebugConfig.headers[headerName];
|
|
143
|
+
const saved = saveApiConfig(apiDebugConfig);
|
|
144
|
+
if (!saved) {
|
|
145
|
+
throw new Error('Failed to save API configuration');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
success: true,
|
|
150
|
+
message: `Successfully deleted header: ${headerName}`,
|
|
151
|
+
headers: apiDebugConfig.headers,
|
|
152
|
+
timestamp: new Date().toISOString()
|
|
153
|
+
};
|
|
154
|
+
} else {
|
|
155
|
+
return {
|
|
156
|
+
success: false,
|
|
157
|
+
message: `Header '${headerName}' not found`,
|
|
158
|
+
headers: apiDebugConfig.headers,
|
|
159
|
+
timestamp: new Date().toISOString()
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
throw new Error(`Failed to delete header: ${err.message}`);
|
|
164
|
+
}
|
|
165
|
+
} else if (action === 'search') {
|
|
166
|
+
if (!keyword) {
|
|
167
|
+
throw new Error('Missing keyword parameter for search action');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const apiDebugConfig = loadApiConfig();
|
|
172
|
+
let searchResults = [];
|
|
173
|
+
|
|
174
|
+
if (apiDebugConfig.list && Array.isArray(apiDebugConfig.list)) {
|
|
175
|
+
searchResults = apiDebugConfig.list.filter((item) => {
|
|
176
|
+
const urlMatch = item.url && item.url.toLowerCase().includes(keyword.toLowerCase());
|
|
177
|
+
const descMatch = item.description && item.description.toLowerCase().includes(keyword.toLowerCase());
|
|
178
|
+
return urlMatch || descMatch;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
success: true,
|
|
184
|
+
message: `Found ${searchResults.length} matching API(s) for keyword: "${keyword}"`,
|
|
185
|
+
keyword: keyword,
|
|
186
|
+
results: searchResults,
|
|
187
|
+
totalCount: searchResults.length,
|
|
188
|
+
timestamp: new Date().toISOString()
|
|
189
|
+
};
|
|
190
|
+
} catch (err) {
|
|
191
|
+
throw new Error(`Failed to search APIs: ${err.message}`);
|
|
192
|
+
}
|
|
193
|
+
} else if (action === 'list') {
|
|
194
|
+
try {
|
|
195
|
+
const apiDebugConfig = loadApiConfig();
|
|
196
|
+
const apiList = apiDebugConfig.list || [];
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
success: true,
|
|
200
|
+
message: `Found ${apiList.length} configured API(s)`,
|
|
201
|
+
apis: apiList,
|
|
202
|
+
totalCount: apiList.length,
|
|
203
|
+
config: {
|
|
204
|
+
baseUrl: apiDebugConfig.baseUrl,
|
|
205
|
+
headers: apiDebugConfig.headers
|
|
206
|
+
},
|
|
207
|
+
timestamp: new Date().toISOString()
|
|
208
|
+
};
|
|
209
|
+
} catch (err) {
|
|
210
|
+
throw new Error(`Failed to list APIs: ${err.message}`);
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
throw new Error('Invalid action. Must be "get", "set", "updateBaseUrl", "updateHeaders", "deleteHeader", "addApi", "deleteApi", "search", or "list"');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = api_config;
|