@samanhappy/mcphub 1.0.31 → 1.0.33

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 (311) hide show
  1. package/README.md +11 -1
  2. package/README.zh.md +12 -2
  3. package/dist/betterAuth.js +13 -5
  4. package/dist/betterAuth.js.map +1 -1
  5. package/dist/cli/commands/cache.js.map +1 -1
  6. package/dist/cli/commands/call.js +2 -2
  7. package/dist/cli/commands/call.js.map +1 -1
  8. package/dist/cli/commands/config.js.map +1 -1
  9. package/dist/cli/commands/discover.js.map +1 -1
  10. package/dist/cli/commands/export.js.map +1 -1
  11. package/dist/cli/commands/groups.js.map +1 -1
  12. package/dist/cli/commands/install.js +1 -1
  13. package/dist/cli/commands/install.js.map +1 -1
  14. package/dist/cli/commands/keys.js.map +1 -1
  15. package/dist/cli/commands/login.js.map +1 -1
  16. package/dist/cli/commands/servers.js.map +1 -1
  17. package/dist/cli/commands/tools.js +2 -2
  18. package/dist/cli/commands/tools.js.map +1 -1
  19. package/dist/cli/context.js +1 -1
  20. package/dist/cli/context.js.map +1 -1
  21. package/dist/cli/http.js +3 -1
  22. package/dist/cli/http.js.map +1 -1
  23. package/dist/clients/openapi.js +498 -76
  24. package/dist/clients/openapi.js.map +1 -1
  25. package/dist/config/DaoConfigService.js +3 -2
  26. package/dist/config/DaoConfigService.js.map +1 -1
  27. package/dist/config/configManager.js +9 -8
  28. package/dist/config/configManager.js.map +1 -1
  29. package/dist/config/index.js +54 -8
  30. package/dist/config/index.js.map +1 -1
  31. package/dist/config/jwt.js +2 -1
  32. package/dist/config/jwt.js.map +1 -1
  33. package/dist/config/migrationUtils.js +46 -45
  34. package/dist/config/migrationUtils.js.map +1 -1
  35. package/dist/constants/oauthServerDefaults.js +5 -0
  36. package/dist/constants/oauthServerDefaults.js.map +1 -1
  37. package/dist/controllers/activityController.js +6 -5
  38. package/dist/controllers/activityController.js.map +1 -1
  39. package/dist/controllers/authController.js +5 -4
  40. package/dist/controllers/authController.js.map +1 -1
  41. package/dist/controllers/bearerKeyController.js +7 -6
  42. package/dist/controllers/bearerKeyController.js.map +1 -1
  43. package/dist/controllers/betterAuthController.js +2 -1
  44. package/dist/controllers/betterAuthController.js.map +1 -1
  45. package/dist/controllers/builtinPromptController.js +13 -5
  46. package/dist/controllers/builtinPromptController.js.map +1 -1
  47. package/dist/controllers/builtinResourceController.js +14 -6
  48. package/dist/controllers/builtinResourceController.js.map +1 -1
  49. package/dist/controllers/changelogController.js +5 -2
  50. package/dist/controllers/changelogController.js.map +1 -1
  51. package/dist/controllers/cloudController.js +11 -10
  52. package/dist/controllers/cloudController.js.map +1 -1
  53. package/dist/controllers/configController.js +4 -3
  54. package/dist/controllers/configController.js.map +1 -1
  55. package/dist/controllers/contextCostController.js +3 -2
  56. package/dist/controllers/contextCostController.js.map +1 -1
  57. package/dist/controllers/groupController.js +2 -1
  58. package/dist/controllers/groupController.js.map +1 -1
  59. package/dist/controllers/healthController.js +2 -1
  60. package/dist/controllers/healthController.js.map +1 -1
  61. package/dist/controllers/hostedInternalController.js +2 -1
  62. package/dist/controllers/hostedInternalController.js.map +1 -1
  63. package/dist/controllers/logController.js +5 -4
  64. package/dist/controllers/logController.js.map +1 -1
  65. package/dist/controllers/mcpbController.js +23 -20
  66. package/dist/controllers/mcpbController.js.map +1 -1
  67. package/dist/controllers/oauthCallbackController.js +54 -27
  68. package/dist/controllers/oauthCallbackController.js.map +1 -1
  69. package/dist/controllers/oauthClientController.js +7 -6
  70. package/dist/controllers/oauthClientController.js.map +1 -1
  71. package/dist/controllers/oauthDynamicRegistrationController.js +22 -4
  72. package/dist/controllers/oauthDynamicRegistrationController.js.map +1 -1
  73. package/dist/controllers/oauthServerController.js +62 -15
  74. package/dist/controllers/oauthServerController.js.map +1 -1
  75. package/dist/controllers/openApiController.js +7 -6
  76. package/dist/controllers/openApiController.js.map +1 -1
  77. package/dist/controllers/promptController.js +2 -1
  78. package/dist/controllers/promptController.js.map +1 -1
  79. package/dist/controllers/registryController.js +4 -3
  80. package/dist/controllers/registryController.js.map +1 -1
  81. package/dist/controllers/serverController.js +154 -65
  82. package/dist/controllers/serverController.js.map +1 -1
  83. package/dist/controllers/templateController.js +3 -2
  84. package/dist/controllers/templateController.js.map +1 -1
  85. package/dist/controllers/toolController.js +2 -1
  86. package/dist/controllers/toolController.js.map +1 -1
  87. package/dist/dao/DaoFactory.js +3 -2
  88. package/dist/dao/DaoFactory.js.map +1 -1
  89. package/dist/dao/ServerDao.js.map +1 -1
  90. package/dist/dao/UserConfigDaoDbImpl.js.map +1 -1
  91. package/dist/dao/UserDao.js.map +1 -1
  92. package/dist/dao/base/JsonFileBaseDao.js +3 -2
  93. package/dist/dao/base/JsonFileBaseDao.js.map +1 -1
  94. package/dist/dao/examples.js +35 -34
  95. package/dist/dao/examples.js.map +1 -1
  96. package/dist/db/connection.js +59 -58
  97. package/dist/db/connection.js.map +1 -1
  98. package/dist/db/index.js +2 -1
  99. package/dist/db/index.js.map +1 -1
  100. package/dist/db/repositories/ServerRepository.js +3 -3
  101. package/dist/db/repositories/ServerRepository.js.map +1 -1
  102. package/dist/db/repositories/SystemConfigRepository.js +2 -1
  103. package/dist/db/repositories/SystemConfigRepository.js.map +1 -1
  104. package/dist/db/repositories/UserConfigRepository.js.map +1 -1
  105. package/dist/db/repositories/VectorEmbeddingRepository.js +6 -5
  106. package/dist/db/repositories/VectorEmbeddingRepository.js.map +1 -1
  107. package/dist/index.js +28 -19
  108. package/dist/index.js.map +1 -1
  109. package/dist/middlewares/auth.js +4 -3
  110. package/dist/middlewares/auth.js.map +1 -1
  111. package/dist/middlewares/index.js +2 -1
  112. package/dist/middlewares/index.js.map +1 -1
  113. package/dist/middlewares/userContext.js +7 -6
  114. package/dist/middlewares/userContext.js.map +1 -1
  115. package/dist/models/OAuth.js +7 -6
  116. package/dist/models/OAuth.js.map +1 -1
  117. package/dist/models/User.js +13 -12
  118. package/dist/models/User.js.map +1 -1
  119. package/dist/routes/index.js +11 -10
  120. package/dist/routes/index.js.map +1 -1
  121. package/dist/scripts/dao-demo.js +28 -27
  122. package/dist/scripts/dao-demo.js.map +1 -1
  123. package/dist/server.js +36 -30
  124. package/dist/server.js.map +1 -1
  125. package/dist/services/activityLoggingService.js +3 -2
  126. package/dist/services/activityLoggingService.js.map +1 -1
  127. package/dist/services/authorizationService.js +28 -0
  128. package/dist/services/authorizationService.js.map +1 -0
  129. package/dist/services/betterAuthConfig.js +1 -4
  130. package/dist/services/betterAuthConfig.js.map +1 -1
  131. package/dist/services/betterAuthSession.js +8 -7
  132. package/dist/services/betterAuthSession.js.map +1 -1
  133. package/dist/services/changelogService.js +5 -2
  134. package/dist/services/changelogService.js.map +1 -1
  135. package/dist/services/cimdClientService.js +195 -0
  136. package/dist/services/cimdClientService.js.map +1 -0
  137. package/dist/services/cloudService.js +10 -9
  138. package/dist/services/cloudService.js.map +1 -1
  139. package/dist/services/contextCostService.js +6 -2
  140. package/dist/services/contextCostService.js.map +1 -1
  141. package/dist/services/groupService.js +8 -7
  142. package/dist/services/groupService.js.map +1 -1
  143. package/dist/services/hostedAuthService.js +3 -2
  144. package/dist/services/hostedAuthService.js.map +1 -1
  145. package/dist/services/hostedControlPlaneClient.js.map +1 -1
  146. package/dist/services/hostedEventSubscriber.js +9 -8
  147. package/dist/services/hostedEventSubscriber.js.map +1 -1
  148. package/dist/services/hostedRuntimeCatalogNames.js +1 -3
  149. package/dist/services/hostedRuntimeCatalogNames.js.map +1 -1
  150. package/dist/services/keepAliveService.js +7 -6
  151. package/dist/services/keepAliveService.js.map +1 -1
  152. package/dist/services/marketService.js +2 -1
  153. package/dist/services/marketService.js.map +1 -1
  154. package/dist/services/mcpOAuthProvider.js +22 -21
  155. package/dist/services/mcpOAuthProvider.js.map +1 -1
  156. package/dist/services/mcpService.js +239 -130
  157. package/dist/services/mcpService.js.map +1 -1
  158. package/dist/services/oauthClientRegistration.js +30 -29
  159. package/dist/services/oauthClientRegistration.js.map +1 -1
  160. package/dist/services/oauthServerService.js +39 -18
  161. package/dist/services/oauthServerService.js.map +1 -1
  162. package/dist/services/oauthService.js +16 -15
  163. package/dist/services/oauthService.js.map +1 -1
  164. package/dist/services/oauthSettingsStore.js +4 -3
  165. package/dist/services/oauthSettingsStore.js.map +1 -1
  166. package/dist/services/openApiGeneratorService.js +1 -3
  167. package/dist/services/openApiGeneratorService.js.map +1 -1
  168. package/dist/services/openApiToolStatsService.js +37 -0
  169. package/dist/services/openApiToolStatsService.js.map +1 -0
  170. package/dist/services/registry.js +2 -1
  171. package/dist/services/registry.js.map +1 -1
  172. package/dist/services/requestContextService.js.map +1 -1
  173. package/dist/services/serverConfigPresenter.js +85 -0
  174. package/dist/services/serverConfigPresenter.js.map +1 -0
  175. package/dist/services/smartRoutingService.js +6 -5
  176. package/dist/services/smartRoutingService.js.map +1 -1
  177. package/dist/services/sseService.js +52 -51
  178. package/dist/services/sseService.js.map +1 -1
  179. package/dist/services/templateService.js +105 -23
  180. package/dist/services/templateService.js.map +1 -1
  181. package/dist/services/toolResultCompressionService.js +8 -5
  182. package/dist/services/toolResultCompressionService.js.map +1 -1
  183. package/dist/services/upstreamOAuthDisconnectService.js +9 -6
  184. package/dist/services/upstreamOAuthDisconnectService.js.map +1 -1
  185. package/dist/services/userService.js +5 -4
  186. package/dist/services/userService.js.map +1 -1
  187. package/dist/services/vectorSearchService.js +126 -112
  188. package/dist/services/vectorSearchService.js.map +1 -1
  189. package/dist/utils/cacheUtils.js +5 -4
  190. package/dist/utils/cacheUtils.js.map +1 -1
  191. package/dist/utils/corsOrigin.js +42 -0
  192. package/dist/utils/corsOrigin.js.map +1 -0
  193. package/dist/utils/dbRetry.js +2 -5
  194. package/dist/utils/dbRetry.js.map +1 -1
  195. package/dist/utils/frontendShell.js +2 -1
  196. package/dist/utils/frontendShell.js.map +1 -1
  197. package/dist/utils/i18n.js.map +1 -1
  198. package/dist/utils/logger.js +170 -0
  199. package/dist/utils/logger.js.map +1 -0
  200. package/dist/utils/migration.js +51 -50
  201. package/dist/utils/migration.js.map +1 -1
  202. package/dist/utils/oauthIssuer.js +57 -0
  203. package/dist/utils/oauthIssuer.js.map +1 -0
  204. package/dist/utils/openApiRequestBody.js +220 -0
  205. package/dist/utils/openApiRequestBody.js.map +1 -0
  206. package/dist/utils/parameterConversion.js +2 -1
  207. package/dist/utils/parameterConversion.js.map +1 -1
  208. package/dist/utils/path.js +9 -8
  209. package/dist/utils/path.js.map +1 -1
  210. package/dist/utils/processTree.js +47 -0
  211. package/dist/utils/processTree.js.map +1 -0
  212. package/dist/utils/proxyTrust.js.map +1 -1
  213. package/dist/utils/rateLimit.js +8 -0
  214. package/dist/utils/rateLimit.js.map +1 -1
  215. package/dist/utils/requireAdmin.js +15 -0
  216. package/dist/utils/requireAdmin.js.map +1 -0
  217. package/dist/utils/serverConfigPersistence.js +3 -0
  218. package/dist/utils/serverConfigPersistence.js.map +1 -1
  219. package/dist/utils/serverConfigValidation.js +13 -0
  220. package/dist/utils/serverConfigValidation.js.map +1 -0
  221. package/dist/utils/serverNameValidation.js +56 -0
  222. package/dist/utils/serverNameValidation.js.map +1 -0
  223. package/dist/utils/serverShutdown.js +3 -2
  224. package/dist/utils/serverShutdown.js.map +1 -1
  225. package/dist/utils/smartRouting.js +4 -3
  226. package/dist/utils/smartRouting.js.map +1 -1
  227. package/dist/utils/ssrf.js +23 -10
  228. package/dist/utils/ssrf.js.map +1 -1
  229. package/dist/utils/systemConfigCache.js +40 -2
  230. package/dist/utils/systemConfigCache.js.map +1 -1
  231. package/dist/utils/tokenCost.js.map +1 -1
  232. package/dist/utils/tokenTruncation.js +9 -4
  233. package/dist/utils/tokenTruncation.js.map +1 -1
  234. package/dist/utils/version.js +3 -2
  235. package/dist/utils/version.js.map +1 -1
  236. package/frontend/dist/assets/{ActivityPage-Dx53zxEM.js → ActivityPage-Du9Nh-Jy.js} +2 -2
  237. package/frontend/dist/assets/{ActivityPage-Dx53zxEM.js.map → ActivityPage-Du9Nh-Jy.js.map} +1 -1
  238. package/frontend/dist/assets/ConfirmDialog-3APts2Y9.js +2 -0
  239. package/frontend/dist/assets/ConfirmDialog-3APts2Y9.js.map +1 -0
  240. package/frontend/dist/assets/Dashboard-J4G-AZkI.js +2 -0
  241. package/frontend/dist/assets/{Dashboard-ZYVsSvOw.js.map → Dashboard-J4G-AZkI.js.map} +1 -1
  242. package/frontend/dist/assets/{DeleteDialog-DNe-zTEl.js → DeleteDialog-D9ycSRvn.js} +2 -2
  243. package/frontend/dist/assets/{DeleteDialog-DNe-zTEl.js.map → DeleteDialog-D9ycSRvn.js.map} +1 -1
  244. package/frontend/dist/assets/{EndpointCopy-ngxLPeDF.js → EndpointCopy-DFeLcU9A.js} +2 -2
  245. package/frontend/dist/assets/{EndpointCopy-ngxLPeDF.js.map → EndpointCopy-DFeLcU9A.js.map} +1 -1
  246. package/frontend/dist/assets/GroupsPage-CGMZBiXd.js +33 -0
  247. package/frontend/dist/assets/{GroupsPage-JPtupGpI.js.map → GroupsPage-CGMZBiXd.js.map} +1 -1
  248. package/frontend/dist/assets/{LoginPage-B6rU3Ugr.js → LoginPage-g5pJLBk0.js} +2 -2
  249. package/frontend/dist/assets/{LoginPage-B6rU3Ugr.js.map → LoginPage-g5pJLBk0.js.map} +1 -1
  250. package/frontend/dist/assets/{LogsPage-CXq382fb.js → LogsPage-Bkv7xhNf.js} +2 -2
  251. package/frontend/dist/assets/{LogsPage-CXq382fb.js.map → LogsPage-Bkv7xhNf.js.map} +1 -1
  252. package/frontend/dist/assets/MarketPage-WkLrckIr.js +3 -0
  253. package/frontend/dist/assets/MarketPage-WkLrckIr.js.map +1 -0
  254. package/frontend/dist/assets/{OAuthConsentPage-C-n6tv3-.js → OAuthConsentPage-CA35XoBK.js} +2 -2
  255. package/frontend/dist/assets/{OAuthConsentPage-C-n6tv3-.js.map → OAuthConsentPage-CA35XoBK.js.map} +1 -1
  256. package/frontend/dist/assets/{Pagination-DLSuQ8hw.js → Pagination-Dwko3g48.js} +2 -2
  257. package/frontend/dist/assets/{Pagination-DLSuQ8hw.js.map → Pagination-Dwko3g48.js.map} +1 -1
  258. package/frontend/dist/assets/{PromptsPage-BLpy76K4.js → PromptsPage-BuDdW69R.js} +2 -2
  259. package/frontend/dist/assets/{PromptsPage-BLpy76K4.js.map → PromptsPage-BuDdW69R.js.map} +1 -1
  260. package/frontend/dist/assets/{ResourcesPage-C5nynRqE.js → ResourcesPage-Cu-eD5e2.js} +2 -2
  261. package/frontend/dist/assets/{ResourcesPage-C5nynRqE.js.map → ResourcesPage-Cu-eD5e2.js.map} +1 -1
  262. package/frontend/dist/assets/{ServersPage-Ca5W9cPW.js → ServersPage-B1Np5x3N.js} +8 -8
  263. package/frontend/dist/assets/ServersPage-B1Np5x3N.js.map +1 -0
  264. package/frontend/dist/assets/{SettingsPage-DgFBjp_O.js → SettingsPage-BQN8-ZQh.js} +4 -4
  265. package/frontend/dist/assets/SettingsPage-BQN8-ZQh.js.map +1 -0
  266. package/frontend/dist/assets/{StatusDot-CmSkukx0.js → StatusDot-WyvgqplP.js} +2 -2
  267. package/frontend/dist/assets/{StatusDot-CmSkukx0.js.map → StatusDot-WyvgqplP.js.map} +1 -1
  268. package/frontend/dist/assets/{ToggleGroup-sG_2LQom.js → ToggleGroup-CmeedsZU.js} +2 -2
  269. package/frontend/dist/assets/{ToggleGroup-sG_2LQom.js.map → ToggleGroup-CmeedsZU.js.map} +1 -1
  270. package/frontend/dist/assets/UsersPage-DqDFrIIW.js +2 -0
  271. package/frontend/dist/assets/UsersPage-DqDFrIIW.js.map +1 -0
  272. package/frontend/dist/assets/contextCost-CilWqGWT.js +2 -0
  273. package/frontend/dist/assets/contextCost-CilWqGWT.js.map +1 -0
  274. package/frontend/dist/assets/{framework-vendor-I9Y6AgD5.js → framework-vendor-BHAQX05P.js} +3 -3
  275. package/frontend/dist/assets/{framework-vendor-I9Y6AgD5.js.map → framework-vendor-BHAQX05P.js.map} +1 -1
  276. package/frontend/dist/assets/{i18n-vendor-y9bLzIeh.js → i18n-vendor-CMcB7XUR.js} +2 -2
  277. package/frontend/dist/assets/{i18n-vendor-y9bLzIeh.js.map → i18n-vendor-CMcB7XUR.js.map} +1 -1
  278. package/frontend/dist/assets/{icons-vendor-BFl4A9fq.js → icons-vendor-HZHHWeTa.js} +2 -2
  279. package/frontend/dist/assets/{icons-vendor-BFl4A9fq.js.map → icons-vendor-HZHHWeTa.js.map} +1 -1
  280. package/frontend/dist/assets/index-BvU2-VT8.js +3 -0
  281. package/frontend/dist/assets/{index-CLugsCsy.js.map → index-BvU2-VT8.js.map} +1 -1
  282. package/frontend/dist/assets/index-DtrCVU1h.css +1 -0
  283. package/frontend/dist/assets/passwordValidation-DTLWgUy9.js +2 -0
  284. package/frontend/dist/assets/passwordValidation-DTLWgUy9.js.map +1 -0
  285. package/frontend/dist/assets/{resourceService-33rS8QGJ.js → resourceService-BFH4w47h.js} +2 -2
  286. package/frontend/dist/assets/{resourceService-33rS8QGJ.js.map → resourceService-BFH4w47h.js.map} +1 -1
  287. package/frontend/dist/assets/useCostData-CoM1SxdS.js +2 -0
  288. package/frontend/dist/assets/useCostData-CoM1SxdS.js.map +1 -0
  289. package/frontend/dist/assets/useSettingsData-DKrECrG1.js +2 -0
  290. package/frontend/dist/assets/{useSettingsData-fK2bv9oN.js.map → useSettingsData-DKrECrG1.js.map} +1 -1
  291. package/frontend/dist/assets/variableDetection-CIrYDjrq.js +16 -0
  292. package/frontend/dist/assets/variableDetection-CIrYDjrq.js.map +1 -0
  293. package/frontend/dist/index.html +5 -5
  294. package/package.json +2 -2
  295. package/frontend/dist/assets/ConfirmDialog-CYfXEuyU.js +0 -2
  296. package/frontend/dist/assets/ConfirmDialog-CYfXEuyU.js.map +0 -1
  297. package/frontend/dist/assets/Dashboard-ZYVsSvOw.js +0 -2
  298. package/frontend/dist/assets/GroupsPage-JPtupGpI.js +0 -33
  299. package/frontend/dist/assets/MarketPage-BGnQQyXK.js +0 -3
  300. package/frontend/dist/assets/MarketPage-BGnQQyXK.js.map +0 -1
  301. package/frontend/dist/assets/ServersPage-Ca5W9cPW.js.map +0 -1
  302. package/frontend/dist/assets/SettingsPage-DgFBjp_O.js.map +0 -1
  303. package/frontend/dist/assets/UsersPage-PHkFXLL8.js +0 -2
  304. package/frontend/dist/assets/UsersPage-PHkFXLL8.js.map +0 -1
  305. package/frontend/dist/assets/contextCost-D_iNNbhX.js +0 -2
  306. package/frontend/dist/assets/contextCost-D_iNNbhX.js.map +0 -1
  307. package/frontend/dist/assets/index-CGvImZ3F.css +0 -1
  308. package/frontend/dist/assets/index-CLugsCsy.js +0 -3
  309. package/frontend/dist/assets/useSettingsData-fK2bv9oN.js +0 -2
  310. package/frontend/dist/assets/variableDetection-ByJ_cnxa.js +0 -16
  311. package/frontend/dist/assets/variableDetection-ByJ_cnxa.js.map +0 -1
