@sfdxy/anypoint-connect 0.15.0 → 0.16.3

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 (185) hide show
  1. package/README.md +32 -14
  2. package/dist/analysis/LogAnalyzer.d.ts +9 -3
  3. package/dist/analysis/LogAnalyzer.d.ts.map +1 -1
  4. package/dist/analysis/LogAnalyzer.js +28 -17
  5. package/dist/analysis/LogAnalyzer.js.map +1 -1
  6. package/dist/analysis/error-context.d.ts +2 -0
  7. package/dist/analysis/error-context.d.ts.map +1 -1
  8. package/dist/analysis/error-context.js +2 -1
  9. package/dist/analysis/error-context.js.map +1 -1
  10. package/dist/analysis/filter.d.ts +54 -0
  11. package/dist/analysis/filter.d.ts.map +1 -0
  12. package/dist/analysis/filter.js +100 -0
  13. package/dist/analysis/filter.js.map +1 -0
  14. package/dist/analysis/parser.d.ts.map +1 -1
  15. package/dist/analysis/parser.js +31 -14
  16. package/dist/analysis/parser.js.map +1 -1
  17. package/dist/analysis/stats.d.ts +25 -0
  18. package/dist/analysis/stats.d.ts.map +1 -1
  19. package/dist/analysis/stats.js +34 -0
  20. package/dist/analysis/stats.js.map +1 -1
  21. package/dist/analysis/summary.d.ts +51 -0
  22. package/dist/analysis/summary.d.ts.map +1 -0
  23. package/dist/analysis/summary.js +47 -0
  24. package/dist/analysis/summary.js.map +1 -0
  25. package/dist/analysis/types.d.ts +14 -0
  26. package/dist/analysis/types.d.ts.map +1 -1
  27. package/dist/api/ApiManagerApi.d.ts +53 -0
  28. package/dist/api/ApiManagerApi.d.ts.map +1 -1
  29. package/dist/api/ApiManagerApi.js +53 -2
  30. package/dist/api/ApiManagerApi.js.map +1 -1
  31. package/dist/api/CloudHub2Api.d.ts +24 -4
  32. package/dist/api/CloudHub2Api.d.ts.map +1 -1
  33. package/dist/api/CloudHub2Api.js +10 -22
  34. package/dist/api/CloudHub2Api.js.map +1 -1
  35. package/dist/api/ExchangeApi.d.ts +15 -0
  36. package/dist/api/ExchangeApi.d.ts.map +1 -1
  37. package/dist/api/ExchangeApi.js +51 -0
  38. package/dist/api/ExchangeApi.js.map +1 -1
  39. package/dist/api/LogsApi.d.ts +72 -19
  40. package/dist/api/LogsApi.d.ts.map +1 -1
  41. package/dist/api/LogsApi.js +116 -99
  42. package/dist/api/LogsApi.js.map +1 -1
  43. package/dist/api/RuntimesApi.d.ts +56 -0
  44. package/dist/api/RuntimesApi.d.ts.map +1 -0
  45. package/dist/api/RuntimesApi.js +58 -0
  46. package/dist/api/RuntimesApi.js.map +1 -0
  47. package/dist/cli.js +5 -1
  48. package/dist/cli.js.map +1 -1
  49. package/dist/client/AnypointClient.d.ts +2 -0
  50. package/dist/client/AnypointClient.d.ts.map +1 -1
  51. package/dist/client/AnypointClient.js +3 -0
  52. package/dist/client/AnypointClient.js.map +1 -1
  53. package/dist/client/smoke-test.d.ts +111 -0
  54. package/dist/client/smoke-test.d.ts.map +1 -0
  55. package/dist/client/smoke-test.js +275 -0
  56. package/dist/client/smoke-test.js.map +1 -0
  57. package/dist/commands/api.d.ts +1 -1
  58. package/dist/commands/api.d.ts.map +1 -1
  59. package/dist/commands/api.js +147 -10
  60. package/dist/commands/api.js.map +1 -1
  61. package/dist/commands/app-change-cli.d.ts +52 -0
  62. package/dist/commands/app-change-cli.d.ts.map +1 -0
  63. package/dist/commands/app-change-cli.js +149 -0
  64. package/dist/commands/app-change-cli.js.map +1 -0
  65. package/dist/commands/apps.d.ts +1 -1
  66. package/dist/commands/apps.d.ts.map +1 -1
  67. package/dist/commands/apps.js +198 -17
  68. package/dist/commands/apps.js.map +1 -1
  69. package/dist/commands/compare.d.ts +8 -0
  70. package/dist/commands/compare.d.ts.map +1 -0
  71. package/dist/commands/compare.js +104 -0
  72. package/dist/commands/compare.js.map +1 -0
  73. package/dist/commands/deploy.d.ts +4 -3
  74. package/dist/commands/deploy.d.ts.map +1 -1
  75. package/dist/commands/deploy.js +59 -33
  76. package/dist/commands/deploy.js.map +1 -1
  77. package/dist/commands/index.d.ts +1 -0
  78. package/dist/commands/index.d.ts.map +1 -1
  79. package/dist/commands/index.js +1 -0
  80. package/dist/commands/index.js.map +1 -1
  81. package/dist/commands/logs.d.ts +3 -2
  82. package/dist/commands/logs.d.ts.map +1 -1
  83. package/dist/commands/logs.js +158 -17
  84. package/dist/commands/logs.js.map +1 -1
  85. package/dist/commands/runtimes.d.ts +7 -0
  86. package/dist/commands/runtimes.d.ts.map +1 -0
  87. package/dist/commands/runtimes.js +66 -0
  88. package/dist/commands/runtimes.js.map +1 -0
  89. package/dist/config/profiles.d.ts +8 -0
  90. package/dist/config/profiles.d.ts.map +1 -1
  91. package/dist/config/profiles.js.map +1 -1
  92. package/dist/index.d.ts +9 -1
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +9 -0
  95. package/dist/index.js.map +1 -1
  96. package/dist/mcp/server.d.ts.map +1 -1
  97. package/dist/mcp/server.js +2 -1
  98. package/dist/mcp/server.js.map +1 -1
  99. package/dist/mcp/tools/analysis.d.ts.map +1 -1
  100. package/dist/mcp/tools/analysis.js +74 -30
  101. package/dist/mcp/tools/analysis.js.map +1 -1
  102. package/dist/mcp/tools/anypoint-mq.d.ts.map +1 -1
  103. package/dist/mcp/tools/anypoint-mq.js +5 -2
  104. package/dist/mcp/tools/anypoint-mq.js.map +1 -1
  105. package/dist/mcp/tools/api-manager.d.ts +2 -1
  106. package/dist/mcp/tools/api-manager.d.ts.map +1 -1
  107. package/dist/mcp/tools/api-manager.js +127 -38
  108. package/dist/mcp/tools/api-manager.js.map +1 -1
  109. package/dist/mcp/tools/app-inputs.d.ts +92 -0
  110. package/dist/mcp/tools/app-inputs.d.ts.map +1 -0
  111. package/dist/mcp/tools/app-inputs.js +142 -0
  112. package/dist/mcp/tools/app-inputs.js.map +1 -0
  113. package/dist/mcp/tools/apps-deploy.d.ts +6 -1
  114. package/dist/mcp/tools/apps-deploy.d.ts.map +1 -1
  115. package/dist/mcp/tools/apps-deploy.js +279 -307
  116. package/dist/mcp/tools/apps-deploy.js.map +1 -1
  117. package/dist/mcp/tools/apps-lifecycle.d.ts +3 -0
  118. package/dist/mcp/tools/apps-lifecycle.d.ts.map +1 -1
  119. package/dist/mcp/tools/apps-lifecycle.js +105 -136
  120. package/dist/mcp/tools/apps-lifecycle.js.map +1 -1
  121. package/dist/mcp/tools/apps-read.d.ts +2 -1
  122. package/dist/mcp/tools/apps-read.d.ts.map +1 -1
  123. package/dist/mcp/tools/apps-read.js +177 -14
  124. package/dist/mcp/tools/apps-read.js.map +1 -1
  125. package/dist/mcp/tools/artifacts.d.ts +8 -0
  126. package/dist/mcp/tools/artifacts.d.ts.map +1 -0
  127. package/dist/mcp/tools/artifacts.js +81 -0
  128. package/dist/mcp/tools/artifacts.js.map +1 -0
  129. package/dist/mcp/tools/index.d.ts +1 -0
  130. package/dist/mcp/tools/index.d.ts.map +1 -1
  131. package/dist/mcp/tools/index.js +1 -0
  132. package/dist/mcp/tools/index.js.map +1 -1
  133. package/dist/mcp/tools/logs.d.ts +50 -2
  134. package/dist/mcp/tools/logs.d.ts.map +1 -1
  135. package/dist/mcp/tools/logs.js +221 -38
  136. package/dist/mcp/tools/logs.js.map +1 -1
  137. package/dist/mcp/tools/shared.d.ts +27 -0
  138. package/dist/mcp/tools/shared.d.ts.map +1 -1
  139. package/dist/mcp/tools/shared.js +42 -0
  140. package/dist/mcp/tools/shared.js.map +1 -1
  141. package/dist/safety/deployment.d.ts +47 -2
  142. package/dist/safety/deployment.d.ts.map +1 -1
  143. package/dist/safety/deployment.js +148 -14
  144. package/dist/safety/deployment.js.map +1 -1
  145. package/dist/safety/paths.d.ts +29 -0
  146. package/dist/safety/paths.d.ts.map +1 -0
  147. package/dist/safety/paths.js +76 -0
  148. package/dist/safety/paths.js.map +1 -0
  149. package/dist/safety/runtime.d.ts +24 -0
  150. package/dist/safety/runtime.d.ts.map +1 -0
  151. package/dist/safety/runtime.js +59 -0
  152. package/dist/safety/runtime.js.map +1 -0
  153. package/dist/safety/secret-sources.d.ts +51 -0
  154. package/dist/safety/secret-sources.d.ts.map +1 -0
  155. package/dist/safety/secret-sources.js +124 -0
  156. package/dist/safety/secret-sources.js.map +1 -0
  157. package/dist/safety/secrets.d.ts +32 -0
  158. package/dist/safety/secrets.d.ts.map +1 -0
  159. package/dist/safety/secrets.js +91 -0
  160. package/dist/safety/secrets.js.map +1 -0
  161. package/dist/utils/errors.d.ts +2 -1
  162. package/dist/utils/errors.d.ts.map +1 -1
  163. package/dist/utils/errors.js +5 -3
  164. package/dist/utils/errors.js.map +1 -1
  165. package/dist/workflows/app-change.d.ts +124 -0
  166. package/dist/workflows/app-change.d.ts.map +1 -0
  167. package/dist/workflows/app-change.js +305 -0
  168. package/dist/workflows/app-change.js.map +1 -0
  169. package/dist/workflows/app-state.d.ts +60 -0
  170. package/dist/workflows/app-state.d.ts.map +1 -0
  171. package/dist/workflows/app-state.js +95 -0
  172. package/dist/workflows/app-state.js.map +1 -0
  173. package/dist/workflows/artifact-compare.d.ts +134 -0
  174. package/dist/workflows/artifact-compare.d.ts.map +1 -0
  175. package/dist/workflows/artifact-compare.js +419 -0
  176. package/dist/workflows/artifact-compare.js.map +1 -0
  177. package/dist/workflows/asset-naming.d.ts +72 -0
  178. package/dist/workflows/asset-naming.d.ts.map +1 -0
  179. package/dist/workflows/asset-naming.js +116 -0
  180. package/dist/workflows/asset-naming.js.map +1 -0
  181. package/dist/workflows/jar-deployment.d.ts +76 -39
  182. package/dist/workflows/jar-deployment.d.ts.map +1 -1
  183. package/dist/workflows/jar-deployment.js +125 -64
  184. package/dist/workflows/jar-deployment.js.map +1 -1
  185. package/package.json +3 -3
