harness-mcp-v2 3.2.19 → 3.2.21

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 (111) hide show
  1. package/README.md +146 -75
  2. package/build/client/harness-client.d.ts.map +1 -1
  3. package/build/client/harness-client.js +32 -6
  4. package/build/client/harness-client.js.map +1 -1
  5. package/build/config.d.ts +10 -0
  6. package/build/config.d.ts.map +1 -1
  7. package/build/config.js +15 -0
  8. package/build/config.js.map +1 -1
  9. package/build/data/schemas/v0/pipeline.d.ts.map +1 -1
  10. package/build/data/schemas/v0/pipeline.js +1388 -580
  11. package/build/data/schemas/v0/pipeline.js.map +1 -1
  12. package/build/data/schemas/v0/template.d.ts.map +1 -1
  13. package/build/data/schemas/v0/template.js +1142 -166
  14. package/build/data/schemas/v0/template.js.map +1 -1
  15. package/build/data/schemas/v1/pipeline.d.ts.map +1 -1
  16. package/build/data/schemas/v1/pipeline.js +302 -90
  17. package/build/data/schemas/v1/pipeline.js.map +1 -1
  18. package/build/data/schemas/v1/template.d.ts.map +1 -1
  19. package/build/data/schemas/v1/template.js +708 -488
  20. package/build/data/schemas/v1/template.js.map +1 -1
  21. package/build/prompts/deploy-ai-agent.d.ts +3 -0
  22. package/build/prompts/deploy-ai-agent.d.ts.map +1 -0
  23. package/build/prompts/deploy-ai-agent.js +300 -0
  24. package/build/prompts/deploy-ai-agent.js.map +1 -0
  25. package/build/prompts/feature-flag-rollout.d.ts.map +1 -1
  26. package/build/prompts/feature-flag-rollout.js +19 -8
  27. package/build/prompts/feature-flag-rollout.js.map +1 -1
  28. package/build/prompts/index.d.ts.map +1 -1
  29. package/build/prompts/index.js +2 -0
  30. package/build/prompts/index.js.map +1 -1
  31. package/build/registry/extractors.d.ts +87 -10
  32. package/build/registry/extractors.d.ts.map +1 -1
  33. package/build/registry/extractors.js +426 -26
  34. package/build/registry/extractors.js.map +1 -1
  35. package/build/registry/index.d.ts.map +1 -1
  36. package/build/registry/index.js +34 -15
  37. package/build/registry/index.js.map +1 -1
  38. package/build/registry/scope-utils.d.ts +45 -0
  39. package/build/registry/scope-utils.d.ts.map +1 -1
  40. package/build/registry/scope-utils.js +78 -0
  41. package/build/registry/scope-utils.js.map +1 -1
  42. package/build/registry/toolsets/chaos-descriptions.d.ts +72 -20
  43. package/build/registry/toolsets/chaos-descriptions.d.ts.map +1 -1
  44. package/build/registry/toolsets/chaos-descriptions.js +435 -140
  45. package/build/registry/toolsets/chaos-descriptions.js.map +1 -1
  46. package/build/registry/toolsets/chaos.d.ts.map +1 -1
  47. package/build/registry/toolsets/chaos.js +967 -193
  48. package/build/registry/toolsets/chaos.js.map +1 -1
  49. package/build/registry/toolsets/feature-flags.d.ts.map +1 -1
  50. package/build/registry/toolsets/feature-flags.js +888 -85
  51. package/build/registry/toolsets/feature-flags.js.map +1 -1
  52. package/build/registry/toolsets/governance.d.ts.map +1 -1
  53. package/build/registry/toolsets/governance.js +4 -0
  54. package/build/registry/toolsets/governance.js.map +1 -1
  55. package/build/registry/toolsets/iacm.d.ts.map +1 -1
  56. package/build/registry/toolsets/iacm.js +787 -11
  57. package/build/registry/toolsets/iacm.js.map +1 -1
  58. package/build/registry/toolsets/infrastructure.d.ts.map +1 -1
  59. package/build/registry/toolsets/infrastructure.js +30 -5
  60. package/build/registry/toolsets/infrastructure.js.map +1 -1
  61. package/build/registry/toolsets/pipelines.d.ts.map +1 -1
  62. package/build/registry/toolsets/pipelines.js +208 -5
  63. package/build/registry/toolsets/pipelines.js.map +1 -1
  64. package/build/registry/toolsets/release-management.d.ts +8 -0
  65. package/build/registry/toolsets/release-management.d.ts.map +1 -0
  66. package/build/registry/toolsets/release-management.js +783 -0
  67. package/build/registry/toolsets/release-management.js.map +1 -0
  68. package/build/registry/toolsets/sto.d.ts.map +1 -1
  69. package/build/registry/toolsets/sto.js +93 -17
  70. package/build/registry/toolsets/sto.js.map +1 -1
  71. package/build/registry/types.d.ts +52 -4
  72. package/build/registry/types.d.ts.map +1 -1
  73. package/build/resources/harness-schema.d.ts.map +1 -1
  74. package/build/resources/harness-schema.js +2 -1
  75. package/build/resources/harness-schema.js.map +1 -1
  76. package/build/tools/entity-schema/live.d.ts +6 -5
  77. package/build/tools/entity-schema/live.d.ts.map +1 -1
  78. package/build/tools/entity-schema/live.js +114 -10
  79. package/build/tools/entity-schema/live.js.map +1 -1
  80. package/build/tools/entity-schema/types.d.ts +7 -1
  81. package/build/tools/entity-schema/types.d.ts.map +1 -1
  82. package/build/tools/harness-create.d.ts.map +1 -1
  83. package/build/tools/harness-create.js +5 -1
  84. package/build/tools/harness-create.js.map +1 -1
  85. package/build/tools/harness-delete.d.ts.map +1 -1
  86. package/build/tools/harness-delete.js +5 -1
  87. package/build/tools/harness-delete.js.map +1 -1
  88. package/build/tools/harness-execute.d.ts.map +1 -1
  89. package/build/tools/harness-execute.js +15 -9
  90. package/build/tools/harness-execute.js.map +1 -1
  91. package/build/tools/harness-get.d.ts.map +1 -1
  92. package/build/tools/harness-get.js +5 -1
  93. package/build/tools/harness-get.js.map +1 -1
  94. package/build/tools/harness-list.d.ts.map +1 -1
  95. package/build/tools/harness-list.js +18 -2
  96. package/build/tools/harness-list.js.map +1 -1
  97. package/build/tools/harness-schema.d.ts +2 -1
  98. package/build/tools/harness-schema.d.ts.map +1 -1
  99. package/build/tools/harness-schema.js +10 -8
  100. package/build/tools/harness-schema.js.map +1 -1
  101. package/build/tools/harness-update.d.ts.map +1 -1
  102. package/build/tools/harness-update.js +5 -1
  103. package/build/tools/harness-update.js.map +1 -1
  104. package/build/tools/index.js +1 -1
  105. package/build/tools/index.js.map +1 -1
  106. package/build/utils/url-parser.d.ts +2 -0
  107. package/build/utils/url-parser.d.ts.map +1 -1
  108. package/build/utils/url-parser.js +55 -0
  109. package/build/utils/url-parser.js.map +1 -1
  110. package/npm-shrinkwrap.json +2 -2
  111. package/package.json +1 -1
