@1mcp/agent 0.34.4 → 0.35.0-beta.2

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 (333) hide show
  1. package/README.md +1 -0
  2. package/build/admin/assets/admin-console-C5_zD_qW.css +1 -0
  3. package/build/admin/assets/admin-console-CbHHoqLr.js +61 -0
  4. package/build/admin/index.html +13 -0
  5. package/build/application/services/healthService.d.ts +32 -0
  6. package/build/application/services/healthService.d.ts.map +1 -1
  7. package/build/application/services/healthService.js +49 -0
  8. package/build/application/services/healthService.js.map +1 -1
  9. package/build/commands/admin/admin.d.ts +90 -0
  10. package/build/commands/admin/admin.d.ts.map +1 -0
  11. package/build/commands/admin/admin.js +545 -0
  12. package/build/commands/admin/admin.js.map +1 -0
  13. package/build/commands/admin/index.d.ts +3 -0
  14. package/build/commands/admin/index.d.ts.map +1 -0
  15. package/build/commands/admin/index.js +99 -0
  16. package/build/commands/admin/index.js.map +1 -0
  17. package/build/commands/auth/index.d.ts.map +1 -1
  18. package/build/commands/auth/index.js +24 -5
  19. package/build/commands/auth/index.js.map +1 -1
  20. package/build/commands/auth/login.d.ts +1 -0
  21. package/build/commands/auth/login.d.ts.map +1 -1
  22. package/build/commands/auth/login.js +6 -10
  23. package/build/commands/auth/login.js.map +1 -1
  24. package/build/commands/auth/logout.d.ts +3 -0
  25. package/build/commands/auth/logout.d.ts.map +1 -1
  26. package/build/commands/auth/logout.js +19 -32
  27. package/build/commands/auth/logout.js.map +1 -1
  28. package/build/commands/auth/runtimeTargetContext.d.ts +25 -0
  29. package/build/commands/auth/runtimeTargetContext.d.ts.map +1 -0
  30. package/build/commands/auth/runtimeTargetContext.js +69 -0
  31. package/build/commands/auth/runtimeTargetContext.js.map +1 -0
  32. package/build/commands/auth/status.d.ts +1 -0
  33. package/build/commands/auth/status.d.ts.map +1 -1
  34. package/build/commands/auth/status.js +10 -33
  35. package/build/commands/auth/status.js.map +1 -1
  36. package/build/commands/inspect/index.d.ts.map +1 -1
  37. package/build/commands/inspect/index.js +4 -0
  38. package/build/commands/inspect/index.js.map +1 -1
  39. package/build/commands/inspect/inspect.d.ts +1 -0
  40. package/build/commands/inspect/inspect.d.ts.map +1 -1
  41. package/build/commands/inspect/inspect.js +2 -2
  42. package/build/commands/inspect/inspect.js.map +1 -1
  43. package/build/commands/instructions/index.d.ts.map +1 -1
  44. package/build/commands/instructions/index.js +4 -0
  45. package/build/commands/instructions/index.js.map +1 -1
  46. package/build/commands/instructions/instructions.d.ts +1 -0
  47. package/build/commands/instructions/instructions.d.ts.map +1 -1
  48. package/build/commands/instructions/instructions.js.map +1 -1
  49. package/build/commands/instructions/instructionsUtils.js +1 -1
  50. package/build/commands/instructions/instructionsUtils.js.map +1 -1
  51. package/build/commands/mcp/enable.d.ts +89 -2
  52. package/build/commands/mcp/enable.d.ts.map +1 -1
  53. package/build/commands/mcp/enable.js +555 -35
  54. package/build/commands/mcp/enable.js.map +1 -1
  55. package/build/commands/mcp/index.d.ts.map +1 -1
  56. package/build/commands/mcp/index.js +10 -0
  57. package/build/commands/mcp/index.js.map +1 -1
  58. package/build/commands/mcp/restart.d.ts +69 -0
  59. package/build/commands/mcp/restart.d.ts.map +1 -0
  60. package/build/commands/mcp/restart.js +259 -0
  61. package/build/commands/mcp/restart.js.map +1 -0
  62. package/build/commands/mcp/status.d.ts +24 -1
  63. package/build/commands/mcp/status.d.ts.map +1 -1
  64. package/build/commands/mcp/status.js +158 -14
  65. package/build/commands/mcp/status.js.map +1 -1
  66. package/build/commands/proxy/index.d.ts +1 -0
  67. package/build/commands/proxy/index.d.ts.map +1 -1
  68. package/build/commands/proxy/index.js +4 -0
  69. package/build/commands/proxy/index.js.map +1 -1
  70. package/build/commands/run/index.d.ts.map +1 -1
  71. package/build/commands/run/index.js +4 -0
  72. package/build/commands/run/index.js.map +1 -1
  73. package/build/commands/run/run.d.ts +1 -0
  74. package/build/commands/run/run.d.ts.map +1 -1
  75. package/build/commands/run/run.js +2 -2
  76. package/build/commands/run/run.js.map +1 -1
  77. package/build/commands/serve/index.d.ts +8 -0
  78. package/build/commands/serve/index.d.ts.map +1 -1
  79. package/build/commands/serve/index.js +12 -0
  80. package/build/commands/serve/index.js.map +1 -1
  81. package/build/commands/serve/serve.d.ts +13 -0
  82. package/build/commands/serve/serve.d.ts.map +1 -1
  83. package/build/commands/serve/serve.js +115 -57
  84. package/build/commands/serve/serve.js.map +1 -1
  85. package/build/commands/serve/serveBackground.d.ts +48 -26
  86. package/build/commands/serve/serveBackground.d.ts.map +1 -1
  87. package/build/commands/serve/serveBackground.js +230 -61
  88. package/build/commands/serve/serveBackground.js.map +1 -1
  89. package/build/commands/serve/serveRestart.d.ts +1 -1
  90. package/build/commands/serve/serveRestart.js +1 -1
  91. package/build/commands/serve/serveStatus.d.ts +20 -10
  92. package/build/commands/serve/serveStatus.d.ts.map +1 -1
  93. package/build/commands/serve/serveStatus.js +174 -13
  94. package/build/commands/serve/serveStatus.js.map +1 -1
  95. package/build/commands/serve/serveStop.d.ts +13 -4
  96. package/build/commands/serve/serveStop.d.ts.map +1 -1
  97. package/build/commands/serve/serveStop.js +212 -23
  98. package/build/commands/serve/serveStop.js.map +1 -1
  99. package/build/commands/shared/apiClient.d.ts +5 -1
  100. package/build/commands/shared/apiClient.d.ts.map +1 -1
  101. package/build/commands/shared/apiClient.js +17 -9
  102. package/build/commands/shared/apiClient.js.map +1 -1
  103. package/build/commands/shared/clientSurfaceAttachment.d.ts +14 -0
  104. package/build/commands/shared/clientSurfaceAttachment.d.ts.map +1 -1
  105. package/build/commands/shared/clientSurfaceAttachment.js +51 -9
  106. package/build/commands/shared/clientSurfaceAttachment.js.map +1 -1
  107. package/build/commands/shared/commandRunner.d.ts +3 -1
  108. package/build/commands/shared/commandRunner.d.ts.map +1 -1
  109. package/build/commands/shared/commandRunner.js +59 -2
  110. package/build/commands/shared/commandRunner.js.map +1 -1
  111. package/build/commands/shared/serveTargetResolver.d.ts +14 -1
  112. package/build/commands/shared/serveTargetResolver.d.ts.map +1 -1
  113. package/build/commands/shared/serveTargetResolver.js +145 -3
  114. package/build/commands/shared/serveTargetResolver.js.map +1 -1
  115. package/build/commands/target/index.d.ts +3 -0
  116. package/build/commands/target/index.d.ts.map +1 -0
  117. package/build/commands/target/index.js +171 -0
  118. package/build/commands/target/index.js.map +1 -0
  119. package/build/commands/target/target.d.ts +78 -0
  120. package/build/commands/target/target.d.ts.map +1 -0
  121. package/build/commands/target/target.js +471 -0
  122. package/build/commands/target/target.js.map +1 -0
  123. package/build/config/configManager.d.ts +2 -0
  124. package/build/config/configManager.d.ts.map +1 -1
  125. package/build/config/configManager.js +19 -8
  126. package/build/config/configManager.js.map +1 -1
  127. package/build/config/mcpConfigMerge.d.ts.map +1 -1
  128. package/build/config/mcpConfigMerge.js +13 -2
  129. package/build/config/mcpConfigMerge.js.map +1 -1
  130. package/build/constants/mcp.d.ts +1 -1
  131. package/build/constants/mcp.d.ts.map +1 -1
  132. package/build/constants/mcp.js +1 -1
  133. package/build/constants/mcp.js.map +1 -1
  134. package/build/core/client/clientManager.d.ts +24 -1
  135. package/build/core/client/clientManager.d.ts.map +1 -1
  136. package/build/core/client/clientManager.js +268 -2
  137. package/build/core/client/clientManager.js.map +1 -1
  138. package/build/core/client/connectionHandler.d.ts.map +1 -1
  139. package/build/core/client/connectionHandler.js +18 -1
  140. package/build/core/client/connectionHandler.js.map +1 -1
  141. package/build/core/configChangeHandler.d.ts +1 -0
  142. package/build/core/configChangeHandler.d.ts.map +1 -1
  143. package/build/core/configChangeHandler.js +14 -0
  144. package/build/core/configChangeHandler.js.map +1 -1
  145. package/build/core/loading/mcpLoadingManager.d.ts.map +1 -1
  146. package/build/core/loading/mcpLoadingManager.js +16 -4
  147. package/build/core/loading/mcpLoadingManager.js.map +1 -1
  148. package/build/core/runtime/runtimeIdentityService.d.ts +30 -0
  149. package/build/core/runtime/runtimeIdentityService.d.ts.map +1 -0
  150. package/build/core/runtime/runtimeIdentityService.js +63 -0
  151. package/build/core/runtime/runtimeIdentityService.js.map +1 -0
  152. package/build/core/server/agentConfig.d.ts +4 -0
  153. package/build/core/server/agentConfig.d.ts.map +1 -1
  154. package/build/core/server/agentConfig.js +3 -0
  155. package/build/core/server/agentConfig.js.map +1 -1
  156. package/build/core/server/backendStdioSupervisor.d.ts +73 -0
  157. package/build/core/server/backendStdioSupervisor.d.ts.map +1 -0
  158. package/build/core/server/backendStdioSupervisor.js +191 -0
  159. package/build/core/server/backendStdioSupervisor.js.map +1 -0
  160. package/build/core/server/backgroundLaunchConfig.d.ts +15 -0
  161. package/build/core/server/backgroundLaunchConfig.d.ts.map +1 -0
  162. package/build/core/server/backgroundLaunchConfig.js +46 -0
  163. package/build/core/server/backgroundLaunchConfig.js.map +1 -0
  164. package/build/core/server/backgroundRuntimeSupervisor.d.ts +44 -0
  165. package/build/core/server/backgroundRuntimeSupervisor.d.ts.map +1 -0
  166. package/build/core/server/backgroundRuntimeSupervisor.js +252 -0
  167. package/build/core/server/backgroundRuntimeSupervisor.js.map +1 -0
  168. package/build/core/server/backgroundRuntimeSupervisorState.d.ts +30 -0
  169. package/build/core/server/backgroundRuntimeSupervisorState.d.ts.map +1 -0
  170. package/build/core/server/backgroundRuntimeSupervisorState.js +81 -0
  171. package/build/core/server/backgroundRuntimeSupervisorState.js.map +1 -0
  172. package/build/core/server/clientInstanceFactory.d.ts.map +1 -1
  173. package/build/core/server/clientInstanceFactory.js +1 -0
  174. package/build/core/server/clientInstanceFactory.js.map +1 -1
  175. package/build/core/server/clientInstancePool.d.ts +17 -5
  176. package/build/core/server/clientInstancePool.d.ts.map +1 -1
  177. package/build/core/server/clientInstancePool.js +198 -33
  178. package/build/core/server/clientInstancePool.js.map +1 -1
  179. package/build/core/server/clientInstancePoolTypes.d.ts +7 -1
  180. package/build/core/server/clientInstancePoolTypes.d.ts.map +1 -1
  181. package/build/core/server/clientInstancePoolTypes.js.map +1 -1
  182. package/build/core/server/runtimeScopeOwnership.d.ts +61 -0
  183. package/build/core/server/runtimeScopeOwnership.d.ts.map +1 -0
  184. package/build/core/server/runtimeScopeOwnership.js +544 -0
  185. package/build/core/server/runtimeScopeOwnership.js.map +1 -0
  186. package/build/core/server/serverManager.d.ts +2 -0
  187. package/build/core/server/serverManager.d.ts.map +1 -1
  188. package/build/core/server/serverManager.js +30 -0
  189. package/build/core/server/serverManager.js.map +1 -1
  190. package/build/core/server/templateIdentity.d.ts +10 -0
  191. package/build/core/server/templateIdentity.d.ts.map +1 -1
  192. package/build/core/server/templateIdentity.js +33 -0
  193. package/build/core/server/templateIdentity.js.map +1 -1
  194. package/build/core/server/templateServerCleanup.d.ts.map +1 -1
  195. package/build/core/server/templateServerCleanup.js +7 -1
  196. package/build/core/server/templateServerCleanup.js.map +1 -1
  197. package/build/core/server/templateServerManager.d.ts +13 -0
  198. package/build/core/server/templateServerManager.d.ts.map +1 -1
  199. package/build/core/server/templateServerManager.js +129 -4
  200. package/build/core/server/templateServerManager.js.map +1 -1
  201. package/build/core/tools/handlers/serverManagementHandler.d.ts +117 -11
  202. package/build/core/tools/handlers/serverManagementHandler.d.ts.map +1 -1
  203. package/build/core/tools/handlers/serverManagementHandler.js +103 -45
  204. package/build/core/tools/handlers/serverManagementHandler.js.map +1 -1
  205. package/build/core/tools/internal/adapters/helpers/configTransformers.d.ts +2 -2
  206. package/build/core/tools/internal/adapters/management/managementAdapter.d.ts +2 -2
  207. package/build/core/tools/internal/adapters/management/managementAdapter.d.ts.map +1 -1
  208. package/build/core/tools/internal/adapters/management/managementAdapter.js +45 -26
  209. package/build/core/tools/internal/adapters/management/managementAdapter.js.map +1 -1
  210. package/build/core/tools/internal/adapters/management/types.d.ts +21 -2
  211. package/build/core/tools/internal/adapters/management/types.d.ts.map +1 -1
  212. package/build/core/tools/internal/managementHandlers.d.ts.map +1 -1
  213. package/build/core/tools/internal/managementHandlers.js +73 -22
  214. package/build/core/tools/internal/managementHandlers.js.map +1 -1
  215. package/build/core/tools/internal/schemas/discovery.d.ts +3 -3
  216. package/build/core/tools/internal/schemas/edit.js +1 -1
  217. package/build/core/tools/internal/schemas/edit.js.map +1 -1
  218. package/build/core/tools/internal/schemas/index.d.ts +90 -19
  219. package/build/core/tools/internal/schemas/index.d.ts.map +1 -1
  220. package/build/core/tools/internal/schemas/installation.d.ts +7 -7
  221. package/build/core/tools/internal/schemas/management.d.ts +80 -9
  222. package/build/core/tools/internal/schemas/management.d.ts.map +1 -1
  223. package/build/core/tools/internal/schemas/management.js +69 -2
  224. package/build/core/tools/internal/schemas/management.js.map +1 -1
  225. package/build/core/types/client.d.ts +9 -2
  226. package/build/core/types/client.d.ts.map +1 -1
  227. package/build/core/types/client.js +4 -0
  228. package/build/core/types/client.js.map +1 -1
  229. package/build/core/types/transport.d.ts +34 -4
  230. package/build/core/types/transport.d.ts.map +1 -1
  231. package/build/core/types/transport.js +26 -3
  232. package/build/core/types/transport.js.map +1 -1
  233. package/build/domains/admin/adminBackendRestartService.d.ts +41 -0
  234. package/build/domains/admin/adminBackendRestartService.d.ts.map +1 -0
  235. package/build/domains/admin/adminBackendRestartService.js +22 -0
  236. package/build/domains/admin/adminBackendRestartService.js.map +1 -0
  237. package/build/domains/admin/adminConfiguredServerService.d.ts +275 -0
  238. package/build/domains/admin/adminConfiguredServerService.d.ts.map +1 -0
  239. package/build/domains/admin/adminConfiguredServerService.js +1926 -0
  240. package/build/domains/admin/adminConfiguredServerService.js.map +1 -0
  241. package/build/domains/admin/adminConnectivityChecker.d.ts +7 -0
  242. package/build/domains/admin/adminConnectivityChecker.d.ts.map +1 -0
  243. package/build/domains/admin/adminConnectivityChecker.js +42 -0
  244. package/build/domains/admin/adminConnectivityChecker.js.map +1 -0
  245. package/build/domains/admin/adminDomain.d.ts +36 -0
  246. package/build/domains/admin/adminDomain.d.ts.map +1 -0
  247. package/build/domains/admin/adminDomain.js +52 -0
  248. package/build/domains/admin/adminDomain.js.map +1 -0
  249. package/build/domains/admin/adminIdentityService.d.ts +75 -0
  250. package/build/domains/admin/adminIdentityService.d.ts.map +1 -0
  251. package/build/domains/admin/adminIdentityService.js +321 -0
  252. package/build/domains/admin/adminIdentityService.js.map +1 -0
  253. package/build/domains/admin/adminOAuthService.d.ts +26 -0
  254. package/build/domains/admin/adminOAuthService.d.ts.map +1 -0
  255. package/build/domains/admin/adminOAuthService.js +71 -0
  256. package/build/domains/admin/adminOAuthService.js.map +1 -0
  257. package/build/domains/admin/adminOperationService.d.ts +203 -0
  258. package/build/domains/admin/adminOperationService.d.ts.map +1 -0
  259. package/build/domains/admin/adminOperationService.js +749 -0
  260. package/build/domains/admin/adminOperationService.js.map +1 -0
  261. package/build/domains/admin/adminPresetService.d.ts +140 -0
  262. package/build/domains/admin/adminPresetService.d.ts.map +1 -0
  263. package/build/domains/admin/adminPresetService.js +248 -0
  264. package/build/domains/admin/adminPresetService.js.map +1 -0
  265. package/build/domains/admin/runtimeScopeAdminLock.d.ts +22 -0
  266. package/build/domains/admin/runtimeScopeAdminLock.d.ts.map +1 -0
  267. package/build/domains/admin/runtimeScopeAdminLock.js +121 -0
  268. package/build/domains/admin/runtimeScopeAdminLock.js.map +1 -0
  269. package/build/domains/admin/runtimeServerManagerBackendRestartService.d.ts +20 -0
  270. package/build/domains/admin/runtimeServerManagerBackendRestartService.d.ts.map +1 -0
  271. package/build/domains/admin/runtimeServerManagerBackendRestartService.js +78 -0
  272. package/build/domains/admin/runtimeServerManagerBackendRestartService.js.map +1 -0
  273. package/build/domains/config-change/configChange.d.ts +6 -1
  274. package/build/domains/config-change/configChange.d.ts.map +1 -1
  275. package/build/domains/config-change/configChange.js +344 -0
  276. package/build/domains/config-change/configChange.js.map +1 -1
  277. package/build/domains/config-change/types.d.ts +18 -3
  278. package/build/domains/config-change/types.d.ts.map +1 -1
  279. package/build/domains/preset/manager/presetManager.d.ts +1 -0
  280. package/build/domains/preset/manager/presetManager.d.ts.map +1 -1
  281. package/build/domains/preset/manager/presetManager.js +3 -0
  282. package/build/domains/preset/manager/presetManager.js.map +1 -1
  283. package/build/domains/preset/tagAuthoring.d.ts +10 -0
  284. package/build/domains/preset/tagAuthoring.d.ts.map +1 -0
  285. package/build/domains/preset/tagAuthoring.js +94 -0
  286. package/build/domains/preset/tagAuthoring.js.map +1 -0
  287. package/build/domains/runtime-targets/runtimeIdentityVerification.d.ts +51 -0
  288. package/build/domains/runtime-targets/runtimeIdentityVerification.d.ts.map +1 -0
  289. package/build/domains/runtime-targets/runtimeIdentityVerification.js +258 -0
  290. package/build/domains/runtime-targets/runtimeIdentityVerification.js.map +1 -0
  291. package/build/domains/runtime-targets/runtimeTargetStore.d.ts +222 -0
  292. package/build/domains/runtime-targets/runtimeTargetStore.d.ts.map +1 -0
  293. package/build/domains/runtime-targets/runtimeTargetStore.js +1456 -0
  294. package/build/domains/runtime-targets/runtimeTargetStore.js.map +1 -0
  295. package/build/index.d.ts.map +1 -1
  296. package/build/index.js +7 -1
  297. package/build/index.js.map +1 -1
  298. package/build/server.d.ts.map +1 -1
  299. package/build/server.js +18 -2
  300. package/build/server.js.map +1 -1
  301. package/build/transport/http/routes/adminRoutes.d.ts +38 -0
  302. package/build/transport/http/routes/adminRoutes.d.ts.map +1 -0
  303. package/build/transport/http/routes/adminRoutes.js +1475 -0
  304. package/build/transport/http/routes/adminRoutes.js.map +1 -0
  305. package/build/transport/http/routes/healthRoutes.d.ts.map +1 -1
  306. package/build/transport/http/routes/healthRoutes.js +70 -2
  307. package/build/transport/http/routes/healthRoutes.js.map +1 -1
  308. package/build/transport/http/routes/oauthRoutes.d.ts +4 -1
  309. package/build/transport/http/routes/oauthRoutes.d.ts.map +1 -1
  310. package/build/transport/http/routes/oauthRoutes.js +12 -190
  311. package/build/transport/http/routes/oauthRoutes.js.map +1 -1
  312. package/build/transport/http/routes/runtimeIdentityRoutes.d.ts +7 -0
  313. package/build/transport/http/routes/runtimeIdentityRoutes.d.ts.map +1 -0
  314. package/build/transport/http/routes/runtimeIdentityRoutes.js +9 -0
  315. package/build/transport/http/routes/runtimeIdentityRoutes.js.map +1 -0
  316. package/build/transport/http/server.d.ts +3 -0
  317. package/build/transport/http/server.d.ts.map +1 -1
  318. package/build/transport/http/server.js +102 -4
  319. package/build/transport/http/server.js.map +1 -1
  320. package/build/transport/transportFactory.d.ts.map +1 -1
  321. package/build/transport/transportFactory.js +27 -15
  322. package/build/transport/transportFactory.js.map +1 -1
  323. package/build/transport/transportFactory.testSetup.js +2 -0
  324. package/build/transport/transportFactory.testSetup.js.map +1 -1
  325. package/build/utils/core/errorTypes.d.ts +3 -0
  326. package/build/utils/core/errorTypes.d.ts.map +1 -1
  327. package/build/utils/core/errorTypes.js +7 -0
  328. package/build/utils/core/errorTypes.js.map +1 -1
  329. package/build/utils/validation/urlDetection.d.ts +13 -2
  330. package/build/utils/validation/urlDetection.d.ts.map +1 -1
  331. package/build/utils/validation/urlDetection.js +37 -6
  332. package/build/utils/validation/urlDetection.js.map +1 -1
  333. package/package.json +22 -9
