@1mcp/agent 0.35.0-beta.3 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (321) hide show
  1. package/build/admin/assets/admin-AboutRuntimeWorkspace-DH2QC2nJ.js +1 -0
  2. package/build/admin/assets/admin-BackendLogsWorkspace-BKavXT9B.js +1 -0
  3. package/build/admin/assets/admin-InstructionTemplatesWorkspace-CvG4EPdZ.js +1 -0
  4. package/build/admin/assets/admin-OAuthServicesWorkspace-B8y2weAX.js +1 -0
  5. package/build/admin/assets/admin-PresetAuthoringWorkspace-BboJVBFl.js +1 -0
  6. package/build/admin/assets/admin-console-D6jyI73o.css +1 -0
  7. package/build/admin/assets/{admin-console-Bbil6BQU.css → admin-console-DmRyrCWD.css} +1 -1
  8. package/build/admin/assets/admin-console-HJH52xGP.js +3 -0
  9. package/build/admin/assets/admin-icons-CYGTDZA5.js +1 -0
  10. package/build/admin/assets/admin-mantine-xwbfu5Gd.js +53 -0
  11. package/build/admin/assets/admin-react-BSaWeqgv.js +8 -0
  12. package/build/admin/assets/admin-rolldown-runtime-Bh1tDfsg.js +1 -0
  13. package/build/admin/favicon.svg +4 -0
  14. package/build/admin/index.html +8 -2
  15. package/build/auth/sdkOAuthClientProvider.d.ts +4 -0
  16. package/build/auth/sdkOAuthClientProvider.d.ts.map +1 -1
  17. package/build/auth/sdkOAuthClientProvider.js +29 -0
  18. package/build/auth/sdkOAuthClientProvider.js.map +1 -1
  19. package/build/auth/sessionTypes.d.ts +2 -0
  20. package/build/auth/sessionTypes.d.ts.map +1 -1
  21. package/build/auth/sessionTypes.js.map +1 -1
  22. package/build/commands/inspect/inspect.d.ts +1 -0
  23. package/build/commands/inspect/inspect.d.ts.map +1 -1
  24. package/build/commands/inspect/inspect.js +4 -1
  25. package/build/commands/inspect/inspect.js.map +1 -1
  26. package/build/commands/instructions/instructions.d.ts.map +1 -1
  27. package/build/commands/instructions/instructions.js +56 -1
  28. package/build/commands/instructions/instructions.js.map +1 -1
  29. package/build/commands/mcp/add.js +2 -2
  30. package/build/commands/mcp/add.js.map +1 -1
  31. package/build/commands/mcp/list.js +1 -1
  32. package/build/commands/mcp/list.js.map +1 -1
  33. package/build/commands/mcp/update.js +1 -1
  34. package/build/commands/mcp/update.js.map +1 -1
  35. package/build/commands/proxy/proxy.d.ts.map +1 -1
  36. package/build/commands/proxy/proxy.js +2 -0
  37. package/build/commands/proxy/proxy.js.map +1 -1
  38. package/build/commands/run/run.d.ts +1 -0
  39. package/build/commands/run/run.d.ts.map +1 -1
  40. package/build/commands/run/run.js +4 -1
  41. package/build/commands/run/run.js.map +1 -1
  42. package/build/commands/serve/index.d.ts +10 -0
  43. package/build/commands/serve/index.d.ts.map +1 -1
  44. package/build/commands/serve/index.js +10 -0
  45. package/build/commands/serve/index.js.map +1 -1
  46. package/build/commands/serve/serve.d.ts +3 -0
  47. package/build/commands/serve/serve.d.ts.map +1 -1
  48. package/build/commands/serve/serve.js +20 -0
  49. package/build/commands/serve/serve.js.map +1 -1
  50. package/build/commands/serve/serveBackground.d.ts.map +1 -1
  51. package/build/commands/serve/serveBackground.js +2 -0
  52. package/build/commands/serve/serveBackground.js.map +1 -1
  53. package/build/commands/serve/templateContextTrust.d.ts +12 -0
  54. package/build/commands/serve/templateContextTrust.d.ts.map +1 -0
  55. package/build/commands/serve/templateContextTrust.js +23 -0
  56. package/build/commands/serve/templateContextTrust.js.map +1 -0
  57. package/build/commands/shared/apiClient.d.ts +3 -0
  58. package/build/commands/shared/apiClient.d.ts.map +1 -1
  59. package/build/commands/shared/apiClient.js +6 -0
  60. package/build/commands/shared/apiClient.js.map +1 -1
  61. package/build/commands/shared/clientSurfaceAttachment.d.ts +11 -0
  62. package/build/commands/shared/clientSurfaceAttachment.d.ts.map +1 -1
  63. package/build/commands/shared/clientSurfaceAttachment.js +64 -6
  64. package/build/commands/shared/clientSurfaceAttachment.js.map +1 -1
  65. package/build/commands/shared/inspectApiSchemas.d.ts +8 -8
  66. package/build/commands/shared/serveClient.d.ts +2 -1
  67. package/build/commands/shared/serveClient.d.ts.map +1 -1
  68. package/build/commands/shared/serveClient.js +3 -1
  69. package/build/commands/shared/serveClient.js.map +1 -1
  70. package/build/commands/shared/serveTargetResolver.d.ts +4 -0
  71. package/build/commands/shared/serveTargetResolver.d.ts.map +1 -1
  72. package/build/commands/shared/serveTargetResolver.js +22 -22
  73. package/build/commands/shared/serveTargetResolver.js.map +1 -1
  74. package/build/commands/wait/wait.d.ts.map +1 -1
  75. package/build/commands/wait/wait.js +1 -0
  76. package/build/commands/wait/wait.js.map +1 -1
  77. package/build/config/configManager.d.ts +11 -1
  78. package/build/config/configManager.d.ts.map +1 -1
  79. package/build/config/configManager.js +13 -0
  80. package/build/config/configManager.js.map +1 -1
  81. package/build/config/configuredServerTargets.d.ts +3 -0
  82. package/build/config/configuredServerTargets.d.ts.map +1 -0
  83. package/build/config/configuredServerTargets.js +8 -0
  84. package/build/config/configuredServerTargets.js.map +1 -0
  85. package/build/config/mcpConfigManager.d.ts +3 -0
  86. package/build/config/mcpConfigManager.d.ts.map +1 -1
  87. package/build/config/mcpConfigManager.js +15 -0
  88. package/build/config/mcpConfigManager.js.map +1 -1
  89. package/build/constants/api.d.ts +1 -0
  90. package/build/constants/api.d.ts.map +1 -1
  91. package/build/constants/api.js +1 -0
  92. package/build/constants/api.js.map +1 -1
  93. package/build/constants/mcp.d.ts +1 -1
  94. package/build/constants/mcp.d.ts.map +1 -1
  95. package/build/constants/mcp.js +1 -1
  96. package/build/constants/mcp.js.map +1 -1
  97. package/build/core/capabilities/capabilityAggregator.d.ts.map +1 -1
  98. package/build/core/capabilities/capabilityAggregator.js +33 -14
  99. package/build/core/capabilities/capabilityAggregator.js.map +1 -1
  100. package/build/core/capabilities/capabilityCatalog.d.ts +23 -1
  101. package/build/core/capabilities/capabilityCatalog.d.ts.map +1 -1
  102. package/build/core/capabilities/capabilityCatalog.js +81 -7
  103. package/build/core/capabilities/capabilityCatalog.js.map +1 -1
  104. package/build/core/capabilities/capabilityPagination.d.ts +40 -0
  105. package/build/core/capabilities/capabilityPagination.d.ts.map +1 -0
  106. package/build/core/capabilities/capabilityPagination.js +287 -0
  107. package/build/core/capabilities/capabilityPagination.js.map +1 -0
  108. package/build/core/capabilities/capabilityVisibility.d.ts +2 -1
  109. package/build/core/capabilities/capabilityVisibility.d.ts.map +1 -1
  110. package/build/core/capabilities/capabilityVisibility.js +2 -1
  111. package/build/core/capabilities/capabilityVisibility.js.map +1 -1
  112. package/build/core/capabilities/configuredToolSnapshot.d.ts +12 -0
  113. package/build/core/capabilities/configuredToolSnapshot.d.ts.map +1 -0
  114. package/build/core/capabilities/configuredToolSnapshot.js +98 -0
  115. package/build/core/capabilities/configuredToolSnapshot.js.map +1 -0
  116. package/build/core/capabilities/lazyLoadingOrchestrator.d.ts.map +1 -1
  117. package/build/core/capabilities/lazyLoadingOrchestrator.js +8 -4
  118. package/build/core/capabilities/lazyLoadingOrchestrator.js.map +1 -1
  119. package/build/core/capabilities/metaToolProvider.js +3 -3
  120. package/build/core/capabilities/metaToolProvider.js.map +1 -1
  121. package/build/core/client/clientManager.d.ts.map +1 -1
  122. package/build/core/client/clientManager.js +11 -6
  123. package/build/core/client/clientManager.js.map +1 -1
  124. package/build/core/client/postAuthOAuthRecovery.d.ts +6 -0
  125. package/build/core/client/postAuthOAuthRecovery.d.ts.map +1 -0
  126. package/build/core/client/postAuthOAuthRecovery.js +74 -0
  127. package/build/core/client/postAuthOAuthRecovery.js.map +1 -0
  128. package/build/core/configChangeHandler.d.ts +2 -0
  129. package/build/core/configChangeHandler.d.ts.map +1 -1
  130. package/build/core/configChangeHandler.js +38 -11
  131. package/build/core/configChangeHandler.js.map +1 -1
  132. package/build/core/context/templateContextTrust.d.ts +66 -0
  133. package/build/core/context/templateContextTrust.d.ts.map +1 -0
  134. package/build/core/context/templateContextTrust.js +184 -0
  135. package/build/core/context/templateContextTrust.js.map +1 -0
  136. package/build/core/instructions/effectiveServerInstructions.d.ts +17 -0
  137. package/build/core/instructions/effectiveServerInstructions.d.ts.map +1 -0
  138. package/build/core/instructions/effectiveServerInstructions.js +12 -0
  139. package/build/core/instructions/effectiveServerInstructions.js.map +1 -0
  140. package/build/core/instructions/instructionAggregator.d.ts +51 -2
  141. package/build/core/instructions/instructionAggregator.d.ts.map +1 -1
  142. package/build/core/instructions/instructionAggregator.js +191 -30
  143. package/build/core/instructions/instructionAggregator.js.map +1 -1
  144. package/build/core/instructions/instructionsDistribution.d.ts +53 -0
  145. package/build/core/instructions/instructionsDistribution.d.ts.map +1 -1
  146. package/build/core/instructions/instructionsDistribution.js +25 -0
  147. package/build/core/instructions/instructionsDistribution.js.map +1 -1
  148. package/build/core/instructions/templateTypes.d.ts +25 -3
  149. package/build/core/instructions/templateTypes.d.ts.map +1 -1
  150. package/build/core/instructions/templateTypes.js +45 -0
  151. package/build/core/instructions/templateTypes.js.map +1 -1
  152. package/build/core/protocol/notificationHandlers.d.ts.map +1 -1
  153. package/build/core/protocol/notificationHandlers.js +26 -4
  154. package/build/core/protocol/notificationHandlers.js.map +1 -1
  155. package/build/core/protocol/promptRequestHandlers.d.ts.map +1 -1
  156. package/build/core/protocol/promptRequestHandlers.js +20 -11
  157. package/build/core/protocol/promptRequestHandlers.js.map +1 -1
  158. package/build/core/protocol/requestHandlerUtils.d.ts +4 -0
  159. package/build/core/protocol/requestHandlerUtils.d.ts.map +1 -1
  160. package/build/core/protocol/requestHandlerUtils.js +24 -4
  161. package/build/core/protocol/requestHandlerUtils.js.map +1 -1
  162. package/build/core/protocol/resourceRequestHandlers.d.ts.map +1 -1
  163. package/build/core/protocol/resourceRequestHandlers.js +38 -18
  164. package/build/core/protocol/resourceRequestHandlers.js.map +1 -1
  165. package/build/core/protocol/toolRequestHandlers.d.ts.map +1 -1
  166. package/build/core/protocol/toolRequestHandlers.js +79 -23
  167. package/build/core/protocol/toolRequestHandlers.js.map +1 -1
  168. package/build/core/server/agentConfig.d.ts +4 -0
  169. package/build/core/server/agentConfig.d.ts.map +1 -1
  170. package/build/core/server/agentConfig.js +3 -0
  171. package/build/core/server/agentConfig.js.map +1 -1
  172. package/build/core/server/clientInstancePool.js +2 -2
  173. package/build/core/server/clientInstancePool.js.map +1 -1
  174. package/build/core/server/connectionManager.d.ts.map +1 -1
  175. package/build/core/server/connectionManager.js +2 -0
  176. package/build/core/server/connectionManager.js.map +1 -1
  177. package/build/core/server/disabledTools.d.ts +2 -0
  178. package/build/core/server/disabledTools.d.ts.map +1 -1
  179. package/build/core/server/disabledTools.js +7 -4
  180. package/build/core/server/disabledTools.js.map +1 -1
  181. package/build/core/server/requestContextPreparation.d.ts +2 -1
  182. package/build/core/server/requestContextPreparation.d.ts.map +1 -1
  183. package/build/core/server/requestContextPreparation.js.map +1 -1
  184. package/build/core/server/runtimeScopeOwnership.js +1 -1
  185. package/build/core/server/runtimeScopeOwnership.js.map +1 -1
  186. package/build/core/server/serverManager.d.ts.map +1 -1
  187. package/build/core/server/serverManager.js +1 -0
  188. package/build/core/server/serverManager.js.map +1 -1
  189. package/build/core/server/templateIdentity.d.ts +1 -0
  190. package/build/core/server/templateIdentity.d.ts.map +1 -1
  191. package/build/core/server/templateIdentity.js +11 -1
  192. package/build/core/server/templateIdentity.js.map +1 -1
  193. package/build/core/server/templateServerManager.d.ts +5 -1
  194. package/build/core/server/templateServerManager.d.ts.map +1 -1
  195. package/build/core/server/templateServerManager.js +27 -9
  196. package/build/core/server/templateServerManager.js.map +1 -1
  197. package/build/core/server/toolDescriptionOverrides.d.ts +9 -0
  198. package/build/core/server/toolDescriptionOverrides.d.ts.map +1 -0
  199. package/build/core/server/toolDescriptionOverrides.js +44 -0
  200. package/build/core/server/toolDescriptionOverrides.js.map +1 -0
  201. package/build/core/tools/handlers/serverManagementHandler.d.ts +15 -1
  202. package/build/core/tools/handlers/serverManagementHandler.d.ts.map +1 -1
  203. package/build/core/tools/internal/adapters/helpers/configTransformers.d.ts +2 -0
  204. package/build/core/tools/internal/adapters/helpers/configTransformers.d.ts.map +1 -1
  205. package/build/core/tools/internal/schemas/discovery.d.ts +2 -2
  206. package/build/core/tools/internal/schemas/index.d.ts +9 -9
  207. package/build/core/tools/internal/schemas/installation.d.ts +3 -3
  208. package/build/core/tools/internal/schemas/management.d.ts +4 -4
  209. package/build/core/types/server.d.ts +2 -0
  210. package/build/core/types/server.d.ts.map +1 -1
  211. package/build/core/types/server.js.map +1 -1
  212. package/build/core/types/transport.d.ts +43 -0
  213. package/build/core/types/transport.d.ts.map +1 -1
  214. package/build/core/types/transport.js +60 -1
  215. package/build/core/types/transport.js.map +1 -1
  216. package/build/domains/admin/adminConfiguredServerService.d.ts +131 -4
  217. package/build/domains/admin/adminConfiguredServerService.d.ts.map +1 -1
  218. package/build/domains/admin/adminConfiguredServerService.js +766 -39
  219. package/build/domains/admin/adminConfiguredServerService.js.map +1 -1
  220. package/build/domains/admin/adminDomain.d.ts +13 -0
  221. package/build/domains/admin/adminDomain.d.ts.map +1 -1
  222. package/build/domains/admin/adminDomain.js +16 -0
  223. package/build/domains/admin/adminDomain.js.map +1 -1
  224. package/build/domains/admin/adminInstructionPreviewRuntime.d.ts +7 -0
  225. package/build/domains/admin/adminInstructionPreviewRuntime.d.ts.map +1 -0
  226. package/build/domains/admin/adminInstructionPreviewRuntime.js +154 -0
  227. package/build/domains/admin/adminInstructionPreviewRuntime.js.map +1 -0
  228. package/build/domains/admin/adminInstructionTemplateService.d.ts +169 -0
  229. package/build/domains/admin/adminInstructionTemplateService.d.ts.map +1 -0
  230. package/build/domains/admin/adminInstructionTemplateService.js +181 -0
  231. package/build/domains/admin/adminInstructionTemplateService.js.map +1 -0
  232. package/build/domains/admin/configuredToolInventory.d.ts +46 -0
  233. package/build/domains/admin/configuredToolInventory.d.ts.map +1 -0
  234. package/build/domains/admin/configuredToolInventory.js +135 -0
  235. package/build/domains/admin/configuredToolInventory.js.map +1 -0
  236. package/build/domains/backend-logs/backendLogSource.d.ts.map +1 -1
  237. package/build/domains/backend-logs/backendLogSource.js.map +1 -1
  238. package/build/domains/config-change/configChange.d.ts +2 -1
  239. package/build/domains/config-change/configChange.d.ts.map +1 -1
  240. package/build/domains/config-change/configChange.js +241 -13
  241. package/build/domains/config-change/configChange.js.map +1 -1
  242. package/build/domains/config-change/types.d.ts +34 -2
  243. package/build/domains/config-change/types.d.ts.map +1 -1
  244. package/build/domains/installation/serverInstallationSourceResolution.js +2 -0
  245. package/build/domains/installation/serverInstallationSourceResolution.js.map +1 -1
  246. package/build/domains/installation/serverInstallationTypes.d.ts +8 -0
  247. package/build/domains/installation/serverInstallationTypes.d.ts.map +1 -1
  248. package/build/domains/installation/serverInstallationWorkflow.js +16 -3
  249. package/build/domains/installation/serverInstallationWorkflow.js.map +1 -1
  250. package/build/domains/instruction-template/instructionTemplateManager.d.ts +58 -0
  251. package/build/domains/instruction-template/instructionTemplateManager.d.ts.map +1 -0
  252. package/build/domains/instruction-template/instructionTemplateManager.js +167 -0
  253. package/build/domains/instruction-template/instructionTemplateManager.js.map +1 -0
  254. package/build/domains/runtime-targets/runtimeIdentityVerification.d.ts +1 -0
  255. package/build/domains/runtime-targets/runtimeIdentityVerification.d.ts.map +1 -1
  256. package/build/domains/runtime-targets/runtimeIdentityVerification.js +6 -5
  257. package/build/domains/runtime-targets/runtimeIdentityVerification.js.map +1 -1
  258. package/build/transport/http/middlewares/httpRequestLogger.d.ts.map +1 -1
  259. package/build/transport/http/middlewares/httpRequestLogger.js +3 -2
  260. package/build/transport/http/middlewares/httpRequestLogger.js.map +1 -1
  261. package/build/transport/http/routes/adminRoutes.d.ts +2 -0
  262. package/build/transport/http/routes/adminRoutes.d.ts.map +1 -1
  263. package/build/transport/http/routes/adminRoutes.js +422 -23
  264. package/build/transport/http/routes/adminRoutes.js.map +1 -1
  265. package/build/transport/http/routes/apiRoutes.d.ts.map +1 -1
  266. package/build/transport/http/routes/apiRoutes.js +2 -0
  267. package/build/transport/http/routes/apiRoutes.js.map +1 -1
  268. package/build/transport/http/routes/inspectRequestContext.d.ts +2 -0
  269. package/build/transport/http/routes/inspectRequestContext.d.ts.map +1 -1
  270. package/build/transport/http/routes/inspectRequestContext.js +15 -6
  271. package/build/transport/http/routes/inspectRequestContext.js.map +1 -1
  272. package/build/transport/http/routes/inspectRoutes.d.ts +9 -0
  273. package/build/transport/http/routes/inspectRoutes.d.ts.map +1 -1
  274. package/build/transport/http/routes/inspectRoutes.js +15 -10
  275. package/build/transport/http/routes/inspectRoutes.js.map +1 -1
  276. package/build/transport/http/routes/instructionsRoutes.d.ts +4 -0
  277. package/build/transport/http/routes/instructionsRoutes.d.ts.map +1 -0
  278. package/build/transport/http/routes/instructionsRoutes.js +147 -0
  279. package/build/transport/http/routes/instructionsRoutes.js.map +1 -0
  280. package/build/transport/http/routes/streamableHttpRoutes.d.ts.map +1 -1
  281. package/build/transport/http/routes/streamableHttpRoutes.js +13 -4
  282. package/build/transport/http/routes/streamableHttpRoutes.js.map +1 -1
  283. package/build/transport/http/routes/toolRoutes.d.ts.map +1 -1
  284. package/build/transport/http/routes/toolRoutes.js +5 -4
  285. package/build/transport/http/routes/toolRoutes.js.map +1 -1
  286. package/build/transport/http/server.d.ts.map +1 -1
  287. package/build/transport/http/server.js +17 -0
  288. package/build/transport/http/server.js.map +1 -1
  289. package/build/transport/http/storage/streamableSessionRepository.d.ts.map +1 -1
  290. package/build/transport/http/storage/streamableSessionRepository.js +2 -0
  291. package/build/transport/http/storage/streamableSessionRepository.js.map +1 -1
  292. package/build/transport/http/streamableSessionLifecycle.d.ts +3 -1
  293. package/build/transport/http/streamableSessionLifecycle.d.ts.map +1 -1
  294. package/build/transport/http/streamableSessionLifecycle.js +23 -6
  295. package/build/transport/http/streamableSessionLifecycle.js.map +1 -1
  296. package/build/transport/http/utils/contextExtractor.d.ts +14 -16
  297. package/build/transport/http/utils/contextExtractor.d.ts.map +1 -1
  298. package/build/transport/http/utils/contextExtractor.js +124 -33
  299. package/build/transport/http/utils/contextExtractor.js.map +1 -1
  300. package/build/transport/http/utils/templateContextAuthority.d.ts +13 -0
  301. package/build/transport/http/utils/templateContextAuthority.d.ts.map +1 -0
  302. package/build/transport/http/utils/templateContextAuthority.js +121 -0
  303. package/build/transport/http/utils/templateContextAuthority.js.map +1 -0
  304. package/build/transport/stdioProxyTransport.d.ts +3 -0
  305. package/build/transport/stdioProxyTransport.d.ts.map +1 -1
  306. package/build/transport/stdioProxyTransport.js +7 -2
  307. package/build/transport/stdioProxyTransport.js.map +1 -1
  308. package/build/utils/validation/discoveredServer.d.ts +15 -0
  309. package/build/utils/validation/discoveredServer.d.ts.map +1 -0
  310. package/build/utils/validation/discoveredServer.js +2 -0
  311. package/build/utils/validation/discoveredServer.js.map +1 -0
  312. package/build/utils/validation/urlDetection.d.ts +4 -5
  313. package/build/utils/validation/urlDetection.d.ts.map +1 -1
  314. package/build/utils/validation/urlDetection.js +94 -25
  315. package/build/utils/validation/urlDetection.js.map +1 -1
  316. package/package.json +2 -2
  317. package/build/admin/assets/admin-console-BOl7XR0p.js +0 -61
  318. package/build/utils/ui/pagination.d.ts +0 -35
  319. package/build/utils/ui/pagination.d.ts.map +0 -1
  320. package/build/utils/ui/pagination.js +0 -166
  321. package/build/utils/ui/pagination.js.map +0 -1
