@samanhappy/mcphub 1.1.7 → 1.1.9

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 (140) hide show
  1. package/dist/clients/openapi.js +2 -1
  2. package/dist/clients/openapi.js.map +1 -1
  3. package/dist/controllers/authController.js +27 -1
  4. package/dist/controllers/authController.js.map +1 -1
  5. package/dist/controllers/configController.js +4 -1
  6. package/dist/controllers/configController.js.map +1 -1
  7. package/dist/controllers/oauthServerController.js +12 -14
  8. package/dist/controllers/oauthServerController.js.map +1 -1
  9. package/dist/controllers/promptController.js +2 -2
  10. package/dist/controllers/promptController.js.map +1 -1
  11. package/dist/controllers/serverController.js +201 -12
  12. package/dist/controllers/serverController.js.map +1 -1
  13. package/dist/dao/OAuthTokenDaoDbImpl.js +2 -0
  14. package/dist/dao/OAuthTokenDaoDbImpl.js.map +1 -1
  15. package/dist/dao/ServerDao.js.map +1 -1
  16. package/dist/dao/ServerDaoDbImpl.js.map +1 -1
  17. package/dist/db/entities/OAuthToken.js +4 -0
  18. package/dist/db/entities/OAuthToken.js.map +1 -1
  19. package/dist/db/entities/Server.js.map +1 -1
  20. package/dist/middlewares/auth.js +7 -25
  21. package/dist/middlewares/auth.js.map +1 -1
  22. package/dist/middlewares/userContext.js +6 -34
  23. package/dist/middlewares/userContext.js.map +1 -1
  24. package/dist/models/OAuth.js +14 -93
  25. package/dist/models/OAuth.js.map +1 -1
  26. package/dist/routes/index.js +3 -1
  27. package/dist/routes/index.js.map +1 -1
  28. package/dist/services/betterAuthConfig.js +1 -0
  29. package/dist/services/betterAuthConfig.js.map +1 -1
  30. package/dist/services/contextCostService.js +3 -1
  31. package/dist/services/contextCostService.js.map +1 -1
  32. package/dist/services/credentialBindingService.js +8 -3
  33. package/dist/services/credentialBindingService.js.map +1 -1
  34. package/dist/services/elicitationService.js +93 -0
  35. package/dist/services/elicitationService.js.map +1 -0
  36. package/dist/services/mcpService.js +167 -95
  37. package/dist/services/mcpService.js.map +1 -1
  38. package/dist/services/oauthServerService.js +15 -1
  39. package/dist/services/oauthServerService.js.map +1 -1
  40. package/dist/services/openApiGeneratorService.js +3 -1
  41. package/dist/services/openApiGeneratorService.js.map +1 -1
  42. package/dist/services/personalOAuthService.js +70 -8
  43. package/dist/services/personalOAuthService.js.map +1 -1
  44. package/dist/services/registrationService.js +28 -0
  45. package/dist/services/registrationService.js.map +1 -0
  46. package/dist/services/serverConfigPresenter.js +6 -1
  47. package/dist/services/serverConfigPresenter.js.map +1 -1
  48. package/dist/services/smartRoutingService.js +7 -6
  49. package/dist/services/smartRoutingService.js.map +1 -1
  50. package/dist/services/sseService.js +1 -2
  51. package/dist/services/sseService.js.map +1 -1
  52. package/dist/services/templateService.js +5 -4
  53. package/dist/services/templateService.js.map +1 -1
  54. package/dist/services/userService.js +22 -4
  55. package/dist/services/userService.js.map +1 -1
  56. package/dist/utils/credentialTemplate.js +76 -6
  57. package/dist/utils/credentialTemplate.js.map +1 -1
  58. package/dist/utils/migration.js +1 -0
  59. package/dist/utils/migration.js.map +1 -1
  60. package/dist/utils/namePattern.js +28 -0
  61. package/dist/utils/namePattern.js.map +1 -0
  62. package/dist/utils/oauthBearer.js +4 -2
  63. package/dist/utils/oauthBearer.js.map +1 -1
  64. package/dist/utils/passthroughHeaders.js +17 -0
  65. package/dist/utils/passthroughHeaders.js.map +1 -0
  66. package/dist/utils/rateLimit.js +1 -1
  67. package/dist/utils/rateLimit.js.map +1 -1
  68. package/dist/utils/serverConfigPersistence.js +1 -3
  69. package/dist/utils/serverConfigPersistence.js.map +1 -1
  70. package/dist/utils/serverConfigValidation.js +2 -1
  71. package/dist/utils/serverConfigValidation.js.map +1 -1
  72. package/dist/utils/settingsSync.js +5 -1
  73. package/dist/utils/settingsSync.js.map +1 -1
  74. package/dist/utils/toolConfig.js +93 -0
  75. package/dist/utils/toolConfig.js.map +1 -0
  76. package/dist/utils/upstreamError.js +9 -2
  77. package/dist/utils/upstreamError.js.map +1 -1
  78. package/dist/utils/userSession.js +29 -0
  79. package/dist/utils/userSession.js.map +1 -0
  80. package/frontend/dist/assets/{ActivityPage-BEpHoGLB.js → ActivityPage-CFRX20v4.js} +2 -2
  81. package/frontend/dist/assets/{ActivityPage-BEpHoGLB.js.map → ActivityPage-CFRX20v4.js.map} +1 -1
  82. package/frontend/dist/assets/{CopyClientConfigDialog-B4RDMLQ7.js → CopyClientConfigDialog-CDEiSInj.js} +2 -2
  83. package/frontend/dist/assets/{CopyClientConfigDialog-B4RDMLQ7.js.map → CopyClientConfigDialog-CDEiSInj.js.map} +1 -1
  84. package/frontend/dist/assets/CredentialsPage-BCLHY_fN.js +2 -0
  85. package/frontend/dist/assets/CredentialsPage-BCLHY_fN.js.map +1 -0
  86. package/frontend/dist/assets/{Dashboard-Bp8ZGDp5.js → Dashboard-B3Y6nAFL.js} +2 -2
  87. package/frontend/dist/assets/{Dashboard-Bp8ZGDp5.js.map → Dashboard-B3Y6nAFL.js.map} +1 -1
  88. package/frontend/dist/assets/{EndpointCopy-CH7rfwDL.js → EndpointCopy-BMxT99Ne.js} +2 -2
  89. package/frontend/dist/assets/{EndpointCopy-CH7rfwDL.js.map → EndpointCopy-BMxT99Ne.js.map} +1 -1
  90. package/frontend/dist/assets/GroupsPage-5iCk1LKT.js +33 -0
  91. package/frontend/dist/assets/GroupsPage-5iCk1LKT.js.map +1 -0
  92. package/frontend/dist/assets/LoginPage-BjYzY6RH.js +2 -0
  93. package/frontend/dist/assets/LoginPage-BjYzY6RH.js.map +1 -0
  94. package/frontend/dist/assets/{LogsPage-B4RyT3TS.js → LogsPage-CGcFNKJn.js} +2 -2
  95. package/frontend/dist/assets/{LogsPage-B4RyT3TS.js.map → LogsPage-CGcFNKJn.js.map} +1 -1
  96. package/frontend/dist/assets/{MarketPage-DV7Bs6bj.js → MarketPage-B7JPqFw3.js} +2 -2
  97. package/frontend/dist/assets/{MarketPage-DV7Bs6bj.js.map → MarketPage-B7JPqFw3.js.map} +1 -1
  98. package/frontend/dist/assets/{OAuthConsentPage-DdxMIqDO.js → OAuthConsentPage-BYi_kgNj.js} +2 -2
  99. package/frontend/dist/assets/{OAuthConsentPage-DdxMIqDO.js.map → OAuthConsentPage-BYi_kgNj.js.map} +1 -1
  100. package/frontend/dist/assets/{PromptsPage-61Uj8gCp.js → PromptsPage-x6X5trLX.js} +2 -2
  101. package/frontend/dist/assets/{PromptsPage-61Uj8gCp.js.map → PromptsPage-x6X5trLX.js.map} +1 -1
  102. package/frontend/dist/assets/{ResourcesPage-BSgOwVld.js → ResourcesPage-BJZCPBs8.js} +2 -2
  103. package/frontend/dist/assets/{ResourcesPage-BSgOwVld.js.map → ResourcesPage-BJZCPBs8.js.map} +1 -1
  104. package/frontend/dist/assets/ServersPage-CsyGLden.js +39 -0
  105. package/frontend/dist/assets/ServersPage-CsyGLden.js.map +1 -0
  106. package/frontend/dist/assets/{SettingsPage-C8zYH_jq.js → SettingsPage-C53DI0Sz.js} +6 -6
  107. package/frontend/dist/assets/SettingsPage-C53DI0Sz.js.map +1 -0
  108. package/frontend/dist/assets/{StatusDot-R86yLB-f.js → StatusDot-BDJd3Iix.js} +2 -2
  109. package/frontend/dist/assets/{StatusDot-R86yLB-f.js.map → StatusDot-BDJd3Iix.js.map} +1 -1
  110. package/frontend/dist/assets/{ToggleGroup-DN1ttokb.js → ToggleGroup-BbmRhILg.js} +2 -2
  111. package/frontend/dist/assets/{ToggleGroup-DN1ttokb.js.map → ToggleGroup-BbmRhILg.js.map} +1 -1
  112. package/frontend/dist/assets/{UsersPage-BCMjm9JG.js → UsersPage-NUG6vF8C.js} +2 -2
  113. package/frontend/dist/assets/{UsersPage-BCMjm9JG.js.map → UsersPage-NUG6vF8C.js.map} +1 -1
  114. package/frontend/dist/assets/{clipboard-DjzVWhkB.js → clipboard-7BggsilD.js} +2 -2
  115. package/frontend/dist/assets/{clipboard-DjzVWhkB.js.map → clipboard-7BggsilD.js.map} +1 -1
  116. package/frontend/dist/assets/index-B_IITM9e.js +3 -0
  117. package/frontend/dist/assets/index-B_IITM9e.js.map +1 -0
  118. package/frontend/dist/assets/index-CnqLKewL.css +1 -0
  119. package/frontend/dist/assets/{resourceService-DRZl8_Vm.js → resourceService-B674Fopr.js} +2 -2
  120. package/frontend/dist/assets/{resourceService-DRZl8_Vm.js.map → resourceService-B674Fopr.js.map} +1 -1
  121. package/frontend/dist/assets/{useCostData-DVLGVcuy.js → useCostData-8Z1HjAL5.js} +2 -2
  122. package/frontend/dist/assets/{useCostData-DVLGVcuy.js.map → useCostData-8Z1HjAL5.js.map} +1 -1
  123. package/frontend/dist/assets/variableDetection-C0nP3Jj2.js +18 -0
  124. package/frontend/dist/assets/variableDetection-C0nP3Jj2.js.map +1 -0
  125. package/frontend/dist/index.html +2 -2
  126. package/package.json +2 -2
  127. package/frontend/dist/assets/CredentialsPage-46Z8Qv7O.js +0 -2
  128. package/frontend/dist/assets/CredentialsPage-46Z8Qv7O.js.map +0 -1
  129. package/frontend/dist/assets/GroupsPage-DTjOja6T.js +0 -33
  130. package/frontend/dist/assets/GroupsPage-DTjOja6T.js.map +0 -1
  131. package/frontend/dist/assets/LoginPage-o6ibb_j3.js +0 -2
  132. package/frontend/dist/assets/LoginPage-o6ibb_j3.js.map +0 -1
  133. package/frontend/dist/assets/ServersPage-DYumMhzV.js +0 -39
  134. package/frontend/dist/assets/ServersPage-DYumMhzV.js.map +0 -1
  135. package/frontend/dist/assets/SettingsPage-C8zYH_jq.js.map +0 -1
  136. package/frontend/dist/assets/index--NhF_yyy.css +0 -1
  137. package/frontend/dist/assets/index-P_O2kAcB.js +0 -3
  138. package/frontend/dist/assets/index-P_O2kAcB.js.map +0 -1
  139. package/frontend/dist/assets/variableDetection-DiE4h0o7.js +0 -16
  140. package/frontend/dist/assets/variableDetection-DiE4h0o7.js.map +0 -1