@@ -1,385 +1,357 @@
1
1
  /**
2
2
  * MCP Tool Registrar — Application deployment tools (production-guarded)
3
- * deploy_jar, deploy_app, update_app_artifact, rollback_app
3
+ * deploy_jar, deploy_app, update_app, update_app_artifact, rollback_app
4
+ *
5
+ * Every tool previews by default and applies on confirm: true; production also needs
6
+ * confirmProduction: true. Changes to an existing app go through one workflow
7
+ * (src/workflows/app-change.ts): one PATCH, a runtime check against the target, and
8
+ * an optional stop, change, start sequence.
4
9
  */
5
10
  import { z } from 'zod';
6
- import { mcpError, mcpText, dryRunPreview, resolveEnvironment } from './shared.js';
7
- import { buildCreatePayload, mergeForArtifactUpdate, resolveRollbackTarget } from '../../safety/deployment.js';
11
+ import { guardWrite, mcpText, resolveEnvironment, writeInputs } from './shared.js';
12
+ import { modeInput, runAppChangeTool, runtimeFromArgs, runtimeInputs, settingsFromArgs, settingsInputs, updateStrategyInput, waitInputs, } from './app-inputs.js';
13
+ import { buildCreatePayload, resolveRollbackTarget } from '../../safety/deployment.js';
14
+ import { redactText } from '../../safety/secrets.js';
8
15
  import { errorMessage } from '../../utils/errors.js';