@@ -94,43 +94,131 @@ Workflow: list templates → get_variables to discover required inputs → creat
94
94
  Supports list, get, delete, plus execute actions: list_revisions, get_variables, get_yaml, compare_revisions, and create_from_template.`;
95
95
  export const descChaosExperimentVariable = `Variables for a chaos experiment. List variables to discover required runtime inputs before running an experiment.`;
96
96
  export const descChaosInfrastructure = `Linux/machine infrastructure registered for chaos experiments and load testing. For Kubernetes infrastructure, use chaos_k8s_infrastructure. Supports list.`;
97
+ export const descChaosService = `Chaos Service (Service Management) — a logical service onboarded into Harness Chaos Engineering with an associated Service Discovery agent, environment, infrastructure, and probe associations. Distinct from harness_service (CD service) and from chaos_loadtest. Supports list, get, create, update, and delete.`;
98
+ export const descChaosServiceEnvironmentIds = `Filter by one or more environment identifiers. Comma-separated string for multiple values (e.g. "prodEnv,stagingEnv"). Matches the chaos service's environmentId exactly.`;
99
+ export const descChaosServiceInfrastructureIds = `Filter by one or more chaos infrastructure identifiers, each in "<environment_id>/<infrastructure_id>" form. Comma-separated string for multiple values (e.g. "env1/infra1,env2/infra2"). IMPORTANT: the 'infrastructureId' field returned by chaos_service list/get is the BARE infra ID only (the environment prefix is stripped server-side before it is returned) — do NOT pass that value alone. Build the filter value by combining that same response's 'environmentId' + "/" + 'infrastructureId'.`;
100
+ export const descChaosServiceTags = `Filter by tags. Comma-separated string; matches services that carry ALL of the listed tags.`;
101
+ export const descChaosServiceIncludeAllScope = `When true, include chaos services from all descendant scopes (org + all projects when scoped at org, or account + all orgs/projects when scoped at account). Defaults to false (strict scope match).`;
102
+ export const descChaosServiceProbeIds = `Filter by one or more probe identities associated with the chaos service. Comma-separated string for multiple values.`;
103
+ export const descChaosServiceOnboardingIdFilter = `Filter by the onboarding batch identifier the chaos service was created under (set during bulk/discovery onboarding).`;
104
+ export const descChaosServiceIdentity = `Chaos service identity (slug). Use the 'identity' field returned by chaos_service list.`;
105
+ export const descChaosServiceSearch = `Search chaos services by name or identity (case-insensitive regex).`;
106
+ export const descListChaosServices = `List chaos services in the given account/org/project scope. Supports pagination (page, limit; default limit 15, max 100), search, sort (sortField: name|lastUpdated|experimentName|lastExecuted with sortAscending), tag filter, and filters by environmentIds / infrastructureIds. Set include_all_scope=true to include descendant scopes.`;
107
+ export const descGetChaosService = `Get a single chaos service by its identity slug within the given account/org/project scope.`;
108
+ export const descDeleteChaosService = `Delete a chaos service by its identity slug within the given account/org/project scope. Soft-deletes the service record and purges its probe mapping associations in a single transaction. Destructive and not reversible via API. Returns { success, correlationID } on 200; does not return the deleted resource.`;
109
+ export const descListChaosServiceExperimentRuns = `List the experiment runs in which this chaos service was executed (service→run linkage resolved via execution nodes). Supports pagination (page, limit), search, sort (sortField: name|lastUpdated with sortAscending), and filters by infra_ids / statuses / step_types. Set include_all_scope=true to include descendant scopes.`;
110
+ export const descListChaosServiceLoadTests = `List the load tests whose serviceReferences include this chaos service. Supports pagination (page, limit), search, sort, and filters by tool_type / environment_ids / infra_ids / tags. Set include_all_scope=true to include descendant scopes.`;
111
+ export const descCreateChaosService = `Onboard (create) a chaos service. This is a GUIDED, ORDERED workflow — do NOT skip ahead or invent identifiers; each step depends on the selection made in the previous one. Do NOT advance to the next step until the current step's selection is made, and NEVER call harness_create(chaos_service) until every step below is resolved and the user has confirmed.
112
+
113
+ STEP 1 — Select a Discovery Agent (REQUIRED FIRST; gate: do not continue without a chosen agent).
114
+ Call harness_list(resource_type='discovered_agent', org_id, project_id). Show each agent's name, identity, serviceCount, and installationType. Ask the user to pick one.
115
+ From the chosen agent capture: agent_id = agent.identity; environment_id = agent.environmentIdentifier. For SD Kubernetes agents set infrastructure_id = agent.identity (BARE, not env-prefixed) and infrastructure_type = "KubernetesV2".
116
+
117
+ STEP 2 — Select the Service to onboard (REQUIRED, only after STEP 1; gate: do not continue without a chosen service).
118
+ 2a. (Optional namespace narrowing) harness_list(resource_type='discovered_namespace', agent_identity=<agent.identity>, environment_id=<environment_id>, all=true). Ask the user to pick a namespace, or skip to list all services.
119
+ 2b. harness_list(resource_type='discovered_service', agent_identity=<agent.identity>, environment_id=<environment_id>, compact=false[, namespace=<picked namespace>]). For each service show: name; namespace = spec.kubernetes.namespace; IP = spec.kubernetes.service.clusterIP (fallback spec.ip[0]); port = spec.kubernetes.service.ports[0].port (fallback spec.port[0]). Ask the user to pick a service.
120
+ Capture external_service_id = <selected discovered service>.id.
121
+
122
+ STEP 3 — Configure Metadata (REQUIRED, only after STEP 2).
123
+ Ask the user for name (REQUIRED), description (optional), and tags (optional). Derive identity as a slug of name unless the user supplies one. identity must be unique in scope — create fails on a duplicate identity or a duplicate external_service_id.
124
+
125
+ STEP 4 — Associate Probes (REQUIRED DECISION, only after STEP 3).
126
+ Explicitly ask the user: attach one or more health-check probes now, or create the service with no probes? Do not assume.
127
+ - If none: probes = [].
128
+ - If attaching: harness_list(resource_type='chaos_probe', infra_type='KubernetesV2'[, entity_type='httpProbe'|'cmdProbe'|'datadogProbe'|... to filter by probe type]). For EACH probe the user selects, read its inputs[] array (from the list item, or harness_get chaos_probe). For every input, prompt the user for a value — MANDATORY when required=true — and REPLACE the placeholder value "<+input>" with the user's value while keeping the rest of the input object (name, path, category, type, reference, ...) unchanged. Build probes = [{ probeId: <probe.identity>, inputs: [<full input objects with the user's values>] }, ...].
129
+
130
+ STEP 5 — Create. Only now call harness_create(resource_type='chaos_service', org_id, project_id, body={ identity, name, description?, tags?, external_service_id, agent_id, environment_id, infrastructure_id, infrastructure_type, probes }). The server re-resolves serviceType and namespace from external_service_id and stores infrastructureId as "<environment_id>/<infrastructure_id>". Returns the full ChaosServiceResponse on 200.
131
+
132
+ Never fabricate external_service_id, agent_id, or probe inputs — always source them from the discovered_agent / discovered_service / chaos_probe responses above.`;
133
+ export const descBodyChaosServiceCreate = `Chaos service create body. Required: identity, name, external_service_id, agent_id, environment_id, infrastructure_id. Optional: description, tags, infrastructure_type, onboarding_id, probes.`;
134
+ export const descChaosServiceName = `Human-readable name for the chaos service. Required.`;
135
+ export const descChaosServiceDescription = `Optional free-form description for the chaos service.`;
136
+ export const descChaosServiceTagsBody = `Optional tags for the chaos service. Accepts a JSON array of strings or a comma-separated string; normalised to string[] before sending.`;
137
+ export const descChaosServiceExternalServiceId = `External service ID (maps to externalServiceId). Required. This is the 'id' field of the chosen record from discovered_service list (STEP 2 of create) — NOT the service name. The server treats it as authoritative and re-resolves the service type and namespace from it, so it must reference a real discovered service under the same agent + environment.`;
138
+ export const descChaosServiceAgentId = `Service Discovery agent identity (maps to agentId). Required. Use the 'identity' field of the agent chosen from discovered_agent list (STEP 1 of create). The agent's environmentIdentifier becomes environment_id, and for SD Kubernetes agents its identity is also the bare infrastructure_id.`;
139
+ export const descChaosServiceEnvironmentId = `Harness environment identifier the service lives in (maps to environmentId). Required.`;
140
+ export const descChaosServiceInfrastructureId = `Chaos infrastructure identifier (maps to infrastructureId). Required. Pass the BARE infra id (NOT env-prefixed) — for SD-onboarded Kubernetes this equals the Discovery agent's identity. The server stores it as "<environment_id>/<infrastructure_id>", so do not pre-prefix it yourself.`;
141
+ export const descChaosServiceInfrastructureType = `Infrastructure type of the underlying chaos infra. Enum: Kubernetes | KubernetesV2 | Windows | Linux | CloudFoundry | Container. Use KubernetesV2 for Service Discovery agents (the modern SD-based Kubernetes chaos infrastructure) — this is the value for services onboarded via the discovered_agent/discovered_service flow. Optional; values are the canonical PascalCase forms.`;
142
+ export const descChaosServiceOnboardingId = `Optional onboarding batch identifier — set when this service is created as part of a multi-service onboarding flow so downstream tools can correlate the batch.`;
143
+ export const descChaosServiceProbes = `Optional probe associations. Array of { probeId, inputs? }. probeId is the identity of an existing chaos probe (from chaos_probe list, filter infra_type='KubernetesV2'). inputs is the probe's own inputs[] array (read it from the chaos_probe list item or harness_get chaos_probe): pass each input object back intact (name, path, category, type, reference, required, ...) but REPLACE its placeholder value "<+input>" with the user-supplied value. Values for inputs marked required=true are mandatory. Do not fabricate or drop input objects — the backend uses each input's path/category to place the value.`;
144
+ export const descChaosServiceProbesUpdate = `Required — full replacement of every probe association (desired-state reconcile; any existing probe omitted here is removed). Array of { probeId, inputs? }, same shape as create's 'probes'. To keep current probes, call harness_get first and resupply its 'probes' array unchanged; pass [] to explicitly detach all probes.`;
145
+ export const descUpdateChaosService = `Update an existing chaos service, identified by the path 'identity' (identity is not renameable — do not put it in the body). This is a FULL REPLACE of the mutable fields (name, description, tags, external_service_id, agent_id, environment_id, infrastructure_id), not a partial patch: description and tags are overwritten with exactly what you send, including being cleared to empty if you omit them — there is no "leave unchanged" behavior for these two fields. To preserve the current description/tags, fetch the service first (harness_get) and re-supply its current values. Probes is a REQUIRED desired-state array: it is reconciled atomically with the field update, and any probe present on the service but not in the array is removed. To keep the current probes, harness_get first and resupply the 'probes' array unchanged; pass [] to intentionally detach every probe. Returns the full ChaosServiceResponse on 200.`;
146
+ export const descBodyChaosServiceUpdate = `Chaos service update body. Required: name, external_service_id, agent_id, environment_id, infrastructure_id (the last four are re-validated server-side via a Service Discovery lookup), and probes (a full desired-state array — see below). Optional (schema-wise): description, tags — but "optional" only means the request validates without them. Omitting description or tags CLEARS them server-side (full replace, no partial patch); re-supply the current value from a prior harness_get if you want it preserved. probes is required precisely because it is a full replace: supply the complete desired set (harness_get first to keep existing ones), or [] to detach all probes. Do NOT include 'identity' — pass it at the top level so it goes on the URL.`;
97
147
  export const descChaosLoadtest = `Load test (Resilience Testing) instance. Supports list, get, create, delete; run/stop via execute actions.
98
- Locust is supported on Linux VM and Kubernetes (script + image modes). K6 is supported on Kubernetes only (script + image modes; UI mode deferred) — LinuxVM K6 is not supported. JMeter is coming soon.
99
- To create one, first pick a load-runner infrastructure: for Linux VM use chaos_infrastructure (loadEnabled infras), for Kubernetes use chaos_enabled_infrastructure. Then pass its environment_id + infra_id with target_url and the Python (locust) script.
148
+ Locust is supported on Linux VM and Kubernetes (script + image modes). K6 is genuinely Kubernetes-only at the backend. JMeter is restricted to Kubernetes by MCP for parity with the Harness UI (the backend itself does not enforce this for JMeter) — Linux VM supports Locust only; MCP rejects K6/JMeter with a Linux target_type at create time. JMeter accepts script/script_image scalars like Locust/K6 (plus properties/env_vars/thresholds/worker_count); 'tool_config' remains available as an advanced escape hatch (e.g. .zip bundles).
149
+ To create one, follow the prerequisite chain declared in relatedResources: (1) pick a load-runner infra via chaos_infrastructure (Linux VM) or chaos_enabled_infrastructure (Kubernetes), then (2) list chaos_service with infrastructure_ids='<environment_id>/<infra_id>' and pick one — or create a chaos_service if the list is empty — feeding its identity into service_references. When picking a Kubernetes infra, remember to set target_type='kubernetes' explicitly; it is not derived from the infra you chose.
100
150
 
101
151
  Response / read schema (what list/get returns — agents NEVER need to construct these on create; MCP builds them automatically):
102
- - inputs: array of {name, value, type ("Integer" | "String"), required?: true}. Well-known names the backend recognises:
103
- targetUsers (Integer, required), durationSeconds (Integer), rampUpTimeSec (Integer),
104
- workerCount (Integer; Kubernetes only), targetUrl (String),
105
- scriptImage (String, required in image mode), scriptEntrypoint (String, required in image mode),
106
- loadArgs (String; image mode only).
107
- - variables: array of {name, value, type ("String" | "Number")} — user-defined non-secret env vars (currently always empty; custom variables are a deferred feature).
152
+ - toolConfig.<tool> (canonical, per loadTestManager LocustSpec / K6Spec / JMeterSpec):
153
+ mode: "script" | "image"
154
+ script: { content? (base64), image?, entrypoint?, loadArgs?, imagePullSecret? }
155
+ tunables: { targetUrl?, targetUsers?, spawnRate? (Locust), rampUpTimeSec? (Locust), durationSeconds? (Locust/K6), workerCount?, hostUrl? (K6), iterations? (K6), rpsLimit? (K6) }
156
+ envVars?: [{key, value, secret?}] (K6/JMeter)
157
+ variables?: template.VariableList (currently always empty; custom variables are a deferred feature)
158
+ properties?: [{key, value, sendToEngines?}] (JMeter)
159
+ thresholds?: [{metric, stat?, operator, value, abortOnFail?}] (JMeter)
108
160
  - yaml: base64-encoded LoadTest manifest. Decoded shape:
109
161
  kind: LoadTest
110
162
  apiVersion: v1alpha1
111
- name: <name>
112
- description: <optional>
113
- tags: [<optional>]
163
+ metadata:
164
+ name: <name>
165
+ description?: <string>
166
+ tags?: [<string>]
167
+ serviceReferences?: [<chaosService identity>]
114
168
  spec:
115
169
  identity: <slug>
116
- toolType: Locust
170
+ toolType: Locust | K6 | JMeter
117
171
  infraType: kubernetes | linux # derived from targetType
118
172
  targetType: kubernetes | machine-chaos-linux | linux-chaos
119
- scriptSource: inline | image
120
- scriptContent: <PLAIN TEXT inside YAML; inline mode only>
173
+ cleanupPolicy: delete | retain
121
174
  infraId: <id>
122
175
  envId: <id>
123
- inputs: [<same shape as response.inputs>]
124
- - scriptContent: base64-encoded Python script (inline mode). Large dropped from list responses.
125
- - Derived convenience scalars (read-side projection of inputs[] by MCP, matching the create-side scalars):
126
- target_url, users, duration_sec, ramp_up_sec, worker_count, script_image, script_entrypoint, load_args.`;
127
- export const descChaosK8sInfrastructure = `Kubernetes chaos infrastructure available for running experiments.
176
+ toolConfig: { <tool>: { mode, script, tunables, ... } }
177
+ - Derived convenience scalars (read-side projection of toolConfig.<tool> by MCP, matching the create-side scalars):
178
+ target_url, users, duration_sec, ramp_up_sec, worker_count, spawn_rate, host_url, iterations, rps_limit,
179
+ script_image, script_entrypoint, load_args, image_pull_secret.`;
180
+ export const descChaosK8sInfrastructure = `Kubernetes chaos infrastructure (chaos server) available for running experiments.
128
181
  Use chaos_environment list first to get an environmentId, then pass it here to filter infrastructures for that environment.
129
182
  Returns infrastructure details including identity, infraID, name, environmentID, status, infraType, infraScope, and isChaosEnabled.
130
183
  The infraID is used as the infra_ref parameter in create_from_template.
131
184
  IMPORTANT: Only infrastructures with status=ACTIVE AND isChaosEnabled=true can be used to create chaos experiments. Always check both fields before selecting an infrastructure.
185
+ Supports list, get, create, plus check_health execute action.
132
186
  Supports filtering by status (ACTIVE, INACTIVE, PENDING), search, and optional inclusion of legacy V1 infrastructures.
133
187
  To list ONLY infrastructures that are ready to run experiments (chaos-enabled AND ACTIVE) with correct totals/pagination, use chaos_enabled_infrastructure instead.`;