@@ -1,9 +1,11 @@
1
+ import { hasPersonalAuthentication } from '../utils/credentialTemplate.js';
2
+ import { matchesNamePattern } from '../utils/namePattern.js';
1
3
  import { validateAllowedCidrs } from '../utils/ssrf.js';
2
4
  import { copyCredentialBindings, deleteCredentialBindings, } from '../services/credentialBindingService.js';
3
5
  import { StdioOptionsError } from '../utils/stdioOptions.js';
4
6
  import { CredentialBindingError } from '../utils/credentialTemplate.js';
5
7
  import { isDeepStrictEqual } from 'node:util';
6
- import { getServersInfo, addServer, addOrUpdateServer, removeServer, closeServer, getServerByName, notifyToolChanged, broadcastToolListChanged, broadcastPromptListChanged, broadcastResourceListChanged, syncToolEmbedding, toggleServerStatus, reconnectServer, reinstallServer, updateServerInfoVisibility, } from '../services/mcpService.js';
8
+ import { getServersInfo, getServerToolsForPrincipal, addServer, addOrUpdateServer, removeServer, closeServer, getServerByName, notifyToolChanged, broadcastToolListChanged, broadcastPromptListChanged, broadcastResourceListChanged, syncToolEmbedding, toggleServerStatus, reconnectServer, reinstallServer, updateServerInfoVisibility, } from '../services/mcpService.js';
7
9
  import { clearAllCaches } from '../utils/cacheUtils.js';