16
+ import { resolveSettingsInput } from '../../workflows/app-change.js';
17
+ import { waitForAppPhase } from '../../workflows/app-state.js';
9
18
  import { describeJarDeployment, executeJarDeployment, planJarDeployment } from '../../workflows/jar-deployment.js';
19
+ const createOnlyInputs = {
20
+ replicas: z.number().min(1).max(8).optional().describe('[new app only] Number of replicas (default: 1)'),
21
+ region: z.string().optional().describe('[new app only] CloudHub 2.0 target region (default: "cloudhub-us-east-2")'),
22
+ vcores: z
23
+ .string()
24
+ .optional()
25
+ .describe('[new app only] vCore size (default: "0.1"). Options: 0.1, 0.2, 0.5, 1, 1.5, 2, 2.5, 3, 4'),
26
+ jvmArgs: z.string().optional().describe('[new app only] JVM arguments'),
27
+ };
28
+ const refInputs = {
29
+ artifactId: z.string().optional().describe('Maven artifact ID. Default: keep the current one'),
30
+ groupId: z.string().optional().describe('Maven group ID. Default: keep the current one'),
31
+ packaging: z.string().optional().describe('Artifact packaging. Default: keep the current one (usually "jar")'),
32
+ };
33
+ function errorText(error, secretValues) {
34
+ return {
35
+ content: [{ type: 'text', text: `Error: ${redactText(errorMessage(error), secretValues)}` }],
36
+ isError: true,
37
+ };
38
+ }
10
39
  export function registerAppDeployTools(server, client) {
11
40
  server.registerTool('deploy_jar', {
12
41
  title: 'Deploy JAR (Publish + Deploy)',
13
- description: 'One-call deploy of a locally built Mule application JAR: publishes it to Exchange, then deploys it to CloudHub 2.0 — creating the app if it does not exist, or safely updating just the artifact ref if it does. For an existing app, create-only settings (runtime, region, vcores, replicas, jvmArgs, properties) are rejected, because an update must not restate infrastructure. Pass confirm:true to run; without it you get a dry-run preview and nothing is published or deployed.',
42
+ description: 'Publishes a locally built Mule application JAR to Exchange and deploys it to CloudHub 2.0: creates the app if it does not exist, otherwise updates it in one change (artifact plus any runtime or settings change). ' +
43
+ 'Asset coordinates come from explicit inputs, then the project naming convention, then the pattern of the asset the app runs today, then the JAR. ' +
44
+ 'Region, vCores, replicas and JVM arguments apply only to a new app. Previews by default; pass confirm: true to publish and deploy.',
14
45
  inputSchema: {
15
46
  jarPath: z
16
47
  .string()
17
- .describe('Path to the built .jar file (e.g. "target/example-api-1.0.0-mule-application.jar")'),
48
+ .describe('Path to the built .jar file (e.g. "target/orders-sapi-1.0.0-mule-application.jar")'),
18
49
  appName: z.string().describe('CloudHub 2.0 application name'),
19
- environment: z.string().describe('Environment name (e.g. "Sandbox", "Production") or environment ID'),
50
+ environment: z.string().describe('Environment name (e.g. "Sandbox") or ID'),
20
51
  assetId: z
21
52
  .string()
22
53
  .optional()
23
- .describe('Exchange asset ID (also used as the deployment artifactId). Defaults to embedded Maven artifactId.'),
24
- assetVersion: z
25
- .string()
26
- .optional()
27
- .describe('Exchange asset and deployment version. Defaults to the embedded Maven version.'),
28
- groupId: z.string().optional().describe('Exchange/Maven group ID (default: the organization ID).'),
29
- // create-only settings (rejected when the app already exists)
30
- runtime: z.string().optional().describe('[new app only] Mule runtime version (default: "4.8.0").'),
31
- replicas: z
32
- .number()
33
- .min(1)
34
- .max(8)
35
- .optional()
36
- .describe('[new app only] Number of replicas (default: 1).'),
37
- region: z
38
- .string()
39
- .optional()
40
- .describe('[new app only] CloudHub 2.0 target region (default: "cloudhub-us-east-2").'),
41
- vcores: z.string().optional().describe('[new app only] vCore size (default: "0.1").'),
42
- properties: z.record(z.string()).optional().describe('[new app only] Application properties.'),
43
- secureProperties: z
44
- .record(z.string())
45
- .optional()
46
- .describe('[new app only] Secure application properties.'),
47
- jvmArgs: z.string().optional().describe('[new app only] JVM arguments.'),
48
- wait: z
49
- .boolean()
50
- .optional()
51
- .describe('Wait for the deployment to reach a running state (default: false).'),
54
+ .describe('Exchange asset ID, also the deployment artifactId. Default: naming convention'),
55
+ assetVersion: z.string().optional().describe('Exchange asset version. Default: naming convention'),
56
+ groupId: z.string().optional().describe('Exchange/Maven group ID (default: the organization ID)'),
57
+ ...runtimeInputs,
58
+ ...createOnlyInputs,
59
+ ...settingsInputs,
60
+ mode: modeInput,
61
+ updateStrategy: updateStrategyInput,
62
+ ...waitInputs,
52
63
  expectedSha256: z
53
64
  .string()
54
65
  .regex(/^[a-fA-F0-9]{64}$/)
55
66
  .optional()
56
- .describe('SHA-256 returned by the build or publication preview; rejects changed artifact bytes.'),
57
- confirm: z
58
- .boolean()
59
- .optional()
60
- .describe('Set true to publish and deploy. When omitted/false, returns a dry-run preview only.'),
67
+ .describe('SHA-256 from the preview; rejects changed artifact bytes'),
68
+ ...writeInputs,
61
69
  },
62
70
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false },
63
- }, async ({ jarPath, appName, environment, assetId, assetVersion, groupId, runtime, replicas, region, vcores, properties, secureProperties, jvmArgs, wait, expectedSha256, confirm, }) => {
71
+ }, async (args) => {
72
+ let secretValues = [];
64
73
  try {
65
- const { orgId, env } = await resolveEnvironment(client, environment);
74
+ const { orgId, env } = await resolveEnvironment(client, args.environment);
66
75
  const plan = await planJarDeployment(client, {
67
- jarPath,
68
- appName,
76
+ jarPath: args.jarPath,
77
+ appName: args.appName,
69
78
  orgId,
70
79
  env,
71
- assetId,
72
- assetVersion,
73
- groupId,
74
- expectedSha256,
75
- runtime,
76
- replicas,
77
- region,
78
- vcores,
79
- properties,
80
- secureProperties,
81
- jvmArgs,
80
+ assetId: args.assetId,
81
+ assetVersion: args.assetVersion,
82
+ groupId: args.groupId,
83
+ expectedSha256: args.expectedSha256,
84
+ runtime: runtimeFromArgs(args),
85
+ replicas: args.replicas,
86
+ region: args.region,
87
+ vcores: args.vcores,
88
+ jvmArgs: args.jvmArgs,
89
+ settings: settingsFromArgs(args),
90
+ mode: args.mode,
91
+ updateStrategy: args.updateStrategy,
82
92
  });
83
- // An update must not restate infra — reject create-only settings for an existing app.
84
- if (plan.rejectedSettings.length > 0) {
85
- return mcpText(`❌ "${appName}" already exists in ${env.name}; deploy_jar updates only the artifact ref and cannot change infrastructure. ` +
86
- `Remove these settings (${plan.rejectedSettings.join(', ')}), or use update_app_settings / a fresh deploy to change them.`);
93
+ secretValues = plan.secretValues;
94
+ const preview = describeJarDeployment(plan);
95
+ if (plan.rejectedSettings.length > 0 || plan.change?.blockers.length) {
96
+ return {
97
+ ...mcpText({
98
+ refused: true,
99
+ message: plan.rejectedSettings.length > 0
100
+ ? `"${args.appName}" already exists in ${env.name}; region, vCores, replicas and JVM arguments apply only to a new app. Remove: ${plan.rejectedSettings.join(', ')}`
101
+ : 'The change cannot be applied.',
102
+ ...preview,
103
+ }),
104
+ isError: true,
105
+ };
87
106
  }
88
- if (!confirm)
89
- return dryRunPreview(describeJarDeployment(plan));
90
- const { published, deployment: applied } = await executeJarDeployment(client, plan);
91
- let deployment = applied;
92
- let waitResult;
93
- if (wait) {
94
- try {
95
- deployment = await client.cloudHub2.waitForDeployment(orgId, env.id, deployment.id);
96
- waitResult = deployment.status;
97
- }
98
- catch (waitErr) {
99
- waitResult = `did not settle: ${errorMessage(waitErr)}`;
100
- }
101
- }
102
- return mcpText({
103
- message: `✅ Deployed "${appName}" to ${env.name} (${plan.existing ? 'updated' : 'created'})`,
104
- published,
105
- deploymentId: deployment.id,
106
- status: deployment.status,
107
- ...(plan.existing ? { previousVersion: plan.existing.application?.ref?.version } : {}),
108
- ...(wait ? { waitResult } : { tip: 'Use get_app_status to monitor progress.' }),
107
+ const gate = guardWrite({
108
+ env,
109
+ confirm: args.confirm,
110
+ confirmProduction: args.confirmProduction,
111
+ preview,
112
+ });
113
+ if (gate)
114
+ return gate;
115
+ const result = await executeJarDeployment(client, plan, {
116
+ wait: args.wait,
117
+ timeoutMs: args.waitTimeoutSeconds ? args.waitTimeoutSeconds * 1000 : undefined,
109
118
  });
119
+ const response = mcpText(JSON.parse(redactText(JSON.stringify({
120
+ message: `Deployed "${args.appName}" to ${env.name} (${plan.existing ? 'updated' : 'created'})`,
121
+ published: result.published,
122
+ deploymentId: result.deployment.id,
123
+ ...(plan.existing ? { previousRef: plan.existing.application?.ref } : {}),
124
+ ...(result.change ? { performed: result.change.performed } : {}),
125
+ ...(result.snapshot
126
+ ? {
127
+ phase: result.snapshot.phase,
128
+ settled: result.snapshot.settled,
129
+ ...(result.snapshot.reason ? { reason: result.snapshot.reason } : {}),
130
+ ...(result.timedOut ? { timedOut: true } : {}),
131
+ }
132
+ : {
133
+ tip: 'Pass wait: true, or call get_app_status with wait: true, to follow the rollout.',
134
+ }),
135
+ }), secretValues)));
136
+ return result.snapshot?.terminal && !result.snapshot.settled
137
+ ? { ...response, isError: true }
138
+ : response;
110
139
  }
111
140
  catch (error) {
112
- return mcpError(error);
141
+ return errorText(error, secretValues);
113
142
  }
114
143
  });
115
144
  server.registerTool('deploy_app', {
116
145
  title: 'Deploy Application',
117
- description: 'Deploys or redeploys a Mule application to CloudHub 2.0 using Maven coordinates (groupId:artifactId:version) referencing an artifact already published to Exchange. If the application already exists it will be updated (redeployed); otherwise a new deployment is created. Returns the deployment ID and initial status — use get_app_status to monitor progress.',
146
+ description: 'Deploys an artifact already in Exchange (groupId:artifactId:version) to CloudHub 2.0. Creates the app if it does not exist; otherwise updates it in one change, including any runtime or settings change. ' +
147
+ 'Region, vCores, replicas and JVM arguments apply only to a new app. Previews by default; pass confirm: true to apply.',
118
148
  inputSchema: {
119
- appName: z.string().describe('Application name (used as deployment name and public URL slug)'),
120
- environment: z.string().describe('Environment name (e.g. "Sandbox", "Production") or environment ID'),
121
- groupId: z.string().describe('Maven group ID of the application artifact (usually the org ID)'),
122
- artifactId: z.string().describe('Maven artifact ID (e.g. "order-management-api")'),
123
- version: z.string().describe('Artifact version (e.g. "1.2.0", "1.0.0-SNAPSHOT")'),
124
- runtime: z
125
- .string()
126
- .optional()
127
- .describe('Mule runtime version (default: "4.8.0"). Examples: "4.6.0", "4.7.0", "4.8.0"'),
128
- replicas: z.number().min(1).max(8).optional().describe('Number of replicas (default: 1, max: 8)'),
129
- region: z
130
- .string()
131
- .optional()
132
- .describe('CloudHub 2.0 target region (default: "cloudhub-us-east-2"). Examples: "cloudhub-us-east-1", "cloudhub-eu-west-1", "cloudhub-ap-southeast-1"'),
133
- vcores: z
134
- .string()
135
- .optional()
136
- .describe('vCore size (default: "0.1"). Options: "0.1", "0.2", "0.5", "1", "1.5", "2", "2.5", "3", "4"'),
137
- properties: z
138
- .record(z.string())
139
- .optional()
140
- .describe('Application properties as key-value pairs to set on deploy'),
141
- secureProperties: z
142
- .record(z.string())
143
- .optional()
144
- .describe('Secure (encrypted) application properties as key-value pairs'),
145
- jvmArgs: z.string().optional().describe('JVM arguments (e.g. "-XX:MaxMetaspaceSize=256m")'),
146
- confirm: z
147
- .boolean()
148
- .optional()
149
- .describe('Set true to apply. When omitted/false, returns a dry-run preview and changes nothing.'),
149
+ appName: z.string().describe('Application name (also the public URL slug)'),
150
+ environment: z.string().describe('Environment name (e.g. "Sandbox") or ID'),
151
+ groupId: z.string().describe('Maven group ID of the artifact (usually the organization ID)'),
152
+ artifactId: z.string().describe('Maven artifact ID (e.g. "orders-sapi")'),
153
+ version: z.string().describe('Artifact version (e.g. "1.2.0")'),
154
+ ...runtimeInputs,
155
+ ...createOnlyInputs,
156
+ ...settingsInputs,
157
+ mode: modeInput,
158
+ updateStrategy: updateStrategyInput,
159
+ ...waitInputs,
160
+ ...writeInputs,
150
161
  },
151
162
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false },
152
- }, async ({ appName, environment, groupId, artifactId, version, runtime, replicas, region, vcores, properties, secureProperties, jvmArgs, confirm, }) => {
163
+ }, async (args) => {
164
+ let secretValues = [];
153
165
  try {
154
- const { orgId, env } = await resolveEnvironment(client, environment);
155
- const existing = await client.cloudHub2.findDetailByName(orgId, env.id, appName);
156
- // ── Update path: existing app → SAFE artifact-ref-only redeploy ──
157
- // Infra params (runtime/region/vcores/replicas/jvmArgs) are intentionally NOT
158
- // restated here; doing so previously clobbered the live deployment. They are
159
- // ignored on redeploy and the caller is told so.
166
+ const { orgId, env } = await resolveEnvironment(client, args.environment);
167
+ const existing = await client.cloudHub2.findDetailByName(orgId, env.id, args.appName);
160
168
  if (existing) {
161
- const ignored = [
162
- runtime && 'runtime',
163
- region && 'region',
164
- vcores && 'vcores',
165
- replicas && 'replicas',
166
- jvmArgs && 'jvmArgs',
167
- properties && 'properties',
168
- secureProperties && 'secureProperties',
169
- ].filter(Boolean);
170
- const merged = mergeForArtifactUpdate(existing, { groupId, artifactId, version });
171
- const currentVersion = existing.application?.ref?.version || null;
172
- if (!confirm) {
173
- return dryRunPreview({
174
- action: 'redeploy (artifact ref only)',
175
- app: appName,
176
- environment: env.name,
177
- current: {
178
- version: currentVersion,
179
- runtime: existing.target?.deploymentSettings?.runtime?.version,
180
- targetId: existing.target?.targetId,
181
- replicas: existing.target?.replicas,
182
- },
183
- next: { ref: merged.application.ref },
184
- preserved: 'runtime, target/space, replicas, resources, settings',
185
- ...(ignored.length
186
- ? {
187
- note: `Ignored on redeploy (use update_app_settings / a fresh deploy to change infra): ${ignored.join(', ')}`,
188
- }
189
- : {}),
190
- });
169
+ const rejected = ['region', 'vcores', 'replicas', 'jvmArgs'].filter((key) => args[key] !== undefined);
170
+ if (rejected.length) {
171
+ return {
172
+ ...mcpText({
173
+ refused: true,
174
+ message: `"${args.appName}" already exists in ${env.name}; ${rejected.join(', ')} apply only to a new app.`,
175
+ }),
176
+ isError: true,
177
+ };
191
178
  }
192
- const deployment = await client.cloudHub2.updateArtifactRef(orgId, env.id, existing.id, merged.application.ref);
193
- return mcpText({
194
- message: `✅ Redeployed "${appName}" in ${env.name} (artifact ref only)`,
195
- deploymentId: deployment.id,
196
- status: deployment.status,
197
- version: `${groupId}:${artifactId}:${version}`,
198
- previousVersion: currentVersion,
199
- preserved: 'runtime, target/space, replicas, resources, settings',
200
- ...(ignored.length ? { ignored } : {}),
201
- tip: 'Use get_app_status to monitor deployment progress.',
202
- });
179
+ return runAppChangeTool(client, args, {
180
+ existing,
181
+ ref: { groupId: args.groupId, artifactId: args.artifactId, version: args.version },
182
+ runtime: runtimeFromArgs(args),
183
+ settings: settingsFromArgs(args),
184
+ mode: args.mode,
185
+ updateStrategy: args.updateStrategy,
186
+ }, { done: 'Updated' });
187
+ }
188
+ const resolved = resolveSettingsInput(settingsFromArgs(args));
189
+ secretValues = resolved.secretValues;
190
+ if (resolved.change?.removeProperties ||
191
+ resolved.change?.removeSecureProperties ||
192
+ resolved.change?.moveToSecure) {
193
+ throw new Error('removeProperties, removeSecureProperties and moveToSecure apply only to an existing app');
203
194
  }
204
- // ── Create path: new app → full payload from the shared builder ──
205
195
  const payload = buildCreatePayload({
206
- appName,
207
- groupId,
208
- artifactId,
209
- version,
210
- runtime,
211
- replicas,
212
- region,
213
- vcores,
214
- properties,
215
- secureProperties,
216
- jvmArgs,
196
+ appName: args.appName,
197
+ groupId: args.groupId,
198
+ artifactId: args.artifactId,
199
+ version: args.version,
200
+ runtime: args.runtime,
201
+ javaVersion: args.javaVersion,
202
+ releaseChannel: args.releaseChannel,
203
+ replicas: args.replicas,
204
+ region: args.region,
205
+ vcores: args.vcores,
206
+ properties: resolved.change?.properties,
207
+ secureProperties: resolved.change?.secureProperties,
208
+ jvmArgs: args.jvmArgs,
209
+ });
210
+ const preview = {
211
+ action: 'create new deployment',
212
+ app: args.appName,
213
+ environment: env.name,
214
+ next: {
215
+ ref: payload.application.ref,
216
+ runtime: payload.target.deploymentSettings.runtime,
217
+ region: payload.target.targetId,
218
+ vCores: payload.application.vCores,
219
+ replicas: payload.target.replicas,
220
+ properties: Object.keys(resolved.change?.properties ?? {}),
221
+ secureProperties: Object.keys(resolved.change?.secureProperties ?? {}),
222
+ },
223
+ };
224
+ const gate = guardWrite({
225
+ env,
226
+ confirm: args.confirm,
227
+ confirmProduction: args.confirmProduction,
228
+ preview,
217
229
  });
218
- if (!confirm) {
219
- return dryRunPreview({
220
- action: 'create new deployment',
221
- app: appName,
222
- environment: env.name,
223
- next: {
224
- version: `${groupId}:${artifactId}:${version}`,
225
- runtime: payload.target.deploymentSettings.runtime.version,
226
- region: payload.target.targetId,
227
- vCores: payload.application.vCores,
228
- replicas: payload.target.replicas,
229
- },
230
+ if (gate)
231
+ return gate;
232
+ const sentAt = Date.now();
233
+ const deployment = await client.cloudHub2.createDeployment(orgId, env.id, payload);
234
+ if (!args.wait) {
235
+ return mcpText({
236
+ message: `Created "${args.appName}" in ${env.name}`,
237
+ deploymentId: deployment.id,
238
+ ...preview.next,
230
239
  });
231
240
  }
232
- const deployment = await client.cloudHub2.createDeployment(orgId, env.id, payload);
233
- return mcpText({
234
- message: `✅ Created new deployment for "${appName}" in ${env.name}`,
241
+ const result = await waitForAppPhase(client.cloudHub2, orgId, env.id, deployment.id, {
242
+ sinceChange: sentAt,
243
+ timeoutMs: args.waitTimeoutSeconds ? args.waitTimeoutSeconds * 1000 : undefined,
244
+ });
245
+ const response = mcpText({
246
+ message: `Created "${args.appName}" in ${env.name}`,
235
247
  deploymentId: deployment.id,
236
- status: deployment.status,
237
- version: `${groupId}:${artifactId}:${version}`,
238
- runtime: payload.target.deploymentSettings.runtime.version,
239
- vCores: payload.application.vCores,
240
- replicas: payload.target.replicas,
241
- region: payload.target.targetId,
242
- tip: 'Use get_app_status to monitor deployment progress.',
248
+ phase: result.snapshot.phase,
249
+ settled: result.snapshot.settled,
250
+ ...(result.snapshot.reason ? { reason: result.snapshot.reason } : {}),
251
+ ...(result.timedOut ? { timedOut: true } : {}),
243
252
  });
253
+ return result.snapshot.terminal && !result.snapshot.settled
254
+ ? { ...response, isError: true }
255
+ : response;
244
256
  }
245
257
  catch (error) {
246
- return mcpError(error);
258
+ return errorText(error, secretValues);
247
259
  }
248
260
  });
261
+ server.registerTool('update_app', {
262
+ title: 'Update Application',
263
+ description: 'Changes an existing CloudHub 2.0 application in one call: artifact version, Mule runtime, properties and update strategy together, as one new spec. ' +
264
+ 'mode stopStart stops the app, applies the change and starts it, so old and new replicas never process events together; use it for apps with streaming, subscription or polling listeners. ' +
265
+ 'The runtime is checked against what the target offers before anything is written; a retired runtime is refused with the nearest supported versions. ' +
266
+ 'Waits for the rollout by default. Previews by default; pass confirm: true to apply.',
267
+ inputSchema: {
268
+ appName: z.string().describe('Application name exactly as deployed (case-insensitive match)'),
269
+ environment: z.string().describe('Environment name or ID'),
270
+ version: z.string().optional().describe('New artifact version. Default: keep the current one'),
271
+ ...refInputs,
272
+ ...runtimeInputs,
273
+ ...settingsInputs,
274
+ mode: modeInput,
275
+ updateStrategy: updateStrategyInput,
276
+ wait: z.boolean().optional().describe('Wait until every replica runs the new spec (default: true)'),
277
+ waitTimeoutSeconds: waitInputs.waitTimeoutSeconds,
278
+ ...writeInputs,
279
+ },
280
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false },
281
+ }, async (args) => runAppChangeTool(client, { ...args, wait: args.wait ?? true }, {
282
+ ref: {
283
+ version: args.version,
284
+ artifactId: args.artifactId,
285
+ groupId: args.groupId,
286
+ packaging: args.packaging,
287
+ },
288
+ runtime: runtimeFromArgs(args),
289
+ settings: settingsFromArgs(args),
290
+ mode: args.mode,
291
+ updateStrategy: args.updateStrategy,
292
+ }, { done: 'Updated' }));
249
293
  server.registerTool('update_app_artifact', {
250
- title: 'Update Application Artifact (Safe Redeploy)',
251
- description: 'Safely redeploys an existing CloudHub 2.0 application to a new artifact version by PATCHing ONLY the application reference. The live Mule runtime, deployment target/space, replica count, resources, and settings are all preserved — this is the correct tool for a production version bump, and is preferred over deploy_app for existing apps. Optionally waits for the deployment to reach a running state. Pass confirm:true to apply; without it you get a dry-run preview of the ref change.',
294
+ title: 'Update Application Artifact',
295
+ description: 'Moves an existing CloudHub 2.0 application to another artifact version, optionally with a runtime change in the same update. Target, replicas, resources and settings are preserved. ' +
296
+ 'Use runtime when the current runtime has been retired; the preview names supported versions. For settings changes in the same call use update_app. Previews by default; pass confirm: true to apply.',
252
297
  inputSchema: {
253
298
  appName: z.string().describe('Application name exactly as deployed (case-insensitive match)'),
254
- environment: z.string().describe('Environment name (e.g. "Production") or environment ID'),
255
- version: z.string().describe('New artifact version to deploy (e.g. "1.4.12")'),
256
- artifactId: z.string().optional().describe('Maven artifact ID. Default: keep the existing one.'),
257
- groupId: z.string().optional().describe('Maven group ID. Default: keep the existing one.'),
258
- packaging: z
259
- .string()
260
- .optional()
261
- .describe('Artifact packaging. Default: keep existing (usually "jar").'),
262
- wait: z
263
- .boolean()
264
- .optional()
265
- .describe('Wait for the redeploy to reach a running state before returning (default: false).'),
266
- confirm: z
267
- .boolean()
268
- .optional()
269
- .describe('Set true to apply. When omitted/false, returns a dry-run preview and changes nothing.'),
299
+ environment: z.string().describe('Environment name or ID'),
300
+ version: z.string().describe('New artifact version (e.g. "1.4.12")'),
301
+ ...refInputs,
302
+ ...runtimeInputs,
303
+ mode: modeInput,
304
+ ...waitInputs,
305
+ ...writeInputs,
270
306
  },
271
307
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false },
272
- }, async ({ appName, environment, version, artifactId, groupId, packaging, wait, confirm }) => {
273
- try {
274
- const { orgId, env } = await resolveEnvironment(client, environment);
275
- const existing = await client.cloudHub2.findDetailByName(orgId, env.id, appName);
276
- if (!existing) {
277
- return mcpText(`Application "${appName}" not found in ${env.name}. Use deploy_app to create a new deployment.`);
278
- }
279
- const merged = mergeForArtifactUpdate(existing, { groupId, artifactId, version, packaging });
280
- const oldRef = existing.application?.ref;
281
- if (!confirm) {
282
- return dryRunPreview({
283
- action: 'update artifact ref (safe redeploy)',
284
- app: appName,
285
- environment: env.name,
286
- current: { ref: oldRef },
287
- next: { ref: merged.application.ref },
288
- preserved: 'runtime, target/space, replicas, resources, settings',
289
- });
290
- }
291
- let deployment = await client.cloudHub2.updateArtifactRef(orgId, env.id, existing.id, merged.application.ref);
292
- let waitResult;
293
- if (wait) {
294
- try {
295
- deployment = await client.cloudHub2.waitForDeployment(orgId, env.id, deployment.id);
296
- waitResult = deployment.status;
297
- }
298
- catch (waitErr) {
299
- waitResult = `did not settle: ${errorMessage(waitErr)}`;
300
- }
301
- }
302
- return mcpText({
303
- message: `✅ Updated "${appName}" in ${env.name} to v${version}`,
304
- deploymentId: deployment.id,
305
- status: deployment.status,
306
- from: oldRef,
307
- to: merged.application.ref,
308
- preserved: 'runtime, target/space, replicas, resources, settings',
309
- ...(wait ? { waitResult } : { tip: 'Pass wait:true or use get_app_status to monitor progress.' }),
310
- });
311
- }
312
- catch (error) {
313
- return mcpError(error);
314
- }
315
- });
308
+ }, async (args) => runAppChangeTool(client, args, {
309
+ ref: {
310
+ version: args.version,
311
+ artifactId: args.artifactId,
312
+ groupId: args.groupId,
313
+ packaging: args.packaging,
314
+ },
315
+ runtime: runtimeFromArgs(args),
316
+ mode: args.mode,
317
+ }, { done: 'Updated artifact of' }));
316
318
  server.registerTool('rollback_app', {
317
319
  title: 'Roll Back Application',
318
- description: 'Rolls an existing CloudHub 2.0 application back to a previous artifact by PATCHing only the application reference (runtime, target, replicas, and settings are preserved). By default it resolves the last successful or newest distinct historical artifact ref; pass toVersion to select a specific artifact version. Pass confirm:true to apply; without it you get a dry-run preview.',
320
+ description: 'Rolls an existing CloudHub 2.0 application back to a previous artifact; runtime, target, replicas and settings are preserved. By default it picks the last successful or newest distinct earlier artifact; toVersion selects one. Previews by default; pass confirm: true to apply.',
319
321
  inputSchema: {
320
322
  appName: z.string().describe('Application name exactly as deployed (case-insensitive match)'),
321
- environment: z.string().describe('Environment name (e.g. "Production") or environment ID'),
323
+ environment: z.string().describe('Environment name or ID'),
322
324
  toVersion: z
323
325
  .string()
324
326
  .optional()
325
- .describe('Artifact version to roll back to. Default: newest distinct historical artifact.'),
326
- wait: z
327
- .boolean()
328
- .optional()
329
- .describe('Wait for the rollback to reach a running state (default: false).'),
330
- confirm: z
331
- .boolean()
332
- .optional()
333
- .describe('Set true to apply. When omitted/false, returns a dry-run preview and changes nothing.'),
327
+ .describe('Artifact version to roll back to. Default: newest distinct earlier artifact'),
328
+ mode: modeInput,
329
+ ...waitInputs,
330
+ ...writeInputs,
334
331
  },
335
332
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false },
336
- }, async ({ appName, environment, toVersion, wait, confirm }) => {
333
+ }, async (args) => {
337
334
  try {
338
- const { orgId, env } = await resolveEnvironment(client, environment);
339
- const existing = await client.cloudHub2.findDetailByName(orgId, env.id, appName);
335
+ const { orgId, env } = await resolveEnvironment(client, args.environment);
336
+ const existing = await client.cloudHub2.findDetailByName(orgId, env.id, args.appName);
340
337
  if (!existing) {
341
- return mcpText(`Application "${appName}" not found in ${env.name}`);
338
+ return {
339
+ ...mcpText({ message: `Application "${args.appName}" not found in ${env.name}` }),
340
+ isError: true,
341
+ };
342
342
  }
343
343
  const specs = await client.cloudHub2.getDeploymentSpecs(orgId, env.id, existing.id);
344
- const target = resolveRollbackTarget(existing, specs, toVersion);
344
+ const target = resolveRollbackTarget(existing, specs, args.toVersion);
345
345
  if (!target) {
346
- return mcpText(`No distinct rollback target for "${appName}" in ${env.name}. ` +
347
- 'Pass toVersion to select a specific artifact version.');
348
- }
349
- if (!confirm) {
350
- return dryRunPreview({
351
- action: 'rollback (artifact ref only)',
352
- app: appName,
353
- environment: env.name,
354
- current: { ref: existing.application.ref, specId: existing.desiredVersion },
355
- next: { ref: target.ref, sourceSpecId: target.sourceSpecId },
356
- preserved: 'runtime, target/space, replicas, resources, settings',
346
+ return mcpText({
347
+ noop: true,
348
+ message: `No distinct rollback target for "${args.appName}" in ${env.name}. Pass toVersion to select one.`,
357
349
  });
358
350
  }
359
- let deployment = await client.cloudHub2.rollbackToRef(orgId, env.id, existing.id, target.ref);
360
- let waitResult;
361
- if (wait) {
362
- try {
363
- deployment = await client.cloudHub2.waitForDeployment(orgId, env.id, deployment.id);
364
- waitResult = deployment.status;
365
- }
366
- catch (waitErr) {
367
- waitResult = `did not settle: ${errorMessage(waitErr)}`;
368
- }
369
- }
370
- return mcpText({
371
- message: `✅ Rolled back "${appName}" in ${env.name}: ${existing.application.ref.version} → ${target.ref.version}`,
372
- deploymentId: deployment.id,
373
- status: deployment.status,
374
- rolledBackFrom: existing.application.ref,
375
- rolledBackTo: target.ref,
376
- sourceSpecId: target.sourceSpecId,
377
- preserved: 'runtime, target/space, replicas, resources, settings',
378
- ...(wait ? { waitResult } : {}),
379
- });
351
+ return runAppChangeTool(client, args, { existing, ref: target.ref, mode: args.mode }, { done: 'Rolled back' });
380
352
  }
381
353
  catch (error) {
382
- return mcpError(error);
354
+ return errorText(error, []);
383
355
  }
384
356
  });
385
357
  }