188
+ export const descCreateK8sInfra = `Register a new Kubernetes chaos infrastructure (chaos server) in the given Harness project.
189
+
190
+ This registers the chaos agent/delegate metadata in Harness and returns an access token plus Helm/manifest instructions for installing the chaos server in your cluster.
191
+
192
+ GUIDED workflow — resolve each step before calling harness_create:
193
+
194
+ STEP 1 — Select environment (REQUIRED):
195
+ harness_list resource_type=chaos_environment (or resource_type=environment). Ask the user to pick one.
196
+ Capture environment_id = chosen environment identifier.
197
+
198
+ STEP 2 — Select backing CD infrastructure (REQUIRED for standard onboarding):
199
+ harness_list resource_type=infrastructure with environment_id=<env from Step 1>.
200
+ Ask the user which Kubernetes infrastructure definition to attach. Capture infra_id = infrastructure identifier.
201
+ The backend validates this Harness CD infra exists in the selected environment.
202
+
203
+ STEP 3 — Configure chaos server metadata (REQUIRED):
204
+ Ask for name and identity (slug). identity must be unique in scope; infra_id defaults to identity when omitted.
205
+ Defaults (override only if user asks): infra_namespace=hce, service_account=litmus, infra_scope=CLUSTER, infra_type=KUBERNETES.
206
+
207
+ STEP 4 — Create:
208
+ harness_create(resource_type='chaos_k8s_infrastructure', org_id, project_id, body={ identity, name, environment_id, infra_id?, k8s_connector_id?, infra_namespace?, service_account?, infra_scope?, infra_type?, description?, tags?, ai_enabled? })
209
+
210
+ Returns identity, name, token (for agent install), and uniqueId. After create, apply the returned manifest/Helm chart in the target cluster and wait for status=ACTIVE + isChaosEnabled=true before running experiments.`;
211
+ export const descBodyK8sInfraCreate = `Chaos K8s infrastructure (chaos server) registration body. Required: identity, name, environment_id. infra_id defaults to identity when omitted.`;
212
+ export const descK8sInfraIdentityCreate = `Unique slug for the chaos infrastructure. Also used as infraID when infra_id is omitted. Must be unique within the project scope.`;
213
+ export const descK8sInfraNameCreate = `Human-readable display name for the chaos server.`;
214
+ export const descK8sInfraEnvironmentIdCreate = `Harness environment identifier where the chaos server will run. Use chaos_environment or environment list to find valid IDs.`;
215
+ export const descK8sInfraInfraIdCreate = `Harness CD infrastructure definition identifier in the selected environment. Defaults to identity when omitted. The backend verifies this infra exists.`;
216
+ export const descK8sInfraConnectorIdCreate = `Optional Kubernetes connector identifier when required by your onboarding flow.`;
217
+ export const descK8sInfraNamespaceCreate = `Kubernetes namespace where chaos components are installed. Default: hce.`;
218
+ export const descK8sInfraServiceAccountCreate = `Kubernetes service account for the chaos server. Default: litmus.`;
219
+ export const descK8sInfraScopeCreate = `Installation scope. CLUSTER (default) or NAMESPACE.`;
220
+ export const descK8sInfraTypeCreate = `Infrastructure type. KUBERNETES (default) or KUBERNETESV2.`;
221
+ export const descK8sInfraAiEnabledCreate = `Enable AI recommendations for this infrastructure. Default: false.`;
134
222
  export const descChaosHub = `ChaosHub — a Git-backed repository that provides version-controlled chaos fault, experiment, probe, and action templates.
135
223
  Every project includes a default Enterprise ChaosHub with pre-built templates; custom hubs can be created to bring in organization-specific chaos artifacts.
136
224
  Supports list, get, create, update, and delete.
@@ -357,112 +445,306 @@ Requires hub_identity to identify which chaos hub owns the template.
357
445
  Returns a success confirmation on completion.`;
358
446
  export const descListExperimentVariables = `List variables for a chaos experiment (experiment-level and task-level). By default treats experiment_id as a human-readable identity slug (e.g. "exp-without-runtime"); pass is_identity=false to use the internal UUID instead.`;
359
447
  export const descListLinuxInfra = `List chaos Linux infrastructures (load runners)`;
360
- export const descListLoadtests = `List load test instances`;
448
+ export const descListLoadtests = `List load test instances.
449
+
450
+ Pagination: use the top-level harness_list 'page' / 'size' arguments (0-indexed page, size 1-100) -- NOT 'limit'; a top-level 'limit' argument is silently dropped and the default size (20) applies instead.
451
+ Filters (tool_type, environment_id, tags, sort_field, sort_ascending, search) must be passed inside harness_list's 'filters' object, e.g. filters: { tool_type: "Locust" }.
452
+ KNOWN LIMITATION: 'search' matches only the load test's display name (case-insensitive substring) -- it does NOT match identity/slug, and is not fuzzy or prefix-tolerant. Use an exact or partial name substring, not an identity-style term.
453
+ compact:true (the harness_list default) strips toolType/targetType/scriptSource/toolConfig/recentRuns from each item to keep responses lean -- pass compact:false, or call harness_get on a specific loadtest_id, for full tool config and run history.`;
361
454
  export const descGetLoadtest = `Get load test instance details`;