@@ -1,7 +1,9 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { mergeGlobalAndServerConfig } from '../../config/mcpConfigMerge.js';
3
+ import { normalizeDisabledToolsForServer } from '../../core/server/disabledTools.js';
3
4
  import { GLOBAL_TRANSPORT_CONFIG_KEYS, transportConfigSchema, } from '../../core/types/index.js';
4
- import { fingerprintConfiguredServerDefaults, fingerprintConfiguredServerSecretValue, fingerprintConfiguredServerTarget, isConfiguredServerTargetDisabled, } from '../../domains/config-change/configChange.js';
5
+ import { fingerprintConfiguredServerConfigDocument, fingerprintConfiguredServerDefaults, fingerprintConfiguredServerSecretValue, fingerprintConfiguredServerTarget, isConfiguredServerTargetDisabled, } from '../../domains/config-change/configChange.js';
6
+ import { createServerInstallationWorkflow, } from '../../domains/installation/serverInstallationWorkflow.js';
5
7
  import { z } from 'zod';
6
8
  const ENV_PLACEHOLDER_PATTERN = /\$\{[^}]+\}|\$[A-Za-z_][A-Za-z0-9_]*/u;
7
9
  const previewTransportConfigSchema = transportConfigSchema.extend({
@@ -12,6 +14,365 @@ const previewTransportConfigSchema = transportConfigSchema.extend({
12
14
  })
13
15
  .optional(),
14
16
  });