@@ -6,10 +6,16 @@ import { createSafeJSON } from '../utils/serialization.js';
6
6
  import { cloneDefaultOAuthServerConfig } from '../constants/oauthServerDefaults.js';
7
7
  import { getBearerKeyDao, getGroupDao, getOAuthClientDao, getOAuthTokenDao, getServerDao, getSystemConfigDao, getUserConfigDao, getUserDao, } from '../dao/DaoFactory.js';
8
8
  import { UserContextService } from '../services/userContextService.js';
9
+ import { authorizationService } from '../services/authorizationService.js';
10
+ import { presentServerForPrincipal, presentServerInfoForPrincipal, } from '../services/serverConfigPresenter.js';
9
11
  import { disconnectUpstreamOAuth } from '../services/upstreamOAuthDisconnectService.js';
10
12
  import { normalizeServerConfigForPersistence } from '../utils/serverConfigPersistence.js';
13
+ import { isPrivilegedServerConfig } from '../utils/serverConfigValidation.js';
14
+ import { validateServerName } from '../utils/serverNameValidation.js';
11
15
  import { setCachedSystemConfig } from '../utils/systemConfigCache.js';
12
16
  import { DEFAULT_INSTALL_BASE_URL, withResolvedInstallBaseUrl } from '../utils/installBaseUrl.js';