8
10
  import { removeServerToolEmbeddings, syncAllServerToolsEmbeddings, } from '../services/vectorSearchService.js';
9
11
  import { createSafeJSON } from '../utils/serialization.js';
@@ -24,6 +26,9 @@ import { getSmartRoutingConfig, parseBooleanEnvVar, parseFullSchemaTopN, parseSi
24
26
  import { TOOL_DEFINITION_FIELDS } from '../constants/smartRoutingDefaults.js';
25
27
  import { previewOpenApiToolStats } from '../services/openApiToolStatsService.js';
26
28
  import { logger } from '../utils/logger.js';
29
+ import { findToolConfigKey, resolveRuntimeToolName } from '../utils/toolConfig.js';
30
+ // Runtime names of a connected server's tools, to resolve which tool a request means
31
+ const liveToolNames = (serverName) => (getServerByName(serverName)?.tools ?? []).map((tool) => tool.name);
27
32
  const getRequestUser = (req) => {
28
33
  return req.user || null;
29
34
  };
@@ -65,7 +70,9 @@ const ensureNonAdminCanManageConfig = (req, res, config) => {
65
70
  success: false,
66
71
  message: config.oauth?.allowInsecureTokenEndpoint === true
67
72
  ? 'Only admins can create or modify servers allowing HTTP OAuth token endpoints'
68
- : 'Only admins can create or modify stdio-based servers',
73
+ : config.passthroughHeaders?.length || config.openapi?.passthroughHeaders?.length
74
+ ? 'Only admins can create or modify servers forwarding caller headers'
75
+ : 'Only admins can create or modify stdio-based servers',
69
76
  });