@@ -0,0 +1,1475 @@
1
+ import { createHmac } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { parseTemplateConnectionKey } from '../../../core/server/templateIdentity.js';
6
+ import { AdminConfiguredServerApplyError, AdminConfiguredServerNotFoundError, } from '../../../domains/admin/adminConfiguredServerService.js';
7
+ import { ADMIN_SESSION_COOKIE_NAME, AdminIdentityError, } from '../../../domains/admin/adminIdentityService.js';
8
+ import { AdminPresetConflictError, AdminPresetNotFoundError, } from '../../../domains/admin/adminPresetService.js';
9
+ import { sensitiveOperationLimiter } from '../../../transport/http/middlewares/securityMiddleware.js';
10
+ import { sanitizeErrorMessage } from '../../../utils/validation/sanitization.js';
11
+ import express, { Router } from 'express';
12
+ import rateLimit from 'express-rate-limit';
13
+ import { createRequire } from 'node:module';
14
+ import { z } from 'zod';
15
+ const FAILED_LOGIN_LIMIT = 5;
16
+ const FAILED_LOGIN_WINDOW_MS = 15 * 60 * 1000;
17
+ const FAILED_LOGIN_MAX_ACTIVE_KEYS = 10_000;
18
+ const ADMIN_AUTH_RATE_LIMIT = 30;
19
+ const ADMIN_AUTH_RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000;
20
+ const ADMIN_STATUS_RATE_LIMIT = 120;
21
+ const ADMIN_STATUS_RATE_LIMIT_WINDOW_MS = 60 * 1000;
22
+ const ADMIN_USERNAME_MAX_LENGTH = 256;
23
+ const ADMIN_PASSWORD_MAX_LENGTH = 4096;
24
+ const CLI_ADMIN_PROTOCOL_VERSION = '1';
25
+ const CLI_ADMIN_RESPONSE_MAX_BYTES = 256 * 1024;
26
+ const CLI_ADMIN_RESPONSE_TOO_LARGE_MESSAGE = 'CLI Admin response exceeded the maximum supported size; use a narrower or paginated request.';
27
+ const CLI_SESSION_OPERATIONS = ['admin.login', 'admin.status', 'admin.logout'];
28
+ const ADMIN_API_PROTOCOL_VERSION = '1';
29
+ const TEMPLATE_INSTANCE_ID_DISPLAY_LENGTH = 12;
30
+ const packageMetadata = createRequire(import.meta.url)('../../../../package.json');
31
+ const CLI_CONFIGURED_SERVER_OPERATIONS = ['mcp.enable', 'mcp.disable'];
32
+ const CLI_BACKEND_RESTART_OPERATION = 'mcp.restart';
33
+ const adminLoginBodySchema = z.object({
34
+ username: z.string().trim().min(1).max(ADMIN_USERNAME_MAX_LENGTH),
35
+ password: z.string().min(1).max(ADMIN_PASSWORD_MAX_LENGTH),
36
+ });
37
+ const adminOAuthServiceParamsSchema = z.object({
38
+ serviceId: z.string().trim().min(1).max(256),
39
+ });
40
+ const cliConfiguredServerMutationBodySchema = z.object({
41
+ targetName: z.string().trim().min(1).max(256),
42
+ dryRun: z.boolean().optional(),
43
+ });
44
+ const cliBackendRestartBodySchema = z
45
+ .object({
46
+ targetName: z.string().trim().min(1).max(256),
47
+ instance: z.string().trim().min(1).max(64).optional(),
48
+ allInstances: z.boolean().optional(),
49
+ })
50
+ .refine((value) => !(value.instance !== undefined && value.allInstances === true), {
51
+ message: 'instance and allInstances are mutually exclusive',
52
+ });
53
+ export function createAdminRoutes(options) {
54
+ if (!options.adminEnabled) {
55
+ options.adminService.revokeAllSessions();
56
+ return null;
57
+ }
58
+ const router = Router();
59
+ const failedLoginLimiter = new FailedLoginLimiter();
60
+ const authenticationLimiter = rateLimit({
61
+ windowMs: ADMIN_AUTH_RATE_LIMIT_WINDOW_MS,
62
+ max: ADMIN_AUTH_RATE_LIMIT,
63
+ standardHeaders: true,
64
+ legacyHeaders: false,
65
+ });
66
+ const statusLimiter = rateLimit({
67
+ windowMs: ADMIN_STATUS_RATE_LIMIT_WINDOW_MS,
68
+ max: ADMIN_STATUS_RATE_LIMIT,
69
+ standardHeaders: true,
70
+ legacyHeaders: false,
71
+ });
72
+ const adminConsoleAssets = resolveAdminConsoleAssets(options.adminConsoleAssetsDir);
73
+ options.adminService.bootstrapFirstAdminFromEnvironment();
74
+ router.get('/cli/v1/capabilities', (req, res) => {
75
+ const identity = options.getRuntimeIdentity();
76
+ const setupRequired = !options.adminService.hasAdminAccount();
77
+ const mutationAvailability = cliMutationAvailability(options, setupRequired);
78
+ const configuredServerOperationsSupported = Boolean(options.configuredServerService);
79
+ const backendRestartSupported = Boolean(options.backendRestartService);
80
+ const mcpMutationsReady = (configuredServerOperationsSupported || backendRestartSupported) && mutationAvailability.available;
81
+ const mcpOperations = [
82
+ ...(configuredServerOperationsSupported ? [...CLI_CONFIGURED_SERVER_OPERATIONS] : []),
83
+ ...(backendRestartSupported ? [CLI_BACKEND_RESTART_OPERATION] : []),
84
+ ];
85
+ const mcpMutationOperations = mutationAvailability.available
86
+ ? [
87
+ ...(configuredServerOperationsSupported ? ['enable', 'disable'] : []),
88
+ ...(backendRestartSupported ? ['restart'] : []),
89
+ ]
90
+ : [];
91
+ const mcpReadinessStatus = cliMcpReadinessStatus(mutationAvailability);
92
+ sendCliSuccess(req, res, {
93
+ runtime: toCliRuntimeIdentity(identity),
94
+ supportedOperations: [...CLI_SESSION_OPERATIONS, ...mcpOperations],
95
+ adminSurface: {
96
+ enabled: true,
97
+ status: setupRequired ? 'setupRequired' : 'loginRequired',
98
+ },
99
+ mutationReadiness: {
100
+ mcp: {
101
+ enabled: mcpMutationsReady,
102
+ status: mcpReadinessStatus,
103
+ operations: mcpMutationOperations,
104
+ },
105
+ },
106
+ adminMutationsAvailable: mutationAvailability.available,
107
+ ...(mutationAvailability.reason ? { adminMutationsUnavailableReason: mutationAvailability.reason } : {}),
108
+ features: {
109
+ adminSessions: true,
110
+ bearerSessionAuth: true,
111
+ csrfTokens: true,
112
+ mcpEnableDisable: configuredServerOperationsSupported && mutationAvailability.available,
113
+ mcpRestart: backendRestartSupported && mutationAvailability.available,
114
+ },
115
+ });
116
+ });
117
+ router.post('/cli/v1/session/login', authenticationLimiter, async (req, res) => {
118
+ const parsedBody = adminLoginBodySchema.safeParse(req.body);
119
+ if (!parsedBody.success) {
120
+ sendCliError(req, res, {
121
+ status: 400,
122
+ code: 'admin_login_request_invalid',
123
+ message: 'Admin username and password are required and must be valid strings',
124
+ retryable: false,
125
+ });
126
+ return;
127
+ }
128
+ const { username, password } = parsedBody.data;
129
+ const source = getLoginSource(req);
130
+ if (failedLoginLimiter.isLimited(username, source)) {
131
+ sendCliError(req, res, {
132
+ status: 429,
133
+ code: 'admin_login_rate_limited',
134
+ message: 'Too many failed admin login attempts',
135
+ retryable: true,
136
+ });
137
+ return;
138
+ }
139
+ try {
140
+ const login = await options.adminService.login({
141
+ username,
142
+ password,
143
+ });
144
+ failedLoginLimiter.reset(username, source);
145
+ sendCliSuccess(req, res, {
146
+ sessionToken: login.sessionToken,
147
+ csrfToken: login.csrfToken,
148
+ expiresAt: login.expiresAt,
149
+ account: toCliAdminAccount(login.account),
150
+ });
151
+ }
152
+ catch (error) {
153
+ failedLoginLimiter.recordFailure(username, source);
154
+ sendCliAdminError(req, res, error);
155
+ }
156
+ });
157
+ router.get('/cli/v1/session/status', (req, res) => {
158
+ const sessionToken = getBearerSessionToken(req);
159
+ const session = options.adminService.validateSession(sessionToken);
160
+ const runtime = toCliRuntimeIdentity(options.getRuntimeIdentity());
161
+ if (!session) {
162
+ sendCliSuccess(req, res, {
163
+ authenticated: false,
164
+ runtime,
165
+ });
166
+ return;
167
+ }
168
+ sendCliSuccess(req, res, {
169
+ authenticated: true,
170
+ runtime,
171
+ account: toCliAdminAccount(session.account),
172
+ expiresAt: session.expiresAt,
173
+ });
174
+ });
175
+ router.post('/cli/v1/session/logout', (req, res) => {
176
+ const sessionToken = getBearerSessionToken(req);
177
+ const session = options.adminService.validateSession(sessionToken);
178
+ options.adminService.revokeSession(sessionToken);
179
+ sendCliSuccess(req, res, {
180
+ revoked: Boolean(session),
181
+ });
182
+ });
183
+ router.post('/cli/v1/operations/enable-server', async (req, res) => {
184
+ await handleCliConfiguredServerMutation(req, res, options, 'enableConfiguredServer');
185
+ });
186
+ router.post('/cli/v1/operations/disable-server', async (req, res) => {
187
+ await handleCliConfiguredServerMutation(req, res, options, 'disableConfiguredServer');
188
+ });
189
+ router.post('/cli/v1/operations/restart-server', async (req, res) => {
190
+ await handleCliBackendRestart(req, res, options);
191
+ });
192
+ router.post('/api/session/login', authenticationLimiter, async (req, res) => {
193
+ const parsedBody = adminLoginBodySchema.safeParse(req.body);
194
+ if (!parsedBody.success) {
195
+ res.status(400).json({ error: 'admin_login_request_invalid' });
196
+ return;
197
+ }
198
+ const { username, password } = parsedBody.data;
199
+ const source = getLoginSource(req);
200
+ if (failedLoginLimiter.isLimited(username, source)) {
201
+ res.status(429).json({ error: 'admin_login_rate_limited' });
202
+ return;
203
+ }
204
+ try {
205
+ const login = await options.adminService.login({
206
+ username,
207
+ password,
208
+ });
209
+ failedLoginLimiter.reset(username, source);
210
+ setAdminSessionCookie(res, options.getRuntimeIdentity().externalUrl, login.sessionToken, login.expiresAt);
211
+ res.status(200).json({
212
+ authenticated: true,
213
+ account: login.account,
214
+ csrfToken: login.csrfToken,
215
+ expiresAt: login.expiresAt,
216
+ });
217
+ }
218
+ catch (error) {
219
+ failedLoginLimiter.recordFailure(username, source);
220
+ sendAdminError(res, error);
221
+ }
222
+ });
223
+ router.use('/api', (req, res, next) => {
224
+ const sessionToken = getAdminSessionCookie(req);
225
+ const session = options.adminService.validateSession(sessionToken);
226
+ if (!session) {
227
+ res.status(401).json(unauthenticatedAdminApiResponse(options));
228
+ return;
229
+ }
230
+ if (isUnsafeMethod(req.method) && !options.adminService.validateCsrf(sessionToken, req.header('X-CSRF-Token'))) {
231
+ res.status(403).json({ error: 'csrf_required' });
232
+ return;
233
+ }
234
+ next();
235
+ });
236
+ router.get('/api/session', (req, res) => {
237
+ const session = options.adminService.validateSession(getAdminSessionCookie(req));
238
+ if (!session) {
239
+ res.status(401).json({ authenticated: false });
240
+ return;
241
+ }
242
+ res.status(200).json({
243
+ authenticated: true,
244
+ account: session.account,
245
+ csrfToken: session.csrfToken,
246
+ expiresAt: session.expiresAt,
247
+ });
248
+ });
249
+ router.post('/api/session/logout', (req, res) => {
250
+ options.adminService.revokeSession(getAdminSessionCookie(req));
251
+ clearAdminSessionCookie(res, options.getRuntimeIdentity().externalUrl);
252
+ res.status(200).json({ ok: true });
253
+ });
254
+ router.get('/api/status', statusLimiter, (req, res) => {
255
+ const session = options.adminService.validateSession(getAdminSessionCookie(req));
256
+ if (!session) {
257
+ res.status(401).json({ authenticated: false });
258
+ return;
259
+ }
260
+ res.status(200).json({
261
+ ok: true,
262
+ runtime: options.getRuntimeIdentity(),
263
+ session: {
264
+ authenticated: true,
265
+ account: toAdminConsoleAccount(session.account),
266
+ expiresAt: session.expiresAt,
267
+ },
268
+ oauth: sanitizeOAuthDashboard(options.getOAuthDashboard?.() ?? { status: 'ready', services: [] }),
269
+ audit: {
270
+ facts: options.configuredServerService?.getRecentAuditFacts({ limit: 10 }) ??
271
+ options.presetService?.getRecentAuditFacts({ limit: 10 }) ??
272
+ [],
273
+ },
274
+ about: buildAboutMetadata(options.getRuntimeIdentity(), {
275
+ buildVersion: req.header('X-Admin-UI-Build-Version'),
276
+ protocolVersion: req.header('X-Admin-UI-Protocol-Version'),
277
+ }),
278
+ });
279
+ });
280
+ router.post('/api/oauth/:serviceId/authorize', sensitiveOperationLimiter, async (req, res) => {
281
+ if (!options.oauthService) {
282
+ res.status(503).json({ error: 'backend_oauth_runtime_unavailable' });
283
+ return;
284
+ }
285
+ const serviceId = parseAdminOAuthServiceId(req, res);
286
+ if (!serviceId)
287
+ return;
288
+ const result = await options.oauthService.authorizeService({
289
+ context: buildAdminOperationContext(req, options, { type: 'backend_oauth_service', id: serviceId }),
290
+ serviceId,
291
+ });
292
+ sendAdminOAuthOperationResult(res, result);
293
+ });
294
+ router.post('/api/oauth/:serviceId/restart', sensitiveOperationLimiter, async (req, res) => {
295
+ if (!options.oauthService) {
296
+ res.status(503).json({ error: 'backend_oauth_runtime_unavailable' });
297
+ return;
298
+ }
299
+ const serviceId = parseAdminOAuthServiceId(req, res);
300
+ if (!serviceId)
301
+ return;
302
+ const result = await options.oauthService.restartService({
303
+ context: buildAdminOperationContext(req, options, { type: 'backend_oauth_service', id: serviceId }),
304
+ serviceId,
305
+ });
306
+ sendAdminOAuthOperationResult(res, result);
307
+ });
308
+ router.get('/api/presets', async (req, res) => {
309
+ if (!options.presetService)
310
+ return void res.status(404).json({ error: 'admin_presets_unavailable' });
311
+ const result = await options.presetService.listPresets({
312
+ context: buildAdminOperationContext(req, options, { type: 'preset_collection' }),
313
+ });
314
+ sendAdminOperationResult(res, result);
315
+ });
316
+ router.get('/api/presets/:name', async (req, res) => {
317
+ if (!options.presetService)
318
+ return void res.status(404).json({ error: 'admin_presets_unavailable' });
319
+ try {
320
+ sendAdminOperationResult(res, await options.presetService.getPreset({
321
+ context: buildAdminOperationContext(req, options, { type: 'preset', id: req.params.name }),
322
+ name: req.params.name,
323
+ }));
324
+ }
325
+ catch (error) {
326
+ sendPresetError(res, error);
327
+ }
328
+ });
329
+ router.post('/api/presets/preview', async (req, res) => {
330
+ if (!options.presetService)
331
+ return void res.status(404).json({ error: 'admin_presets_unavailable' });
332
+ try {
333
+ sendAdminOperationResult(res, await options.presetService.previewPreset({
334
+ context: buildAdminOperationContext(req, options, {
335
+ type: 'preset',
336
+ id: getBodyString(req.body, 'sourceName') || getBodyString(getBodyValue(req.body, 'draft'), 'name'),
337
+ }),
338
+ draft: getPresetDraft(req.body),
339
+ sourceName: getBodyString(req.body, 'sourceName') || undefined,
340
+ }));
341
+ }
342
+ catch (error) {
343
+ sendPresetError(res, error);
344
+ }
345
+ });
346
+ router.post('/api/presets', async (req, res) => {
347
+ await handlePresetMutation(req, res, options, 'create');
348
+ });
349
+ router.post('/api/presets/:name/update', async (req, res) => {
350
+ await handlePresetMutation(req, res, options, 'update');
351
+ });
352
+ router.post('/api/presets/:name/duplicate', async (req, res) => {
353
+ await handlePresetMutation(req, res, options, 'duplicate');
354
+ });
355
+ router.post('/api/presets/:name/delete-preview', async (req, res) => {
356
+ if (!options.presetService)
357
+ return void res.status(404).json({ error: 'admin_presets_unavailable' });
358
+ try {
359
+ sendAdminOperationResult(res, await options.presetService.previewDeletePreset({
360
+ context: buildAdminOperationContext(req, options, { type: 'preset', id: req.params.name }),
361
+ name: req.params.name,
362
+ revision: getBodyString(req.body, 'revision'),
363
+ }));
364
+ }
365
+ catch (error) {
366
+ sendPresetError(res, error);
367
+ }
368
+ });
369
+ router.delete('/api/presets/:name', async (req, res) => {
370
+ if (!options.presetService)
371
+ return void res.status(404).json({ error: 'admin_presets_unavailable' });
372
+ try {
373
+ sendAdminOperationResult(res, await options.presetService.deletePreset({
374
+ context: buildAdminOperationContext(req, options, { type: 'preset', id: req.params.name }),
375
+ name: req.params.name,
376
+ revision: getBodyString(req.body, 'revision'),
377
+ previewFingerprint: getBodyString(req.body, 'previewFingerprint'),
378
+ }));
379
+ }
380
+ catch (error) {
381
+ sendPresetError(res, error);
382
+ }
383
+ });
384
+ router.get('/api/configured-servers', async (req, res) => {
385
+ if (!options.configuredServerService) {
386
+ res.status(404).json({ error: 'admin_configured_servers_unavailable' });
387
+ return;
388
+ }
389
+ const result = await options.configuredServerService.listConfiguredServers({
390
+ context: buildAdminOperationContext(req, options, { type: 'configured_server_collection' }),
391
+ });
392
+ if (!result.ok) {
393
+ sendAdminOperationResult(res, result);
394
+ return;
395
+ }
396
+ res.status(200).json({
397
+ ok: true,
398
+ operationId: result.operationId,
399
+ servers: result.result.servers,
400
+ });
401
+ });
402
+ router.get('/api/configured-servers/:name', async (req, res) => {
403
+ await handleConfiguredServerDetail(req, res, options);
404
+ });
405
+ router.post('/api/configured-servers/:name/preview', async (req, res) => {
406
+ await handleConfiguredServerPreview(req, res, options);
407
+ });
408
+ router.post('/api/configured-servers/:name/apply', async (req, res) => {
409
+ await handleConfiguredServerApply(req, res, options);
410
+ });
411
+ router.post('/api/configured-servers/:name/enable', async (req, res) => {
412
+ await handleConfiguredServerMutation(req, res, options, 'enableConfiguredServer');
413
+ });
414
+ router.post('/api/configured-servers/:name/disable', async (req, res) => {
415
+ await handleConfiguredServerMutation(req, res, options, 'disableConfiguredServer');
416
+ });
417
+ router.use('/assets', express.static(path.join(adminConsoleAssets.rootDir, 'assets'), {
418
+ immutable: true,
419
+ maxAge: '1y',
420
+ }));
421
+ router.use('/assets', (_req, res) => {
422
+ res.status(404).type('text/plain').send('Admin Console asset not found');
423
+ });
424
+ router.get(['/', '/*splat'], (req, res, next) => {
425
+ if (isAdminApiPath(req.path)) {
426
+ next();
427
+ return;
428
+ }
429
+ sendAdminConsoleIndex(res, adminConsoleAssets.indexPath);
430
+ });
431
+ return router;
432
+ }
433
+ function resolveAdminConsoleAssets(configuredDir) {
434
+ const rootDir = configuredDir ?? resolveDefaultAdminConsoleAssetsDir();
435
+ return {
436
+ rootDir,
437
+ indexPath: path.join(rootDir, 'index.html'),
438
+ };
439
+ }
440
+ export function resolveDefaultAdminConsoleAssetsDir() {
441
+ return fileURLToPath(new URL('../../../admin', import.meta.url));
442
+ }
443
+ function isAdminApiPath(pathname) {
444
+ return (pathname === '/api' || pathname.startsWith('/api/') || pathname === '/cli/v1' || pathname.startsWith('/cli/v1/'));
445
+ }
446
+ function sendAdminConsoleIndex(res, indexPath) {
447
+ if (!fs.existsSync(indexPath)) {
448
+ res.status(503).type('text/plain').send('Admin Console assets are not available. Run the package build first.');
449
+ return;
450
+ }
451
+ res.status(200).sendFile(indexPath, { dotfiles: 'allow' });
452
+ }
453
+ function unauthenticatedAdminApiResponse(options) {
454
+ return options.adminService.hasAdminAccount()
455
+ ? { authenticated: false }
456
+ : { authenticated: false, adminStatus: 'setupRequired' };
457
+ }
458
+ async function handleCliConfiguredServerMutation(req, res, options, operationName) {
459
+ if (!options.configuredServerService) {
460
+ sendCliError(req, res, {
461
+ status: 404,
462
+ code: 'admin_configured_servers_unavailable',
463
+ message: 'Configured server administration is unavailable',
464
+ retryable: false,
465
+ });
466
+ return;
467
+ }
468
+ if (isMutationLocked(options)) {
469
+ sendCliError(req, res, {
470
+ status: 409,
471
+ code: 'runtime_scope_locked',
472
+ message: 'Runtime scope admin mutations are locked by another writer',
473
+ retryable: true,
474
+ details: {
475
+ operationName,
476
+ reason: 'writer_lock_unavailable',
477
+ },
478
+ });
479
+ return;
480
+ }
481
+ const sessionToken = getBearerSessionToken(req);
482
+ if (!sessionToken) {
483
+ sendCliError(req, res, {
484
+ status: 401,
485
+ code: 'admin_session_required',
486
+ message: 'A valid admin session bearer token is required',
487
+ retryable: false,
488
+ });
489
+ return;
490
+ }
491
+ const session = options.adminService.validateSession(sessionToken);
492
+ if (!session) {
493
+ sendCliError(req, res, {
494
+ status: 401,
495
+ code: 'admin_session_required',
496
+ message: 'A valid admin session bearer token is required',
497
+ retryable: false,
498
+ });
499
+ return;
500
+ }
501
+ const parsedBody = cliConfiguredServerMutationBodySchema.safeParse(req.body);
502
+ if (!parsedBody.success) {
503
+ sendCliError(req, res, {
504
+ status: 400,
505
+ code: 'validation_request_invalid',
506
+ message: 'Configured server targetName and dryRun must have valid types',
507
+ retryable: false,
508
+ });
509
+ return;
510
+ }
511
+ const { targetName, dryRun = false } = parsedBody.data;
512
+ const context = buildCliAdminOperationContext(req, options, session.account, sessionToken, {
513
+ type: 'configured_server',
514
+ id: targetName,
515
+ });
516
+ const input = {
517
+ context,
518
+ targetName,
519
+ ...(dryRun ? { dryRun: true } : {}),
520
+ confirmationRequirements: dryRun
521
+ ? []
522
+ : cliConfiguredServerConfirmationRequirements(options, targetName, operationName),
523
+ };
524
+ const result = operationName === 'enableConfiguredServer'
525
+ ? await options.configuredServerService.enableConfiguredServer(input)
526
+ : await options.configuredServerService.disableConfiguredServer(input);
527
+ sendCliAdminOperationResult(req, res, result);
528
+ }
529
+ async function handleCliBackendRestart(req, res, options) {
530
+ if (!options.backendRestartService) {
531
+ sendCliError(req, res, {
532
+ status: 404,
533
+ code: 'backend_restart_unavailable',
534
+ message: 'Backend restart administration is unavailable',
535
+ retryable: false,
536
+ });
537
+ return;
538
+ }
539
+ if (isMutationLocked(options)) {
540
+ sendCliError(req, res, {
541
+ status: 409,
542
+ code: 'runtime_scope_locked',
543
+ message: 'Runtime scope admin mutations are locked by another writer',
544
+ retryable: true,
545
+ details: { operationName: 'restartBackend', reason: 'writer_lock_unavailable' },
546
+ });
547
+ return;
548
+ }
549
+ const sessionToken = getBearerSessionToken(req);
550
+ const session = sessionToken ? options.adminService.validateSession(sessionToken) : null;
551
+ if (!sessionToken || !session) {
552
+ sendCliError(req, res, {
553
+ status: 401,
554
+ code: 'admin_session_required',
555
+ message: 'A valid admin session bearer token is required',
556
+ retryable: false,
557
+ });
558
+ return;
559
+ }
560
+ const parsedBody = cliBackendRestartBodySchema.safeParse(req.body);
561
+ if (!parsedBody.success) {
562
+ sendCliError(req, res, {
563
+ status: 400,
564
+ code: 'validation_request_invalid',
565
+ message: 'Backend targetName and restart selectors must have valid, mutually exclusive values',
566
+ retryable: false,
567
+ });
568
+ return;
569
+ }
570
+ const { targetName, instance, allInstances = false } = parsedBody.data;
571
+ const selection = instance
572
+ ? { mode: 'instance', instanceIdOrPrefix: instance }
573
+ : allInstances
574
+ ? { mode: 'all_instances' }
575
+ : { mode: 'target_default' };
576
+ const context = buildCliAdminOperationContext(req, options, session.account, sessionToken, {
577
+ type: 'backend',
578
+ id: targetName,
579
+ });
580
+ const result = await options.backendRestartService.restartBackend({
581
+ context,
582
+ targetName,
583
+ selection,
584
+ confirmationRequirements: cliBackendRestartConfirmationRequirements(options, targetName),
585
+ });
586
+ if (result.ok && isBackendRestartFailureResult(result.result)) {
587
+ sendCliBackendRestartOutcome(req, res, result.result);
588
+ return;
589
+ }
590
+ sendCliAdminOperationResult(req, res, result);
591
+ }
592
+ function sendCliBackendRestartOutcome(req, res, result) {
593
+ const response = backendRestartFailure(result.outcome);
594
+ sendCliError(req, res, {
595
+ status: response.status,
596
+ code: response.code,
597
+ message: response.message,
598
+ retryable: false,
599
+ details: {
600
+ targetName: result.targetName,
601
+ ...(result.candidateInstanceIds ? { candidateInstanceIds: result.candidateInstanceIds } : {}),
602
+ },
603
+ });
604
+ }
605
+ function isBackendRestartFailureResult(result) {
606
+ return result.outcome !== 'restarted';
607
+ }
608
+ function backendRestartFailure(outcome) {
609
+ switch (outcome) {
610
+ case 'target_not_found':
611
+ return { status: 404, code: 'backend_not_found', message: 'Backend target was not found' };
612
+ case 'target_disabled':
613
+ return { status: 409, code: 'backend_disabled', message: 'Backend target is disabled' };
614
+ case 'instance_not_found':
615
+ return { status: 404, code: 'backend_instance_not_found', message: 'Backend instance was not found' };
616
+ case 'instance_ambiguous':
617
+ return { status: 409, code: 'backend_instance_ambiguous', message: 'Backend instance prefix is ambiguous' };
618
+ case 'no_active_instances':
619
+ return {
620
+ status: 409,
621
+ code: 'backend_no_active_instances',
622
+ message: 'Backend has no active instances to restart',
623
+ };
624
+ case 'no_unhealthy_instances':
625
+ return {
626
+ status: 409,
627
+ code: 'backend_no_unhealthy_instances',
628
+ message: 'Backend has no unhealthy active instances to restart by default',
629
+ };
630
+ }
631
+ }
632
+ async function handleConfiguredServerDetail(req, res, options) {
633
+ if (!options.configuredServerService) {
634
+ res.status(404).json({ error: 'admin_configured_servers_unavailable' });
635
+ return;
636
+ }
637
+ const targetName = req.params.name;
638
+ try {
639
+ const result = await options.configuredServerService.getConfiguredServerDetail({
640
+ context: buildAdminOperationContext(req, options, { type: 'configured_server', id: targetName }),
641
+ targetName,
642
+ });
643
+ if (!result.ok) {
644
+ sendAdminOperationResult(res, result);
645
+ return;
646
+ }
647
+ res.status(200).json({
648
+ ok: true,
649
+ operationId: result.operationId,
650
+ server: result.result.server,
651
+ editContract: result.result.editContract,
652
+ });
653
+ }
654
+ catch (error) {
655
+ if (error instanceof AdminConfiguredServerNotFoundError) {
656
+ res.status(404).json({
657
+ ok: false,
658
+ error: error.code,
659
+ code: error.code,
660
+ message: 'Configured server target was not found',
661
+ target: { type: 'configured_server', id: error.targetName },
662
+ });
663
+ return;
664
+ }
665
+ throw error;
666
+ }
667
+ }
668
+ async function handleConfiguredServerPreview(req, res, options) {
669
+ if (!options.configuredServerService) {
670
+ res.status(404).json({ error: 'admin_configured_servers_unavailable' });
671
+ return;
672
+ }
673
+ const targetName = req.params.name;
674
+ const edit = getBodyValue(req.body, 'edit');
675
+ try {
676
+ const result = await options.configuredServerService.previewConfiguredServerEdit({
677
+ context: buildAdminOperationContext(req, options, { type: 'configured_server', id: targetName }),
678
+ targetName,
679
+ edit: edit === undefined ? {} : edit,
680
+ connectivityCheck: getBodyString(req.body, 'connectivityCheck') === 'manual' ? 'manual' : 'auto',
681
+ });
682
+ if (!result.ok) {
683
+ sendAdminOperationResult(res, result);
684
+ return;
685
+ }
686
+ res.status(200).json({
687
+ ok: true,
688
+ operationId: result.operationId,
689
+ preview: result.result,
690
+ });
691
+ }
692
+ catch (error) {
693
+ if (error instanceof AdminConfiguredServerNotFoundError) {
694
+ res.status(404).json({
695
+ ok: false,
696
+ error: error.code,
697
+ code: error.code,
698
+ message: 'Configured server target was not found',
699
+ target: { type: 'configured_server', id: error.targetName },
700
+ });
701
+ return;
702
+ }
703
+ throw error;
704
+ }
705
+ }
706
+ async function handleConfiguredServerApply(req, res, options) {
707
+ if (!options.configuredServerService) {
708
+ res.status(404).json({ error: 'admin_configured_servers_unavailable' });
709
+ return;
710
+ }
711
+ if (isMutationLocked(options)) {
712
+ sendAdminOperationResult(res, {
713
+ ok: false,
714
+ status: 'runtime_scope_locked',
715
+ code: 'runtime_scope_locked',
716
+ retryable: true,
717
+ operationName: 'applyConfiguredServerEdit',
718
+ reason: 'writer_lock_unavailable',
719
+ });
720
+ return;
721
+ }
722
+ const targetName = req.params.name;
723
+ try {
724
+ const result = await options.configuredServerService.applyConfiguredServerEdit({
725
+ context: buildAdminOperationContext(req, options, { type: 'configured_server', id: targetName }),
726
+ targetName,
727
+ edit: getBodyValue(req.body, 'edit') ?? {},
728
+ previewFingerprint: getBodyString(req.body, 'previewFingerprint'),
729
+ });
730
+ if (!result.ok && result.status === 'mutation_failed' && isConfiguredServerApplyErrorCode(result.error)) {
731
+ sendConfiguredServerApplyError(res, result.error);
732
+ return;
733
+ }
734
+ sendAdminOperationResult(res, result);
735
+ }
736
+ catch (error) {
737
+ if (error instanceof AdminConfiguredServerNotFoundError) {
738
+ sendConfiguredServerApplyError(res, error.code);
739
+ return;
740
+ }
741
+ if (error instanceof AdminConfiguredServerApplyError) {
742
+ sendConfiguredServerApplyError(res, error.code);
743
+ return;
744
+ }
745
+ throw error;
746
+ }
747
+ }
748
+ function isConfiguredServerApplyErrorCode(value) {
749
+ return value.startsWith('configured_server_');
750
+ }
751
+ function sendConfiguredServerApplyError(res, code) {
752
+ const status = code === 'configured_server_not_found' ? 404 : 409;
753
+ res.status(status).json({
754
+ ok: false,
755
+ error: code,
756
+ code,
757
+ message: configuredServerApplyErrorMessage(code),
758
+ });
759
+ }
760
+ function configuredServerApplyErrorMessage(code) {
761
+ switch (code) {
762
+ case 'configured_server_stale_preview':
763
+ return 'The configured server changed after preview. Preview the edit again.';
764
+ case 'configured_server_destination_conflict':
765
+ return 'The requested configured server target name is already in use.';
766
+ case 'configured_server_connectivity_blocked':
767
+ return 'Connectivity validation did not pass for the enabled remote server.';
768
+ case 'configured_server_edit_invalid':
769
+ return 'The configured server edit is invalid.';
770
+ case 'configured_server_edit_unchanged':
771
+ return 'The configured server edit does not contain any changes.';
772
+ case 'configured_server_not_found':
773
+ return 'Configured server target was not found.';
774
+ default:
775
+ return 'The configured server edit could not be applied.';
776
+ }
777
+ }
778
+ async function handleConfiguredServerMutation(req, res, options, operationName) {
779
+ if (!options.configuredServerService) {
780
+ res.status(404).json({ error: 'admin_configured_servers_unavailable' });
781
+ return;
782
+ }
783
+ if (isMutationLocked(options)) {
784
+ sendAdminOperationResult(res, {
785
+ ok: false,
786
+ status: 'runtime_scope_locked',
787
+ code: 'runtime_scope_locked',
788
+ retryable: true,
789
+ operationName,
790
+ reason: 'writer_lock_unavailable',
791
+ });
792
+ return;
793
+ }
794
+ const targetName = req.params.name;
795
+ const context = buildAdminOperationContext(req, options, { type: 'configured_server', id: targetName });
796
+ const input = { context, targetName };
797
+ const result = operationName === 'enableConfiguredServer'
798
+ ? await options.configuredServerService.enableConfiguredServer(input)
799
+ : await options.configuredServerService.disableConfiguredServer(input);
800
+ sendAdminOperationResult(res, result);
801
+ }
802
+ function cliMutationAvailability(options, setupRequired) {
803
+ if (!options.configuredServerService && !options.backendRestartService) {
804
+ return {
805
+ available: false,
806
+ reason: 'mutation_service_unavailable',
807
+ };
808
+ }
809
+ if (setupRequired) {
810
+ return {
811
+ available: false,
812
+ reason: 'setup_required',
813
+ };
814
+ }
815
+ return options.adminMutationAvailability ?? { available: true };
816
+ }
817
+ function isMutationLocked(options) {
818
+ return (options.adminMutationAvailability?.available === false &&
819
+ options.adminMutationAvailability.reason === 'writer_lock_unavailable');
820
+ }
821
+ function cliMcpReadinessStatus(mutationAvailability) {
822
+ if (mutationAvailability.available) {
823
+ return 'ready';
824
+ }
825
+ return mutationAvailability.reason === 'mutation_service_unavailable'
826
+ ? 'unavailable'
827
+ : (mutationAvailability.reason ?? 'unavailable');
828
+ }
829
+ export class FailedLoginLimiter {
830
+ now;
831
+ maxActiveKeys;
832
+ windowMs;
833
+ attempts = new Map();
834
+ constructor(now = Date.now, maxActiveKeys = FAILED_LOGIN_MAX_ACTIVE_KEYS, windowMs = FAILED_LOGIN_WINDOW_MS) {
835
+ this.now = now;
836
+ this.maxActiveKeys = maxActiveKeys;
837
+ this.windowMs = windowMs;
838
+ }
839
+ isLimited(username, origin) {
840
+ this.pruneExpired();
841
+ const attempt = this.getAttempt(username, origin);
842
+ if (attempt) {
843
+ return attempt.count >= FAILED_LOGIN_LIMIT;
844
+ }
845
+ return this.attempts.size >= this.maxActiveKeys;
846
+ }
847
+ recordFailure(username, origin) {
848
+ const key = this.key(username, origin);
849
+ this.pruneExpired();
850
+ const now = this.now();
851
+ const attempt = this.getAttempt(username, origin);
852
+ if (!attempt && this.attempts.size >= this.maxActiveKeys) {
853
+ return;
854
+ }
855
+ this.attempts.set(key, attempt ? { ...attempt, count: attempt.count + 1 } : { count: 1, firstFailureAt: now });
856
+ }
857
+ reset(username, origin) {
858
+ this.attempts.delete(this.key(username, origin));
859
+ }
860
+ getAttempt(username, origin) {
861
+ const key = this.key(username, origin);
862
+ const attempt = this.attempts.get(key);
863
+ if (!attempt) {
864
+ return null;
865
+ }
866
+ if (this.now() - attempt.firstFailureAt > this.windowMs) {
867
+ this.attempts.delete(key);
868
+ return null;
869
+ }
870
+ return attempt;
871
+ }
872
+ key(username, origin) {
873
+ return `${username.trim() || '<missing>'}\0${origin}`;
874
+ }
875
+ pruneExpired() {
876
+ const now = this.now();
877
+ for (const [key, attempt] of this.attempts) {
878
+ if (now - attempt.firstFailureAt > this.windowMs) {
879
+ this.attempts.delete(key);
880
+ }
881
+ }
882
+ }
883
+ }
884
+ function isUnsafeMethod(method) {
885
+ return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS';
886
+ }
887
+ function getLoginSource(req) {
888
+ return req.ip ?? req.socket.remoteAddress ?? req.header('origin') ?? 'unknown';
889
+ }
890
+ function setAdminSessionCookie(res, externalUrl, sessionToken, expiresAt) {
891
+ res.cookie(ADMIN_SESSION_COOKIE_NAME, sessionToken, {
892
+ httpOnly: true,
893
+ sameSite: 'strict',
894
+ secure: isHttpsRuntime(externalUrl),
895
+ path: '/admin',
896
+ expires: new Date(expiresAt),
897
+ });
898
+ }
899
+ function clearAdminSessionCookie(res, externalUrl) {
900
+ res.clearCookie(ADMIN_SESSION_COOKIE_NAME, {
901
+ httpOnly: true,
902
+ sameSite: 'strict',
903
+ secure: isHttpsRuntime(externalUrl),
904
+ path: '/admin',
905
+ });
906
+ }
907
+ function getAdminSessionCookie(req) {
908
+ const cookieHeader = req.header('cookie');
909
+ if (!cookieHeader) {
910
+ return undefined;
911
+ }
912
+ for (const cookie of cookieHeader.split(';')) {
913
+ const [name, ...valueParts] = cookie.trim().split('=');
914
+ if (name === ADMIN_SESSION_COOKIE_NAME) {
915
+ return valueParts.join('=');
916
+ }
917
+ }
918
+ return undefined;
919
+ }
920
+ function getBearerSessionToken(req) {
921
+ const authorization = req.header('authorization')?.trim();
922
+ if (!authorization) {
923
+ return undefined;
924
+ }
925
+ const bearerScheme = 'Bearer';
926
+ if (authorization.length <= bearerScheme.length ||
927
+ authorization.slice(0, bearerScheme.length).toLowerCase() !== bearerScheme.toLowerCase()) {
928
+ return undefined;
929
+ }
930
+ let tokenStart = bearerScheme.length;
931
+ if (authorization.charCodeAt(tokenStart) !== 0x20) {
932
+ return undefined;
933
+ }
934
+ while (authorization.charCodeAt(tokenStart) === 0x20) {
935
+ tokenStart += 1;
936
+ }
937
+ const token = authorization.slice(tokenStart);
938
+ return token && !/\s/u.test(token) ? token : undefined;
939
+ }
940
+ function isHttpsRuntime(externalUrl) {
941
+ try {
942
+ return new URL(externalUrl).protocol === 'https:';
943
+ }
944
+ catch {
945
+ return false;
946
+ }
947
+ }
948
+ function sendCliSuccess(req, res, result) {
949
+ const requestId = getRequestId(req);
950
+ sendBoundedCliEnvelope(res, 200, {
951
+ ok: true,
952
+ cliProtocolVersion: CLI_ADMIN_PROTOCOL_VERSION,
953
+ requestId,
954
+ warnings: [],
955
+ result,
956
+ });
957
+ }
958
+ function sendCliAdminError(req, res, error) {
959
+ if (error instanceof AdminIdentityError) {
960
+ const status = error.code === 'invalid_credentials' ? 401 : error.code === 'admin_account_not_found' ? 404 : 400;
961
+ sendCliError(req, res, {
962
+ status,
963
+ code: error.code,
964
+ message: error.message,
965
+ retryable: false,
966
+ });
967
+ return;
968
+ }
969
+ sendCliError(req, res, {
970
+ status: 500,
971
+ code: 'admin_cli_request_failed',
972
+ message: 'Admin CLI request failed',
973
+ retryable: false,
974
+ });
975
+ }
976
+ function sendCliError(req, res, error) {
977
+ const requestId = getRequestId(req);
978
+ sendBoundedCliEnvelope(res, error.status, {
979
+ ok: false,
980
+ cliProtocolVersion: CLI_ADMIN_PROTOCOL_VERSION,
981
+ requestId,
982
+ error: {
983
+ code: error.code,
984
+ message: error.message,
985
+ retryable: error.retryable,
986
+ requestId,
987
+ ...(error.retryAfterMs === undefined ? {} : { retryAfterMs: error.retryAfterMs }),
988
+ ...(error.recoveryCommand ? { recoveryCommand: error.recoveryCommand } : {}),
989
+ ...(error.details === undefined ? {} : { details: error.details }),
990
+ },
991
+ warnings: [],
992
+ });
993
+ }
994
+ function sendBoundedCliEnvelope(res, status, envelope) {
995
+ if (Buffer.byteLength(JSON.stringify(envelope), 'utf8') > CLI_ADMIN_RESPONSE_MAX_BYTES) {
996
+ res.status(422).json({
997
+ ok: false,
998
+ cliProtocolVersion: CLI_ADMIN_PROTOCOL_VERSION,
999
+ requestId: envelope.requestId,
1000
+ error: {
1001
+ code: 'validation_response_too_large',
1002
+ message: CLI_ADMIN_RESPONSE_TOO_LARGE_MESSAGE,
1003
+ retryable: false,
1004
+ requestId: envelope.requestId,
1005
+ details: {
1006
+ maxBytes: CLI_ADMIN_RESPONSE_MAX_BYTES,
1007
+ },
1008
+ },
1009
+ warnings: [],
1010
+ });
1011
+ return;
1012
+ }
1013
+ res.status(status).json(envelope);
1014
+ }
1015
+ function sendCliAdminOperationResult(req, res, result) {
1016
+ if (result.ok) {
1017
+ sendCliSuccess(req, res, {
1018
+ operationId: result.operationId,
1019
+ operationName: result.operationName,
1020
+ replayed: result.replayed,
1021
+ ...(typeof result.result === 'object' && result.result !== null ? result.result : { value: result.result }),
1022
+ });
1023
+ return;
1024
+ }
1025
+ sendCliError(req, res, {
1026
+ status: cliOperationErrorStatus(result.status),
1027
+ code: result.code,
1028
+ message: cliOperationErrorMessage(result.status),
1029
+ retryable: result.retryable,
1030
+ retryAfterMs: result.status === 'operation_in_progress' ? result.retryAfterMs : undefined,
1031
+ details: cliOperationErrorDetails(result),
1032
+ });
1033
+ }
1034
+ function sendAdminError(res, error) {
1035
+ if (error instanceof AdminIdentityError) {
1036
+ const status = error.code === 'invalid_credentials' ? 401 : error.code === 'admin_account_not_found' ? 404 : 400;
1037
+ res.status(status).json({ error: error.code });
1038
+ return;
1039
+ }
1040
+ throw error;
1041
+ }
1042
+ function sendAdminOperationResult(res, result) {
1043
+ if (result.ok) {
1044
+ res.status(200).json({
1045
+ ok: true,
1046
+ operationId: result.operationId,
1047
+ replayed: result.replayed,
1048
+ result: result.result,
1049
+ });
1050
+ return;
1051
+ }
1052
+ const status = result.status === 'idempotency_key_required' ? 400 : result.status === 'mutation_failed' ? 409 : 409;
1053
+ res.status(status).json(result);
1054
+ }
1055
+ function sendAdminOAuthOperationResult(res, result) {
1056
+ if (!result.ok && result.status === 'mutation_failed') {
1057
+ const status = result.error === 'backend_oauth_service_not_found'
1058
+ ? 404
1059
+ : result.error === 'backend_oauth_runtime_unavailable'
1060
+ ? 503
1061
+ : 502;
1062
+ res.status(status).json({ ok: false, error: result.error });
1063
+ return;
1064
+ }
1065
+ sendAdminOperationResult(res, result);
1066
+ }
1067
+ function parseAdminOAuthServiceId(req, res) {
1068
+ const parsed = adminOAuthServiceParamsSchema.safeParse(req.params);
1069
+ if (!parsed.success) {
1070
+ res.status(400).json({ error: 'backend_oauth_service_id_invalid' });
1071
+ return null;
1072
+ }
1073
+ return parsed.data.serviceId;
1074
+ }
1075
+ async function handlePresetMutation(req, res, options, action) {
1076
+ if (!options.presetService) {
1077
+ res.status(404).json({ error: 'admin_presets_unavailable' });
1078
+ return;
1079
+ }
1080
+ const draft = getPresetDraft(req.body);
1081
+ const common = {
1082
+ context: buildAdminOperationContext(req, options, { type: 'preset', id: req.params.name || draft.name }),
1083
+ draft,
1084
+ revision: getBodyString(req.body, 'revision'),
1085
+ previewFingerprint: getBodyString(req.body, 'previewFingerprint'),
1086
+ };
1087
+ try {
1088
+ const result = action === 'create'
1089
+ ? await options.presetService.createPreset(common)
1090
+ : action === 'update'
1091
+ ? await options.presetService.updatePreset({ ...common, sourceName: req.params.name })
1092
+ : await options.presetService.duplicatePreset({ ...common, sourceName: req.params.name });
1093
+ if (!result.ok && result.status === 'mutation_failed' && result.error === 'preset_revision_conflict') {
1094
+ res.status(409).json({ error: 'preset_revision_conflict' });
1095
+ return;
1096
+ }
1097
+ sendAdminOperationResult(res, result);
1098
+ }
1099
+ catch (error) {
1100
+ sendPresetError(res, error);
1101
+ }
1102
+ }
1103
+ function sendPresetError(res, error) {
1104
+ if (error instanceof AdminPresetNotFoundError) {
1105
+ res.status(404).json({ error: error.code });
1106
+ return;
1107
+ }
1108
+ if (error instanceof AdminPresetConflictError) {
1109
+ res.status(409).json({ error: error.code });
1110
+ return;
1111
+ }
1112
+ if (error instanceof Error) {
1113
+ res.status(400).json({ error: 'preset_validation_failed', message: sanitizeErrorMessage(error.message) });
1114
+ return;
1115
+ }
1116
+ throw error;
1117
+ }
1118
+ function getPresetDraft(body) {
1119
+ const value = getBodyValue(body, 'draft');
1120
+ const record = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
1121
+ const strategy = record.strategy;
1122
+ return {
1123
+ name: typeof record.name === 'string' ? record.name : '',
1124
+ description: typeof record.description === 'string' ? record.description : undefined,
1125
+ strategy: strategy === 'and' || strategy === 'advanced' ? strategy : 'or',
1126
+ tagQuery: record.tagQuery && typeof record.tagQuery === 'object' && !Array.isArray(record.tagQuery)
1127
+ ? record.tagQuery
1128
+ : {},
1129
+ };
1130
+ }
1131
+ function buildAboutMetadata(runtime, ui) {
1132
+ return {
1133
+ productName: '1MCP Agent',
1134
+ runtimeVersion: runtime.runtimeVersion,
1135
+ adminUiBuildVersion: ui.buildVersion || undefined,
1136
+ adminApiProtocolVersion: ADMIN_API_PROTOCOL_VERSION,
1137
+ adminUiProtocolVersion: ui.protocolVersion || undefined,
1138
+ protocolCompatible: ui.protocolVersion === ADMIN_API_PROTOCOL_VERSION,
1139
+ runtime: {
1140
+ runtimeScopeId: runtime.runtimeScopeId,
1141
+ externalUrl: runtime.externalUrl || undefined,
1142
+ },
1143
+ build: {
1144
+ commit: process.env.ADMIN_UI_BUILD_COMMIT || undefined,
1145
+ timestamp: process.env.ADMIN_UI_BUILD_TIMESTAMP || undefined,
1146
+ },
1147
+ project: {
1148
+ repository: normalizeRepositoryUrl(packageMetadata.repository?.url) ?? packageMetadata.homepage,
1149
+ documentation: packageMetadata.documentation,
1150
+ issues: packageMetadata.bugs?.url,
1151
+ license: packageMetadata.license,
1152
+ },
1153
+ };
1154
+ }
1155
+ function normalizeRepositoryUrl(value) {
1156
+ return value?.replace(/^git\+/u, '').replace(/\.git$/u, '');
1157
+ }
1158
+ function buildAdminOperationContext(req, options, target) {
1159
+ const sessionToken = getAdminSessionCookie(req);
1160
+ const session = options.adminService.validateSession(sessionToken);
1161
+ if (!session) {
1162
+ throw new Error('Admin operation context requested without a valid session');
1163
+ }
1164
+ const runtimeIdentity = options.getRuntimeIdentity();
1165
+ const operationName = operationNameForRequest(req);
1166
+ return {
1167
+ actor: {
1168
+ type: 'admin_session',
1169
+ accountId: session.account.id,
1170
+ sessionId: sessionToken,
1171
+ },
1172
+ origin: 'browser',
1173
+ target,
1174
+ runtimeIdentity: {
1175
+ runtimeScopeId: runtimeIdentity.runtimeScopeId,
1176
+ runtimeVersion: runtimeIdentity.runtimeVersion,
1177
+ },
1178
+ request: {
1179
+ requestId: getRequestId(req),
1180
+ jsonMode: true,
1181
+ },
1182
+ idempotencyKey: req.header('Idempotency-Key'),
1183
+ requestFingerprint: target.type === 'backend_oauth_service'
1184
+ ? backendOAuthRequestFingerprint(operationName, target.id)
1185
+ : configuredServerRequestFingerprint(operationName, target.id, req.body),
1186
+ confirmationFacts: getBodyRecord(req.body, 'confirmationFacts'),
1187
+ };
1188
+ }
1189
+ function cliConfiguredServerConfirmationRequirements(options, targetName, operationName) {
1190
+ const identity = options.getRuntimeIdentity();
1191
+ if (isLoopbackRuntimeUrl(identity.externalUrl)) {
1192
+ return [];
1193
+ }
1194
+ return [
1195
+ {
1196
+ code: 'confirm_non_loopback_runtime',
1197
+ expected: true,
1198
+ target: {
1199
+ type: 'configured_server',
1200
+ id: targetName,
1201
+ },
1202
+ },
1203
+ {
1204
+ code: 'confirmedOperation',
1205
+ expected: operationName === 'enableConfiguredServer' ? 'mcp.enable' : 'mcp.disable',
1206
+ target: {
1207
+ type: 'configured_server',
1208
+ id: targetName,
1209
+ },
1210
+ },
1211
+ {
1212
+ code: 'confirmedRuntimeScopeId',
1213
+ expected: identity.runtimeScopeId,
1214
+ target: {
1215
+ type: 'configured_server',
1216
+ id: targetName,
1217
+ },
1218
+ },
1219
+ {
1220
+ code: 'confirmationSource',
1221
+ expected: 'cli_flag',
1222
+ target: {
1223
+ type: 'configured_server',
1224
+ id: targetName,
1225
+ },
1226
+ },
1227
+ ];
1228
+ }
1229
+ function cliBackendRestartConfirmationRequirements(options, targetName) {
1230
+ const identity = options.getRuntimeIdentity();
1231
+ if (isLoopbackRuntimeUrl(identity.externalUrl)) {
1232
+ return [];
1233
+ }
1234
+ const target = { type: 'backend', id: targetName };
1235
+ return [
1236
+ { code: 'confirm_non_loopback_runtime', expected: true, target },
1237
+ { code: 'confirmedOperation', expected: 'mcp.restart', target },
1238
+ { code: 'confirmedRuntimeScopeId', expected: identity.runtimeScopeId, target },
1239
+ { code: 'confirmationSource', expected: 'cli_flag', target },
1240
+ ];
1241
+ }
1242
+ function buildCliAdminOperationContext(req, options, account, sessionToken, target) {
1243
+ const runtimeIdentity = options.getRuntimeIdentity();
1244
+ const operationName = operationNameForRequest(req);
1245
+ return {
1246
+ actor: {
1247
+ type: 'admin_session',
1248
+ accountId: account.id,
1249
+ sessionId: sessionToken,
1250
+ },
1251
+ origin: 'cli',
1252
+ target,
1253
+ runtimeIdentity: {
1254
+ runtimeScopeId: runtimeIdentity.runtimeScopeId,
1255
+ runtimeVersion: runtimeIdentity.runtimeVersion,
1256
+ },
1257
+ request: {
1258
+ requestId: getRequestId(req),
1259
+ jsonMode: true,
1260
+ },
1261
+ idempotencyKey: req.header('Idempotency-Key'),
1262
+ requestFingerprint: configuredServerRequestFingerprint(operationName, target.id, req.body),
1263
+ confirmationFacts: getBodyRecord(req.body, 'confirmationFacts'),
1264
+ };
1265
+ }
1266
+ function configuredServerRequestFingerprint(operationName, targetName, body) {
1267
+ const previewFingerprint = getBodyString(body, 'previewFingerprint');
1268
+ const normalized = stableJsonStringify({
1269
+ schemaVersion: 1,
1270
+ operationName,
1271
+ target: {
1272
+ type: operationName === 'restartBackend' ? 'backend' : 'configured_server',
1273
+ id: targetName ?? '',
1274
+ },
1275
+ ...(operationName === 'applyConfiguredServerEdit'
1276
+ ? {
1277
+ previewFingerprint,
1278
+ editDigest: keyedRequestFingerprint(previewFingerprint, 'configured-server-edit', stableJsonStringify(getBodyValue(body, 'edit') ?? {})),
1279
+ }
1280
+ : {}),
1281
+ ...(operationName === 'restartBackend'
1282
+ ? {
1283
+ restartSelection: {
1284
+ instance: getBodyString(body, 'instance'),
1285
+ allInstances: getBodyValue(body, 'allInstances') === true,
1286
+ },
1287
+ }
1288
+ : {}),
1289
+ });
1290
+ return operationName === 'applyConfiguredServerEdit'
1291
+ ? `configured_server_apply_${keyedRequestFingerprint(previewFingerprint, 'configured-server-apply', normalized)}`
1292
+ : normalized;
1293
+ }
1294
+ function keyedRequestFingerprint(key, domain, value) {
1295
+ return createHmac('sha256', key).update(domain).update('\0').update(value).digest('hex');
1296
+ }
1297
+ function backendOAuthRequestFingerprint(operationName, serviceId) {
1298
+ return stableJsonStringify({
1299
+ schemaVersion: 1,
1300
+ operationName,
1301
+ target: {
1302
+ type: 'backend_oauth_service',
1303
+ id: serviceId ?? '',
1304
+ },
1305
+ });
1306
+ }
1307
+ function stableJsonStringify(value) {
1308
+ if (Array.isArray(value)) {
1309
+ return `[${value.map((item) => stableJsonStringify(item)).join(',')}]`;
1310
+ }
1311
+ if (value && typeof value === 'object') {
1312
+ const entries = Object.entries(value)
1313
+ .filter(([, entryValue]) => entryValue !== undefined)
1314
+ .sort(([left], [right]) => left.localeCompare(right));
1315
+ return `{${entries
1316
+ .map(([key, entryValue]) => `${JSON.stringify(key)}:${stableJsonStringify(entryValue)}`)
1317
+ .join(',')}}`;
1318
+ }
1319
+ return JSON.stringify(value);
1320
+ }
1321
+ function cliOperationErrorStatus(status) {
1322
+ switch (status) {
1323
+ case 'idempotency_key_required':
1324
+ return 400;
1325
+ case 'admin_operation_journal_unavailable':
1326
+ return 503;
1327
+ default:
1328
+ return 409;
1329
+ }
1330
+ }
1331
+ function cliOperationErrorMessage(status) {
1332
+ switch (status) {
1333
+ case 'idempotency_key_required':
1334
+ return 'Idempotency key is required';
1335
+ case 'idempotency_conflict':
1336
+ return 'Idempotency key conflicts with another request';
1337
+ case 'operation_in_progress':
1338
+ return 'Admin operation is still in progress';
1339
+ case 'operation_state_unknown':
1340
+ return 'Admin operation state is unknown';
1341
+ case 'mutation_confirmation_required':
1342
+ return 'Additional mutation confirmation is required';
1343
+ case 'mutation_failed':
1344
+ return 'Configured server mutation failed';
1345
+ case 'admin_operation_journal_unavailable':
1346
+ return 'Admin operation journal is unavailable';
1347
+ case 'runtime_scope_mismatch':
1348
+ return 'Runtime scope mismatch';
1349
+ case 'runtime_scope_locked':
1350
+ return 'Runtime scope admin mutations are locked by another writer';
1351
+ default:
1352
+ return 'Admin operation failed';
1353
+ }
1354
+ }
1355
+ function cliOperationErrorDetails(result) {
1356
+ const details = {
1357
+ operationName: result.operationName,
1358
+ };
1359
+ if (result.status === 'operation_state_unknown') {
1360
+ details.target = result.target;
1361
+ details.reservedAt = result.reservedAt;
1362
+ details.recovery = result.recovery;
1363
+ }
1364
+ if (result.status === 'runtime_scope_locked') {
1365
+ details.reason = result.reason;
1366
+ }
1367
+ if (result.status === 'mutation_confirmation_required') {
1368
+ details.confirmationRequirements = result.confirmationRequirements;
1369
+ }
1370
+ if (result.status === 'mutation_failed') {
1371
+ details.error = result.error;
1372
+ }
1373
+ return details;
1374
+ }
1375
+ function operationNameForRequest(req) {
1376
+ if (req.path.startsWith('/api/oauth/')) {
1377
+ return req.path.endsWith('/restart') ? 'restartBackendOAuth' : 'authorizeBackendOAuth';
1378
+ }
1379
+ if (req.path.endsWith('/restart-server')) {
1380
+ return 'restartBackend';
1381
+ }
1382
+ if (req.path.endsWith('/enable') || req.path.endsWith('/enable-server')) {
1383
+ return 'enableConfiguredServer';
1384
+ }
1385
+ if (req.path.endsWith('/disable') || req.path.endsWith('/disable-server')) {
1386
+ return 'disableConfiguredServer';
1387
+ }
1388
+ if (req.path.endsWith('/preview')) {
1389
+ return 'previewConfiguredServerEdit';
1390
+ }
1391
+ if (req.path.endsWith('/apply')) {
1392
+ return 'applyConfiguredServerEdit';
1393
+ }
1394
+ if (req.method === 'GET' && req.path.startsWith('/api/configured-servers/')) {
1395
+ return 'getConfiguredServerDetail';
1396
+ }
1397
+ return 'listConfiguredServers';
1398
+ }
1399
+ function getRequestId(req) {
1400
+ const requestId = req.header('X-Request-Id');
1401
+ return requestId?.trim() || `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
1402
+ }
1403
+ function toAdminConsoleAccount(account) {
1404
+ return {
1405
+ id: account.id,
1406
+ username: account.username,
1407
+ role: account.role,
1408
+ };
1409
+ }
1410
+ function toCliAdminAccount(account) {
1411
+ return {
1412
+ username: account.username,
1413
+ role: account.role,
1414
+ };
1415
+ }
1416
+ function toCliRuntimeIdentity(identity) {
1417
+ return {
1418
+ identityProtocolVersion: identity.identityProtocolVersion,
1419
+ runtimeScopeId: identity.runtimeScopeId,
1420
+ runtimeVersion: identity.runtimeVersion,
1421
+ };
1422
+ }
1423
+ function sanitizeOAuthDashboard(dashboard) {
1424
+ if (dashboard.status !== 'ready') {
1425
+ return dashboard;
1426
+ }
1427
+ return {
1428
+ ...dashboard,
1429
+ services: dashboard.services.map((service) => ({
1430
+ ...service,
1431
+ id: service.name,
1432
+ displayName: backendOAuthServiceDisplayName(service.name),
1433
+ lastError: service.lastError ? sanitizeErrorMessage(service.lastError) : undefined,
1434
+ })),
1435
+ };
1436
+ }
1437
+ function backendOAuthServiceDisplayName(serviceId) {
1438
+ const identity = parseTemplateConnectionKey(serviceId);
1439
+ if (identity.kind !== 'rendered' || identity.renderedHash.length <= TEMPLATE_INSTANCE_ID_DISPLAY_LENGTH) {
1440
+ return serviceId;
1441
+ }
1442
+ return `${identity.templateName}:${identity.renderedHash.slice(0, TEMPLATE_INSTANCE_ID_DISPLAY_LENGTH)}`;
1443
+ }
1444
+ function getBodyValue(body, key) {
1445
+ if (!body || typeof body !== 'object') {
1446
+ return undefined;
1447
+ }
1448
+ return body[key];
1449
+ }
1450
+ function getBodyString(body, key) {
1451
+ if (!body || typeof body !== 'object') {
1452
+ return '';
1453
+ }
1454
+ const value = body[key];
1455
+ return typeof value === 'string' ? value : '';
1456
+ }
1457
+ function getBodyRecord(body, key) {
1458
+ if (!body || typeof body !== 'object') {
1459
+ return undefined;
1460
+ }
1461
+ const value = body[key];
1462
+ return value && typeof value === 'object' && !Array.isArray(value)
1463
+ ? { ...value }
1464
+ : undefined;
1465
+ }
1466
+ function isLoopbackRuntimeUrl(value) {
1467
+ try {
1468
+ const hostname = new URL(value).hostname.toLowerCase();
1469
+ return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '[::1]';
1470
+ }
1471
+ catch {
1472
+ return false;
1473
+ }
1474
+ }
1475
+ //# sourceMappingURL=adminRoutes.js.map