17
+ import { previewOpenApiToolStats } from '../services/openApiToolStatsService.js';
18
+ import { logger } from '../utils/logger.js';
13
19
  const getRequestUser = (req) => {
14
20
  return req.user || null;
15
21
  };
@@ -22,12 +28,6 @@ const canAccessServer = (user, server) => {
22
28
  }
23
29
  return server.owner === user.username;
24
30
  };
25
- const isPrivilegedServerConfig = (config) => {
26
- return Boolean(config.type === 'stdio' ||
27
- config.command ||
28
- (Array.isArray(config.args) && config.args.length > 0) ||
29
- (!config.url && !config.openapi?.url && !config.openapi?.schema));
30
- };
31
31
  const loadAuthorizedServer = async (req, res, serverName) => {
32
32
  const serverDao = getServerDao();
33
33
  const server = await serverDao.findById(serverName);
@@ -194,16 +194,21 @@ export const getAllServers = async (req, res) => {
194
194
  // No pagination, get all servers (will be filtered by mcpService)
195
195
  serversInfo = await getServersInfo();
196
196
  }
197
+ // #1036 Phase 1: withhold OAuth session-recovery fields from principals
198
+ // who may not read the full server configuration (defense in depth; the
199
+ // config block on list entries is already narrowed to safe metadata).
200
+ const presentedServersInfo = serversInfo.map((info) => presentServerInfoForPrincipal(info, currentUser));
201
+ const presentedAllServers = allServers?.map((info) => presentServerInfoForPrincipal(info, currentUser));
197
202
  const response = {
198
203
  success: true,
199
- data: createSafeJSON(serversInfo),
200
- ...(allServers && { allServers: createSafeJSON(allServers) }),
204
+ data: createSafeJSON(presentedServersInfo),
205
+ ...(presentedAllServers && { allServers: createSafeJSON(presentedAllServers) }),
201
206
  ...(pagination && { pagination }),
202
207
  };
203
208
  res.json(response);
204
209
  }