70
77
  return false;
71
78
  }
@@ -89,8 +96,10 @@ const clearDescriptionOverride = (items, itemName) => {
89
96
  return nextItems;
90
97
  }
91
98
  const { description: _description, ...remainingConfig } = itemConfig;
92
- if (remainingConfig.enabled === false) {
93
- nextItems[itemName] = { enabled: false };
99
+ // Keep the entry while it still says something: a disable or hidden parameters.
100
+ if (remainingConfig.enabled === false ||
101
+ Object.keys(remainingConfig).some((key) => key !== 'enabled')) {
102
+ nextItems[itemName] = remainingConfig;
94
103
  }
95
104
  else {
96
105
  delete nextItems[itemName];
@@ -645,7 +654,10 @@ export const batchCreateServers = async (req, res) => {
645
654
  success: false,
646
655
  message: normalizedConfig.oauth?.allowInsecureTokenEndpoint === true
647
656
  ? 'Only admins can create or modify servers allowing HTTP OAuth token endpoints'
648
- : 'Only admins can create or modify stdio-based servers',
657
+ : normalizedConfig.passthroughHeaders?.length ||
658
+ normalizedConfig.openapi?.passthroughHeaders?.length
659
+ ? 'Only admins can create or modify servers forwarding caller headers'
660
+ : 'Only admins can create or modify stdio-based servers',
649
661
  });
650
662
  failureCount++;
651
663
  continue;
@@ -873,7 +885,9 @@ export const updateServer = async (req, res) => {
873
885
  if (!existingServer) {
874
886
  return;
875
887
  }
876
- if ((existingServer.oauth?.allowInsecureTokenEndpoint === true &&
888
+ if (((existingServer.oauth?.allowInsecureTokenEndpoint === true ||
889
+ Boolean(existingServer.passthroughHeaders?.length ||
890
+ existingServer.openapi?.passthroughHeaders?.length)) &&
877
891
  !ensureNonAdminCanManageConfig(req, res, existingServer)) ||
878
892
  !ensureNonAdminCanManageConfig(req, res, normalizedConfig)) {
879
893
  return;
@@ -1292,6 +1306,87 @@ export const clearCache = async (req, res) => {
1292
1306
  });
1293
1307
  }
1294
1308
  };
1309
+ // Set the status of currently discovered tools in one update.
1310
+ export const batchToggleTools = async (req, res) => {
1311
+ try {
1312
+ const serverName = req.params.serverName;
1313
+ const { enabled, tools: patterns } = (req.body ?? {});
1314
+ if (typeof enabled !== 'boolean' ||
1315
+ !Array.isArray(patterns) ||
1316
+ patterns.length === 0 ||
1317
+ patterns.length > 100 ||
1318
+ patterns.some((pattern) => typeof pattern !== 'string' || !pattern.trim() || pattern.length > 256)) {
1319
+ res
1320
+ .status(400)
1321
+ .json({
1322
+ success: false,
1323
+ message: 'Provide enabled (boolean) and 1–100 non-empty tool names or patterns (up to 256 characters each)',
1324
+ });
1325
+ return;
1326
+ }
1327
+ const server = await loadAuthorizedServer(req, res, serverName);
1328
+ if (!server)
1329
+ return;
1330
+ const liveServer = getServerByName(serverName);
1331
+ let inventory = liveServer?.tools;
1332
+ if (hasPersonalAuthentication(server)) {
1333
+ const principal = getRequestUser(req);
1334
+ // Never borrow another user's inventory or fall back to the global placeholder.
1335
+ if (!principal) {
1336
+ res.status(403).json({ success: false, message: 'Forbidden' });
1337
+ return;
1338
+ }
1339
+ try {
1340
+ inventory = await getServerToolsForPrincipal(serverName, principal);
1341
+ }
1342
+ catch {
1343
+ res.status(409).json({
1344
+ success: false,
1345
+ message: 'Personal tool inventory is unavailable; connect with your personal authentication first',
1346
+ });
1347
+ return;
1348
+ }
1349
+ }
1350
+ if (!inventory) {
1351
+ res
1352
+ .status(409)
1353
+ .json({
1354
+ success: false,
1355
+ message: 'Server tool inventory is unavailable; connect the server first',
1356
+ });
1357
+ return;
1358
+ }
1359
+ const runtimeNames = inventory.map((tool) => tool.name);
1360
+ const prefix = `${serverName}${getNameSeparator()}`;
1361
+ const matchedTools = runtimeNames.filter((name) => patterns.some((pattern) => {
1362
+ // Exact names follow the same runtime-first resolution as the single-tool API.
1363
+ if (!pattern.includes('*') && !pattern.includes('?')) {
1364
+ return resolveRuntimeToolName(serverName, pattern, runtimeNames) === name;
1365
+ }
1366
+ const bareName = name.startsWith(prefix) ? name.slice(prefix.length) : name;
1367
+ return matchesNamePattern(name, pattern) || matchesNamePattern(bareName, pattern);
1368
+ }));
1369
+ if (matchedTools.length) {
1370
+ const tools = { ...server.tools };
1371
+ for (const name of matchedTools) {
1372
+ const key = findToolConfigKey(tools, serverName, name, runtimeNames);
1373
+ tools[key] = { ...tools[key], enabled };
1374
+ }
1375
+ if (!(await getServerDao().updateTools(serverName, tools))) {
1376
+ res.status(500).json({ success: false, message: 'Failed to save settings' });
1377
+ return;
1378
+ }
1379
+ if (liveServer?.config)
1380
+ liveServer.config = { ...liveServer.config, tools };
1381
+ notifyToolChanged();
1382
+ }
1383
+ const data = { matchedTools, enabled };
1384
+ res.json({ success: true, data });
1385
+ }
1386
+ catch {
1387
+ res.status(500).json({ success: false, message: 'Internal server error' });
1388
+ }
1389
+ };
1295
1390
  // Toggle tool status for a specific server
1296
1391
  export const toggleTool = async (req, res) => {
1297
1392
  try {
@@ -1320,8 +1415,9 @@ export const toggleTool = async (req, res) => {
1320
1415
  const serverDao = getServerDao();
1321
1416
  // Initialize tools config if it doesn't exist
1322
1417
  const tools = server.tools || {};
1323
- // Set the tool's enabled state (preserve existing description if any)
1324
- tools[toolName] = { ...tools[toolName], enabled };
1418
+ // Set the tool's enabled state in its existing entry (keeps description and hidden parameters)
1419
+ const configKey = findToolConfigKey(tools, serverName, toolName, liveToolNames(serverName));
1420
+ tools[configKey] = { ...tools[configKey], enabled };
1325
1421
  // Update via DAO (supports both file and database modes)
1326
1422
  const result = await serverDao.updateTools(serverName, tools);
1327
1423
  if (!result) {
@@ -1385,10 +1481,11 @@ export const updateToolDescription = async (req, res) => {
1385
1481
  // Initialize tools config if it doesn't exist
1386
1482
  const tools = server.tools || {};
1387
1483
  // Set the tool's description
1388
- if (!tools[toolName]) {
1389
- tools[toolName] = { enabled: true };
1484
+ const configKey = findToolConfigKey(tools, serverName, toolName, liveToolNames(serverName));
1485
+ if (!tools[configKey]) {
1486
+ tools[configKey] = { enabled: true };
1390
1487
  }
1391
- tools[toolName].description = description;
1488
+ tools[configKey].description = description;
1392
1489
  // Update via DAO (supports both file and database modes)
1393
1490
  const result = await serverDao.updateTools(serverName, tools);
1394
1491
  if (!result) {
@@ -1420,6 +1517,94 @@ export const updateToolDescription = async (req, res) => {
1420
1517
  });
1421
1518
  }
1422
1519
  };
1520
+ // Set the top-level input parameters of a tool that models do not see. An
1521
+ // empty list removes the setting.
1522
+ export const updateToolHiddenParams = async (req, res) => {
1523
+ try {
1524
+ const serverName = decodeURIComponent(req.params.serverName);
1525
+ const toolName = decodeURIComponent(req.params.toolName);
1526
+ const { hiddenParams } = req.body;
1527
+ if (!serverName || !toolName || ['__proto__', 'constructor', 'prototype'].includes(toolName)) {
1528
+ res.status(400).json({
1529
+ success: false,
1530
+ message: 'Server name and tool name are required',
1531
+ });
1532
+ return;
1533
+ }
1534
+ if (!Array.isArray(hiddenParams) ||
1535
+ !hiddenParams.every((name) => typeof name === 'string' && name.length > 0)) {
1536
+ res.status(400).json({
1537
+ success: false,
1538
+ message: 'hiddenParams must be an array of parameter names',
1539
+ });
1540
+ return;
1541
+ }
1542
+ const server = await loadAuthorizedServer(req, res, serverName);
1543
+ if (!server) {
1544
+ return;
1545
+ }
1546
+ // Checked against the live schema when the tool is known; a disconnected
1547
+ // server's settings are stored as given, and required ones are never hidden.
1548
+ // The dashboard sends the runtime (prefixed) name, a direct caller may send
1549
+ // the bare one.
1550
+ const runtimeName = resolveRuntimeToolName(serverName, toolName, liveToolNames(serverName));
1551
+ const liveTool = getServerByName(serverName)?.tools?.find((tool) => tool.name === runtimeName);
1552
+ if (liveTool) {
1553
+ const schema = liveTool.inputSchema;
1554
+ const required = Array.isArray(schema.required) ? schema.required : [];
1555
+ const properties = schema.properties ?? {};
1556
+ const invalid = hiddenParams.find((name) => required.includes(name) || !Object.prototype.hasOwnProperty.call(properties, name));
1557
+ if (invalid) {
1558
+ res.status(400).json({
1559
+ success: false,
1560
+ message: required.includes(invalid)
1561
+ ? `Parameter '${invalid}' is required and cannot be hidden`
1562
+ : `Tool ${toolName} has no parameter '${invalid}'`,
1563
+ });
1564
+ return;
1565
+ }
1566
+ }
1567
+ // Write into the entry that already exists under either name, so a bare-name
1568
+ // description and a prefixed-name hiddenParams never shadow each other.
1569
+ const tools = { ...(server.tools || {}) };
1570
+ const configKey = findToolConfigKey(tools, serverName, toolName, liveToolNames(serverName));
1571
+ const { hiddenParams: _previous, ...toolConfig } = tools[configKey] ?? { enabled: true };
1572
+ const nextConfig = hiddenParams.length
1573
+ ? { ...toolConfig, hiddenParams: [...new Set(hiddenParams)] }
1574
+ : toolConfig;
1575
+ if (nextConfig.enabled !== false && Object.keys(nextConfig).every((key) => key === 'enabled')) {
1576
+ delete tools[configKey];
1577
+ }
1578
+ else {
1579
+ tools[configKey] = nextConfig;
1580
+ }
1581
+ const result = await getServerDao().updateTools(serverName, tools);
1582
+ if (!result) {
1583
+ res.status(500).json({
1584
+ success: false,
1585
+ message: 'Failed to save settings',
1586
+ });
1587
+ return;
1588
+ }
1589
+ const liveServer = getServerByName(serverName);
1590
+ if (liveServer?.config) {
1591
+ liveServer.config = { ...liveServer.config, tools };
1592
+ }
1593
+ notifyToolChanged();
1594
+ syncToolEmbedding(serverName, toolName);
1595
+ res.json({
1596
+ success: true,
1597
+ data: { hiddenParams: tools[configKey]?.hiddenParams ?? [] },
1598
+ message: `Tool ${toolName} hidden parameters updated successfully`,
1599
+ });
1600
+ }
1601
+ catch (error) {
1602
+ res.status(500).json({
1603
+ success: false,
1604
+ message: 'Internal server error',
1605
+ });
1606
+ }
1607
+ };
1423
1608
  // Reset tool description override for a specific server back to the upstream default
1424
1609
  export const resetToolDescription = async (req, res) => {
1425
1610
  try {
@@ -1437,7 +1622,7 @@ export const resetToolDescription = async (req, res) => {
1437
1622
  return;
1438
1623
  }
1439
1624
  const serverDao = getServerDao();
1440
- const tools = clearDescriptionOverride(server.tools || {}, toolName);
1625
+ const tools = clearDescriptionOverride(server.tools || {}, findToolConfigKey(server.tools, serverName, toolName, liveToolNames(serverName)));
1441
1626
  const result = await serverDao.updateTools(serverName, tools);
1442
1627
  if (!result) {
1443
1628
  res.status(500).json({
@@ -1577,6 +1762,7 @@ export const updateSystemConfig = async (req, res) => {
1577
1762
  !hasSessionRebuildUpdate &&
1578
1763
  !hasOAuthServerUpdate &&
1579
1764
  !hasBetterAuthUpdate &&
1765
+ typeof auth?.allowRegistration !== 'boolean' &&
1580
1766
  !hasActivityLogUpdate &&
1581
1767
  !hasNetworkUpdate) {
1582
1768
  res.status(400).json({
@@ -2053,6 +2239,9 @@ export const updateSystemConfig = async (req, res) => {
2053
2239
  target.dynamicRegistration = dynamicTarget;
2054
2240
  }
2055
2241
  }
2242
+ if (typeof auth?.allowRegistration === 'boolean') {
2243
+ systemConfig.auth.allowRegistration = auth.allowRegistration;
2244
+ }
2056
2245
  if (auth?.betterAuth) {
2057
2246
  const target = systemConfig.auth.betterAuth;
2058
2247
  const providersTarget = target.providers || {};