aicodeswitch 3.0.6 → 3.0.8

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 CHANGED
@@ -153,6 +153,11 @@ aicodeswitch内部,会根据“源类型”来转换数据。例如,你的
153
153
  你可以在 aicodeswitch 中集中统一管理 skills,把skills分发给claude code和codex,随时启用和停用skills。
154
154
  另外,你可以基于自然语言搜索skills,找到skill之后,支持一键安装。
155
155
 
156
+ ## MCP管理
157
+
158
+ 你可以在 aicodeswitch 中集中统一管理 MCP,把MCP分发给claude code和codex,随时启用和停用MCP。
159
+ 并且,你还可以通过MCP来实现图像理解。
160
+
156
161
  ## 日志
157
162
 
158
163
  在**日志**页面,您可以查看:
package/bin/cli.js CHANGED
@@ -7,7 +7,7 @@ const commands = {
7
7
  start: require('./start'),
8
8
  stop: require('./stop'),
9
9
  restart: require('./restart'),
10
- update: require('./update'),
10
+ upgrade: require('./upgrade'),
11
11
  restore: require('./restore'),
12
12
  version: require('./version'),
13
13
  ui: require('./ui'),
@@ -22,7 +22,7 @@ Commands:
22
22
  stop Stop the AI Code Switch server
23
23
  restart Restart the AI Code Switch server
24
24
  ui Open the web UI in browser (starts server if needed)
25
- update Update to the latest version and restart
25
+ upgrade Upgrade to the latest version and restart
26
26
  restore Restore original configuration files
27
27
  version Show current version information
28
28
 
@@ -31,7 +31,7 @@ Example:
31
31
  aicos stop
32
32
  aicos restart
33
33
  aicos ui
34
- aicos update
34
+ aicos upgrade
35
35
  aicos restore
36
36
  aicos restore claude-code
37
37
  aicos restore codex
@@ -29,7 +29,7 @@ const getLatestVersion = () => {
29
29
  path: `/${PACKAGE_NAME}`,
30
30
  method: 'GET',
31
31
  headers: {
32
- 'User-Agent': 'aicodeswitch-update'
32
+ 'User-Agent': 'aicodeswitch-upgrade'
33
33
  }
34
34
  };
35
35
 
@@ -142,8 +142,8 @@ const compareVersions = (v1, v2) => {
142
142
  return 0;
143
143
  };
144
144
 
145
- // 主更新逻辑
146
- const update = async () => {
145
+ // 主升级逻辑
146
+ const upgrade = async () => {
147
147
  console.log('\n');
148
148
 
149
149
  const currentVersion = getCurrentVersion();
@@ -180,8 +180,8 @@ const update = async () => {
180
180
  } catch (err) {
181
181
  checkSpinner.fail(chalk.red('Failed to check for updates'));
182
182
  console.log(chalk.yellow(`\nError: ${err.message}\n`));
183
- console.log(chalk.white('You can manually update by running:\n'));
184
- console.log(chalk.cyan(' npm update -g aicodeswitch\n'));
183
+ console.log(chalk.white('You can manually upgrade by running:\n'));
184
+ console.log(chalk.cyan(' npm install -g aicodeswitch\n'));
185
185
  process.exit(1);
186
186
  }
187
187
 
@@ -212,7 +212,7 @@ const update = async () => {
212
212
  chalk.yellow.bold('⬆️ New version available!\n\n') +
213
213
  chalk.white('Current: ') + chalk.gray(currentVersion) + '\n' +
214
214
  chalk.white('Latest: ') + chalk.green.bold(latestVersion) + '\n\n' +
215
- chalk.gray('Preparing to update...'),
215
+ chalk.gray('Preparing to upgrade...'),
216
216
  {
217
217
  padding: 1,
218
218
  margin: 1,
@@ -230,9 +230,9 @@ const update = async () => {
230
230
  console.log(boxen(
231
231
  chalk.yellow.bold('⚠️ Sudo privileges required\n\n') +
232
232
  chalk.white('This operation requires ') + chalk.yellow.bold('sudo') + chalk.white(' privileges.\n\n') +
233
- chalk.white('Please run the following command to update:\n\n') +
233
+ chalk.white('Please run the following command to upgrade:\n\n') +
234
234
  chalk.cyan.bold(' sudo npm install -g ' + PACKAGE_NAME + '@latest\n\n') +
235
- chalk.gray('After updating, run ') + chalk.cyan('aicos restart') + chalk.gray(' to restart the server.'),
235
+ chalk.gray('After upgrading, run ') + chalk.cyan('aicos restart') + chalk.gray(' to restart the server.'),
236
236
  {
237
237
  padding: 1,
238
238
  margin: 1,
@@ -244,26 +244,26 @@ const update = async () => {
244
244
  process.exit(0);
245
245
  }
246
246
 
247
- // 执行更新
248
- const updateSpinner = ora({
249
- text: chalk.cyan('Updating to latest version...'),
247
+ // 执行升级
248
+ const upgradeSpinner = ora({
249
+ text: chalk.cyan('Upgrading to latest version...'),
250
250
  color: 'cyan'
251
251
  }).start();
252
252
 
253
253
  try {
254
254
  await execCommand('npm', ['install', '-g', `${PACKAGE_NAME}@latest`]);
255
- updateSpinner.succeed(chalk.green('Update successful!'));
255
+ upgradeSpinner.succeed(chalk.green('Upgrade successful!'));
256
256
  } catch (err) {
257
- updateSpinner.fail(chalk.red('Update failed'));
258
- console.log(chalk.yellow(`\nUpdate failed with error code ${err.code || 'unknown'}\n`));
259
- console.log(chalk.white('You can try manually updating:\n'));
257
+ upgradeSpinner.fail(chalk.red('Upgrade failed'));
258
+ console.log(chalk.yellow(`\nUpgrade failed with error code ${err.code || 'unknown'}\n`));
259
+ console.log(chalk.white('You can try manually upgrading:\n'));
260
260
  console.log(chalk.cyan(` npm install -g ${PACKAGE_NAME}@latest\n`));
261
261
  process.exit(1);
262
262
  }
263
263
 
264
264
  console.log('');
265
265
  console.log(boxen(
266
- chalk.green.bold('✓ Successfully updated!\n\n') +
266
+ chalk.green.bold('✓ Successfully upgraded!\n\n') +
267
267
  chalk.white('Previous version: ') + chalk.gray(currentVersion) + '\n' +
268
268
  chalk.white('New version: ') + chalk.green.bold(latestVersion),
269
269
  {
@@ -280,4 +280,4 @@ const update = async () => {
280
280
  console.log('\n');
281
281
  };
282
282
 
283
- module.exports = update;
283
+ module.exports = upgrade;
package/bin/version.js CHANGED
@@ -38,7 +38,7 @@ const version = () => {
38
38
  ));
39
39
 
40
40
  console.log(chalk.cyan('💡 Tips:\n'));
41
- console.log(chalk.white(' • Check for updates: ') + chalk.yellow('aicos update'));
41
+ console.log(chalk.white(' • Check for updates: ') + chalk.yellow('aicos upgrade'));
42
42
  console.log(chalk.white(' • Restart server: ') + chalk.yellow('aicos restart\n'));
43
43
 
44
44
  process.exit(0);
@@ -45,6 +45,7 @@ class FileSystemDatabaseManager {
45
45
  get errorLogsFile() { return path_1.default.join(this.dataPath, 'error-logs.json'); }
46
46
  get blacklistFile() { return path_1.default.join(this.dataPath, 'blacklist.json'); }
47
47
  get statisticsFile() { return path_1.default.join(this.dataPath, 'statistics.json'); }
48
+ get mcpFile() { return path_1.default.join(this.dataPath, 'mcps.json'); }
48
49
  // 创建空的统计数据结构
49
50
  createEmptyStatistics() {
50
51
  return {
@@ -130,6 +131,12 @@ class FileSystemDatabaseManager {
130
131
  writable: true,
131
132
  value: new Map()
132
133
  });
134
+ Object.defineProperty(this, "mcps", {
135
+ enumerable: true,
136
+ configurable: true,
137
+ writable: true,
138
+ value: []
139
+ });
133
140
  // 持久化统计数据
134
141
  Object.defineProperty(this, "statistics", {
135
142
  enumerable: true,
@@ -215,6 +222,7 @@ class FileSystemDatabaseManager {
215
222
  this.loadErrorLogs(),
216
223
  this.loadBlacklist(),
217
224
  this.loadStatistics(),
225
+ this.loadMCPs(),
218
226
  ]);
219
227
  });
220
228
  }
@@ -684,6 +692,22 @@ class FileSystemDatabaseManager {
684
692
  yield promises_1.default.writeFile(this.statisticsFile, JSON.stringify(this.statistics, null, 2));
685
693
  });
686
694
  }
695
+ loadMCPs() {
696
+ return __awaiter(this, void 0, void 0, function* () {
697
+ try {
698
+ const data = yield promises_1.default.readFile(this.mcpFile, 'utf-8');
699
+ this.mcps = JSON.parse(data);
700
+ }
701
+ catch (_a) {
702
+ this.mcps = [];
703
+ }
704
+ });
705
+ }
706
+ saveMCPs() {
707
+ return __awaiter(this, void 0, void 0, function* () {
708
+ yield promises_1.default.writeFile(this.mcpFile, JSON.stringify(this.mcps, null, 2));
709
+ });
710
+ }
687
711
  ensureDefaultConfig() {
688
712
  return __awaiter(this, void 0, void 0, function* () {
689
713
  if (!this.config) {
@@ -1263,6 +1287,101 @@ class FileSystemDatabaseManager {
1263
1287
  return count;
1264
1288
  });
1265
1289
  }
1290
+ /**
1291
+ * 搜索请求日志内容
1292
+ * @param query 搜索关键词
1293
+ * @param limit 返回��量限制
1294
+ * @param offset 偏移量
1295
+ * @returns 匹配的日志列表
1296
+ */
1297
+ searchLogs(query_1) {
1298
+ return __awaiter(this, arguments, void 0, function* (query, limit = 100, offset = 0) {
1299
+ const searchQuery = query.toLowerCase().trim();
1300
+ if (!searchQuery) {
1301
+ return this.getLogs(limit, offset);
1302
+ }
1303
+ const allMatches = [];
1304
+ const sortedShards = [...this.logShardsIndex].sort((a, b) => b.endTime - a.endTime);
1305
+ // 遍历所有分片进行搜索
1306
+ for (const shard of sortedShards) {
1307
+ const shardLogs = yield this.loadLogShard(shard.filename);
1308
+ for (const log of shardLogs) {
1309
+ if (this.logMatchesQuery(log, searchQuery)) {
1310
+ allMatches.push(log);
1311
+ }
1312
+ }
1313
+ }
1314
+ // 按时间倒序排列并分页
1315
+ return allMatches
1316
+ .sort((a, b) => b.timestamp - a.timestamp)
1317
+ .slice(offset, offset + limit);
1318
+ });
1319
+ }
1320
+ /**
1321
+ * 搜索请求日志内容数量
1322
+ * @param query 搜索关键词
1323
+ * @returns 匹配的日志数量
1324
+ */
1325
+ searchLogsCount(query) {
1326
+ return __awaiter(this, void 0, void 0, function* () {
1327
+ const searchQuery = query.toLowerCase().trim();
1328
+ if (!searchQuery) {
1329
+ return this.getLogsCount();
1330
+ }
1331
+ let count = 0;
1332
+ for (const shard of this.logShardsIndex) {
1333
+ const shardLogs = yield this.loadLogShard(shard.filename);
1334
+ for (const log of shardLogs) {
1335
+ if (this.logMatchesQuery(log, searchQuery)) {
1336
+ count++;
1337
+ }
1338
+ }
1339
+ }
1340
+ return count;
1341
+ });
1342
+ }
1343
+ /**
1344
+ * 检查日志是否匹配搜索查询
1345
+ */
1346
+ logMatchesQuery(log, query) {
1347
+ // 搜索请求体
1348
+ if (log.body) {
1349
+ const bodyStr = typeof log.body === 'string' ? log.body : JSON.stringify(log.body);
1350
+ if (bodyStr.toLowerCase().includes(query)) {
1351
+ return true;
1352
+ }
1353
+ }
1354
+ // 搜索响应体
1355
+ if (log.responseBody && typeof log.responseBody === 'string') {
1356
+ if (log.responseBody.toLowerCase().includes(query)) {
1357
+ return true;
1358
+ }
1359
+ }
1360
+ // 搜索流式响应块
1361
+ if (log.streamChunks && log.streamChunks.length > 0) {
1362
+ for (const chunk of log.streamChunks) {
1363
+ if (chunk.toLowerCase().includes(query)) {
1364
+ return true;
1365
+ }
1366
+ }
1367
+ }
1368
+ // 搜索错误信息
1369
+ if (log.error && log.error.toLowerCase().includes(query)) {
1370
+ return true;
1371
+ }
1372
+ // 搜索路径
1373
+ if (log.path && log.path.toLowerCase().includes(query)) {
1374
+ return true;
1375
+ }
1376
+ // 搜索模型名称
1377
+ if (log.requestModel && log.requestModel.toLowerCase().includes(query)) {
1378
+ return true;
1379
+ }
1380
+ if (log.targetModel && log.targetModel.toLowerCase().includes(query)) {
1381
+ return true;
1382
+ }
1383
+ return false;
1384
+ }
1266
1385
  // Error log operations
1267
1386
  addErrorLog(log) {
1268
1387
  return __awaiter(this, void 0, void 0, function* () {
@@ -1294,6 +1413,78 @@ class FileSystemDatabaseManager {
1294
1413
  return count;
1295
1414
  });
1296
1415
  }
1416
+ /**
1417
+ * 搜索错误日志内容
1418
+ * @param query 搜索关键词
1419
+ * @param limit 返回数量限制
1420
+ * @param offset 偏移量
1421
+ * @returns 匹配的错误日志列表
1422
+ */
1423
+ searchErrorLogs(query_1) {
1424
+ return __awaiter(this, arguments, void 0, function* (query, limit = 100, offset = 0) {
1425
+ const searchQuery = query.toLowerCase().trim();
1426
+ if (!searchQuery) {
1427
+ return this.getErrorLogs(limit, offset);
1428
+ }
1429
+ const matches = this.errorLogs.filter(log => this.errorLogMatchesQuery(log, searchQuery));
1430
+ return matches
1431
+ .sort((a, b) => b.timestamp - a.timestamp)
1432
+ .slice(offset, offset + limit);
1433
+ });
1434
+ }
1435
+ /**
1436
+ * 搜索错误日志内容数量
1437
+ * @param query 搜索关键词
1438
+ * @returns 匹配的错误日志数量
1439
+ */
1440
+ searchErrorLogsCount(query) {
1441
+ return __awaiter(this, void 0, void 0, function* () {
1442
+ const searchQuery = query.toLowerCase().trim();
1443
+ if (!searchQuery) {
1444
+ return this.getErrorLogsCount();
1445
+ }
1446
+ return this.errorLogs.filter(log => this.errorLogMatchesQuery(log, searchQuery)).length;
1447
+ });
1448
+ }
1449
+ /**
1450
+ * 检查错误日志是否匹配搜索查询
1451
+ */
1452
+ errorLogMatchesQuery(log, query) {
1453
+ // 搜索错误信息
1454
+ if (log.errorMessage && log.errorMessage.toLowerCase().includes(query)) {
1455
+ return true;
1456
+ }
1457
+ // 搜索错误堆栈
1458
+ if (log.errorStack && log.errorStack.toLowerCase().includes(query)) {
1459
+ return true;
1460
+ }
1461
+ // 搜索请求体
1462
+ if (log.requestBody) {
1463
+ const bodyStr = typeof log.requestBody === 'string' ? log.requestBody : JSON.stringify(log.requestBody);
1464
+ if (bodyStr.toLowerCase().includes(query)) {
1465
+ return true;
1466
+ }
1467
+ }
1468
+ // 搜索响应体
1469
+ if (log.responseBody) {
1470
+ const bodyStr = typeof log.responseBody === 'string' ? log.responseBody : JSON.stringify(log.responseBody);
1471
+ if (bodyStr.toLowerCase().includes(query)) {
1472
+ return true;
1473
+ }
1474
+ }
1475
+ // 搜索路径
1476
+ if (log.path && log.path.toLowerCase().includes(query)) {
1477
+ return true;
1478
+ }
1479
+ // 搜索模型名称
1480
+ if (log.requestModel && log.requestModel.toLowerCase().includes(query)) {
1481
+ return true;
1482
+ }
1483
+ if (log.targetModel && log.targetModel.toLowerCase().includes(query)) {
1484
+ return true;
1485
+ }
1486
+ return false;
1487
+ }
1297
1488
  // Service blacklist operations
1298
1489
  isServiceBlacklisted(serviceId, routeId, contentType) {
1299
1490
  return __awaiter(this, void 0, void 0, function* () {
@@ -1442,13 +1633,22 @@ class FileSystemDatabaseManager {
1442
1633
  if (!rule.routeId || typeof rule.routeId !== 'string') {
1443
1634
  return { valid: false, error: `规则[${index}](${rule.id}) 缺少有效的 routeId 字段` };
1444
1635
  }
1445
- if (!rule.targetServiceId || typeof rule.targetServiceId !== 'string') {
1446
- return { valid: false, error: `规则[${index}](${rule.id}) 缺少有效的 targetServiceId 字段` };
1447
- }
1448
1636
  const validContentTypes = ['default', 'background', 'thinking', 'long-context', 'image-understanding', 'model-mapping'];
1449
1637
  if (!rule.contentType || !validContentTypes.includes(rule.contentType)) {
1450
1638
  return { valid: false, error: `规则[${index}](${rule.id}) 的 contentType 无效` };
1451
1639
  }
1640
+ // 如果使用MCP(仅对图像理解类型有效),则不需要验证targetServiceId
1641
+ if (rule.useMCP === true && rule.contentType === 'image-understanding') {
1642
+ if (!rule.mcpId || typeof rule.mcpId !== 'string') {
1643
+ return { valid: false, error: `规则[${index}](${rule.id}) 使用MCP时缺少有效的 mcpId 字段` };
1644
+ }
1645
+ }
1646
+ else {
1647
+ // 不使用MCP时,必须验证targetServiceId
1648
+ if (!rule.targetServiceId || typeof rule.targetServiceId !== 'string') {
1649
+ return { valid: false, error: `规则[${index}](${rule.id}) 缺少有效的 targetServiceId 字段` };
1650
+ }
1651
+ }
1452
1652
  return { valid: true };
1453
1653
  }
1454
1654
  /**
@@ -2027,6 +2227,47 @@ class FileSystemDatabaseManager {
2027
2227
  return entry;
2028
2228
  });
2029
2229
  }
2230
+ // MCP 工具相关操作
2231
+ getMCPs() {
2232
+ return this.mcps.sort((a, b) => b.createdAt - a.createdAt);
2233
+ }
2234
+ getMCP(id) {
2235
+ return this.mcps.find(m => m.id === id);
2236
+ }
2237
+ getMCPsByTarget(targetType) {
2238
+ return this.mcps.filter(m => { var _a; return (_a = m.targets) === null || _a === void 0 ? void 0 : _a.includes(targetType); });
2239
+ }
2240
+ createMCP(mcp) {
2241
+ return __awaiter(this, void 0, void 0, function* () {
2242
+ const id = crypto_1.default.randomUUID();
2243
+ const now = Date.now();
2244
+ const newMCP = Object.assign(Object.assign({}, mcp), { id, createdAt: now, updatedAt: now });
2245
+ this.mcps.push(newMCP);
2246
+ yield this.saveMCPs();
2247
+ return newMCP;
2248
+ });
2249
+ }
2250
+ updateMCP(id, mcp) {
2251
+ return __awaiter(this, void 0, void 0, function* () {
2252
+ const index = this.mcps.findIndex(m => m.id === id);
2253
+ if (index === -1)
2254
+ return false;
2255
+ const now = Date.now();
2256
+ this.mcps[index] = Object.assign(Object.assign(Object.assign({}, this.mcps[index]), mcp), { id, updatedAt: now });
2257
+ yield this.saveMCPs();
2258
+ return true;
2259
+ });
2260
+ }
2261
+ deleteMCP(id) {
2262
+ return __awaiter(this, void 0, void 0, function* () {
2263
+ const index = this.mcps.findIndex(m => m.id === id);
2264
+ if (index === -1)
2265
+ return false;
2266
+ this.mcps.splice(index, 1);
2267
+ yield this.saveMCPs();
2268
+ return true;
2269
+ });
2270
+ }
2030
2271
  // Close method for compatibility (no-op for filesystem database)
2031
2272
  close() {
2032
2273
  // 文件系统数据库不需要关闭连接
@@ -120,7 +120,8 @@ const writeClaudeConfig = (dbManager) => __awaiter(void 0, void 0, void 0, funct
120
120
  ANTHROPIC_AUTH_TOKEN: config.apiKey || "api_key",
121
121
  ANTHROPIC_API_KEY: "",
122
122
  ANTHROPIC_BASE_URL: `http://${host}:${port}/claude-code`,
123
- API_TIMEOUT_MS: "3000000"
123
+ API_TIMEOUT_MS: "3000000",
124
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1
124
125
  }
125
126
  };
126
127
  fs_1.default.writeFileSync(claudeSettingsPath, JSON.stringify(claudeSettings, null, 2));
@@ -777,6 +778,16 @@ const registerRoutes = (dbManager, proxyServer) => {
777
778
  const result = yield dbManager.activateRoute(req.params.id);
778
779
  if (result) {
779
780
  yield proxyServer.reloadRoutes();
781
+ // 激活路由后,同步MCP配置
782
+ const routes = dbManager.getRoutes();
783
+ const route = routes.find(r => r.id === req.params.id);
784
+ if (route) {
785
+ const mcps = dbManager.getMCPs();
786
+ const hasMCPForTarget = mcps.some(m => { var _a; return (_a = m.targets) === null || _a === void 0 ? void 0 : _a.includes(route.targetType); });
787
+ if (hasMCPForTarget) {
788
+ yield writeMCPConfig(route.targetType);
789
+ }
790
+ }
780
791
  }
781
792
  res.json(result);
782
793
  })));
@@ -914,10 +925,38 @@ const registerRoutes = (dbManager, proxyServer) => {
914
925
  const count = yield dbManager.getLogsCount();
915
926
  res.json({ count });
916
927
  })));
928
+ app.get('/api/logs/search', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
929
+ const query = typeof req.query.query === 'string' ? req.query.query : '';
930
+ const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
931
+ const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
932
+ const limit = Number.isFinite(rawLimit) ? rawLimit : 100;
933
+ const offset = Number.isFinite(rawOffset) ? rawOffset : 0;
934
+ const logs = yield dbManager.searchLogs(query, limit, offset);
935
+ res.json(logs);
936
+ })));
937
+ app.get('/api/logs/search/count', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
938
+ const query = typeof req.query.query === 'string' ? req.query.query : '';
939
+ const count = yield dbManager.searchLogsCount(query);
940
+ res.json({ count });
941
+ })));
917
942
  app.get('/api/error-logs/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
918
943
  const count = yield dbManager.getErrorLogsCount();
919
944
  res.json({ count });
920
945
  })));
946
+ app.get('/api/error-logs/search', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
947
+ const query = typeof req.query.query === 'string' ? req.query.query : '';
948
+ const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
949
+ const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
950
+ const limit = Number.isFinite(rawLimit) ? rawLimit : 100;
951
+ const offset = Number.isFinite(rawOffset) ? rawOffset : 0;
952
+ const logs = yield dbManager.searchErrorLogs(query, limit, offset);
953
+ res.json(logs);
954
+ })));
955
+ app.get('/api/error-logs/search/count', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
956
+ const query = typeof req.query.query === 'string' ? req.query.query : '';
957
+ const count = yield dbManager.searchErrorLogsCount(query);
958
+ res.json({ count });
959
+ })));
921
960
  app.get('/api/config', (_req, res) => res.json(dbManager.getConfig()));
922
961
  app.put('/api/config', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
923
962
  const config = req.body;
@@ -1446,6 +1485,132 @@ ${instruction}
1446
1485
  res.status(500).json({ error: '获取工具状态失败' });
1447
1486
  }
1448
1487
  })));
1488
+ // MCP 工具管理相关路由
1489
+ app.get('/api/mcps', (_req, res) => {
1490
+ res.json(dbManager.getMCPs());
1491
+ });
1492
+ app.get('/api/mcps/:id', (req, res) => {
1493
+ const mcp = dbManager.getMCP(req.params.id);
1494
+ if (!mcp) {
1495
+ res.status(404).json({ error: 'MCP工具不存在' });
1496
+ return;
1497
+ }
1498
+ res.json(mcp);
1499
+ });
1500
+ app.post('/api/mcps', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1501
+ const mcpData = req.body;
1502
+ const result = yield dbManager.createMCP(Object.assign(Object.assign({}, mcpData), { targets: mcpData.targets || [] }));
1503
+ // 如果有激活的路由,立即写入MCP配置
1504
+ if (mcpData.targets) {
1505
+ const routes = dbManager.getRoutes();
1506
+ for (const target of mcpData.targets) {
1507
+ const activeRoute = routes.find(r => r.targetType === target && r.isActive);
1508
+ if (activeRoute) {
1509
+ yield writeMCPConfig(target);
1510
+ }
1511
+ }
1512
+ }
1513
+ res.json(result);
1514
+ })));
1515
+ app.put('/api/mcps/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1516
+ const result = yield dbManager.updateMCP(req.params.id, req.body);
1517
+ res.json(result);
1518
+ })));
1519
+ app.delete('/api/mcps/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1520
+ const mcp = dbManager.getMCP(req.params.id);
1521
+ if (!mcp) {
1522
+ res.status(404).json({ error: 'MCP工具不存在' });
1523
+ return;
1524
+ }
1525
+ const result = yield dbManager.deleteMCP(req.params.id);
1526
+ // 从Claude Code和Codex配置中移除该MCP
1527
+ if (mcp.targets) {
1528
+ for (const target of mcp.targets) {
1529
+ yield removeMCPFromConfig(target, mcp.id);
1530
+ }
1531
+ }
1532
+ res.json(result);
1533
+ })));
1534
+ // 写入MCP配置到Claude Code或Codex的全局配置文件
1535
+ const writeMCPConfig = (targetType) => __awaiter(void 0, void 0, void 0, function* () {
1536
+ try {
1537
+ const homeDir = os_1.default.homedir();
1538
+ const mcps = dbManager.getMCPsByTarget(targetType);
1539
+ if (targetType === 'claude-code') {
1540
+ // Claude Code配置文件路径
1541
+ const claudeJsonPath = path_1.default.join(homeDir, '.claude.json');
1542
+ let claudeJson = {};
1543
+ // 读取现有配置
1544
+ if (fs_1.default.existsSync(claudeJsonPath)) {
1545
+ claudeJson = JSON.parse(fs_1.default.readFileSync(claudeJsonPath, 'utf8'));
1546
+ }
1547
+ // 确保mcpServers存在
1548
+ if (!claudeJson.mcpServers) {
1549
+ claudeJson.mcpServers = {};
1550
+ }
1551
+ // 写入所有启用的MCP
1552
+ for (const mcp of mcps) {
1553
+ const mcpConfig = {
1554
+ type: mcp.type,
1555
+ };
1556
+ if (mcp.type === 'stdio') {
1557
+ mcpConfig.command = mcp.command;
1558
+ mcpConfig.args = mcp.args;
1559
+ }
1560
+ else if (mcp.type === 'http') {
1561
+ mcpConfig.url = mcp.url;
1562
+ }
1563
+ else if (mcp.type === 'sse') {
1564
+ mcpConfig.url = mcp.url;
1565
+ }
1566
+ if (mcp.headers && Object.keys(mcp.headers).length > 0) {
1567
+ mcpConfig.headers = mcp.headers;
1568
+ }
1569
+ if (mcp.env && Object.keys(mcp.env).length > 0) {
1570
+ mcpConfig.env = mcp.env;
1571
+ }
1572
+ claudeJson.mcpServers[mcp.id] = mcpConfig;
1573
+ }
1574
+ fs_1.default.writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
1575
+ return true;
1576
+ }
1577
+ else if (targetType === 'codex') {
1578
+ // Codex使用TOML格式,我们暂时不直接写入
1579
+ // 需要后续处理Codex的MCP配置格式
1580
+ // TODO: 实现 Codex MCP 配置写入
1581
+ console.log('[MCP] Codex MCP配置写入暂未实现');
1582
+ return true;
1583
+ }
1584
+ return false;
1585
+ }
1586
+ catch (error) {
1587
+ console.error('Failed to write MCP config:', error);
1588
+ return false;
1589
+ }
1590
+ });
1591
+ // 从配置中移除MCP
1592
+ const removeMCPFromConfig = (targetType, mcpId) => __awaiter(void 0, void 0, void 0, function* () {
1593
+ try {
1594
+ if (targetType === 'claude-code') {
1595
+ const homeDir = os_1.default.homedir();
1596
+ const claudeJsonPath = path_1.default.join(homeDir, '.claude.json');
1597
+ if (!fs_1.default.existsSync(claudeJsonPath)) {
1598
+ return true;
1599
+ }
1600
+ const claudeJson = JSON.parse(fs_1.default.readFileSync(claudeJsonPath, 'utf8'));
1601
+ if (claudeJson.mcpServers && claudeJson.mcpServers[mcpId]) {
1602
+ delete claudeJson.mcpServers[mcpId];
1603
+ fs_1.default.writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
1604
+ }
1605
+ return true;
1606
+ }
1607
+ return false;
1608
+ }
1609
+ catch (error) {
1610
+ console.error('Failed to remove MCP from config:', error);
1611
+ return false;
1612
+ }
1613
+ });
1449
1614
  };
1450
1615
  const start = () => __awaiter(void 0, void 0, void 0, function* () {
1451
1616
  fs_1.default.mkdirSync(dataDir, { recursive: true });