17
+ const configuredServerCreateSecretSchema = z
18
+ .object({
19
+ fieldPath: z.array(z.string()).min(2),
20
+ action: z.literal('replace'),
21
+ replacement: z.object({
22
+ kind: z.enum(['environmentReference', 'inlineSecret']),
23
+ value: z.string(),
24
+ }),
25
+ })
26
+ .strict();
27
+ const configuredServerCreateDraftSchema = z
28
+ .object({
29
+ name: z.string(),
30
+ enabled: z.boolean().optional(),
31
+ tags: z.array(z.string()).optional(),
32
+ transport: z
33
+ .object({
34
+ type: z.enum(['stdio', 'http', 'sse']),
35
+ command: z.string().optional(),
36
+ url: z.string().optional(),
37
+ args: z.array(z.string()).optional(),
38
+ cwd: z.string().optional(),
39
+ env: z.record(z.string(), z.string()).optional(),
40
+ headers: z.record(z.string(), z.string()).optional(),
41
+ timeout: z.number().optional(),
42
+ connectionTimeout: z.number().optional(),
43
+ requestTimeout: z.number().optional(),
44
+ restartOnExit: z.boolean().optional(),
45
+ maxRestarts: z.number().optional(),
46
+ restartDelay: z.number().optional(),
47
+ })
48
+ .strict(),
49
+ secrets: z.array(configuredServerCreateSecretSchema).optional(),
50
+ })
51
+ .strict();
52
+ const CONFIGURED_SERVER_CREATE_TRANSPORT_FIELD_KEYS = new Set([
53
+ 'type',
54
+ 'timeout',
55
+ 'connectionTimeout',
56
+ 'requestTimeout',
57
+ 'url',
58
+ 'headers',
59
+ 'command',
60
+ 'args',
61
+ 'cwd',
62
+ 'env',
63
+ 'restartOnExit',
64
+ 'maxRestarts',
65
+ 'restartDelay',
66
+ ]);
67
+ function normalizeConfiguredServerCreateDraft(value) {
68
+ const parsed = configuredServerCreateDraftSchema.safeParse(value);
69
+ if (!parsed.success) {
70
+ const record = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
71
+ const transport = record.transport && typeof record.transport === 'object' && !Array.isArray(record.transport)
72
+ ? record.transport
73
+ : {};
74
+ const transportType = transport.type === 'http' || transport.type === 'sse' ? transport.type : 'stdio';
75
+ return {
76
+ draft: {},
77
+ source: {
78
+ type: 'direct',
79
+ localName: typeof record.name === 'string' ? record.name : '',
80
+ transport: transportType,
81
+ },
82
+ validation: {
83
+ status: 'invalid',
84
+ errors: parsed.error.issues.map((issue) => ({
85
+ fieldPath: issue.path.map(String),
86
+ code: 'invalid_create_field',
87
+ message: issue.message,
88
+ })),
89
+ },
90
+ };
91
+ }
92
+ const { transport } = parsed.data;
93
+ const draft = {
94
+ name: parsed.data.name,
95
+ enabled: parsed.data.enabled,
96
+ tags: parsed.data.tags,
97
+ transport: { ...transport },
98
+ secrets: parsed.data.secrets,
99
+ };
100
+ const secretContainer = transport.type === 'stdio' ? 'env' : 'headers';
101
+ const secretValues = configuredServerCreateSecretValues(draft, secretContainer);
102
+ const source = {
103
+ type: 'direct',
104
+ localName: parsed.data.name,
105
+ transport: transport.type,
106
+ enabled: parsed.data.enabled,
107
+ tags: parsed.data.tags,
108
+ command: transport.command,
109
+ url: transport.url,
110
+ args: transport.args,
111
+ cwd: transport.cwd,
112
+ env: mergeConfiguredServerCreateRecord(transport.env, secretContainer === 'env' ? secretValues : undefined),
113
+ headers: mergeConfiguredServerCreateRecord(transport.headers, secretContainer === 'headers' ? secretValues : undefined),
114
+ timeout: transport.timeout,
115
+ connectionTimeout: transport.connectionTimeout,
116
+ requestTimeout: transport.requestTimeout,
117
+ autoRestart: transport.restartOnExit,
118
+ maxRestarts: transport.maxRestarts,
119
+ restartDelay: transport.restartDelay,
120
+ };
121
+ return { draft, source, validation: { status: 'valid', errors: [] } };
122
+ }
123
+ function configuredServerCreateSecretValues(draft, container) {
124
+ const values = {};
125
+ for (const secret of draft.secrets ?? []) {
126
+ if (secret.fieldPath.length !== 2 || secret.fieldPath[0] !== container || !isValidFieldPath(secret.fieldPath)) {
127
+ continue;
128
+ }
129
+ if (!secret.replacement)
130
+ continue;
131
+ values[secret.fieldPath[1]] = replacementValue(secret.replacement);
132
+ }
133
+ return values;
134
+ }
135
+ function mergeConfiguredServerCreateRecord(base, additions) {
136
+ const merged = { ...base, ...additions };
137
+ return Object.keys(merged).length > 0 ? merged : undefined;
138
+ }
139
+ function createWorkflowValidation(workflow, config) {
140
+ const errors = [];
141
+ for (const [field, messages] of Object.entries(workflow.fieldErrors ?? {})) {
142
+ for (const message of messages) {
143
+ errors.push({
144
+ fieldPath: field === 'localName' ? ['name'] : ['transport', field],
145
+ code: 'invalid_create_field',
146
+ message,
147
+ });
148
+ }
149
+ }
150
+ if (workflow.status === 'exists' || workflow.status === 'template_conflict') {
151
+ errors.push({
152
+ fieldPath: ['name'],
153
+ code: workflow.status === 'template_conflict' ? 'template_name_conflict' : 'configured_server_name_conflict',
154
+ message: 'A configured server or Template Server definition already uses this name.',
155
+ });
156
+ }
157
+ if (config) {
158
+ const parsed = previewTransportConfigSchema.safeParse(config);
159
+ if (!parsed.success) {
160
+ for (const issue of parsed.error.issues) {
161
+ errors.push({
162
+ fieldPath: ['transport', ...issue.path.map(String)],
163
+ code: 'invalid_transport_config',
164
+ message: issue.message,
165
+ });
166
+ }
167
+ }
168
+ }
169
+ return { status: errors.length === 0 ? 'valid' : 'invalid', errors };
170
+ }
171
+ function validateConfiguredServerCreateSecrets(draft) {
172
+ const errors = [];
173
+ const expectedContainer = draft.transport?.type === 'stdio' ? 'env' : 'headers';
174
+ for (const [index, secret] of (draft.secrets ?? []).entries()) {
175
+ const basePath = ['secrets', String(index)];
176
+ if (!isValidFieldPath(secret.fieldPath) ||
177
+ secret.fieldPath.length !== 2 ||
178
+ secret.fieldPath[0] !== expectedContainer) {
179
+ errors.push({
180
+ fieldPath: [...basePath, 'fieldPath'],
181
+ code: 'unsupported_secret_field',
182
+ message: `Secret replacement must target ${expectedContainer} for the selected transport.`,
183
+ });
184
+ }
185
+ if (!secret.replacement) {
186
+ errors.push({
187
+ fieldPath: [...basePath, 'replacement'],
188
+ code: 'missing_secret_replacement',
189
+ message: 'Create secret actions require a replacement.',
190
+ });
191
+ continue;
192
+ }
193
+ if (secret.replacement.kind === 'environmentReference' && !isEnvironmentReferenceInput(secret.replacement.value)) {
194
+ errors.push({
195
+ fieldPath: [...basePath, 'replacement', 'value'],
196
+ code: 'invalid_environment_reference',
197
+ message: 'Environment reference must be an environment variable name or substitution expression.',
198
+ });
199
+ }
200
+ if (secret.replacement.kind === 'inlineSecret' && secret.replacement.value.length === 0) {
201
+ errors.push({
202
+ fieldPath: [...basePath, 'replacement', 'value'],
203
+ code: 'empty_inline_secret',
204
+ message: 'Inline secret replacement cannot be empty.',
205
+ });
206
+ }
207
+ }
208
+ return { status: errors.length === 0 ? 'valid' : 'invalid', errors };
209
+ }
210
+ function createConfiguredServerCreateContract() {
211
+ const transportFields = TRANSPORT_FIELD_DEFINITIONS.filter((definition) => CONFIGURED_SERVER_CREATE_TRANSPORT_FIELD_KEYS.has(definition.key)).map((definition) => ({
212
+ fieldPath: ['transport', definition.key],
213
+ label: definition.label,
214
+ control: definition.control,
215
+ value: definition.defaultValue,
216
+ options: definition.key === 'type' ? ['stdio', 'http', 'sse'] : definition.options,
217
+ editable: true,
218
+ applicableTransportTypes: definition.applicableTransportTypes?.filter((type) => type !== 'streamableHttp'),
219
+ }));
220
+ return {
221
+ schemaVersion: 1,
222
+ capabilities: {
223
+ create: { supported: true },
224
+ forceReplacement: { supported: false },
225
+ rawJson: { supported: false },
226
+ preview: { supported: true },
227
+ apply: { supported: true },
228
+ },
229
+ secretPolicy: {
230
+ allowedActions: ['replace'],
231
+ environmentReference: {
232
+ recommended: true,
233
+ storesSecretMaterial: false,
234
+ guidance: 'Store an environment variable name or substitution expression instead of secret material.',
235
+ },
236
+ inlineReplacement: {
237
+ emphasis: 'secondary',
238
+ guidance: 'Inline secret material is stored in the Runtime Scope configuration.',
239
+ },
240
+ },
241
+ fieldGroups: [
242
+ {
243
+ id: 'identity',
244
+ label: 'Identity',
245
+ fields: [
246
+ { fieldPath: ['name'], label: 'Server Name', control: 'text', editable: true },
247
+ { fieldPath: ['enabled'], label: 'Enabled', control: 'switch', value: true, editable: true },
248
+ { fieldPath: ['tags'], label: 'Tags', control: 'tag-list', value: [], editable: true },
249
+ ],
250
+ },
251
+ { id: 'transport', label: 'Transport', fields: transportFields },
252
+ ],
253
+ };
254
+ }
255
+ function createConfiguredServerCreateDiff(proposed, draft) {
256
+ const diff = [
257
+ { fieldPath: ['name'], oldValue: undefined, newValue: proposed.id, riskFlags: [] },
258
+ { fieldPath: ['enabled'], oldValue: undefined, newValue: proposed.enabled, riskFlags: ['connection_critical'] },
259
+ { fieldPath: ['tags'], oldValue: undefined, newValue: proposed.tags, riskFlags: [] },
260
+ ];
261
+ for (const [key, value] of Object.entries(proposed.transport)) {
262
+ diff.push({
263
+ fieldPath: ['transport', key],
264
+ oldValue: undefined,
265
+ newValue: value,
266
+ riskFlags: previewRiskFlags([key]),
267
+ });
268
+ }
269
+ for (const secret of draft.secrets ?? []) {
270
+ diff.push({
271
+ fieldPath: secret.fieldPath,
272
+ oldValue: undefined,
273
+ newValue: previewSecretNewValue(secret),
274
+ secretAction: 'replace',
275
+ riskFlags: ['connection_critical', 'secret'],
276
+ });
277
+ }
278
+ return diff;
279
+ }
280
+ function previewSecretNewValue(secret) {
281
+ const replacement = secret.replacement;
282
+ if (!replacement)
283
+ return { present: false, value: '[REDACTED]', secret: true };
284
+ if (replacement.kind === 'environmentReference') {
285
+ return {
286
+ kind: 'environmentReference',
287
+ value: normalizeEnvironmentReference(replacement.value),
288
+ storesSecretMaterial: false,
289
+ };
290
+ }
291
+ return { present: true, value: '[REDACTED]', secret: true };
292
+ }
293
+ function configuredServerCreatePreviewFingerprint(input) {
294
+ const redactedDraft = {
295
+ ...input.draft,
296
+ secrets: input.draft.secrets?.map((secret) => ({
297
+ fieldPath: secret.fieldPath,
298
+ action: secret.action,
299
+ replacement: secret.replacement ? fingerprintSecretReplacement(secret.replacement) : undefined,
300
+ })),
301
+ };
302
+ return `preview_${createHash('sha256')
303
+ .update(stableStringify({
304
+ schemaVersion: 1,
305
+ configFingerprint: input.configFingerprint,
306
+ draft: redactedDraft,
307
+ proposed: input.proposed,
308
+ diff: input.diff,
309
+ validation: input.validation,
310
+ connectivityCheck: connectivityFingerprintFacts(input.connectivityCheck),
311
+ }))
312
+ .digest('hex')}`;
313
+ }
314
+ function connectivityFingerprintFacts(connectivityCheck) {
315
+ if (connectivityCheck.status !== 'passed')
316
+ return connectivityCheck;
317
+ return { status: connectivityCheck.status, mode: connectivityCheck.mode };
318
+ }
319
+ function previewCreateConfigChange(targetName, workflowStatus, changed) {
320
+ const status = workflowStatus === 'template_conflict'
321
+ ? 'template_conflict'
322
+ : workflowStatus === 'exists'
323
+ ? 'destination_conflict'
324
+ : changed
325
+ ? 'changed'
326
+ : 'failed';
327
+ return {
328
+ status,
329
+ operation: 'create_static',
330
+ configPath: '[redacted]',
331
+ target: {
332
+ name: targetName,
333
+ ...(workflowStatus === 'template_conflict'
334
+ ? { source: 'mcpTemplates' }
335
+ : workflowStatus === 'exists'
336
+ ? { source: 'mcpServers' }
337
+ : {}),
338
+ },
339
+ changed,
340
+ backup: { created: false },
341
+ retentionCleanup: { attempted: false, deletedPaths: [], warnings: [] },
342
+ reload: { status: 'skipped' },
343
+ warnings: [],
344
+ };
345
+ }
346
+ function createDraftCandidateName(value) {
347
+ if (!value || typeof value !== 'object' || Array.isArray(value))
348
+ return '';
349
+ const name = value.name;
350
+ return typeof name === 'string' ? name : '';
351
+ }
352
+ function sanitizeCreateConfirmationFacts(facts) {
353
+ if (!facts)
354
+ return undefined;
355
+ const allowed = [
356
+ 'previewConfirmed',
357
+ 'targetNameConfirmed',
358
+ 'secretChangeConfirmed',
359
+ 'connectionCriticalConfirmed',
360
+ 'connectivityFailureOverrideConfirmed',
361
+ ];
362
+ const sanitized = Object.fromEntries(allowed.filter((key) => key in facts).map((key) => [key, facts[key]]));
363
+ return Object.keys(sanitized).length ? sanitized : undefined;
364
+ }
365
+ function assertSuccessfulConfiguredServerCreate(configChange) {
366
+ if (configChange.status === 'changed')
367
+ return;
368
+ if (configChange.status === 'source_conflict') {
369
+ throw new AdminConfiguredServerApplyError('configured_server_stale_preview');
370
+ }
371
+ if (configChange.status === 'destination_conflict' || configChange.status === 'template_conflict') {
372
+ throw new AdminConfiguredServerApplyError('configured_server_destination_conflict');
373
+ }
374
+ throw new AdminConfiguredServerApplyError('configured_server_create_failed');
375
+ }
15
376
  export class AdminConfiguredServerNotFoundError extends Error {
16
377
  targetName;
17
378
  code = 'configured_server_not_found';
@@ -26,60 +387,157 @@ export class AdminConfiguredServerService {
26
387
  constructor(options) {
27
388
  this.options = options;
28
389
  }
390
+ async getConfiguredServerCreateContract(input) {
391
+ return this.options.operationService.executeReadOnly({
392
+ context: input.context,
393
+ operationName: 'getConfiguredServerCreateContract',
394
+ run: async () => createConfiguredServerCreateContract(),
395
+ });
396
+ }
397
+ async previewConfiguredServerCreate(input) {
398
+ return this.options.operationService.executeDryRun({
399
+ context: input.context,
400
+ operationName: 'previewConfiguredServerCreate',
401
+ run: async () => this.previewConfiguredServerCreateResult(input),
402
+ });
403
+ }
404
+ async applyConfiguredServerCreate(input) {
405
+ const candidateName = createDraftCandidateName(input.draft);
406
+ const context = {
407
+ ...input.context,
408
+ target: { type: 'configured_server', id: candidateName },
409
+ confirmationFacts: sanitizeCreateConfirmationFacts(input.context.confirmationFacts),
410
+ };
411
+ return this.options.operationService.executeMutation({
412
+ context,
413
+ operationName: 'applyConfiguredServerCreate',
414
+ prepare: async () => {
415
+ const document = this.options.readConfigDocument() ?? {};
416
+ const preview = await this.previewConfiguredServerCreateResult({ ...input, connectivityCheck: 'auto' }, true, document);
417
+ if (preview.previewFingerprint !== input.previewFingerprint) {
418
+ throw new AdminConfiguredServerApplyError('configured_server_stale_preview');
419
+ }
420
+ if (preview.validation.status !== 'valid' || !preview.configChange.changed) {
421
+ throw new AdminConfiguredServerApplyError('configured_server_create_invalid');
422
+ }
423
+ const prepared = await this.resolveConfiguredServerCreate(input.draft, document);
424
+ if (!prepared.config) {
425
+ throw new AdminConfiguredServerApplyError('configured_server_create_invalid');
426
+ }
427
+ if (prepared.enabled &&
428
+ !wouldUseLocalStdioTransport(prepared.config) &&
429
+ preview.connectivityCheck.status !== 'passed' &&
430
+ !(preview.connectivityCheck.status === 'skipped' && preview.connectivityCheck.reason === 'checker_unavailable') &&
431
+ !(preview.connectivityCheck.status === 'failed' &&
432
+ input.context.confirmationFacts?.connectivityFailureOverrideConfirmed === true)) {
433
+ throw new AdminConfiguredServerApplyError('configured_server_connectivity_blocked');
434
+ }
435
+ const secretChange = prepared.draft.secrets?.length;
436
+ const configFingerprint = fingerprintConfiguredServerConfigDocument(document);
437
+ return {
438
+ confirmationRequirements: [
439
+ { code: 'previewConfirmed', expected: input.previewFingerprint },
440
+ { code: 'targetNameConfirmed', expected: prepared.targetName },
441
+ { code: 'connectionCriticalConfirmed', expected: true },
442
+ ...(secretChange ? [{ code: 'secretChangeConfirmed', expected: true }] : []),
443
+ ...(preview.connectivityCheck.status === 'failed'
444
+ ? [{ code: 'connectivityFailureOverrideConfirmed', expected: true }]
445
+ : []),
446
+ ],
447
+ run: async () => {
448
+ let configChange;
449
+ try {
450
+ configChange = await this.options.configChangeService.createStaticConfiguredServerTarget({
451
+ targetName: prepared.targetName,
452
+ serverConfig: prepared.config,
453
+ operation: 'install',
454
+ backup: 'skip',
455
+ expectedConfigFingerprint: configFingerprint,
456
+ });
457
+ }
458
+ catch {
459
+ throw new AdminConfiguredServerApplyError('configured_server_create_failed');
460
+ }
461
+ assertSuccessfulConfiguredServerCreate(configChange);
462
+ return {
463
+ targetName: prepared.targetName,
464
+ previewFingerprint: preview.previewFingerprint,
465
+ configChange,
466
+ };
467
+ },
468
+ };
469
+ },
470
+ });
471
+ }
29
472
  async listConfiguredServers(input) {
30
473
  return this.options.operationService.executeReadOnly({
31
474
  context: input.context,
32
475
  operationName: 'listConfiguredServers',
33
- run: async () => ({ servers: this.readConfiguredServers() }),
476
+ run: async () => this.readConfiguredServers(),
34
477
  });
35
478
  }
36
479
  async getConfiguredServerDetail(input) {
480
+ const configuredState = this.readConfiguredServerState(input.targetName, input.targetSource);
481
+ const source = configuredState.source;
37
482
  const context = {
38
483
  ...input.context,
39
- target: { type: 'configured_server', id: input.targetName },
484
+ target: { type: 'configured_server', id: `${source}:${input.targetName}` },
40
485
  };
41
486
  return this.options.operationService.executeReadOnly({
42
487
  context,
43
488
  operationName: 'getConfiguredServerDetail',
44
489
  run: async () => {
45
- const { currentConfig, serverDefaults } = this.readConfiguredServerState(input.targetName);
46
- const server = createConfiguredServerReadModel(input.targetName, mergeGlobalAndServerConfig(serverDefaults, currentConfig));
490
+ const { currentConfig, serverDefaults } = configuredState;
491
+ const server = createConfiguredServerReadModel(input.targetName, mergeGlobalAndServerConfig(serverDefaults, currentConfig), source, fingerprintConfiguredServerTarget(currentConfig), Object.hasOwn(currentConfig, 'instructionOverride') ? currentConfig.instructionOverride : undefined, Object.hasOwn(currentConfig, 'instructionOverride'));
47
492
  return {
48
493
  server,
49
494
  editContract: createConfiguredServerEditContract(server, currentConfig, serverDefaults),
495
+ ...(this.options.readToolInventory
496
+ ? {
497
+ toolInventory: await this.options.readToolInventory({
498
+ targetName: input.targetName,
499
+ source,
500
+ config: currentConfig,
501
+ ...(input.model ? { model: input.model } : {}),
502
+ }),
503
+ }
504
+ : {}),
50
505
  };
51
506
  },
52
507
  });
53
508
  }
54
509
  async previewConfiguredServerEdit(input) {
510
+ const configuredState = this.readConfiguredServerState(input.targetName, input.targetSource);
511
+ const source = configuredState.source;
55
512
  const context = {
56
513
  ...input.context,
57
- target: { type: 'configured_server', id: input.targetName },
514
+ target: { type: 'configured_server', id: `${source}:${input.targetName}` },
58
515
  };
59
- const { currentConfig, serverDefaults } = this.readConfiguredServerState(input.targetName);
516
+ const { currentConfig, serverDefaults } = configuredState;
60
517
  return this.options.operationService.executeDryRun({
61
518
  context,
62
519
  operationName: 'previewConfiguredServerEdit',
63
- run: async () => this.previewConfiguredServerEditResult(input, currentConfig, serverDefaults),
520
+ run: async () => this.previewConfiguredServerEditResult(input, currentConfig, serverDefaults, false, source),
64
521
  });
65
522
  }
66
523
  async applyConfiguredServerEdit(input) {
524
+ const source = this.resolveConfiguredServerSource(input.targetName, input.targetSource);
67
525
  const context = {
68
526
  ...input.context,
69
- target: { type: 'configured_server', id: input.targetName },
527
+ target: { type: 'configured_server', id: `${source}:${input.targetName}` },
70
528
  confirmationFacts: sanitizeApplyConfirmationFacts(input.context.confirmationFacts),
71
529
  };
72
530
  return this.options.operationService.executeMutation({
73
531
  context,
74
532
  operationName: 'applyConfiguredServerEdit',
75
533
  prepare: async () => {
76
- const { currentConfig, serverDefaults } = this.readConfiguredServerState(input.targetName);
77
- const preview = await this.previewConfiguredServerEditResult({ ...input, connectivityCheck: 'auto' }, currentConfig, serverDefaults, true);
534
+ const { currentConfig, serverDefaults } = this.readConfiguredServerState(input.targetName, source);
535
+ const preview = await this.previewConfiguredServerEditResult({ ...input, connectivityCheck: 'auto' }, currentConfig, serverDefaults, true, source);
78
536
  const normalizedEdit = normalizeEditDraft(input.edit);
79
- const currentReadModel = createConfiguredServerReadModel(input.targetName, mergeGlobalAndServerConfig(serverDefaults, currentConfig));
537
+ const currentReadModel = createConfiguredServerReadModel(input.targetName, mergeGlobalAndServerConfig(serverDefaults, currentConfig), source);
80
538
  const applicableEdit = filterApplicableSecretEdits(normalizedEdit.edit, currentReadModel.secretInputs);
81
- const proposedConfig = applyEditDraft(currentConfig, applicableEdit, serverDefaults);
82
- const proposedReadModel = createConfiguredServerReadModel(preview.proposedTargetName, mergeGlobalAndServerConfig(serverDefaults, proposedConfig));
539
+ const proposedConfig = applyEditDraft(currentConfig, applicableEdit, serverDefaults, input.targetName);
540
+ const proposedReadModel = createConfiguredServerReadModel(preview.proposedTargetName, mergeGlobalAndServerConfig(serverDefaults, proposedConfig), source);
83
541
  const connectionCritical = preview.diff.some((entry) => entry.riskFlags.includes('connection_critical'));
84
542
  const secretChange = preview.diff.some((entry) => entry.riskFlags.includes('secret'));
85
543
  if (preview.validation.status !== 'valid') {
@@ -107,6 +565,9 @@ export class AdminConfiguredServerService {
107
565
  : []),
108
566
  ...(secretChange ? [{ code: 'secretChangeConfirmed', expected: true }] : []),
109
567
  ...(connectionCritical ? [{ code: 'connectionCriticalConfirmed', expected: true }] : []),
568
+ ...(preview.toolSelection?.requiresZeroEnabledConfirmation
569
+ ? [{ code: 'zeroEnabledToolsConfirmed', expected: true }]
570
+ : []),
110
571
  ...(preview.connectivityCheck.status === 'failed'
111
572
  ? [{ code: 'connectivityFailureOverrideConfirmed', expected: true }]
112
573
  : []),
@@ -114,6 +575,7 @@ export class AdminConfiguredServerService {
114
575
  run: async () => {
115
576
  const configChange = await this.options.configChangeService.editConfiguredServerTarget({
116
577
  sourceName: input.targetName,
578
+ targetSource: source,
117
579
  targetName: preview.proposedTargetName,
118
580
  serverConfig: proposedConfig,
119
581
  expectedSourceFingerprint: fingerprintConfiguredServerTarget(currentConfig),
@@ -142,17 +604,22 @@ export class AdminConfiguredServerService {
142
604
  }
143
605
  async setConfiguredServerEnabledState(input, enabled) {
144
606
  const operationName = enabled ? 'enableConfiguredServer' : 'disableConfiguredServer';
607
+ const source = input.targetSource ?? 'mcpServers';
145
608
  const context = {
146
609
  ...input.context,
147
- target: { type: 'configured_server', id: input.targetName },
610
+ target: { type: 'configured_server', id: `${source}:${input.targetName}` },
148
611
  };
149
612
  if (input.dryRun) {
150
613
  return this.options.operationService.executeDryRun({
151
614
  context,
152
615
  operationName,
153
616
  run: async () => {
617
+ if (source !== 'mcpServers') {
618
+ throw new Error('Template Server definitions do not support enable or disable operations');
619
+ }
154
620
  const configChange = await this.options.configChangeService.previewConfiguredServerTargetEnabledState({
155
621
  targetName: input.targetName,
622
+ targetSource: source,
156
623
  enabled,
157
624
  backup: 'skip',
158
625
  });
@@ -172,8 +639,12 @@ export class AdminConfiguredServerService {
172
639
  operationName,
173
640
  confirmationRequirements: input.confirmationRequirements,
174
641
  run: async () => {
642
+ if (source !== 'mcpServers') {
643
+ throw new Error('Template Server definitions do not support enable or disable operations');
644
+ }
175
645
  const configChange = await this.options.configChangeService.setConfiguredServerTargetEnabledState({
176
646
  targetName: input.targetName,
647
+ targetSource: source,
177
648
  enabled,
178
649
  backup: 'required',
179
650
  });
@@ -190,49 +661,84 @@ export class AdminConfiguredServerService {
190
661
  readConfiguredServers() {
191
662
  const parsed = this.options.readConfigDocument();
192
663
  if (!parsed) {
193
- return [];
664
+ return { servers: [], configFingerprint: fingerprintConfiguredServerConfigDocument({}) };
194
665
  }
195
- return Object.entries(parsed.mcpServers ?? {}).map(([name, serverConfig]) => createConfiguredServerReadModel(name, mergeGlobalAndServerConfig(parsed.serverDefaults, serverConfig)));
666
+ return {
667
+ configFingerprint: fingerprintConfiguredServerConfigDocument(parsed),
668
+ servers: ['mcpServers', 'mcpTemplates'].flatMap((source) => Object.entries(parsed[source] ?? {}).map(([name, serverConfig]) => createConfiguredServerReadModel(name, mergeGlobalAndServerConfig(parsed.serverDefaults, serverConfig), source, fingerprintConfiguredServerTarget(serverConfig), Object.hasOwn(serverConfig, 'instructionOverride') ? serverConfig.instructionOverride : undefined, Object.hasOwn(serverConfig, 'instructionOverride')))),
669
+ };
196
670
  }
197
- readConfiguredServerState(targetName) {
671
+ readConfiguredServerState(targetName, targetSource) {
198
672
  const parsed = this.options.readConfigDocument();
199
- const currentConfig = parsed?.mcpServers?.[targetName];
673
+ const source = this.resolveConfiguredServerSource(targetName, targetSource, parsed);
674
+ const currentConfig = parsed?.[source]?.[targetName];
200
675
  if (!currentConfig) {
201
676
  throw new AdminConfiguredServerNotFoundError(targetName);
202
677
  }
203
- return { currentConfig, serverDefaults: parsed?.serverDefaults };
678
+ return { currentConfig, serverDefaults: parsed?.serverDefaults, source };
679
+ }
680
+ resolveConfiguredServerSource(targetName, targetSource, document = this.options.readConfigDocument()) {
681
+ if (targetSource)
682
+ return targetSource;
683
+ return document?.mcpTemplates?.[targetName] ? 'mcpTemplates' : 'mcpServers';
204
684
  }
205
685
  readConfiguredServerConfig(targetName) {
206
686
  const parsed = this.options.readConfigDocument();
207
- const currentConfig = parsed?.mcpServers?.[targetName];
687
+ const currentConfig = parsed?.mcpTemplates?.[targetName] ?? parsed?.mcpServers?.[targetName];
208
688
  if (!currentConfig) {
209
689
  throw new AdminConfiguredServerNotFoundError(targetName);
210
690
  }
211
691
  return currentConfig;
212
692
  }
213
- async previewConfiguredServerEditResult(input, currentConfig, serverDefaults, requireCriticalConnectivity = false) {
693
+ async previewConfiguredServerEditResult(input, currentConfig, serverDefaults, requireCriticalConnectivity = false, source = 'mcpServers') {
214
694
  const normalizedEdit = normalizeEditDraft(input.edit);
215
- const currentReadModel = createConfiguredServerReadModel(input.targetName, mergeGlobalAndServerConfig(serverDefaults, currentConfig));
695
+ const sourceValidation = validateEditForSource(source, normalizedEdit.edit);
696
+ const currentReadModel = createConfiguredServerReadModel(input.targetName, mergeGlobalAndServerConfig(serverDefaults, currentConfig), source);
216
697
  const secretValidation = validateSecretEditCapabilities(normalizedEdit.edit, currentReadModel.secretInputs);
217
698
  const transportApplicabilityValidation = validateTransportFieldApplicability(currentConfig, normalizedEdit.edit);
218
699
  const overrideValidation = validateTransportOverrideClears(currentConfig, normalizedEdit.edit);
219
700
  const applicableEdit = filterApplicableSecretEdits(normalizedEdit.edit, currentReadModel.secretInputs);
220
701
  const proposedTargetName = applicableEdit.id?.trim() || input.targetName;
221
- const proposedConfig = applyEditDraft(currentConfig, applicableEdit, serverDefaults);
222
- const proposedReadModel = createConfiguredServerReadModel(proposedTargetName, mergeGlobalAndServerConfig(serverDefaults, proposedConfig));
702
+ const proposedConfig = applyEditDraft(currentConfig, applicableEdit, serverDefaults, input.targetName);
703
+ const proposedReadModel = createConfiguredServerReadModel(proposedTargetName, mergeGlobalAndServerConfig(serverDefaults, proposedConfig), source);
223
704
  const proposedTransportType = configuredTransportType(proposedConfig);
224
705
  const serverValidation = validatePreviewServerConfig(proposedConfig);
225
- const validation = mergeValidation(mergeValidation(mergeValidation(mergeValidation(normalizedEdit.validation, secretValidation), transportApplicabilityValidation), overrideValidation), serverValidation);
706
+ const currentToolInventory = this.options.readToolInventory
707
+ ? await this.options.readToolInventory({
708
+ targetName: input.targetName,
709
+ source,
710
+ config: currentConfig,
711
+ ...(input.model ? { model: input.model } : {}),
712
+ })
713
+ : undefined;
714
+ const toolEditValidation = validateConfiguredToolEdit(applicableEdit, currentToolInventory);
715
+ const validation = mergeValidation(mergeValidation(mergeValidation(mergeValidation(normalizedEdit.validation, secretValidation), transportApplicabilityValidation), overrideValidation), mergeValidation(mergeValidation(serverValidation, sourceValidation), toolEditValidation));
226
716
  const diff = createPreviewDiff(currentReadModel, proposedReadModel, applicableEdit);
717
+ pushDiff(diff, ['disabledTools'], currentConfig.disabledTools ?? [], proposedConfig.disabledTools ?? [], [
718
+ 'tool_visibility',
719
+ ]);
720
+ pushDiff(diff, ['toolDescriptionOverrides'], currentConfig.toolDescriptionOverrides ?? {}, proposedConfig.toolDescriptionOverrides ?? {}, ['tool_metadata']);
227
721
  appendClearedOverrideDiff(diff, currentReadModel, proposedReadModel, applicableEdit);
228
722
  const changed = validation.status === 'valid' && diff.length > 0;
229
723
  const endpointChangedWithPreservedSecrets = endpointAuthorityChanged(currentConfig, proposedConfig) &&
230
724
  hasPreservedSecretInputs(currentReadModel.secretInputs.filter((input) => !proposedTransportType || transportFieldAppliesToType(input.fieldPath[0], proposedTransportType)), applicableEdit);
725
+ const proposedToolInventory = this.options.readToolInventory
726
+ ? await this.options.readToolInventory({
727
+ targetName: proposedTargetName,
728
+ source,
729
+ config: proposedConfig,
730
+ ...(input.model ? { model: input.model } : {}),
731
+ })
732
+ : undefined;
733
+ const toolSelection = currentToolInventory && proposedToolInventory
734
+ ? createConfiguredToolSelectionPreview(currentToolInventory, proposedToolInventory)
735
+ : undefined;
231
736
  return {
232
737
  targetName: input.targetName,
233
738
  proposedTargetName,
234
739
  previewFingerprint: previewFingerprint({
235
740
  targetName: input.targetName,
741
+ targetSource: source,
236
742
  sourceFingerprint: fingerprintConfiguredServerTarget(currentConfig),
237
743
  globalConfigFingerprint: fingerprintConfiguredServerDefaults(serverDefaults),
238
744
  edit: normalizedEdit.edit,
@@ -240,10 +746,11 @@ export class AdminConfiguredServerService {
240
746
  proposed: proposedReadModel,
241
747
  diff,
242
748
  validation,
749
+ toolSelection,
243
750
  }),
244
751
  validation,
245
752
  diff,
246
- configChange: previewConfigChange(input.targetName, changed),
753
+ configChange: previewConfigChange(input.targetName, changed, source),
247
754
  connectivityCheck: await this.previewConnectivityCheck({
248
755
  targetName: proposedTargetName,
249
756
  serverConfig: proposedConfig,
@@ -254,6 +761,7 @@ export class AdminConfiguredServerService {
254
761
  (requireCriticalConnectivity && diff.some((entry) => entry.riskFlags.includes('connection_critical'))),
255
762
  endpointChangedWithPreservedSecrets,
256
763
  }),
764
+ ...(toolSelection ? { toolSelection } : {}),
257
765
  };
258
766
  }
259
767
  async previewConnectivityCheck(input) {
@@ -280,6 +788,84 @@ export class AdminConfiguredServerService {
280
788
  serverConfig: input.serverConfig,
281
789
  });
282
790
  }
791
+ async previewConfiguredServerCreateResult(input, forceConnectivity = false, document = this.options.readConfigDocument() ?? {}) {
792
+ const prepared = await this.resolveConfiguredServerCreate(input.draft, document);
793
+ const proposedReadModel = prepared.config
794
+ ? createConfiguredServerReadModel(prepared.targetName, mergeGlobalAndServerConfig(document.serverDefaults, prepared.config))
795
+ : undefined;
796
+ const validation = prepared.validation;
797
+ const diff = proposedReadModel ? createConfiguredServerCreateDiff(proposedReadModel, prepared.draft) : [];
798
+ const changed = validation.status === 'valid' && prepared.workflow.status === 'preview';
799
+ const connectivityCheck = prepared.config
800
+ ? await this.previewConnectivityCheck({
801
+ targetName: prepared.targetName,
802
+ serverConfig: prepared.config,
803
+ enabled: prepared.enabled,
804
+ validationStatus: validation.status,
805
+ connectionCriticalChanged: true,
806
+ force: forceConnectivity || input.connectivityCheck === 'manual',
807
+ endpointChangedWithPreservedSecrets: false,
808
+ })
809
+ : { status: 'skipped', reason: 'validation_failed' };
810
+ const preview = {
811
+ targetName: prepared.targetName,
812
+ previewFingerprint: configuredServerCreatePreviewFingerprint({
813
+ configFingerprint: fingerprintConfiguredServerConfigDocument(document),
814
+ draft: prepared.draft,
815
+ proposed: proposedReadModel,
816
+ diff,
817
+ validation,
818
+ connectivityCheck,
819
+ }),
820
+ validation,
821
+ diff,
822
+ configChange: previewCreateConfigChange(prepared.targetName, prepared.workflow.status, changed),
823
+ connectivityCheck,
824
+ expectedReload: {
825
+ policy: 'observe_after_write',
826
+ possibleStatuses: ['observed', 'runtime_not_running', 'reload_disabled', 'failed'],
827
+ },
828
+ };
829
+ return preview;
830
+ }
831
+ async resolveConfiguredServerCreate(draftValue, document = this.options.readConfigDocument() ?? {}) {
832
+ const normalized = normalizeConfiguredServerCreateDraft(draftValue);
833
+ const workflow = createServerInstallationWorkflow({
834
+ findConfiguredTarget: (targetName) => {
835
+ if (document.mcpTemplates?.[targetName])
836
+ return { name: targetName, source: 'mcpTemplates' };
837
+ if (document.mcpServers?.[targetName])
838
+ return { name: targetName, source: 'mcpServers' };
839
+ return null;
840
+ },
841
+ });
842
+ const workflowResult = await workflow.run({ mode: 'preview', source: normalized.source });
843
+ const config = workflowResult.config;
844
+ const validation = mergeValidation(mergeValidation(normalized.validation, validateConfiguredServerCreateSecrets(normalized.draft)), createWorkflowValidation(workflowResult, config));
845
+ return {
846
+ draft: normalized.draft,
847
+ source: normalized.source,
848
+ targetName: normalized.source.localName,
849
+ enabled: normalized.source.enabled !== false,
850
+ workflow: workflowResult,
851
+ config,
852
+ validation,
853
+ };
854
+ }
855
+ }
856
+ function validateEditForSource(source, edit) {
857
+ if (source !== 'mcpTemplates')
858
+ return { status: 'valid', errors: [] };
859
+ const supportedTemplateMetadata = new Set(['instructionOverride', 'disabledTools', 'toolDescriptionOverrides']);
860
+ const unsupported = Object.keys(edit).filter((key) => !supportedTemplateMetadata.has(key));
861
+ return {
862
+ status: unsupported.length === 0 ? 'valid' : 'invalid',
863
+ errors: unsupported.map((key) => ({
864
+ fieldPath: [key],
865
+ code: 'template_edit_field_unsupported',
866
+ message: 'Template Server edits may only change instruction and tool metadata.',
867
+ })),
868
+ };
283
869
  }
284
870
  function assertSuccessfulConfigChange(configChange) {
285
871
  if (configChange.reload.status === 'failed') {
@@ -325,6 +911,7 @@ function sanitizeApplyConfirmationFacts(facts) {
325
911
  return undefined;
326
912
  const allowed = ['previewConfirmed', 'targetNameConfirmed', 'secretChangeConfirmed', 'connectionCriticalConfirmed'];
327
913
  allowed.push('connectivityFailureOverrideConfirmed');
914
+ allowed.push('zeroEnabledToolsConfirmed');
328
915
  const sanitized = Object.fromEntries(allowed.filter((key) => key in facts).map((key) => [key, facts[key]]));
329
916
  return Object.keys(sanitized).length > 0 ? sanitized : undefined;
330
917
  }
@@ -337,7 +924,17 @@ function mutationOutcome(enabled, changed) {
337
924
  function normalizeEditDraft(value) {
338
925
  const errors = [];
339
926
  const edit = {};
340
- const supportedKeys = new Set(['id', 'enabled', 'tags', 'transport', 'secrets', 'clearTransportOverrides']);
927
+ const supportedKeys = new Set([
928
+ 'id',
929
+ 'enabled',
930
+ 'tags',
931
+ 'transport',
932
+ 'secrets',
933
+ 'clearTransportOverrides',
934
+ 'instructionOverride',
935
+ 'disabledTools',
936
+ 'toolDescriptionOverrides',
937
+ ]);
341
938
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
342
939
  return {
343
940
  edit,
@@ -399,6 +996,36 @@ function normalizeEditDraft(value) {
399
996
  });
400
997
  }
401
998
  }
999
+ if (record.disabledTools !== undefined) {
1000
+ if (Array.isArray(record.disabledTools) && record.disabledTools.every((name) => typeof name === 'string')) {
1001
+ edit.disabledTools = Array.from(new Set(record.disabledTools.map((name) => name.trim()).filter((name) => name.length > 0))).sort((left, right) => left.localeCompare(right));
1002
+ }
1003
+ else {
1004
+ errors.push({
1005
+ fieldPath: ['disabledTools'],
1006
+ code: 'invalid_disabled_tools',
1007
+ message: 'Disabled tools must be a list of logical tool names.',
1008
+ });
1009
+ }
1010
+ }
1011
+ if (record.toolDescriptionOverrides !== undefined) {
1012
+ if (record.toolDescriptionOverrides &&
1013
+ typeof record.toolDescriptionOverrides === 'object' &&
1014
+ !Array.isArray(record.toolDescriptionOverrides) &&
1015
+ Object.entries(record.toolDescriptionOverrides).every(([name, description]) => name.length > 0 && name === name.trim() && typeof description === 'string')) {
1016
+ edit.toolDescriptionOverrides = Object.fromEntries(Object.entries(record.toolDescriptionOverrides)
1017
+ .map(([name, description]) => [name, description.trim()])
1018
+ .filter(([name, description]) => name.length > 0 && description.length > 0)
1019
+ .sort(([left], [right]) => left.localeCompare(right)));
1020
+ }
1021
+ else {
1022
+ errors.push({
1023
+ fieldPath: ['toolDescriptionOverrides'],
1024
+ code: 'invalid_tool_description_overrides',
1025
+ message: 'Tool description overrides must map logical tool names to descriptions.',
1026
+ });
1027
+ }
1028
+ }
402
1029
  if (record.transport !== undefined) {
403
1030
  if (record.transport && typeof record.transport === 'object' && !Array.isArray(record.transport)) {
404
1031
  edit.transport = normalizeTransportEditDraft(record.transport, errors);
@@ -437,6 +1064,23 @@ function normalizeEditDraft(value) {
437
1064
  });
438
1065
  }
439
1066
  }
1067
+ if (record.instructionOverride !== undefined) {
1068
+ const parsed = z
1069
+ .discriminatedUnion('action', [
1070
+ z.object({ action: z.literal('set'), value: z.string() }).strict(),
1071
+ z.object({ action: z.literal('remove') }).strict(),
1072
+ ])
1073
+ .safeParse(record.instructionOverride);
1074
+ if (parsed.success)
1075
+ edit.instructionOverride = parsed.data;
1076
+ else {
1077
+ errors.push({
1078
+ fieldPath: ['instructionOverride'],
1079
+ code: 'invalid_instruction_override',
1080
+ message: 'Instruction override must set a string value or remove the configured override.',
1081
+ });
1082
+ }
1083
+ }
440
1084
  return {
441
1085
  edit,
442
1086
  validation: { status: errors.length === 0 ? 'valid' : 'invalid', errors },
@@ -465,6 +1109,20 @@ function normalizeTransportEditDraft(record, errors) {
465
1109
  }
466
1110
  return transport;
467
1111
  }
1112
+ function validateConfiguredToolEdit(edit, inventory) {
1113
+ if (!inventory)
1114
+ return { status: 'valid', errors: [] };
1115
+ const allowedNames = new Set(inventory.rows.map((row) => row.name));
1116
+ const requestedNames = new Set([...(edit.disabledTools ?? []), ...Object.keys(edit.toolDescriptionOverrides ?? {})]);
1117
+ const errors = Array.from(requestedNames)
1118
+ .filter((name) => !allowedNames.has(name))
1119
+ .map((name) => ({
1120
+ fieldPath: ['tools', name],
1121
+ code: 'unknown_configured_tool',
1122
+ message: 'Tool changes must refer to an observed tool or a retained unresolved entry.',
1123
+ }));
1124
+ return { status: errors.length === 0 ? 'valid' : 'invalid', errors };
1125
+ }
468
1126
  function isSecretCapableRawTransportEdit(key, value) {
469
1127
  if (key === 'url') {
470
1128
  return isSecretCapableUrlEdit(value);
@@ -761,7 +1419,7 @@ function potentialSecretInput(fieldPath) {
761
1419
  }
762
1420
  return undefined;
763
1421
  }
764
- function applyEditDraft(currentConfig, edit, serverDefaults) {
1422
+ function applyEditDraft(currentConfig, edit, serverDefaults, logicalServerName) {
765
1423
  const nextConfig = cloneServerConfig(currentConfig);
766
1424
  for (const key of edit.clearTransportOverrides ?? []) {
767
1425
  delete nextConfig[key];
@@ -777,6 +1435,27 @@ function applyEditDraft(currentConfig, edit, serverDefaults) {
777
1435
  if (Array.isArray(edit.tags)) {
778
1436
  nextConfig.tags = edit.tags.filter((tag) => typeof tag === 'string');
779
1437
  }
1438
+ if (edit.instructionOverride?.action === 'set') {
1439
+ nextConfig.instructionOverride = edit.instructionOverride.value;
1440
+ }
1441
+ else if (edit.instructionOverride?.action === 'remove') {
1442
+ delete nextConfig.instructionOverride;
1443
+ }
1444
+ if (Array.isArray(edit.disabledTools)) {
1445
+ const disabledTools = logicalServerName
1446
+ ? normalizeDisabledToolsForServer(logicalServerName, edit.disabledTools)
1447
+ : edit.disabledTools;
1448
+ if (disabledTools.length === 0)
1449
+ delete nextConfig.disabledTools;
1450
+ else
1451
+ nextConfig.disabledTools = disabledTools;
1452
+ }
1453
+ if (edit.toolDescriptionOverrides) {
1454
+ if (Object.keys(edit.toolDescriptionOverrides).length === 0)
1455
+ delete nextConfig.toolDescriptionOverrides;
1456
+ else
1457
+ nextConfig.toolDescriptionOverrides = { ...edit.toolDescriptionOverrides };
1458
+ }
780
1459
  if (edit.transport && typeof edit.transport === 'object') {
781
1460
  const currentType = configuredTransportType(currentConfig);
782
1461
  const proposedType = configuredTransportType({ ...currentConfig, ...edit.transport });
@@ -1160,12 +1839,12 @@ function isValidUrlOrEnvReference(value) {
1160
1839
  return false;
1161
1840
  }
1162
1841
  }
1163
- function previewConfigChange(targetName, changed) {
1842
+ function previewConfigChange(targetName, changed, source = 'mcpServers') {
1164
1843
  return {
1165
1844
  status: changed ? 'changed' : 'unchanged',
1166
1845
  operation: 'set_static',
1167
1846
  configPath: '[redacted]',
1168
- target: { name: targetName, source: 'mcpServers' },
1847
+ target: { name: targetName, source },
1169
1848
  changed,
1170
1849
  backup: { created: false },
1171
1850
  retentionCleanup: { attempted: false, deletedPaths: [], warnings: [] },
@@ -1178,6 +1857,7 @@ function previewFingerprint(input) {
1178
1857
  .update(stableStringify({
1179
1858
  schemaVersion: 2,
1180
1859
  targetName: input.targetName,
1860
+ targetSource: input.targetSource,
1181
1861
  sourceFingerprint: input.sourceFingerprint,
1182
1862
  globalConfigFingerprint: input.globalConfigFingerprint,
1183
1863
  edit: redactEditDraftForFingerprint(input.edit),
@@ -1185,9 +1865,39 @@ function previewFingerprint(input) {
1185
1865
  proposed: input.proposed,
1186
1866
  diff: input.diff,
1187
1867
  validation: input.validation,
1868
+ toolSelection: input.toolSelection,
1188
1869
  }))
1189
1870
  .digest('hex')}`;
1190
1871
  }
1872
+ function createConfiguredToolSelectionPreview(current, proposed) {
1873
+ const currentRows = new Map(current.rows.map((row) => [row.name, row]));
1874
+ const proposedRows = new Map(proposed.rows.map((row) => [row.name, row]));
1875
+ const changedTools = Array.from(new Set([...currentRows.keys(), ...proposedRows.keys()]))
1876
+ .filter((name) => {
1877
+ const before = currentRows.get(name);
1878
+ const after = proposedRows.get(name);
1879
+ return (before?.enabled !== after?.enabled ||
1880
+ before?.descriptionOverride !== after?.descriptionOverride ||
1881
+ before?.effectiveDescription !== after?.effectiveDescription);
1882
+ })
1883
+ .sort((left, right) => left.localeCompare(right));
1884
+ return {
1885
+ capabilityGeneration: current.generation,
1886
+ model: proposed.model,
1887
+ changedTools,
1888
+ counts: proposed.counts,
1889
+ approximateTokens: {
1890
+ before: current.approximateTokens.enabled,
1891
+ after: proposed.approximateTokens.enabled,
1892
+ savings: current.approximateTokens.enabled - proposed.approximateTokens.enabled,
1893
+ },
1894
+ targetEnabled: proposed.targetEnabled,
1895
+ effect: proposed.targetEnabled ? 'immediate' : 'deferred_until_target_enabled',
1896
+ requiresZeroEnabledConfirmation: changedTools.length > 0 &&
1897
+ current.rows.some((row) => row.observed && row.enabled) &&
1898
+ !proposed.rows.some((row) => row.observed && row.enabled),
1899
+ };
1900
+ }
1191
1901
  function redactEditDraftForFingerprint(edit) {
1192
1902
  return {
1193
1903
  ...edit,
@@ -1215,6 +1925,9 @@ function createPreviewDiff(current, proposed, edit) {
1215
1925
  pushDiff(diff, ['id'], current.id, proposed.id, proposed.id === current.id ? [] : ['rename']);
1216
1926
  pushDiff(diff, ['enabled'], current.enabled, proposed.enabled, ['connection_critical']);
1217
1927
  pushDiff(diff, ['tags'], current.tags, proposed.tags, []);
1928
+ if (edit.instructionOverride) {
1929
+ pushDiff(diff, ['instructionOverride'], current.instructionOverride, proposed.instructionOverride, []);
1930
+ }
1218
1931
  const secretEditedTopLevelKeys = new Set((edit.secrets ?? []).map((secret) => secret.fieldPath[0]));
1219
1932
  const transportEditedKeys = new Set(Object.keys(edit.transport ?? {}));
1220
1933
  const transportKeys = new Set([...Object.keys(current.transport), ...Object.keys(proposed.transport)]);
@@ -1351,11 +2064,15 @@ function stableStringify(value) {
1351
2064
  }
1352
2065
  return JSON.stringify(value);
1353
2066
  }
1354
- function createConfiguredServerReadModel(name, serverConfig) {
2067
+ function createConfiguredServerReadModel(name, serverConfig, source = 'mcpServers', revision = fingerprintConfiguredServerTarget(serverConfig), instructionOverride = serverConfig.instructionOverride, hasInstructionOverride = Object.hasOwn(serverConfig, 'instructionOverride')) {
1355
2068
  const secretInputs = [];
1356
2069
  const transport = {};
1357
2070
  for (const [key, value] of Object.entries(serverConfig)) {
1358
- if (key === 'disabled' || key === 'tags') {
2071
+ if (key === 'disabled' ||
2072
+ key === 'tags' ||
2073
+ key === 'instructionOverride' ||
2074
+ key === 'disabledTools' ||
2075
+ key === 'toolDescriptionOverrides') {
1359
2076
  continue;
1360
2077
  }
1361
2078
  if (key === 'env') {
@@ -1383,22 +2100,33 @@ function createConfiguredServerReadModel(name, serverConfig) {
1383
2100
  const enabled = !isConfiguredServerTargetDisabled(serverConfig.disabled);
1384
2101
  return {
1385
2102
  id: name,
1386
- source: 'mcpServers',
2103
+ source,
1387
2104
  target: {
1388
2105
  type: 'configured_server',
1389
2106
  id: name,
1390
- source: 'mcpServers',
2107
+ source,
1391
2108
  },
2109
+ revision,
1392
2110
  enabled,
1393
2111
  tags: normalizeTags(serverConfig.tags),
1394
2112
  transportSummary: createTransportSummary(serverConfig, transport),
1395
2113
  mutationAvailability: {
1396
- available: true,
1397
- operations: ['enable', 'disable'],
2114
+ available: source === 'mcpServers',
2115
+ operations: source === 'mcpServers' ? ['enable', 'disable'] : [],
1398
2116
  },
1399
- actionState: createActionState(name, enabled),
2117
+ actionState: source === 'mcpServers'
2118
+ ? createActionState(name, enabled)
2119
+ : {
2120
+ enable: { available: false, label: `Enable ${name}`, disabledReason: 'already_enabled' },
2121
+ disable: { available: false, label: `Disable ${name}`, disabledReason: 'already_disabled' },
2122
+ },
1400
2123
  transport,
1401
2124
  secretInputs,
2125
+ instructionOverride: !hasInstructionOverride
2126
+ ? { state: 'upstream' }
2127
+ : instructionOverride === ''
2128
+ ? { state: 'suppress', value: '' }
2129
+ : { state: 'replace', value: instructionOverride },
1402
2130
  };
1403
2131
  }
1404
2132
  const STDIO_TRANSPORT_TYPES = ['stdio'];
@@ -1413,7 +2141,6 @@ const TRANSPORT_FIELD_DEFINITIONS = [
1413
2141
  { key: 'timeout', label: 'Deprecated Timeout', control: 'number' },
1414
2142
  { key: 'connectionTimeout', label: 'Connection Timeout', control: 'number' },
1415
2143
  { key: 'requestTimeout', label: 'Request Timeout', control: 'number' },
1416
- { key: 'disabledTools', label: 'Disabled Tools', control: 'string-list', defaultValue: [] },
1417
2144
  { key: 'template', label: 'Template', control: 'record', defaultValue: {} },
1418
2145
  {
1419
2146
  key: 'url',