205
210
  catch (error) {
206
- console.error('Failed to get servers information:', error);
211
+ logger.error('Failed to get servers information:', error);
207
212
  res.status(500).json({
208
213
  success: false,
209
214
  message: 'Failed to get servers information',
@@ -228,7 +233,7 @@ export const getServerShareCandidates = async (req, res) => {
228
233
  res.json({ success: true, data: usernames });
229
234
  }
230
235
  catch (error) {
231
- console.error('Failed to get server share candidates:', error);
236
+ logger.error('Failed to get server share candidates:', error);
232
237
  res.status(500).json({
233
238
  success: false,
234
239
  message: 'Failed to get server share candidates',
@@ -308,23 +313,64 @@ export const getAllSettings = async (req, res) => {
308
313
  res.json(response);
309
314
  }
310
315
  catch (error) {
311
- console.error('Failed to get server settings:', error);
316
+ logger.error('Failed to get server settings:', error);
312
317
  res.status(500).json({
313
318
  success: false,
314
319
  message: 'Failed to get server settings',
315
320
  });
316
321
  }
317
322
  };
323
+ /**
324
+ * Preview the tool list an OpenAPI import would generate, without persisting
325
+ * anything (#1082). Large specs can silently produce a tools/list that does
326
+ * not fit in a model's context window; this endpoint lets the form surface
327
+ * the numbers at the point of confirming the import.
328
+ */
329
+ export const previewOpenApiToolStatsHandler = async (req, res) => {
330
+ try {
331
+ const { config } = req.body;
332
+ if (!config || typeof config !== 'object') {
333
+ res.status(400).json({
334
+ success: false,
335
+ message: 'Server configuration is required',
336
+ });
337
+ return;
338
+ }
339
+ const normalizedConfig = normalizeServerConfigForPersistence(config);
340
+ if (!normalizedConfig.openapi?.url && !normalizedConfig.openapi?.schema) {
341
+ res.status(400).json({
342
+ success: false,
343
+ message: 'OpenAPI specification URL or schema is required',
344
+ });
345
+ return;
346
+ }
347
+ // Assign the requesting user as owner so initialize()'s admin check (and
348
+ // therefore the internal-network SSRF allowance) matches what a real
349
+ // add-server request would produce for the same caller.
350
+ assignServerOwner(req, normalizedConfig);
351
+ const stats = await previewOpenApiToolStats(normalizedConfig);
352
+ res.json({ success: true, data: stats });
353
+ }
354
+ catch (error) {
355
+ logger.warn('Failed to preview OpenAPI tool stats:', error);
356
+ res.status(400).json({
357
+ success: false,
358
+ message: error instanceof Error ? error.message : 'Failed to analyze OpenAPI specification',
359
+ });
360
+ }
361
+ };
318
362
  export const createServer = async (req, res) => {
319
363
  try {
320
364
  const { name, config } = req.body;
321
- if (!name || typeof name !== 'string') {
365
+ const nameValidation = validateServerName(name);
366
+ if (!nameValidation.valid) {
322
367
  res.status(400).json({
323
368
  success: false,
324
- message: 'Server name is required',
369
+ message: nameValidation.message,
325
370
  });
326
371
  return;
327
372
  }
373
+ const serverName = nameValidation.normalized;
328
374
  if (!config || typeof config !== 'object') {
329
375
  res.status(400).json({
330
376
  success: false,
@@ -391,20 +437,19 @@ export const createServer = async (req, res) => {
391
437
  return;
392
438
  }
393
439
  // Set default keep-alive interval for SSE servers if not specified
394
- if ((normalizedConfig.type === 'sse' ||
395
- (!normalizedConfig.type && normalizedConfig.url)) &&
440
+ if ((normalizedConfig.type === 'sse' || (!normalizedConfig.type && normalizedConfig.url)) &&
396
441
  !normalizedConfig.keepAliveInterval) {
397
442
  normalizedConfig.keepAliveInterval = 60000; // Default 60 seconds for SSE servers
398
443
  }
399
444
  assignServerOwner(req, normalizedConfig);
400
- const result = await addServer(name, normalizedConfig);
445
+ const result = await addServer(serverName, normalizedConfig);
401
446
  if (result.success) {
402
447
  res.json({
403
448
  success: true,
404
449
  message: 'Server added successfully',
405
450
  });
406
- notifyToolChanged(name, { reportEmbeddingProgress: true }).catch((error) => {
407
- console.error('Failed to trigger embedding sync for created server:', error);
451
+ notifyToolChanged(serverName, { reportEmbeddingProgress: true }).catch((error) => {
452
+ logger.error('Failed to trigger embedding sync for created server:', error);
408
453
  });
409
454
  }
410
455
  else {
@@ -442,8 +487,9 @@ export const batchCreateServers = async (req, res) => {
442
487
  }
443
488
  // Helper function to validate a single server configuration
444
489
  const validateServerConfig = (name, config) => {
445
- if (!name || typeof name !== 'string') {
446
- return { valid: false, message: 'Server name is required and must be a string' };
490
+ const nameValidation = validateServerName(name);
491
+ if (!nameValidation.valid) {
492
+ return { valid: false, message: nameValidation.message };
447
493
  }
448
494
  if (!config || typeof config !== 'object') {
449
495
  return { valid: false, message: 'Server configuration is required and must be an object' };
@@ -491,7 +537,7 @@ export const batchCreateServers = async (req, res) => {
491
537
  if (normalizedConfig.headers && normalizedConfig.type === 'stdio') {
492
538
  return { valid: false, message: 'Headers are not supported for stdio server type' };
493
539
  }
494
- return { valid: true };
540
+ return { valid: true, normalizedName: nameValidation.normalized };
495
541
  };
496
542
  // Process each server
497
543
  const results = [];
@@ -513,17 +559,17 @@ export const batchCreateServers = async (req, res) => {
513
559
  failureCount++;
514
560
  continue;
515
561
  }
562
+ const serverName = validation.normalizedName ?? name;
516
563
  try {
517
564
  // Set default keep-alive interval for SSE servers if not specified
518
565
  const normalizedConfig = normalizeServerConfigForPersistence(config);
519
- if ((normalizedConfig.type === 'sse' ||
520
- (!normalizedConfig.type && normalizedConfig.url)) &&
566
+ if ((normalizedConfig.type === 'sse' || (!normalizedConfig.type && normalizedConfig.url)) &&
521
567
  !normalizedConfig.keepAliveInterval) {
522
568
  normalizedConfig.keepAliveInterval = 60000; // Default 60 seconds for SSE servers
523
569
  }
524
570
  if (isPrivilegedServerConfig(normalizedConfig) && currentUser?.isAdmin !== true) {
525
571
  results.push({
526
- name,
572
+ name: serverName,
527
573
  success: false,
528
574
  message: 'Only admins can create or modify stdio-based servers',
529
575
  });
@@ -531,19 +577,21 @@ export const batchCreateServers = async (req, res) => {
531
577
  continue;
532
578
  }
533
579
  // Set owner property if not provided
534
- normalizedConfig.owner = currentUser?.isAdmin ? normalizedConfig.owner || defaultOwner : defaultOwner;
580
+ normalizedConfig.owner = currentUser?.isAdmin
581
+ ? normalizedConfig.owner || defaultOwner
582
+ : defaultOwner;
535
583
  // Attempt to add server
536
- const result = await addServer(name, normalizedConfig);
584
+ const result = await addServer(serverName, normalizedConfig);
537
585
  if (result.success) {
538
586
  results.push({
539
- name,
587
+ name: serverName,
540
588
  success: true,
541
589
  });
542
590
  successCount++;
543
591
  }
544
592
  else {
545
593
  results.push({
546
- name,
594
+ name: serverName,
547
595
  success: false,
548
596
  message: result.message || 'Failed to add server',
549
597
  });
@@ -552,7 +600,7 @@ export const batchCreateServers = async (req, res) => {
552
600
  }
553
601
  catch (error) {
554
602
  results.push({
555
- name,
603
+ name: serverName,
556
604
  success: false,
557
605
  message: error instanceof Error ? error.message : 'Internal server error',
558
606
  });
@@ -577,12 +625,12 @@ export const batchCreateServers = async (req, res) => {
577
625
  .filter((result) => result.success)
578
626
  .map((result) => result.name);
579
627
  Promise.all(successfulServerNames.map((serverName) => notifyToolChanged(serverName, { reportEmbeddingProgress: true }))).catch((error) => {
580
- console.error('Failed to trigger embedding sync for batch-created servers:', error);
628
+ logger.error('Failed to trigger embedding sync for batch-created servers:', error);
581
629
  });
582
630
  }
583
631
  }
584
632
  catch (error) {
585
- console.error('Batch create servers error:', error);
633
+ logger.error('Batch create servers error:', error);
586
634
  res.status(500).json({
587
635
  success: false,
588
636
  message: 'Internal server error',
@@ -706,8 +754,7 @@ export const updateServer = async (req, res) => {
706
754
  return;
707
755
  }
708
756
  // Set default keep-alive interval for SSE servers if not specified
709
- if ((normalizedConfig.type === 'sse' ||
710
- (!normalizedConfig.type && normalizedConfig.url)) &&
757
+ if ((normalizedConfig.type === 'sse' || (!normalizedConfig.type && normalizedConfig.url)) &&
711
758
  !normalizedConfig.keepAliveInterval) {
712
759
  normalizedConfig.keepAliveInterval = 60000; // Default 60 seconds for SSE servers
713
760
  }
@@ -715,19 +762,32 @@ export const updateServer = async (req, res) => {
715
762
  assignServerOwner(req, normalizedConfig, existingServer.owner);
716
763
  // Check if server name is being changed
717
764
  const isRenaming = newName && newName !== name;
765
+ // Final server name used for the rest of the update flow (the new name when
766
+ // renaming, otherwise the original name).
767
+ let finalName = name;
718
768
  // If renaming, validate the new name and update references
719
769
  if (isRenaming) {
720
770
  const serverDao = getServerDao();
771
+ const nameValidation = validateServerName(newName);
772
+ if (!nameValidation.valid) {
773
+ res.status(400).json({
774
+ success: false,
775
+ message: nameValidation.message,
776
+ });
777
+ return;
778
+ }
779
+ const targetName = nameValidation.normalized;
780
+ finalName = targetName;
721
781
  // Check if new name already exists
722
- if (await serverDao.exists(newName)) {
782
+ if (await serverDao.exists(targetName)) {
723
783
  res.status(400).json({
724
784
  success: false,
725
- message: `Server name '${newName}' already exists`,
785
+ message: `Server name '${targetName}' already exists`,
726
786
  });
727
787
  return;
728
788
  }
729
789
  // Rename the server
730
- const renamed = await serverDao.rename(name, newName);
790
+ const renamed = await serverDao.rename(name, targetName);
731
791
  if (!renamed) {
732
792
  res.status(404).json({
733
793
  success: false,
@@ -743,10 +803,10 @@ export const updateServer = async (req, res) => {
743
803
  closeServer(name);
744
804
  // Update references in groups
745
805
  const groupDao = getGroupDao();
746
- await groupDao.updateServerName(name, newName);
806
+ await groupDao.updateServerName(name, targetName);
747
807
  // Update references in bearer keys
748
808
  const bearerKeyDao = getBearerKeyDao();
749
- await bearerKeyDao.updateServerName(name, newName);
809
+ await bearerKeyDao.updateServerName(name, targetName);
750
810
  // Drop embeddings stored under the old name so search_tools does not
751
811
  // advertise phantom tools; addOrUpdateServer below regenerates them
752
812
  // under the new name. A failure here must not abort the rename.
@@ -754,14 +814,12 @@ export const updateServer = async (req, res) => {
754
814
  await removeServerToolEmbeddings(name);
755
815
  }
756
816
  catch (error) {
757
- console.warn('Failed to remove embeddings for renamed server', {
817
+ logger.warn('Failed to remove embeddings for renamed server', {
758
818
  serverName: name,
759
819
  error,
760
820
  });
761
821
  }
762
822
  }
763
- // Use the final server name (new name if renaming, otherwise original name)
764
- const finalName = isRenaming ? newName : name;
765
823
  // Fast path: if no connection-relevant field changed, persist and update
766
824
  // in-memory access metadata without tearing down the runtime. This covers
767
825
  // visibility/sharedWithUsers edits, description-only edits, owner changes,
@@ -823,15 +881,34 @@ export const updateServer = async (req, res) => {
823
881
  export const getServerConfig = async (req, res) => {
824
882
  try {
825
883
  const { name } = req.params;
826
- const serverConfig = await loadAuthorizedServer(req, res, name);
827
- if (!serverConfig) {
884
+ const serverDao = getServerDao();
885
+ const serverRecord = await serverDao.findById(name);
886
+ if (!serverRecord) {
887
+ res.status(404).json({
888
+ success: false,
889
+ message: 'Server not found',
890
+ });
891
+ return;
892
+ }
893
+ // #1036 Phase 1: config read and server use are separate decisions.
894
+ // Owner/admin get the full configuration; visibility-admitted shared users
895
+ // (public / group members) get a safe view without secret-bearing fields;
896
+ // everyone else is rejected exactly as before.
897
+ const principal = getRequestUser(req);
898
+ const canReadFullConfig = authorizationService.can('server.config.read', serverRecord, principal);
899
+ if (!canReadFullConfig && !authorizationService.can('server.invoke', serverRecord, principal)) {
900
+ res.status(403).json({
901
+ success: false,
902
+ message: 'Forbidden',
903
+ });
828
904
  return;
829
905
  }
830
906
  // Get runtime info (status, tools) from getServersInfo
831
907
  const allServers = await getServersInfo();
832
908
  const serverInfo = allServers.find((s) => s.name === name);
833
909
  // Extract config without the name field
834
- const { name: serverName, ...config } = serverConfig;
910
+ const { name: serverName, ...config } = serverRecord;
911
+ const presented = presentServerForPrincipal(config, principal);
835
912
  // OpenAPI tools can carry circular $ref cycles left by SwaggerParser.dereference
836
913
  // (recursive schemas), which would make res.json throw. Mirror the list endpoint
837
914
  // and sanitize via createSafeJSON. See #959.
@@ -841,13 +918,13 @@ export const getServerConfig = async (req, res) => {
841
918
  name: serverName,
842
919
  status: serverInfo?.status || 'disconnected',
843
920
  tools: serverInfo?.tools || [],
844
- config,
921
+ config: presented.data,
845
922
  }),
846
923
  };
847
924
  res.json(response);
848
925
  }
849
926
  catch (error) {
850
- console.error('Failed to get server configuration:', error);
927
+ logger.error('Failed to get server configuration:', error);
851
928
  res.status(500).json({
852
929
  success: false,
853
930
  message: 'Failed to get server configuration',
@@ -929,7 +1006,7 @@ export const reloadServer = async (req, res) => {
929
1006
  });
930
1007
  }
931
1008
  catch (error) {
932
- console.error('Failed to reload server:', error);
1009
+ logger.error('Failed to reload server:', error);
933
1010
  res.status(500).json({
934
1011
  success: false,
935
1012
  message: 'Failed to reload server',
@@ -976,7 +1053,7 @@ export const disconnectServerOAuth = async (req, res) => {
976
1053
  });
977
1054
  }
978
1055
  catch (error) {
979
- console.error('Failed to disconnect server OAuth:', error);
1056
+ logger.error('Failed to disconnect server OAuth:', error);
980
1057
  res.status(500).json({
981
1058
  success: false,
982
1059
  message: 'Failed to disconnect server OAuth',
@@ -1005,11 +1082,10 @@ export const reinstallServerHandler = async (req, res) => {
1005
1082
  });
1006
1083
  }
1007
1084
  catch (error) {
1008
- console.error('Failed to reinstall server:', error);
1085
+ logger.error('Failed to reinstall server:', error);
1009
1086
  const message = error instanceof Error ? error.message : 'Failed to reinstall server';
1010
1087
  // Validation errors (unsupported command, disabled server) → 400
1011
- if (message.includes('does not support cache refresh') ||
1012
- message.includes('disabled server')) {
1088
+ if (message.includes('does not support cache refresh') || message.includes('disabled server')) {
1013
1089
  res.status(400).json({ success: false, message });
1014
1090
  }
1015
1091
  else {
@@ -1036,7 +1112,7 @@ export const clearCache = async (req, res) => {
1036
1112
  });
1037
1113
  }
1038
1114
  catch (error) {
1039
- console.error('Failed to clear cache:', error);
1115
+ logger.error('Failed to clear cache:', error);
1040
1116
  res.status(500).json({
1041
1117
  success: false,
1042
1118
  message: 'Failed to clear cache',
@@ -1050,7 +1126,7 @@ export const toggleTool = async (req, res) => {
1050
1126
  const serverName = decodeURIComponent(req.params.serverName);
1051
1127
  const toolName = decodeURIComponent(req.params.toolName);
1052
1128
  const { enabled } = req.body;
1053
- if (!serverName || !toolName) {
1129
+ if (!serverName || !toolName || ['__proto__', 'constructor', 'prototype'].includes(toolName)) {
1054
1130
  res.status(400).json({
1055
1131
  success: false,
1056
1132
  message: 'Server name and tool name are required',
@@ -1103,7 +1179,7 @@ export const updateToolDescription = async (req, res) => {
1103
1179
  const serverName = decodeURIComponent(req.params.serverName);
1104
1180
  const toolName = decodeURIComponent(req.params.toolName);
1105
1181
  const { description } = req.body;
1106
- if (!serverName || !toolName) {
1182
+ if (!serverName || !toolName || ['__proto__', 'constructor', 'prototype'].includes(toolName)) {
1107
1183
  res.status(400).json({
1108
1184
  success: false,
1109
1185
  message: 'Server name and tool name are required',
@@ -1158,7 +1234,7 @@ export const resetToolDescription = async (req, res) => {
1158
1234
  try {
1159
1235
  const serverName = decodeURIComponent(req.params.serverName);
1160
1236
  const toolName = decodeURIComponent(req.params.toolName);
1161
- if (!serverName || !toolName) {
1237
+ if (!serverName || !toolName || ['__proto__', 'constructor', 'prototype'].includes(toolName)) {
1162
1238
  res.status(400).json({
1163
1239
  success: false,
1164
1240
  message: 'Server name and tool name are required',
@@ -1546,7 +1622,8 @@ export const updateSystemConfig = async (req, res) => {
1546
1622
  systemConfig.smartRouting.azureOpenaiApiKey = smartRouting.azureOpenaiApiKey?.trim();
1547
1623
  }
1548
1624
  if (typeof smartRouting.azureOpenaiApiVersion === 'string') {
1549
- systemConfig.smartRouting.azureOpenaiApiVersion = smartRouting.azureOpenaiApiVersion?.trim();
1625
+ systemConfig.smartRouting.azureOpenaiApiVersion =
1626
+ smartRouting.azureOpenaiApiVersion?.trim();
1550
1627
  }
1551
1628
  if (typeof smartRouting.azureOpenaiEmbeddingDeployment === 'string') {
1552
1629
  systemConfig.smartRouting.azureOpenaiEmbeddingDeployment =
@@ -1760,15 +1837,15 @@ export const updateSystemConfig = async (req, res) => {
1760
1837
  });
1761
1838
  // If smart routing configuration changed, sync all existing server tools
1762
1839
  if (needsSync) {
1763
- console.log('SmartRouting configuration changed - syncing all existing server tools...');
1840
+ logger.log('SmartRouting configuration changed - syncing all existing server tools...');
1764
1841
  // Run sync asynchronously to avoid blocking the response
1765
1842
  syncAllServerToolsEmbeddings().catch((error) => {
1766
- console.error('Failed to sync server tools embeddings:', error);
1843
+ logger.error('Failed to sync server tools embeddings:', error);
1767
1844
  });
1768
1845
  }
1769
1846
  }
1770
1847
  catch (saveError) {
1771
- console.error('Failed to save system configuration:', saveError);
1848
+ logger.error('Failed to save system configuration:', saveError);
1772
1849
  res.status(500).json({
1773
1850
  success: false,
1774
1851
  message: 'Failed to save system configuration',
@@ -1789,7 +1866,9 @@ export const togglePrompt = async (req, res) => {
1789
1866
  const serverName = decodeURIComponent(req.params.serverName);
1790
1867
  const promptName = decodeURIComponent(req.params.promptName);
1791
1868
  const { enabled } = req.body;
1792
- if (!serverName || !promptName) {
1869
+ if (!serverName ||
1870
+ !promptName ||
1871
+ ['__proto__', 'constructor', 'prototype'].includes(promptName)) {
1793
1872
  res.status(400).json({
1794
1873
  success: false,
1795
1874
  message: 'Server name and prompt name are required',
@@ -1842,7 +1921,9 @@ export const updatePromptDescription = async (req, res) => {
1842
1921
  const serverName = decodeURIComponent(req.params.serverName);
1843
1922
  const promptName = decodeURIComponent(req.params.promptName);
1844
1923
  const { description } = req.body;
1845
- if (!serverName || !promptName) {
1924
+ if (!serverName ||
1925
+ !promptName ||
1926
+ ['__proto__', 'constructor', 'prototype'].includes(promptName)) {
1846
1927
  res.status(400).json({
1847
1928
  success: false,
1848
1929
  message: 'Server name and prompt name are required',
@@ -1895,7 +1976,9 @@ export const resetPromptDescription = async (req, res) => {
1895
1976
  try {
1896
1977
  const serverName = decodeURIComponent(req.params.serverName);
1897
1978
  const promptName = decodeURIComponent(req.params.promptName);
1898
- if (!serverName || !promptName) {
1979
+ if (!serverName ||
1980
+ !promptName ||
1981
+ ['__proto__', 'constructor', 'prototype'].includes(promptName)) {
1899
1982
  res.status(400).json({
1900
1983
  success: false,
1901
1984
  message: 'Server name and prompt name are required',
@@ -1941,7 +2024,9 @@ export const toggleResource = async (req, res) => {
1941
2024
  const serverName = decodeURIComponent(req.params.serverName);
1942
2025
  const resourceUri = decodeURIComponent(req.params.resourceUri);
1943
2026
  const { enabled } = req.body;
1944
- if (!serverName || !resourceUri) {
2027
+ if (!serverName ||
2028
+ !resourceUri ||
2029
+ ['__proto__', 'constructor', 'prototype'].includes(resourceUri)) {
1945
2030
  res.status(400).json({
1946
2031
  success: false,
1947
2032
  message: 'Server name and resource URI are required',
@@ -1994,7 +2079,9 @@ export const updateResourceDescription = async (req, res) => {
1994
2079
  const serverName = decodeURIComponent(req.params.serverName);
1995
2080
  const resourceUri = decodeURIComponent(req.params.resourceUri);
1996
2081
  const { description } = req.body;
1997
- if (!serverName || !resourceUri) {
2082
+ if (!serverName ||
2083
+ !resourceUri ||
2084
+ ['__proto__', 'constructor', 'prototype'].includes(resourceUri)) {
1998
2085
  res.status(400).json({
1999
2086
  success: false,
2000
2087
  message: 'Server name and resource URI are required',
@@ -2047,7 +2134,9 @@ export const resetResourceDescription = async (req, res) => {
2047
2134
  try {
2048
2135
  const serverName = decodeURIComponent(req.params.serverName);
2049
2136
  const resourceUri = decodeURIComponent(req.params.resourceUri);
2050
- if (!serverName || !resourceUri) {
2137
+ if (!serverName ||
2138
+ !resourceUri ||
2139
+ ['__proto__', 'constructor', 'prototype'].includes(resourceUri)) {
2051
2140
  res.status(400).json({
2052
2141
  success: false,
2053
2142
  message: 'Server name and resource URI are required',