455
+ // Shared, tool-agnostic map of scalar body fields -> toolConfig.<tool>.* wire paths.
456
+ // Referenced verbatim by both descCreateLoadtest (Step 6 taxonomy) and
457
+ // descUpdateLoadtest (scalar-edit path 1). The mapping is identical on create
458
+ // and update -- callers pass the same scalars either way; MCP builds
459
+ // toolConfig.<tool> from them via buildLocustToolConfig / buildK6ToolConfig /
460
+ // buildJMeterToolConfig.
461
+ export const descLoadtestFieldTaxonomy = ` (a) Script file / Custom image -> script | script_image (+script_entrypoint, load_args, image_pull_secret) -> toolConfig.<tool>.script.{content|image|entrypoint|loadArgs|imagePullSecret}. ALL tools; script_image is the only mandatory field in Custom Image mode; script_entrypoint, load_args and image_pull_secret are optional (image_pull_secret only for Private registries; the backend falls back to a tool-specific default entrypoint when omitted).
462
+ (b) Host / Target URL -> target_url (fixed|<+input>) -> toolConfig.<tool>.tunables.targetUrl. LOCUST + K6 ONLY (not exposed for JMeter -- see JMeter step notes), optional. K6 additionally derives tunables.hostUrl from the URL's origin (feeds the script as __ENV.HOST_URL); leave blank if the script/locustfile declares its own host.
463
+ (c) Users -> users (fixed|<+input>) -> toolConfig.<tool>.tunables.targetUsers. LOCUST + K6 ONLY (not exposed for JMeter -- see JMeter step notes).
464
+ (d) Duration (seconds) -> duration_sec (fixed|<+input>) -> toolConfig.<tool>.tunables.durationSeconds. LOCUST + K6 ONLY (not exposed for JMeter -- see JMeter step notes).
465
+ (e) Ramp Up Duration (seconds) -> ramp_up_sec (fixed|<+input>) -> toolConfig.<tool>.tunables.rampUpTimeSec. LOCUST ONLY (no Ramp Up field in the K6 UI form or for JMeter). Must not exceed Duration when both are fixed values.
466
+ (f) Iterations (cap) -> iterations (fixed|<+input>) -> toolConfig.k6.tunables.iterations. K6 ONLY. Duration takes precedence when both are set.
467
+ (g) RPS Limit -> rps_limit (fixed|<+input>) -> toolConfig.k6.tunables.rpsLimit. K6 ONLY. Global cap, split evenly across replicas; always applies (unlike (c)/(d)/(f), which k6 ignores once the script declares its own scenarios/stages).
468
+ (h) Worker Count / Replicas -> worker_count (fixed|<+input>) -> toolConfig.<tool>.tunables.workerCount. LOCUST + K6 + JMETER, Kubernetes only (VM always runs a single worker/pod -- skip entirely for target_type='machine-chaos-linux'). Locust: Users are DISTRIBUTED across workers, not multiplied. K6: <=1 = single pod; higher values fan out to multiple runner pods via --execution-segment. JMeter: workers MULTIPLY load -- each injector runs the FULL uploaded plan unchanged; thread counts inside the file are NOT sliced across workers.
469
+ (i) Environment Variables -> env_vars -> toolConfig.<tool>.envVars[]. K6 + JMETER. Structured array; each entry is a literal {key, value}, a runtime {key, value: "<+input>"}, or a secret reference {key, secret_id, secret_scope?}. See the env_vars field's own description (descLoadtestEnvVars) for the full validator, reserved-name list, and secret_scope discovery flow (harness_list resource_type=secret type=SecretText) -- not repeated here. K6 additionally rejects HOST_URL/K6_VUS/K6_DURATION/K6_ITERATIONS/K6_STAGES/K6_RPS (tool-specific reserved names on top of the shared list).
470
+ (j) Property Overrides -> properties -> toolConfig.jmeter.properties[]. JMETER ONLY. Array of {key, value, send_to_engines?}; injects/overrides JMeter properties at run time without editing the uploaded plan -- reference as \${__P(NAME)} inside the plan. send_to_engines (-G) controls whether the property also reaches remote workers in Distributed Execution; omit for controller-only properties.
471
+ (k) Thresholds -> thresholds -> toolConfig.jmeter.thresholds[]. JMETER ONLY. Array of {metric, stat?, operator, value, abort_on_fail?}; pass/fail criteria evaluated against the run's .jtl results AFTER the test (the uploaded plan's own Assertions still apply per-request during the run). metric one of response_time_ms|error_rate_pct|throughput_rps|latency_ms; operator one of < <= > >= == !=; stat (e.g. p95/p99/avg/median/max) is REQUIRED when metric is response_time_ms or latency_ms.`;
362
472
  export const descCreateLoadtest = `Creates a Locust load test (Resilience Testing) on a Linux VM or Kubernetes load-runner infrastructure.
363
-
364
473
  IMPORTANT: You MUST NOT auto-select, assume, or pre-fill any value on behalf of the user.
365
474
  At every step below, present the options / ask for the value and wait for explicit user confirmation before proceeding.
366
475
  Do NOT assume org/project/infrastructure/script/URL based on previous activity — the user may choose differently each time.
367
- Locust (Python) and K6 (JavaScript) are supported. JMeter is coming soon. K6 requires Kubernetes (LinuxVM K6 is not supported). For K6, "Upload K6 script" and "Custom Image" modes are supported via MCP; "Define test via UI" is deferred.
476
+ Locust (Python), K6 (JavaScript), and JMeter (Java) are supported. K6 and JMeter are Kubernetes-only; Linux VM supports Locust only. All three tools support "Upload script" and "Using Custom Image" via scalar inputs (script, script_image, script_entrypoint, load_args, image_pull_secret) plus tool-specific extras (properties/thresholds for JMeter; env_vars for K6/JMeter). The 'tool_config' body field remains available as an advanced escape hatch (e.g. JMeter .zip bundles pre-base64'd into tool_config.jmeter.script.content). For K6, "Define test via UI" is deferred.
477
+ Required workflow -- follow in order, pausing for user input after each step.
478
+ COMMON STEPS (identical for Locust, K6, and JMeter):
479
+ Each step below shows the slice of the canonical stored LoadTest YAML that its inputs populate (what the user sees in the Harness UI YAML view). You NEVER hand-write this YAML -- MCP synthesizes it from the scalars/objects you pass to harness_create; the slices exist only so you can reason about which of your inputs affects which YAML path and echo an accurate preview back to the user.
480
+ Two paths of the stored YAML are set from top-level body fields rather than the MCP-built manifest itself and are merged by the backend: metadata.serviceReferences (from body.service_references) and spec.cleanupPolicy (from body.cleanup_policy). All other YAML paths below correspond 1:1 to what MCP writes into body.yaml.
481
+
482
+ Step 1 -- Name / description / tags:
483
+ Ask for a name (any non-empty string) -> name. identity is auto-derived from name by stripping non-alphanumerics unless the user supplies one (letters / numbers / underscores).
484
+ description and tags are optional -- ask, but do not require them.
485
+ YAML populated after this step:
486
+ kind: LoadTest
487
+ apiVersion: v1alpha1
488
+ metadata:
489
+ name: <name>
490
+ description: <description> # omitted when empty
491
+ tags: # omitted when empty
492
+ - <tag>
493
+ spec:
494
+ identity: <identity> # auto-derived slug from name unless user supplies one
368
495
 
369
- Required workflow follow in order, pausing for user input after each step:
496
+ Step 2 -- Execution environment target type:
497
+ Ask "Linux VM" (target_type=machine-chaos-linux) or "Kubernetes" (target_type=kubernetes). Wait for the user to choose.
498
+ Linux VM supports Locust only. Kubernetes supports Locust, K6, or JMeter.
499
+ YAML populated after this step (delta only):
500
+ spec:
501
+ targetType: kubernetes | machine-chaos-linux
502
+ infraType: kubernetes | linux # derived from targetType, not user-supplied
503
+
504
+ Step 3 -- Load-runner infrastructure (see chaos_loadtest.relatedResources for canonical guidance):
505
+ Linux VM -> harness_list resource_type=chaos_infrastructure (loadEnabled + ACTIVE only).
506
+ Kubernetes -> harness_list resource_type=chaos_enabled_infrastructure (already ACTIVE + chaos-enabled).
507
+ From the chosen row: environmentID -> environment_id, infraID -> infra_id. Do NOT choose an infra yourself.
508
+ If NO eligible infra is returned (empty list, or every row fails the eligibility filter above), STOP and tell the user no eligible load-runner infra exists in the current scope. Ask them to (a) switch org/project scope, (b) pick a different target type at Step 2 (Linux VM <-> Kubernetes), or (c) exit/stop. Do NOT fall back to an ineligible infra and do NOT proceed to Step 4 -- a load test cannot be created without an eligible infra.
509
+ YAML populated after this step (delta only):
510
+ spec:
511
+ infraId: <infraID from the chosen infra row>
512
+ envId: <environmentID from the chosen infra row>
513
+
514
+ Step 4 -- Resilience Testing Services (chaos_service):
515
+ Call harness_list resource_type=chaos_service with infrastructure_ids='<environment_id>/<infra_id>'. Show the list to the user.
516
+ The user may (a) pick one or more identities from the list, OR (b) onboard a brand-new service via chaos_service's create flow -- offer BOTH options regardless of whether the list is empty. Pass the chosen identity/identities as service_references.
517
+ YAML populated after this step (delta only):
518
+ metadata:
519
+ serviceReferences: # merged in by backend from the top-level service_references body field
520
+ - <chaos_service identity>
521
+ - ...
522
+ Passed on the wire as body.service_references (snake_case scalar), NOT as part of the yaml manifest MCP builds -- the backend writes it into the stored YAML at metadata.serviceReferences.
523
+
524
+ Step 5 -- Tool type:
525
+ Linux VM: Locust only. Kubernetes: ask Locust / K6 / JMeter.
526
+ Pass tool_type='Locust' | 'K6' | 'JMeter'. K6 and JMeter with a non-Kubernetes target_type will be rejected by MCP.
527
+ YAML populated after this step (delta only):
528
+ spec:
529
+ toolType: Locust | K6 | JMeter
530
+ cleanupPolicy: delete # default; see Step 7a to override or add spec.resources (Step 7b)
531
+ toolConfig:
532
+ <lowercase(toolType)>: # e.g. locust / k6 / jmeter
533
+ mode: script | image # tool_config detail filled in Step 6
534
+ script:
535
+ content: "" # base64 on the wire, decoded plain-text in the YAML view; filled in Step 6
536
+ tunables: # filled in Step 8 (load configuration)
537
+ targetUsers: <int>
538
+ durationSeconds: <int>
539
+ rampUpTimeSec: <int>
540
+ workerCount: <int> # Kubernetes only
541
+ Notes:
542
+ - All three tools (Locust / K6 / JMeter) populate toolConfig.<tool> from Step-6 scalars -- no hand-built tool_config needed for standard flows. JMeter's tool_config remains available only as an advanced escape hatch (e.g. pre-base64'd .zip bundles).
543
+ - JMeter has no target_url / users / duration / ramp_up scalar surface -- load shape is baked into the uploaded plan (script mode) or the container image (custom image mode). Server defaults 100/600/30 for missing tunables on save.
544
+
545
+ Step 6 -- Tool-specific configuration (branch on tool_type from Step 5):
546
+ Every tool type supports two authoring modes:
547
+ (a) Upload script (script_source='inline', mode='script') -- paste the raw script; MCP base64-encodes it into toolConfig.<tool>.script.content.
548
+ (b) Using Custom Image (script_source='image', mode='image') -- point at a prebuilt container image; no script is uploaded.
549
+ Mode lives at toolConfig.<tool>.mode; script_source on the wire body mirrors it ('inline' <-> 'script', 'image' <-> 'image').
550
+ Fields marked (*) in a branch below are MANDATORY for that branch -- the UI (and MCP validation) reject creation with any '*' field empty. Unmarked fields are OPTIONAL.
551
+ Fixed vs runtime input: fields noted "(fixed|<+input>)" below accept EITHER a concrete value NOW, OR the literal string '<+input>' meaning "prompt for this value at run time". Pass the placeholder verbatim -- MCP forwards it as-is into the tunables and into the base64 YAML manifest.
552
+ Shared field taxonomy -- each branch below references these by letter instead of restating them:
553
+ ${descLoadtestFieldTaxonomy}
554
+
555
+ Step 6.LocustScript -- Locust + Upload Python script (mode='script'):
556
+
557
+ Ask, in order: (*)(a) Script file, (b) Host URL, (*)(c) Users, (*)(d) Duration, (*)(e) Ramp Up, (h) Worker Count [Kubernetes only].
558
+ YAML populated after this step (delta only):
559
+ spec:
560
+ toolConfig:
561
+ locust:
562
+ mode: script
563
+ script:
564
+ content: | # plain text in YAML view; base64 on wire
565
+ <the Python locust script>
566
+ tunables: # omit keys the user left blank
567
+ targetUrl: http://www.example.com
568
+ targetUsers: 100
569
+ durationSeconds: 600
570
+ rampUpTimeSec: 120
571
+ workerCount: 1 # Kubernetes only
572
+
573
+ Step 6.LocustImage -- Locust + Using Custom Image (mode='image'):
574
+ Kubernetes only (script_source=image, or inferred when script_image is set). VM is always inline.
575
+ Ask, in order:
576
+ (b) Host URL -> target_url (optional; Locust --host; leave blank if the locustfile sets host in code)
577
+ (a) Load Test Image* -> script_image (required; container image, e.g. my-registry/my-load-test:latest)
578
+ (a) Entrypoint -> script_entrypoint (optional; path inside the image, e.g. /scripts/locustfile.py; passed to locust -f; backend falls back to a default when omitted)
579
+ (a) Load args -> load_args (optional; semicolon-separated k=v pairs, e.g. "tags=smoke,fast;headless=true" -- NOT bare CLI flags)
580
+ (a) Image Registry Type -> Public: omit image_pull_secret
581
+ Private: image_pull_secret (name of the Kubernetes image-pull secret)
582
+ (c) Users* -> users
583
+ (d) Duration* -> duration_sec
584
+ (e) Ramp Up* -> ramp_up_sec
585
+ (h) Worker Count -> worker_count (K8s; 0/omit = standalone)
586
+ YAML populated after this step (delta only):
587
+ spec:
588
+ toolConfig:
589
+ locust:
590
+ mode: image
591
+ script:
592
+ image: my-registry/my-load-test:latest
593
+ entrypoint: /scripts/locustfile.py
594
+ loadArgs: tags=smoke # omit when blank
595
+ imagePullSecret: my-pull-secret # omit when Public
596
+ tunables: # omit keys the user left blank
597
+ targetUrl: https://www.google.com
598
+ targetUsers: 100
599
+ durationSeconds: 600
600
+ rampUpTimeSec: 120
601
+ workerCount: 1
602
+
603
+ Step 6.K6Script -- K6 + Upload K6 script (mode='script'):
604
+ Kubernetes-only: MCP rejects K6 with target_type='machine-chaos-linux' at create time.
605
+ Ask, in order: (*)(a) Script file [MANDATORY: script MUST contain 'export default function ...' -- reject before calling MCP if missing, matching Harness UI validateScriptContent], (b) Host URL, (c) Users, (d) Duration, (f) Iterations, (g) RPS Limit, (i) Environment Variables, (h) Replicas.
606
+ Load-profile semantics reminder: (c)/(d)/(f) are ignored by k6 once the script declares its own scenarios/stages -- leave them blank for scripted scenarios. (g) RPS Limit always applies.
607
+ YAML populated after this step (delta only):
608
+ spec:
609
+ toolConfig:
610
+ k6:
611
+ mode: script
612
+ script:
613
+ content: | # plain text in YAML view; base64 on wire
614
+ <the K6 JavaScript source>
615
+ tunables: # omit keys the user left blank
616
+ targetUrl: https://www.examplek6.com
617
+ targetUsers: <+input>
618
+ durationSeconds: <+input>
619
+ iterations: <+input>
620
+ workerCount: 1
621
+ rpsLimit: 100
622
+ envVars: # omit entirely when no env vars are set
623
+ - { key: env_var_1, value: some_val_1, secret: false }
624
+ - { key: env_var_2, value: <+input>, secret: false }
625
+ - { key: somekey, value: secrets.getValue("account.gcp-ca-cert"), secret: true } # scope prefix per env_vars docs: account./org./none for project
626
+
627
+ Step 6.K6Image -- K6 + Using Custom Image (mode='image'):
628
+ Kubernetes only (script_source=image, or inferred when script_image is set).
629
+ Ask, in order:
630
+ (b) Host URL -> target_url (optional; MCP derives toolConfig.k6.tunables.hostUrl from its origin)
631
+ (a) Load Test Image* -> script_image (required; container image, e.g. my-registry/my-load-test:latest)
632
+ (a) Entrypoint -> script_entrypoint (optional; path inside the image, e.g. /script.js; backend falls back to a default when omitted)
633
+ (a) Load args -> load_args (optional; semicolon-separated k=v pairs, e.g. "tags=smoke,fast;headless=true" -- NOT bare CLI flags)
634
+ (a) Image Registry Type -> Public: omit image_pull_secret
635
+ Private: image_pull_secret (name of the Kubernetes image-pull secret)
636
+ (h) Worker Count -> worker_count (K8s; 0/omit = standalone)
637
+ (g) RPS Limit -> rps_limit (optional; toolConfig.k6.tunables.rpsLimit when > 0)
638
+ (i) Environment Variables -> env_vars (see Step 4c)
639
+ Note: Users/Duration/Iterations tunables are meaningless for image mode -- the bundled script controls its own scenarios/stages. Do not ask for them here.
640
+ YAML populated after this step (delta only):
641
+ spec:
642
+ toolConfig:
643
+ k6:
644
+ mode: image
645
+ script:
646
+ image: my-registry/my-load-test:latest # or <+input>
647
+ entrypoint: /script.js # or <+input>
648
+ loadArgs: tags=smoke # omit when blank
649
+ imagePullSecret: my-pull-secret # omit when Public
650
+ tunables:
651
+ workerCount: 1
652
+ rpsLimit: 100 # omit when rps_limit is unset
653
+ envVars: # omit entirely when no env vars are set
654
+ - { key: someKey, value: someValue, secret: false }
655
+
656
+ Step 6.JMeterScript -- JMeter + Upload JMX / XML / ZIP (mode='script'):
657
+ Kubernetes-only: MCP rejects JMeter with target_type='machine-chaos-linux' at create time.
658
+ Ask, in order: (*)(a) Test Plan file [.jmx/.xml -- raw plan text, verbatim; do NOT parse, modify, or pre-encode. A .zip bundle (plan + CSV data/dependencies) is also accepted but must be supplied pre-base64'd via tool_config.jmeter.script.content instead of the scalar 'script' field], (j) Property Overrides, (i) Environment Variables, (k) Thresholds, (h) Workers.
659
+ Notes: (b)/(c)/(d)/(e)/(f)/(g) do not apply to JMeter -- thread count, ramp-up, and duration live inside the uploaded plan's ThreadGroup elements, not as separate MCP inputs. If the user wants to change load shape, tell them to edit the .jmx and re-upload (or use tool_config.jmeter.tunables.{targetUsers,durationSeconds,rampUpTimeSec} as an advanced override -- backend-accepted but not exposed as a scalar here since the Harness UI does not expose it either).
660
+ YAML populated after this step (delta only):
661
+ spec:
662
+ toolConfig:
663
+ jmeter:
664
+ mode: script
665
+ script:
666
+ content: | # plain text in YAML view; base64 on wire
667
+ <the .jmx/.xml plan>
668
+ tunables:
669
+ workerCount: 1 # Kubernetes only
670
+ properties: # omit entirely when no property overrides are set
671
+ - { key: someProperty, value: someValue, sendToEngines: true }
672
+ envVars: # omit entirely when no env vars are set
673
+ - { key: secretKeyAtProject, value: secrets.getValue("datat-api"), secret: true }
674
+ - { key: fixedKey, value: fixedValue }
675
+ - { key: RuntimeKey, value: <+input> }
676
+ thresholds: # omit entirely when no thresholds are set
677
+ - { metric: response_time_ms, stat: p95, operator: <, value: 5000, abortOnFail: false }
678
+
679
+ Step 6.JMeterImage -- JMeter + Using Custom Image (mode='image'):
680
+ Kubernetes-only (script_source=image, or inferred when script_image is set). Custom Image does NOT slice load -- each worker runs the full command independently, so total load ~= plan threads * injectors. Users/Duration/Ramp-up are baked into the image's plan and are NOT asked here (server defaults 100/600/30 when omitted; do not surface them).
681
+ Ask, in order:
682
+ (a) Load Test Image* -> script_image (required; cluster-reachable container image, e.g. my-registry/jmeter:5.6; may be '<+input>')
683
+ (a) Entrypoint -> script_entrypoint (optional; path to the .jmx/.xml test plan inside the image, e.g. /test/plan.jmx; may be '<+input>'; backend falls back to a default plan-file name when omitted)
684
+ (a) Load args -> load_args (optional; JMeter GNU-style long options as semicolon-separated key=value pairs OR bare flags, e.g. "proxyHost=10.0.0.1;forceDeleteResultFile". Each becomes --key=value. Do NOT put JMeter properties (jmeter.save.saveservice.* or \${__P(...)} vars) here -- those go in Property Overrides / -J / -G. Keys must be non-empty, contain no whitespace, and must NOT start with '-')
685
+ (a) Image Registry Type -> Public: omit image_pull_secret
686
+ Private: image_pull_secret (name of the Kubernetes image-pull secret)
687
+ (j) Property Overrides -> properties (optional; array of {key, value, send_to_engines?}. send_to_engines=true adds -G so the property reaches remote workers in Distributed Execution)
688
+ (i) Environment Variables -> env_vars (optional; fixed / '<+input>' runtime / secret via {secret_id, secret_scope?}. See descLoadtestEnvVars for the full reserved-name list and secret discovery flow)
689
+ (k) Thresholds -> thresholds (optional; array of {metric, stat?, operator, value, abort_on_fail?}. metric one of response_time_ms|error_rate_pct|throughput_rps|latency_ms; operator one of < <= > >= == !=; stat REQUIRED for response_time_ms / latency_ms. value is a number -- runtime '<+input>' is NOT supported for thresholds)
690
+ (h) Workers -> worker_count (optional; number of load injectors. Each injector runs the FULL plan unchanged -- workers MULTIPLY load. 0/omit = single standalone injector)
691
+ YAML populated after this step (delta only):
692
+ spec:
693
+ toolConfig:
694
+ jmeter:
695
+ mode: image
696
+ script:
697
+ image: my-registry/jmeter:5.6 # or <+input>
698
+ entrypoint: /test/plan.jmx # or <+input>
699
+ loadArgs: proxyHost=10.0.0.1 # omit when blank
700
+ imagePullSecret: my-pull-secret # omit when Public
701
+ tunables:
702
+ workerCount: 1 # omit when 0/unset
703
+ properties: # omit entirely when no property overrides are set
704
+ - { key: threads1, value: "100", sendToEngines: true }
705
+ - { key: someProperty, value: "200", sendToEngines: false }
706
+ envVars: # omit entirely when no env vars are set
707
+ - { key: somekey, value: secrets.getValue("datat-api"), secret: true }
708
+ - { key: runtime, value: <+input> }
709
+ - { key: fixed, value: somefixed }
710
+ thresholds: # omit entirely when no thresholds are set
711
+ - { metric: response_time_ms, stat: p95, operator: <, value: 5000, abortOnFail: false }
712
+ - { metric: error_rate_pct, stat: avg, operator: <=, value: 50, abortOnFail: true }
713
+ - { metric: throughput_rps, stat: p99, operator: '>', value: 5000, abortOnFail: false }
714
+
715
+ Step 7 -- Advanced Options (all tool types; identical for Locust/K6/JMeter):
716
+
717
+ Step 7a -- Cleanup Policy:
718
+ Ask whether to keep the default -> cleanup_policy='delete' (removes run pods/configmaps/secrets after each execution), or retain them for debugging -> cleanup_policy='retain'. Maps to spec.cleanupPolicy. Mirrors the UI's Advanced Options "Clean-up Load Resources" toggle (ON = delete, OFF = retain).
719
+
720
+ Step 7b -- Resource Requirements (Kubernetes only; OFF by default -- skip unless the user opts in):
721
+ If the user wants to set CPU/memory limits and requests, ask for any of: limits.cpu, limits.memory, requests.cpu, requests.memory (all optional individually). Pass resources={ limits?: {cpu?, memory?}, requests?: {cpu?, memory?} }. cpu is cores/millicores (e.g. "100m", "0.5", "2"); memory is a whole-byte quantity (e.g. "128Mi", "1Gi", "134217728"). Per resource, requests must not exceed limits. Maps to spec.resources. If the user declines, omit 'resources' entirely.
722
+ YAML populated after Step 7 (delta only):
723
+ spec:
724
+ cleanupPolicy: retain # 'delete' when omitted/unset
725
+ resources: # omitted entirely when the user declines Step 7b
726
+ limits:
727
+ cpu: "0.5"
728
+ memory: 1Gi
729
+ requests:
730
+ cpu: 100m
731
+ memory: 128Mi
732
+ Only after the user confirms all of the above (across Steps 1-7) should you call harness_create resource_type=chaos_loadtest. The full input-field reference lives on each field's own description (bodySchema.fields) -- do NOT restate it here.
733
+ Returns the created load test, including the full toolConfig echo (script/tunables/options/envVars as stored), serviceReferences, cleanupPolicy, resources, the base64 canonical yaml, createdAt/updatedAt, and an openInHarness deep link.`;
734
+ export const descDeleteLoadtest = `Delete a load test instance`;
735
+ export const descUpdateLoadtest = `Update fields on an existing load test (PUT -- partial by field, full-replace within a field). Omit a field entirely to leave it unchanged.
370
736
 
371
- Step 1 Select the execution environment target type:
372
- Ask whether the load test runs on "Linux VM" (target_type=machine-chaos-linux) or "Kubernetes" (target_type=kubernetes). Wait for the user to choose.
373
-
374
- Step 1b Select the tool type:
375
- Linux VM only supports Locust. Kubernetes supports Locust or K6 ask which. Pass tool_type='Locust' (default) or 'K6'.
376
- K6 is JavaScript; Locust is Python. K6 currently supports script-mode only (Custom Image and UI are deferred).
377
-
378
- Step 2 — Select a load-runner infrastructure:
379
- Linux VM: call harness_list resource_type=chaos_infrastructure, show the list, wait for the user to pick one. Only infras with loadEnabled=true and status ACTIVE can run load tests.
380
- Kubernetes: call harness_list resource_type=chaos_enabled_infrastructure, show the list, wait for the user to pick one (results are always ACTIVE and chaos-enabled).
381
- From the chosen row take environmentID -> environment_id and infraID -> infra_id. Do NOT choose an infrastructure yourself.
382
-
383
- Step 3 — Collect the target (host) URL:
384
- Ask for the base URL of the application under test (e.g. https://www.google.com) -> target_url. Do NOT assume it.
385
-
386
- Step 4 — Test configuration (branch on tool_type from Step 1b):
387
- Locust (Linux VM or Kubernetes):
388
- (a) Upload Python script (script_source=inline): ask for the raw Python locust script -> script (verbatim; do NOT generate or pre-encode). MCP base64-encodes it into top-level scriptContent.
389
- (b) Custom Image (script_source=image, Kubernetes only): ask for script_image (+ optional script_entrypoint, load_args).
390
- K6 (Kubernetes only):
391
- (a) Upload K6 script (script_source=inline): ask the user to paste the raw K6 JavaScript source. Pass it verbatim in 'script'. MANDATORY rule: the script MUST contain 'export default function ...'. If it does not, REJECT before calling MCP — MCP will throw and no load test will be created. MCP base64-encodes the script into toolConfig.scriptContent; do NOT pre-encode.
392
- (b) Custom Image (script_source=image): ask for the container image -> script_image (e.g. my-registry/my-load-test:latest); optionally an entrypoint inside the image -> script_entrypoint (e.g. /script.js); optional container args -> load_args. MCP routes script_image and script_entrypoint into BOTH toolConfig.customImage (image, entrypoint) AND inputs[] (scriptImage required:true, scriptEntrypoint required:true); load_args rides ONLY in inputs[name=loadArgs]. No script is needed.
393
- (c) Define test via UI: NOT YET SUPPORTED via MCP. Inform the user it is deferred and stop.
394
- Optional K6-only knobs to ask the user about (skip if user says no):
395
- - host_url: origin of the target system (protocol+host, no path). Defaults to the origin of target_url.
396
- - rps_limit: requests-per-second cap (toolConfig.options.rpsLimit). Optional.
397
- - iterations: total iteration cap (toolConfig.iterations). Optional.
398
- - env_vars: see Step 4c.
399
-
400
- Step 4c — Optional K6 environment variables (env_vars):
401
- The env_vars param is a structured array. Each entry sets EITHER a literal value OR a secret reference:
402
- - Literal: {key: "FOO", value: "bar"}
403
- - Secret reference: {key: "TOKEN", secret_id: "<harness-secret-identifier>", secret_scope: "account"|"org"|"project"}
404
- To discover available secrets, call harness_list resource_type=secret type=SecretText. Each list item has
405
- a nested secret.{identifier, orgIdentifier?, projectIdentifier?}. Derive secret_scope from the response:
406
- - no orgIdentifier AND no projectIdentifier → secret_scope: "account"
407
- - orgIdentifier only → secret_scope: "org"
408
- - both orgIdentifier AND projectIdentifier → secret_scope: "project"
409
- MCP builds the wire string 'secrets.getValue("<prefix><id>")' (account.<id> / org.<id> / <id>) and sets
410
- secret: true automatically — do NOT construct the secrets.getValue(...) string yourself.
411
- Disallowed keys (reserved, case-insensitive): RUN_ID, LOAD_TEST_ID, TARGET_USERS, SPAWN_RATE,
412
- SCRIPT_CONTENT_BASE64, TARGET_URL, ACCOUNT_ID, ORG_ID, PROJECT_ID, ENV_ID, DURATION_SECONDS,
413
- CONTROL_PLANE_URL, CONTROL_PLANE_TOKEN, HARNESS_CUSTOM_VAR_NAMES, METRICS_PUSH_INTERVAL, INFRA_ID,
414
- ACCESS_KEY, TENANT_ID, PYTHONPATH, PATH, HOME, USER, SHELL, LANG, TERM, HOSTNAME, PWD,
415
- LD_LIBRARY_PATH, LD_PRELOAD, TMPDIR, TMP, TEMP.
416
- Key pattern: /^[A-Za-z_][A-Za-z0-9_]*$/.
417
-
418
- Step 5 — Load configuration:
419
- Ask users, duration_sec, ramp_up_sec (defaults 100 / 600 / 120 only if user accepts).
420
- K8s: also ask worker_count — do NOT skip (default 0). VM: skip worker_count.
421
-
422
- Step 6 — Collect name and optional metadata:
423
- Ask for a name (any non-empty string). identity is auto-derived from name by stripping non-alphanumerics unless the user provides one (must be letters/numbers/underscores). description and tags are optional — ask, but do not require them.
424
-
425
- Only after the user confirms all of the above should you call harness_create resource_type=chaos_loadtest.
426
-
427
- HOW THE BODY IS BUILT (you do NOT construct any of this — MCP handles it):
428
- - Your scalars (users / duration_sec / ramp_up_sec / worker_count / target_url / script_image / script_entrypoint / load_args)
429
- are translated into a canonical inputs[] array with backend-wire names
430
- (targetUsers / durationSeconds / rampUpTimeSec / workerCount / targetUrl / scriptImage / scriptEntrypoint / loadArgs).
431
- - Inline mode (Locust): your raw 'script' is base64-encoded into top-level scriptContent.
432
- - Image mode (Locust): scriptContent is omitted; script_image / script_entrypoint / load_args ride in inputs[].
433
- - K6 script mode: top-level scriptContent is OMITTED. The base64 script lives in toolConfig.scriptContent only.
434
- toolConfig also carries hostUrl, optional options.rpsLimit, optional iterations, and optional envVars.
435
- - env_vars (K6): structured only. {key, value} for literals; {key, secret_id, secret_scope?} for secrets —
436
- MCP builds the wire 'secrets.getValue("<prefix><id>")' string and sets secret: true automatically.
437
- - A canonical LoadTest YAML manifest is synthesized and base64-encoded into the 'yaml' request field.
438
- - description defaults to "", tags defaults to []. tool_type is fixed to "Locust" server-side (K6/JMeter coming).
737
+ - name / description / tags / environment_id / infra_id / target_type / max_duration_sec: simple scalar replace when supplied.
738
+ - service_references / variables: replace the ENTIRE list when non-null (a non-null service_references must still be non-empty); there is no per-item merge.
739
+ - cleanup_policy / resources: identical top-level spec fields to create -- see their own field descriptions (descLoadtestCleanupPolicy / descLoadtestResources). Not repeated here.
740
+ - Editing the script, image, tunables, properties, thresholds, or env vars (toolConfig.<tool>) requires resupplying the FULL desired sub-object -- the backend overwrites toolConfig.<tool> wholesale on any non-null write (no per-field merge server-side, except variables and JMeter's masked-.zip workspace preservation). You have two ways to do this:
741
+ 1. Scalar fields (recommended, mirrors create): supply 'tool_type' (Locust/K6/JMeter -- required, NOT itself sent to the API since tool_type is immutable after creation; used only so MCP picks the right builder) plus any of the same scalar fields create accepts (target_url, script_source, script, script_image, script_entrypoint, load_args, image_pull_secret, users, spawn_rate, duration_sec, ramp_up_sec, worker_count, host_url, rps_limit, iterations, env_vars, properties, thresholds). MCP rebuilds the complete toolConfig.<tool> object from these exactly as it does on create -- you must resupply every scalar that should remain set, not just the ones changing. Field-to-path mapping:
439
742
 
440
- INPUT FIELDS:
441
- name (string, required): Display name for the load test. Any non-empty string (e.g. "My Load Test", "locust-1").
442
- environment_id (string, required): environmentID of the chosen infrastructure (Step 2).
443
- infra_id (string, required): infraID of the chosen infrastructure (Step 2).
444
- target_url (string, required): Base URL of the application under test (Step 3). Goes into inputs[name=targetUrl].
445
- script (string): Raw Python locust script. VM always requires this; K8s when inline. MCP base64-encodes it into the top-level scriptContent field — do NOT pre-encode.
446
- script_source (string): "inline" (default) or "image". "image" is K8s-only; VM is always "inline". Inferred as "image" when script_image is set.
447
- script_image (string): K8s + image mode only — prebuilt container image. Required when script_source=image. Goes into inputs[name=scriptImage, required:true].
448
- script_entrypoint (string): Custom Image mode — entrypoint file inside the image. Goes into inputs[name=scriptEntrypoint, required:true] when provided.
449
- load_args (string): Custom Image mode — container args as "k=v,v2;k2=v" pairs. Goes into inputs[name=loadArgs] when provided.
450
- target_type (string): machine-chaos-linux (Linux VM, default), linux-chaos, or kubernetes.
451
- users (number, default 100): Number of simulated users. Goes into inputs[name=targetUsers, required:true].
452
- duration_sec (number, default 600): Total test duration in seconds. Goes into inputs[name=durationSeconds].
453
- ramp_up_sec (number, default 120): Ramp-up duration in seconds. Goes into inputs[name=rampUpTimeSec].
454
- worker_count (number, default 0): K8s — agent must ask; default 0. VM: N/A. Goes into inputs[name=workerCount].
455
- tool_type (string, default "Locust"): "Locust" or "K6". K6 requires target_type=kubernetes.
456
- host_url (string, K6 only): K6 host origin (protocol://host, no path). Defaults to origin of target_url. Goes into toolConfig.hostUrl.
457
- rps_limit (number, K6 only): requests-per-second cap. Goes into toolConfig.options.rpsLimit when > 0.
458
- iterations (number, K6 only): total iteration cap. Goes into toolConfig.iterations when > 0.
459
- env_vars (array, K6 only): environment variables. Array of {key, value} (literal) or {key, secret_id, secret_scope?} (secret reference). See Step 4c.
460
- identity (string): Stable slug-safe identifier (letters / numbers / underscores). Auto-derived from name by stripping non-alphanumerics when omitted.
461
- description (string): Optional human-readable description. Defaults to "".
462
- tags (string[]): Optional tags. Defaults to [].
463
-
464
- Returns the created load test (identity, name, environment/infra, targetType, toolType, scriptSource, inputs[], plus derived target_url/users/duration_sec/ramp_up_sec/worker_count convenience scalars) and an openInHarness deep link.`;
465
- export const descDeleteLoadtest = `Delete a load test instance`;
743
+ ${descLoadtestFieldTaxonomy}
744
+ 2. 'tool_config' escape hatch: hand-construct the object yourself (e.g. to preserve a JMeter .zip workspace or other advanced shape not covered by scalars). Pass 'tool_type' alongside it so MCP wraps/unwraps consistently with create; omitting 'tool_type' falls back to legacy behavior where 'tool_config' is sent to the API completely as-is (must already be the full \`{ <tool>: {...} }\` wire shape).
745
+ - No 'yaml' manifest is sent on update (unlike create) -- the typed JSON fields above (cleanupPolicy, resources, toolConfig) are authoritative since the backend falls back to them whenever no yaml body is supplied.
746
+
747
+ Returns the updated load test (same shape as harness_get resource_type=chaos_loadtest).`;
466
748
  export const descListK8sInfra = `List Kubernetes chaos infrastructures available for running experiments.
467
749
  Use chaos_environment list first to get an environmentId, then pass it here to filter infrastructures for that environment.
468
750
  Returns infrastructure details including identity, infraID, name, environmentID, status, infraType, infraScope, and isChaosEnabled.
@@ -801,15 +1083,18 @@ Requires the template identity, hub identity, and two revision numbers.`;
801
1083
  export const descBodyExperimentRun = `Optional runtime inputs for the chaos experiment. Use chaos_experiment_variable list to discover required variables first.`;
802
1084
  export const descBodyNoBody = `No body required. Resource identified by path parameter.`;
803
1085
  export const descBodyCreateFromTemplate = `Chaos experiment from template`;
804
- export const descBodyLoadtestDefinition = `Locust load test definition. Required: name, environment_id, infra_id, target_url. Mode: either 'script' (inline Python, script_source=inline, default) or 'script_image' (Custom Image, script_source=image).
805
- MCP translates your scalars into the wire shape automatically:
806
- - users / duration_sec / ramp_up_sec inputs[targetUsers / durationSeconds / rampUpTimeSec] (Integer)
807
- - target_url inputs[targetUrl] (String)
808
- - worker_count inputs[workerCount] (Integer; Kubernetes only)
809
- - script_image / script_entrypoint / load_args → inputs[scriptImage / scriptEntrypoint / loadArgs] (String; image mode)
810
- - script (raw Python) → top-level scriptContent (base64)
1086
+ export const descBodyLoadtestDefinition = `Load test definition (Locust / K6 / JMeter). Required: name, environment_id, infra_id, target_url. Mode: 'inline' (upload raw script, default) or 'image' (Custom Image via script_image; Kubernetes only).
1087
+ MCP translates your scalars into the canonical toolConfig.<tool> wire shape automatically:
1088
+ - users / duration_sec / ramp_up_sec / worker_count / target_url
1089
+ toolConfig.<tool>.tunables.{targetUsers, durationSeconds, rampUpTimeSec, workerCount, targetUrl}
1090
+ - script (raw Python/JS/JMX) toolConfig.<tool>.script.content (base64)
1091
+ - script_image / script_entrypoint / load_args / image_pull_secret (image mode)
1092
+ toolConfig.<tool>.script.{image, entrypoint, loadArgs, imagePullSecret}
1093
+ - K6 host_url / iterations → toolConfig.k6.tunables.{hostUrl, iterations}
1094
+ - K6 rps_limit → toolConfig.k6.tunables.rpsLimit
1095
+ - env_vars (K6/JMeter) → toolConfig.<tool>.envVars
811
1096
  - A canonical LoadTest YAML manifest is built and base64-encoded into a 'yaml' field.
812
- You never construct inputs[], scriptContent base64, or the yaml field — pass scalars only.`;
1097
+ You never construct toolConfig, script.content base64, or the yaml field — pass scalars only.`;
813
1098
  // ── Field Descriptions ───────────────────────────────────────────────
814
1099
  export const descInputsetIdentity = `Optional inputset identity to use for the experiment run`;
815
1100
  export const descRuntimeInputs = `Runtime input variables: { experiment: [{name, value}], tasks: { taskName: [{name, value}] } }`;
@@ -820,27 +1105,32 @@ export const descInfraRef = `Infrastructure reference in format: environmentId/i
820
1105
  export const descExperimentId = `Chaos experiment identifier. Accepts either the internal UUID (default, with is_identity=false) or the human-readable identity slug (set is_identity=true). Use harness_list with resource_type=chaos_experiment to find experiment IDs.`;
821
1106
  export const descInfraStatus = `Filter by infra status: Active (default) or All`;
822
1107
  export const descLoadtestName = `Display name for the load test. Any non-empty string (e.g. "My Load Test", "locust-1"). The slug-safe identifier rule applies to 'identity' (auto-derived from name) — not to name.`;
823
- export const descLoadtestType = `Load test tool type: "Locust" (default; Python on Linux VM or Kubernetes) or "K6" (JavaScript on Kubernetes only). JMeter is coming soon. K6 supports script mode and Custom Image mode; "Define test via UI" is deferred.`;
1108
+ export const descLoadtestType = `Load test tool type: "Locust" (default; Python on Linux VM or Kubernetes), "K6" (JavaScript, Kubernetes only), or "JMeter" (Java, Kubernetes only in MCP/UI; not a backend restriction). All three support "Upload script" (script mode) and "Using Custom Image" (image mode) as first-class scalar flows; "Define test via UI" is deferred for K6/Locust and does not apply to JMeter.`;
824
1109
  export const descLoadtestIdentity = `Stable identifier (letters, numbers and underscores). Auto-derived from name by stripping non-alphanumerics when omitted.`;
825
1110
  export const descLoadtestDescription = `Optional human-readable description. Defaults to "".`;
826
1111
  export const descLoadtestTags = `Optional tags (array of strings, or comma-separated string). Defaults to [].`;
827
1112
  export const descLoadtestEnvId = `Environment identifier of the load-runner infrastructure. Use the 'environmentID' from chaos_infrastructure (Linux VM) or chaos_enabled_infrastructure (Kubernetes).`;
828
1113
  export const descLoadtestInfraId = `Load-runner infrastructure identifier. Use the 'infraID' from chaos_infrastructure (Linux VM) or chaos_enabled_infrastructure (Kubernetes).`;
829
- export const descLoadtestTargetType = `Execution environment target type: "machine-chaos-linux" (Linux VM, default), "linux-chaos", or "kubernetes". "workerCount" is only emitted into inputs[] for "kubernetes".`;
830
- export const descLoadtestTargetUrl = `Base URL of the application under test (e.g. https://www.google.com). Sent as an entry in inputs[] with name="targetUrl", type="String".`;
831
- export const descLoadtestScript = `Raw script contents for inline mode (script_source=inline). Locust expects Python (locustfile.py); K6 expects JavaScript and MUST contain 'export default function ...' (mandatory — MCP rejects scripts without it). MCP base64-encodes onto the wire — do NOT pre-encode. For Locust this lands at top-level scriptContent; for K6 it lands at toolConfig.scriptContent (and top-level scriptContent is omitted). Required for inline mode; omit for Custom Image mode.`;
832
- export const descLoadtestScriptSource = `Test definition source: "inline" (default — upload a raw Python locust script via 'script') or "image" (use a prebuilt container image via 'script_image'). "image" is K8s-only; VM is always "inline". Inferred as "image" when script_image is set, otherwise "inline".`;
833
- export const descLoadtestScriptImage = `Custom Image mode (both Locust and K6, Kubernetes only): prebuilt container image used as the load test source, e.g. "my-registry/my-load-test:latest". Required when script_source=image. Sent as inputs[name=scriptImage, type=String, required:true]. For K6 also wired into toolConfig.customImage.image.`;
834
- export const descLoadtestScriptEntrypoint = `Custom Image mode (optional, both Locust and K6): entrypoint file inside the image, e.g. "locustfile.py" (Locust) or "/script.js" (K6). Sent as inputs[name=scriptEntrypoint, type=String, required:true] when provided. For K6 also wired into toolConfig.customImage.entrypoint.`;
835
- export const descLoadtestLoadArgs = `Custom Image mode (optional, both Locust and K6): arguments passed to the container as key=value pairs comma-separated for multiple values per key, separated by ';'. Example: "tags=smoke,random;headless=true". Sent as inputs[name=loadArgs, type=String] only for K6 this does NOT go into toolConfig.customImage.`;
836
- export const descLoadtestUsers = `Number of simulated users. Default: 100. Sent as inputs[name=targetUsers, type=Integer, required:true].`;
837
- export const descLoadtestDurationSec = `Total test duration in seconds. Default: 600. Sent as inputs[name=durationSeconds, type=Integer].`;
838
- export const descLoadtestRampUpSec = `Ramp-up duration in seconds. Default: 120. Sent as inputs[name=rampUpTimeSec, type=Integer].`;
839
- export const descLoadtestWorkerCount = `K8s: agent must ask; default 0 (0 = standalone). VM: N/A. Sent as inputs[name=workerCount, type=Integer]. Ignored for Linux target types.`;
1114
+ export const descLoadtestTargetType = `Execution environment target type: "machine-chaos-linux" (Linux VM, default), "linux-chaos", or "kubernetes". "workerCount" is only honoured for "kubernetes".`;
1115
+ export const descLoadtestTargetUrl = `Base URL of the application under test (e.g. https://www.google.com). Maps to toolConfig.<tool>.tunables.targetUrl. For Locust it is passed as --host; leave blank if the locustfile sets host in code.`;
1116
+ export const descLoadtestScript = `Raw script contents for inline mode (script_source=inline). Locust expects Python (locustfile.py); K6 expects JavaScript and MUST contain 'export default function ...' (mandatory — MCP rejects scripts without it); JMeter expects the raw .jmx/.xml test plan text (verbatim; for a .zip bundle use tool_config.jmeter.script.content with a pre-computed base64 string instead). MCP base64-encodes onto the wire — do NOT pre-encode. Lands at toolConfig.<tool>.script.content for all three tools. Required for inline mode; omit for Custom Image mode.`;
1117
+ export const descLoadtestScriptSource = `Test definition source: "inline" (default — upload a raw script via 'script') or "image" (use a prebuilt container image via 'script_image'). "image" is K8s-only; VM is always "inline". Inferred as "image" when script_image is set, otherwise "inline".`;
1118
+ export const descLoadtestScriptImage = `Custom Image mode (Locust/K6/JMeter, Kubernetes only): prebuilt container image used as the load test source, e.g. "my-registry/my-load-test:latest". Required when script_source=image. Maps to toolConfig.<tool>.script.image.`;
1119
+ export const descLoadtestScriptEntrypoint = `Custom Image mode: entrypoint file inside the image. Locust: locustfile path (optional; passed to locust -f), e.g. "locustfile.py". K6: script path (optional; passed to k6 run), e.g. "/script.js". JMeter: path to the .jmx/.xml test plan (optional; backend uses a default plan-file name when omitted), e.g. "/test/plan.jmx". Runtime placeholder '<+input>' is accepted verbatim. Maps to toolConfig.<tool>.script.entrypoint.`;
1120
+ export const descLoadtestLoadArgs = `Custom Image mode (optional): container args as semicolon-separated key=value pairs (commas allowed inside a value). Keys must be non-empty, contain no whitespace, and must NOT start with '-' (bare CLI flags like "--headless" are rejected -- mirrors loadTestManager ValidateLoadArgs). Locust/K6: passed to the tool as CLI args (e.g. "tags=smoke,fast;headless=true"). JMeter: GNU-style long options -- each pair becomes --key=value (e.g. "proxyHost=10.0.0.1;forceDeleteResultFile"). Do NOT put JMeter properties (jmeter.save.saveservice.* or \${__P(...)} plan variables) here -- use 'properties' (which become -J/-G). Maps to toolConfig.<tool>.script.loadArgs.`;
1121
+ export const descLoadtestImagePullSecret = `Custom Image mode (Kubernetes, private registry only): name of the Kubernetes image-pull secret used to pull script_image. Maps to toolConfig.<tool>.script.imagePullSecret. Omit for public images (UI "Image Registry Type = Public"); there is no separate Public/Private field on the wire — presence of this scalar is the private signal.`;
1122
+ export const descLoadtestUsers = `Number of simulated users. Default: 100. Maps to toolConfig.<tool>.tunables.targetUsers.`;
1123
+ export const descLoadtestDurationSec = `Total test duration in seconds. Default: 600. Maps to toolConfig.<tool>.tunables.durationSeconds.`;
1124
+ export const descLoadtestRampUpSec = `Ramp-up duration in seconds. Default: 120. Maps to toolConfig.<tool>.tunables.rampUpTimeSec.`;
1125
+ export const descLoadtestWorkerCount = `K8s: agent must ask; default 0 (0 = standalone). VM: N/A (ignored for Linux target types). Goes to toolConfig.<tool>.tunables.workerCount. Semantics differ per tool: Locust DISTRIBUTES users across workers (load not multiplied); K6 <=1 is a single pod and higher values fan out via --execution-segment; JMeter MULTIPLIES load -- each worker runs the full uploaded plan unchanged.`;
840
1126
  export const descLoadtestHostUrl = `K6 only: host origin (protocol+host with no path, e.g. https://api.example.com). Defaults to the origin parsed from target_url when omitted. Goes into toolConfig.hostUrl.`;
841
- export const descLoadtestRpsLimit = `K6 only: requests-per-second cap. Goes into toolConfig.options.rpsLimit when > 0. Omitted from the wire when unset or 0.`;
1127
+ export const descLoadtestRpsLimit = `K6 only: requests-per-second cap. Goes into toolConfig.k6.tunables.rpsLimit when > 0. Omitted from the wire when unset or 0.`;
842
1128
  export const descLoadtestIterations = `K6 only: total iteration cap. Goes into toolConfig.iterations when > 0. Omitted from the wire when unset or 0.`;
843
- export const descLoadtestEnvVars = `K6 only: environment variables for the K6 runner. Array of entries — each sets EITHER {key, value} for a literal OR {key, secret_id, secret_scope?: "account"|"org"|"project"} for a Harness secret (default secret_scope = "project"). MCP builds the wire 'secrets.getValue("<prefix><id>")' string and sets secret: true automatically — do NOT construct that string yourself. Discover secrets via harness_list resource_type=secret type=SecretText and read each item's secret.{identifier, orgIdentifier?, projectIdentifier?} to derive scope. Key pattern: /^[A-Za-z_][A-Za-z0-9_]*$/. Reserved names (case-insensitive) are rejected: RUN_ID, LOAD_TEST_ID, TARGET_USERS, SPAWN_RATE, SCRIPT_CONTENT_BASE64, TARGET_URL, ACCOUNT_ID, ORG_ID, PROJECT_ID, ENV_ID, DURATION_SECONDS, CONTROL_PLANE_URL, CONTROL_PLANE_TOKEN, HARNESS_CUSTOM_VAR_NAMES, METRICS_PUSH_INTERVAL, INFRA_ID, ACCESS_KEY, TENANT_ID, PYTHONPATH, PATH, HOME, USER, SHELL, LANG, TERM, HOSTNAME, PWD, LD_LIBRARY_PATH, LD_PRELOAD, TMPDIR, TMP, TEMP.`;
1129
+ export const descLoadtestEnvVars = `K6 and JMeter only: environment variables for the runner. Array of entries — each sets EITHER {key, value} for a literal OR {key, secret_id, secret_scope?: "account"|"org"|"project"} for a Harness secret (default secret_scope = "project"). MCP builds the wire 'secrets.getValue("<prefix><id>")' string and sets secret: true automatically — do NOT construct that string yourself. Discover secrets via harness_list resource_type=secret type=SecretText and read each item's secret.{identifier, orgIdentifier?, projectIdentifier?} to derive scope. Key pattern: /^[A-Za-z_][A-Za-z0-9_]*$/. Reserved names (case-insensitive), rejected for BOTH tools: RUN_ID, LOAD_TEST_ID, TARGET_USERS, SPAWN_RATE, SCRIPT_CONTENT_BASE64, TARGET_URL, ACCOUNT_ID, ORG_ID, PROJECT_ID, ENV_ID, DURATION_SECONDS, CONTROL_PLANE_URL, CONTROL_PLANE_TOKEN, HARNESS_CUSTOM_VAR_NAMES, METRICS_PUSH_INTERVAL, INFRA_ID, ACCESS_KEY, TENANT_ID, PYTHONPATH, PATH, HOME, USER, SHELL, LANG, TERM, HOSTNAME, PWD, LD_LIBRARY_PATH, LD_PRELOAD, TMPDIR, TMP, TEMP. K6 ALSO rejects: HOST_URL, K6_VUS, K6_DURATION, K6_ITERATIONS, K6_STAGES, K6_RPS.`;
1130
+ export const descLoadtestCleanupPolicy = `'delete' (default) or 'retain' -- controls whether run resources (pods, configmaps, secrets) are removed after each execution. Choose 'retain' to keep them around for debugging. Maps to spec.cleanupPolicy. Identical field for Locust/K6/JMeter. Mirrors the UI's Advanced Options "Clean-up Load Resources" toggle (ON = delete, OFF = retain).`;
1131
+ export const descLoadtestResources = `Kubernetes-only per-pod resource limits and requests (UI: Advanced Options > Resource Requirements; OFF by default -- omit entirely unless the user opts in). Object shape: { limits?: { cpu?, memory? }, requests?: { cpu?, memory? } }. cpu is cores/millicores (e.g. "100m", "0.5", "2"); memory is a whole-byte quantity (e.g. "128Mi", "1Gi", "134217728"). Only 'cpu' and 'memory' keys are allowed in each section. Per resource, requests must not exceed limits. Maps to spec.resources. Identical field for Locust/K6/JMeter.`;
1132
+ export const descLoadtestProperties = "JMeter only: runtime property overrides injected into the plan without editing the uploaded file -- reference as ${__P(NAME)} in the .jmx. Array of {key, value, send_to_engines?}. send_to_engines (maps to sendToEngines / the JMeter -G flag) controls whether the property also reaches remote Distributed Execution workers; omit (false) for controller-only properties.";
1133
+ export const descLoadtestThresholds = "JMeter only: pass/fail criteria evaluated against the run's .jtl results after the test completes (the uploaded plan's own Assertions still apply per-request during the run). Array of {metric, stat?, operator, value, abort_on_fail?}. metric: one of response_time_ms | error_rate_pct | throughput_rps | latency_ms. operator: one of < | <= | > | >= | == | !=. stat (e.g. p95, p99, avg, median, max) is REQUIRED when metric is response_time_ms or latency_ms. abort_on_fail (maps to abortOnFail) marks the threshold as run-aborting on failure.";
844
1134
  export const descHubIdentityExact = `The unique identity of the ChaosHub. Use harness_list with resource_type=chaos_hub to find hub identities.`;
845
1135
  export const descHubName = `Display name for the ChaosHub.`;
846
1136
  export const descHubNameUpdate = `Updated display name for the ChaosHub.`;
@@ -1285,8 +1575,13 @@ export const descExperimentCronSyntax = `Optional cron expression for scheduling
1285
1575
  // Shared filter / behavior descriptions
1286
1576
  export const descSDAgentIdentity = `Service Discovery agent identity — the path segment shown in the SD UI URL (e.g. 'chaosinfra'). Each agent is bound to one Harness environment and one Kubernetes cluster.`;
1287
1577
  export const descSDEnvironmentId = `Harness environment identifier the SD agent is bound to (e.g. 'dev'). Required by SD's AgentAccessCheck middleware to resolve the agent — the same agent identity may exist in multiple environments.`;
1578
+ export const descSDAgentListEnvironmentId = `Optional narrowing filter — the LIST agents endpoint does not require it (unlike discovered_namespace/discovered_service/discovered_network_map, which use AgentAccessCheck and require both agent_identity + environment_id). Omit to list agents across every environment in scope.`;
1288
1579
  export const descSDFetchAll = `When true, fetch every result and ignore page/limit (the API returns the full unpaginated list). Useful for small/medium clusters; avoid on very large clusters.`;
1289
- export const descSDAgentDiagnostic = `404 from SD endpoints almost always means agent_identity or environment_id is wrong — both are required for AgentAccessCheck to resolve the agent. Both can be confirmed from the SD UI URL or (when added) the discovered_agent list.`;
1580
+ export const descSDAgentDiagnostic = `404 from SD endpoints almost always means agent_identity or environment_id is wrong — both are required for AgentAccessCheck to resolve the agent. Both can be confirmed from the SD UI URL or via discovered_agent list.`;
1581
+ // discovered_agent
1582
+ export const descDiscoveredAgent = `Service Discovery agent — one SD deployment running inside a customer's Kubernetes cluster, bound to a single Harness environment. Its 'identity' field is the value other resources take as 'agent_identity' (discovered_namespace / discovered_service / discovered_network_map) and as 'agent_id' (chaos_service create/update). Soft-deleted agents are hidden from list results. The token field is never returned.`;
1583
+ export const descListDiscoveredAgents = `List Service Discovery agents in the given account/org/project scope. Use this to look up the 'identity' value required by other Service Discovery resources and by chaos_service create/update. Optional environment_id narrows to one environment; omit to list agents across all environments in scope. Optional search does a case-insensitive regex match on the agent name. Supports page/limit pagination or all=true to fetch everything (all=true also skips per-agent installation/service/network-map count enrichment, so responses are lighter).`;
1584
+ export const descDiscoveredAgentSearch = `Case-insensitive substring match against the SD agent name field.`;
1290
1585
  // discovered_namespace
1291
1586
  export const descDiscoveredNamespace = `Read-only snapshot of a Kubernetes Namespace recorded by a Service Discovery agent — includes the namespace name, UID, resource version, labels, annotations, owner references, and the full corev1.NamespaceSpec/Status from the cluster's last sync. Use namespaces as the scope boundary when listing discovered_service or future discovered_workload/discovered_connection resources for the same agent.`;
1292
1587
  export const descListDiscoveredNamespaces = `List Kubernetes namespaces snapshotted by a Service Discovery agent. Requires agent_identity (path) and environment_id (query). Returns soft-deleted-aware results (only live namespaces). Optional name filter (exact match, NOT substring — see name field doc). Supports page/limit pagination or all=true to fetch everything.`;