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
@@ -1,3 +1,4 @@
1
+ import { SCOPE_BEHAVIOR_DOC } from "../scope-utils.js";
1
2
  // ─── Response extractors ─────────────────────────────────────────────────────
2
3
  /** IaCM page size is fixed at 30 by the API. */
3
4
  const IACM_PAGE_SIZE = 30;
@@ -22,6 +23,42 @@ const workspaceListExtract = (raw) => {
22
23
  * IACM workspace get: API returns a single workspace object directly.
23
24
  */
24
25
  const workspaceGetExtract = (raw) => raw;
26
+ /**
27
+ * Workspace create/update return `{ policy_evaluation? }` only — not the workspace.
28
+ * Project that stable shape so agents don't treat the write response as workspace
29
+ * metadata; follow up with harness_get for the workspace itself.
30
+ */
31
+ const workspaceWriteExtract = (raw) => {
32
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
33
+ return {};
34
+ }
35
+ const rec = raw;
36
+ return {
37
+ policy_evaluation: rec.policy_evaluation ?? null,
38
+ };
39
+ };
40
+ /**
41
+ * Variable-set list: API returns `{ items: [...] }` with no pagination params.
42
+ * Normalize to the shared list shape used by other IaCM resources.
43
+ */
44
+ const variableSetListExtract = (raw) => {
45
+ const items = Array.isArray(raw)
46
+ ? raw
47
+ : raw && typeof raw === "object" && Array.isArray(raw.items)
48
+ ? (raw.items)
49
+ : [];
50
+ return {
51
+ items,
52
+ page_count: items.length,
53
+ has_more: false,
54
+ pagination_note: `Returned ${items.length} variable set(s). This endpoint is not paginated — treat page_count as the full result size for the selected scope.`,
55
+ };
56
+ };
57
+ /**
58
+ * Variable-set get/create/update return the VariableSet resource directly
59
+ * (identifier, name, variables, connectors, …) — not a policy_evaluation envelope.
60
+ */
61
+ const variableSetExtract = (raw) => raw;
25
62
  /**
26
63
  * IACM resources list: API returns a ResourcesResponse with resources, outputs,
27
64
  * dataSources, and pagination metadata. Pass the full structure through so the
@@ -66,6 +103,25 @@ const moduleListExtract = (raw) => {
66
103
  : `All modules on this page returned (${items.length} items). has_more=false means this is the last page.`,
67
104
  };
68
105
  };
106
+ /** Module get/create/update return the module resource (or create result) directly. */
107
+ const moduleExtract = (raw) => raw;
108
+ /**
109
+ * Provider list: API returns a raw JSON array of provider objects.
110
+ */
111
+ const providerListExtract = (raw) => {
112
+ const items = Array.isArray(raw) ? raw : [];
113
+ const has_more = items.length >= IACM_PAGE_SIZE;
114
+ return {
115
+ items,
116
+ page_count: items.length,
117
+ has_more,
118
+ pagination_note: has_more
119
+ ? `Only ${items.length} providers returned (page is full). Call again with page+1 to fetch the next batch. Do NOT report ${items.length} as the total provider count.`
120
+ : `All providers on this page returned (${items.length} items). has_more=false means this is the last page.`,
121
+ };
122
+ };
123
+ /** Provider get/create/version ops return the API payload directly. */
124
+ const providerExtract = (raw) => raw;
69
125
  /**
70
126
  * IACM workspace costs: API returns a raw JSON array of cost entries.
71
127
  * Costs are typically a small finite list so has_more acts as a safety signal only.
@@ -117,15 +173,469 @@ const requireProjectScope = async (ctx) => {
117
173
  "and will fail silently without them.");
118
174
  }
119
175
  };
176
+ /**
177
+ * Resolve account/org/project path prefix for IaCM variable-set endpoints.
178
+ * Paths use `/variable-sets` for list and `/variable-set` for get/create/update.
179
+ */
180
+ const resolveVariableSetScopePrefix = (input, config) => {
181
+ const requestedScope = input.resource_scope;
182
+ const org = input.org_id ?? config.HARNESS_ORG;
183
+ const project = input.project_id ?? config.HARNESS_PROJECT;
184
+ const useProject = requestedScope === "project" ||
185
+ (!requestedScope && Boolean(org && project));
186
+ const useOrg = requestedScope === "org" ||
187
+ useProject ||
188
+ (!requestedScope && Boolean(org));
189
+ if (requestedScope === "project" && (!org || !project)) {
190
+ throw new Error('resource_scope="project" requires org_id and project_id (or HARNESS_ORG / HARNESS_PROJECT defaults).');
191
+ }
192
+ if (requestedScope === "org" && !org) {
193
+ throw new Error('resource_scope="org" requires org_id (or a HARNESS_ORG default).');
194
+ }
195
+ if (useProject && org && project) {
196
+ return `/iacm/api/orgs/${encodeURIComponent(org)}/projects/${encodeURIComponent(project)}`;
197
+ }
198
+ if (useOrg && org) {
199
+ return `/iacm/api/orgs/${encodeURIComponent(org)}`;
200
+ }
201
+ return "/iacm/api";
202
+ };
203
+ const variableSetListPath = (input, config) => `${resolveVariableSetScopePrefix(input, config)}/variable-sets`;
204
+ const variableSetCollectionPath = (input, config) => `${resolveVariableSetScopePrefix(input, config)}/variable-set`;
205
+ const variableSetItemPath = (input, config) => {
206
+ const id = input.variable_set_id;
207
+ if (!id) {
208
+ throw new Error('Missing required field "variable_set_id" for iacm_variable_set. Pass it via params or as resource_id.');
209
+ }
210
+ return `${variableSetCollectionPath(input, config)}/${encodeURIComponent(id)}`;
211
+ };
212
+ const providerCreatePath = (input) => {
213
+ const body = input.body ?? {};
214
+ const type = input.type ?? body.type;
215
+ if (!type) {
216
+ throw new Error('Missing required field "type" for iacm_provider create. Pass it in body.type (e.g. "aws", "terraform").');
217
+ }
218
+ return `/iacm/api/providers/${encodeURIComponent(type)}`;
219
+ };
220
+ const providerCreateBody = (input) => {
221
+ const body = { ...(input.body ?? {}) };
222
+ delete body.type;
223
+ return body;
224
+ };
225
+ const providerVersionMethod = (input) => input.version ? "PUT" : "POST";
226
+ const providerVersionPath = (input) => {
227
+ const id = input.id;
228
+ if (!id) {
229
+ throw new Error('Missing required field "id" for iacm_provider update. Pass the provider id via params or as resource_id.');
230
+ }
231
+ if (input.version) {
232
+ return `/iacm/api/providers/${encodeURIComponent(id)}/version/${encodeURIComponent(String(input.version))}`;
233
+ }
234
+ return `/iacm/api/providers/${encodeURIComponent(id)}/version`;
235
+ };
236
+ const providerVersionBody = (input) => {
237
+ const body = { ...(input.body ?? {}) };
238
+ if (input.version) {
239
+ delete body.version;
240
+ }
241
+ else if (body.version === undefined || body.version === "") {
242
+ throw new Error('Missing required field "version" for iacm_provider version create. ' +
243
+ "Pass version in body when creating a new version, or pass params.version to update an existing version.");
244
+ }
245
+ return body;
246
+ };
247
+ // ─── Workspace request schemas ───────────────────────────────────────────────
248
+ const workspaceVariableValueFields = [
249
+ { name: "key", type: "string", required: true, description: "Variable key (must match the map entry key)" },
250
+ { name: "value", type: "string", required: true, description: "Variable value, or secret reference when value_type is secret" },
251
+ {
252
+ name: "value_type",
253
+ type: "string",
254
+ required: true,
255
+ description: "Variable value type: string or secret",
256
+ },
257
+ ];
258
+ const workspaceOptionalFields = [
259
+ { name: "description", type: "string", required: false, description: "Long-form workspace description" },
260
+ { name: "provisioner_version", type: "string", required: false, description: "Provisioner version; defaults to latest" },
261
+ {
262
+ name: "provisioner_configuration",
263
+ type: "object",
264
+ required: false,
265
+ description: "Provisioner-specific configuration as a string-to-string map " +
266
+ "(IaCM wire type is map[string]string; OpenAPI may show string due to a custom goa type).",
267
+ },
268
+ { name: "terragrunt_provider", type: "boolean", required: false, description: "Whether the workspace uses Terragrunt" },
269
+ { name: "terragrunt_version", type: "string", required: false, description: "Terragrunt version" },
270
+ { name: "run_all", type: "boolean", required: false, description: "Run all Terragrunt modules" },
271
+ {
272
+ name: "repository",
273
+ type: "string",
274
+ required: false,
275
+ description: "Infrastructure repository name. Required by IaCM when repository_connector is empty " +
276
+ '(API returns "repository name can\'t be empty").',
277
+ },
278
+ { name: "repository_branch", type: "string", required: false, description: "Repository branch" },
279
+ { name: "repository_commit", type: "string", required: false, description: "Repository commit or tag" },
280
+ { name: "repository_sha", type: "string", required: false, description: "Repository commit SHA" },
281
+ { name: "repository_connector", type: "string", required: false, description: "Harness connector for the infrastructure repository" },
282
+ { name: "repository_path", type: "string", required: false, description: "Path to the infrastructure code within the repository" },
283
+ {
284
+ name: "repository_submodules",
285
+ type: "string",
286
+ required: false,
287
+ description: "Submodule checkout mode: false, true, or recursive",
288
+ },
289
+ {
290
+ name: "sparse_checkout",
291
+ type: "array",
292
+ required: false,
293
+ itemType: "string",
294
+ description: "Git sparse-checkout path patterns as a JSON string array " +
295
+ "(IaCM wire type is string[]; OpenAPI may show string due to a custom goa type).",
296
+ },
297
+ {
298
+ name: "cost_estimation_enabled",
299
+ type: "boolean",
300
+ required: false,
301
+ description: "Enable cost-estimation operations for the workspace",
302
+ },
303
+ {
304
+ name: "terraform_variable_files",
305
+ type: "array",
306
+ required: false,
307
+ itemType: "workspace Terraform variable-file reference",
308
+ description: "Variable files stored in repositories other than the workspace repository",
309
+ },
310
+ { name: "budget", type: "number", required: false, description: "Workspace budget" },
311
+ {
312
+ name: "default_pipelines",
313
+ type: "object",
314
+ required: false,
315
+ description: "Operation-to-default-pipeline map with optional workspace overrides",
316
+ },
317
+ {
318
+ name: "variable_sets",
319
+ type: "array",
320
+ required: false,
321
+ itemType: "string",
322
+ description: "Identifiers of variable sets attached to the workspace",
323
+ },
324
+ { name: "tags", type: "object", required: false, description: "String key-value tags" },
325
+ {
326
+ name: "provider_connectors",
327
+ type: "array",
328
+ required: false,
329
+ itemType: "workspace provider connector",
330
+ description: "Additional provider connector definitions",
331
+ },
332
+ {
333
+ name: "prune_sensitive_data",
334
+ type: "boolean",
335
+ required: false,
336
+ description: "Prune sensitive data from workspace output",
337
+ },
338
+ {
339
+ name: "ccm_cost_enabled",
340
+ type: "boolean",
341
+ required: false,
342
+ description: "Enable CCM cost integration for the workspace",
343
+ },
344
+ ];
345
+ const workspaceCreateSchema = {
346
+ description: "IaCM workspace definition. Create from a template by also supplying associated_template.",
347
+ fields: [
348
+ { name: "identifier", type: "string", required: true, description: "Unique workspace identifier" },
349
+ { name: "name", type: "string", required: true, description: "Workspace display name" },
350
+ {
351
+ name: "provider_connector",
352
+ type: "string",
353
+ required: true,
354
+ description: "Harness connector reference for the infrastructure provider",
355
+ },
356
+ { name: "provisioner", type: "string", required: true, description: "Provisioner such as terraform, opentofu, terragrunt, or awscdk" },
357
+ {
358
+ name: "terraform_variables",
359
+ type: "object",
360
+ required: true,
361
+ description: "Map of Terraform variables (key → { key, value, value_type }). " +
362
+ 'Use {} when none are required. value_type is "string" or "secret".',
363
+ fields: workspaceVariableValueFields,
364
+ },
365
+ {
366
+ name: "environment_variables",
367
+ type: "object",
368
+ required: true,
369
+ description: "Map of environment variables (key → { key, value, value_type }). " +
370
+ 'Use {} when none are required. value_type is "string" or "secret".',
371
+ fields: workspaceVariableValueFields,
372
+ },
373
+ {
374
+ name: "associated_template",
375
+ type: "object",
376
+ required: false,
377
+ description: "Optional workspace template reference",
378
+ fields: [
379
+ { name: "template_id", type: "string", required: true, description: "Template identifier" },
380
+ { name: "version", type: "string", required: true, description: "Template version" },
381
+ ],
382
+ },
383
+ ...workspaceOptionalFields,
384
+ ],
385
+ };
386
+ const workspaceUpdateSchema = {
387
+ description: "Complete IaCM workspace update definition. Send the full workspace body (not a partial patch). " +
388
+ "Include repository (and related git fields) when updating a git-backed workspace — " +
389
+ "IaCM rejects an empty repository when repository_connector is empty.",
390
+ fields: [
391
+ { name: "name", type: "string", required: true, description: "Workspace display name" },
392
+ {
393
+ name: "provider_connector",
394
+ type: "string",
395
+ required: true,
396
+ description: "Harness connector reference for the infrastructure provider",
397
+ },
398
+ { name: "provisioner", type: "string", required: true, description: "Provisioner such as terraform, opentofu, terragrunt, or awscdk" },
399
+ {
400
+ name: "terraform_variables",
401
+ type: "object",
402
+ required: true,
403
+ description: "Complete Terraform variable map (key → { key, value, value_type }). " +
404
+ 'Use {} when none are configured. value_type is "string" or "secret".',
405
+ fields: workspaceVariableValueFields,
406
+ },
407
+ {
408
+ name: "environment_variables",
409
+ type: "object",
410
+ required: true,
411
+ description: "Complete environment variable map (key → { key, value, value_type }). " +
412
+ 'Use {} when none are configured. value_type is "string" or "secret".',
413
+ fields: workspaceVariableValueFields,
414
+ },
415
+ ...workspaceOptionalFields,
416
+ ],
417
+ };
418
+ const variableSetOptionalCollections = [
419
+ {
420
+ name: "terraform_variable_files",
421
+ type: "array",
422
+ required: false,
423
+ itemType: "variable-set Terraform variable-file reference",
424
+ description: "Variable files stored in external repositories. On update this is full-replacement: " +
425
+ "omit or [] clears existing files. Prefer get-then-put and resend current files to keep them.",
426
+ },
427
+ {
428
+ name: "connectors",
429
+ type: "array",
430
+ required: false,
431
+ itemType: "variable-set connector",
432
+ description: "Connector references attached to the variable set ({ connector_ref, type }). " +
433
+ "On update this is full-replacement: omit or [] clears existing connectors. " +
434
+ "Prefer get-then-put and resend current connectors to keep them.",
435
+ },
436
+ ];
437
+ const variableSetCreateSchema = {
438
+ description: "IaCM variable set definition for create. Optional maps use key → { key, value, value_type } " +
439
+ 'where value_type is "string" or "secret".',
440
+ fields: [
441
+ { name: "identifier", type: "string", required: true, description: "Unique variable set identifier" },
442
+ { name: "name", type: "string", required: true, description: "Variable set display name" },
443
+ { name: "description", type: "string", required: false, description: "Long-form variable set description" },
444
+ {
445
+ name: "environment_variables",
446
+ type: "object",
447
+ required: false,
448
+ description: "Environment variable map (key → { key, value, value_type }). " +
449
+ 'Use {} when none are configured. value_type is "string" or "secret".',
450
+ fields: workspaceVariableValueFields,
451
+ },
452
+ {
453
+ name: "terraform_variables",
454
+ type: "object",
455
+ required: false,
456
+ description: "Terraform variable map (key → { key, value, value_type }). " +
457
+ 'Use {} when none are configured. value_type is "string" or "secret".',
458
+ fields: workspaceVariableValueFields,
459
+ },
460
+ ...variableSetOptionalCollections,
461
+ ],
462
+ };
463
+ const variableSetUpdateSchema = {
464
+ description: "Complete IaCM variable set update body (HTTP PUT — not a partial patch). " +
465
+ "Identifier comes from the path (variable_set_id / resource_id). " +
466
+ "IaCM merges collections by full replacement: omitting terraform_variables, environment_variables, " +
467
+ "connectors, or terraform_variable_files clears those collections. " +
468
+ "Always harness_get first, then PUT the full desired state (or {} / [] to clear).",
469
+ fields: [
470
+ { name: "name", type: "string", required: true, description: "Variable set display name" },
471
+ { name: "description", type: "string", required: false, description: "Long-form variable set description" },
472
+ {
473
+ name: "environment_variables",
474
+ type: "object",
475
+ required: true,
476
+ description: "Complete environment variable map (key → { key, value, value_type }). " +
477
+ "Required on update so agents cannot accidentally wipe vars by omitting the field — " +
478
+ 'send the current map from harness_get, or {} to clear. value_type is "string" or "secret".',
479
+ fields: workspaceVariableValueFields,
480
+ },
481
+ {
482
+ name: "terraform_variables",
483
+ type: "object",
484
+ required: true,
485
+ description: "Complete Terraform variable map (key → { key, value, value_type }). " +
486
+ "Required on update so agents cannot accidentally wipe vars by omitting the field — " +
487
+ 'send the current map from harness_get, or {} to clear. value_type is "string" or "secret".',
488
+ fields: workspaceVariableValueFields,
489
+ },
490
+ ...variableSetOptionalCollections,
491
+ ],
492
+ };
493
+ const moduleOptionalFields = [
494
+ { name: "description", type: "string", required: false, description: "Free-form module description" },
495
+ {
496
+ name: "tags",
497
+ type: "string",
498
+ required: false,
499
+ description: "Comma-separated tags (e.g. networking,vpc,aws)",
500
+ },
501
+ { name: "repository", type: "string", required: false, description: "Git repository name containing the module source" },
502
+ { name: "repository_branch", type: "string", required: false, description: "Repository branch" },
503
+ { name: "repository_commit", type: "string", required: false, description: "Repository commit or tag" },
504
+ {
505
+ name: "repository_connector",
506
+ type: "string",
507
+ required: false,
508
+ description: "Harness Git connector identifier; leave empty for Harness Code repositories",
509
+ },
510
+ { name: "repository_path", type: "string", required: false, description: "Path to the module within the repository" },
511
+ {
512
+ name: "git_tag_style",
513
+ type: "string",
514
+ required: false,
515
+ description: "Git tag pattern for versioning (e.g. module-name-*)",
516
+ },
517
+ {
518
+ name: "storage_type",
519
+ type: "string",
520
+ required: false,
521
+ description: "Version storage mode: git_reference or artifact",
522
+ },
523
+ {
524
+ name: "onboarding_pipeline",
525
+ type: "string",
526
+ required: false,
527
+ description: "Harness pipeline identifier used for module onboarding/metadata sync",
528
+ },
529
+ {
530
+ name: "onboarding_pipeline_org",
531
+ type: "string",
532
+ required: false,
533
+ description: "Org where the onboarding pipeline is defined",
534
+ },
535
+ {
536
+ name: "onboarding_pipeline_project",
537
+ type: "string",
538
+ required: false,
539
+ description: "Project where the onboarding pipeline is defined",
540
+ },
541
+ {
542
+ name: "onboarding_pipeline_sync",
543
+ type: "boolean",
544
+ required: false,
545
+ description: "When true, sync metadata automatically on new Git tags",
546
+ },
547
+ {
548
+ name: "org",
549
+ type: "string",
550
+ required: false,
551
+ description: "Org where the Git connector is defined (defaults to scope_org)",
552
+ },
553
+ {
554
+ name: "project",
555
+ type: "string",
556
+ required: false,
557
+ description: "Project where the Git connector is defined (defaults to scope_project)",
558
+ },
559
+ ];
560
+ const moduleCreateSchema = {
561
+ description: "IaCM module registry create definition. Account comes from auth headers. " +
562
+ "Module visibility scope is set with resource_scope / org_id / project_id (sent as scope_org/scope_project query params), " +
563
+ "never as body fields — body.org / body.project only locate the module's Git connector.",
564
+ fields: [
565
+ { name: "name", type: "string", required: true, description: "Module name; unique within the same scope and system" },
566
+ {
567
+ name: "system",
568
+ type: "string",
569
+ required: true,
570
+ description: "Target provider/system in lowercase letters only (e.g. aws, gcp, azure, kubernetes)",
571
+ },
572
+ ...moduleOptionalFields,
573
+ ],
574
+ };
575
+ const moduleUpdateSchema = {
576
+ description: "IaCM module registry update definition (HTTP PUT). Module id comes from the path (id / resource_id) and the " +
577
+ "module's scope from resource_scope / org_id / project_id (scope_org/scope_project query params, not body fields). " +
578
+ "Required body fields: name, system. Optional fields are pointers on the API — omit them only when " +
579
+ "you intend to clear; prefer harness_get then PUT the full desired module (get-then-put).",
580
+ fields: [
581
+ { name: "name", type: "string", required: true, description: "Module name; unique within the same scope and system" },
582
+ {
583
+ name: "system",
584
+ type: "string",
585
+ required: true,
586
+ description: "Target provider/system in lowercase letters only (e.g. aws, gcp, azure, kubernetes)",
587
+ },
588
+ ...moduleOptionalFields,
589
+ ],
590
+ };
591
+ const providerCreateSchema = {
592
+ // type is REQUIRED at the call layer (enforced by pathBuilder), but declared required:false
593
+ // here because bodySchema validates the BUILT body — and providerCreateBody strips type
594
+ // from the wire payload (type is a path segment, not a JSON field).
595
+ description: "IaCM provider registry create definition. Account-scoped (Harness-Account header only). " +
596
+ "body.type is REQUIRED (path segment, e.g. aws); only description is forwarded in the JSON body. " +
597
+ "API response is { id } only — call harness_get with that id for full details.",
598
+ fields: [
599
+ {
600
+ name: "type",
601
+ type: "string",
602
+ required: false,
603
+ description: "REQUIRED (enforced by pathBuilder). Provider type path segment (e.g. aws, terraform). Stripped from the JSON body.",
604
+ },
605
+ { name: "description", type: "string", required: false, description: "Optional provider description" },
606
+ ],
607
+ };
608
+ const providerUpdateSchema = {
609
+ description: "Version-oriented provider update. There is no metadata PUT /providers/{id}. " +
610
+ "Omit params.version to POST a new version (include body.version). " +
611
+ "Pass params.version to PUT an existing version. Version writes may return an empty 201/204 body.",
612
+ fields: [
613
+ {
614
+ name: "version",
615
+ type: "string",
616
+ required: false,
617
+ description: "Required in body when creating a new version (no params.version). Omitted from body on PUT update.",
618
+ },
619
+ {
620
+ name: "protocol",
621
+ type: "array",
622
+ required: true,
623
+ itemType: "string",
624
+ description: "Supported Terraform/OpenTofu protocol versions (e.g. [\"5.0\"])",
625
+ },
626
+ { name: "gpg_key_id", type: "string", required: true, description: "GPG signing key id for the provider version" },
627
+ ],
628
+ };
120
629
  // ─── Toolset definition ─────────────────────────────────────────────────────
121
630
  export const iacmToolset = {
122
631
  name: "iacm",
123
632
  displayName: "Infrastructure as Code Management (IaCM)",
124
- description: "Harness IaCM (Infrastructure as Code Management) — manage Terraform workspaces, " +
125
- "inspect provisioned resources and Terraform outputs, browse the module registry, " +
126
- "review workspace cost history, and diff resource changes from past plan/apply/destroy activities. " +
127
- "Use iacm_workspace to list and get workspaces, iacm_resource for Terraform resources and outputs, " +
128
- "iacm_module for the module registry, iacm_workspace_costs for cost breakdown, " +
633
+ description: "Harness IaCM (Infrastructure as Code Management) — manage Terraform workspaces " +
634
+ "(list/get/create/update), shared variable sets, provisioned resources and Terraform outputs, " +
635
+ "the module and provider registries, workspace cost history, and resource-change diffs from plan/apply/destroy. " +
636
+ "Use iacm_workspace to list, get, create, or update workspaces; iacm_variable_set for reusable " +
637
+ "variable sets (account/org/project); iacm_resource for Terraform resources and outputs; " +
638
+ "iacm_module (account/org/project) and iacm_provider for private registries; iacm_workspace_costs for cost breakdown; " +
129
639
  "and iacm_activity_resource_change for activity diffs.",
130
640
  optIn: false,
131
641
  resources: [
@@ -140,7 +650,8 @@ export const iacmToolset = {
140
650
  "The response includes has_more (true = more pages exist) and page_count (items on THIS page only). " +
141
651
  "IMPORTANT: page_count is NOT the total number of workspaces. " +
142
652
  "To find the true total or list all workspaces, keep calling with page+1 until has_more=false, then sum the page_counts. " +
143
- "Use harness_get with workspace_id to fetch full details of a single workspace. " +
653
+ "Use harness_get with workspace_id to fetch full details, harness_create to create from scratch or a template, " +
654
+ "and harness_update with workspace_id to update an existing workspace. " +
144
655
  "See also: iacm_resource for Terraform resources, iacm_workspace_costs for cost breakdown.",
145
656
  toolset: "iacm",
146
657
  scope: "project",
@@ -224,6 +735,121 @@ export const iacmToolset = {
224
735
  description: "Get full metadata for a specific IaCM workspace by its identifier. " +
225
736
  "Returns id, identifier, name, status, last_run, variables, cost_summary, and project_id.",
226
737
  },
738
+ create: {
739
+ method: "POST",
740
+ path: "/iacm/api/orgs/{org}/projects/{project}/workspaces",
741
+ pathParams: { org_id: "org", project_id: "project" },
742
+ preflight: requireProjectScope,
743
+ operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
744
+ bodyBuilder: (input) => input.body,
745
+ bodySchema: workspaceCreateSchema,
746
+ skipScopeBodyInjection: true,
747
+ responseExtractor: workspaceWriteExtract,
748
+ description: "Create an IaCM workspace. Supply the full required workspace body; to create from a template, " +
749
+ "also provide associated_template with template_id and version. IaCM enforces permissions, validation, and policy evaluation. " +
750
+ "Response is { policy_evaluation } only — not the workspace. Follow up with harness_get " +
751
+ "(resource_type=iacm_workspace, workspace_id=<identifier>) to fetch the created workspace.",
752
+ },
753
+ update: {
754
+ method: "PUT",
755
+ path: "/iacm/api/orgs/{org}/projects/{project}/workspaces/{workspaceId}",
756
+ pathParams: {
757
+ org_id: "org",
758
+ project_id: "project",
759
+ workspace_id: "workspaceId",
760
+ },
761
+ preflight: requireProjectScope,
762
+ operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
763
+ bodyBuilder: (input) => input.body,
764
+ bodySchema: workspaceUpdateSchema,
765
+ skipScopeBodyInjection: true,
766
+ responseExtractor: workspaceWriteExtract,
767
+ description: "Update an IaCM workspace by identifier. This endpoint uses full-replacement semantics for core fields: " +
768
+ "include name, provider_connector, provisioner, terraform_variables, and environment_variables. " +
769
+ "IaCM enforces permissions, validation, and policy evaluation. " +
770
+ "Response is { policy_evaluation } only — not the workspace. Follow up with harness_get " +
771
+ "(resource_type=iacm_workspace, workspace_id=<identifier>) to fetch the updated workspace.",
772
+ },
773
+ },
774
+ },
775
+ // ─── Variable Sets ─────────────────────────────────────────────────────
776
+ {
777
+ resourceType: "iacm_variable_set",
778
+ displayName: "IaCM Variable Set",
779
+ description: "A reusable IaCM variable set containing Terraform variables, environment variables, " +
780
+ "variable files, and connector references that can be attached to workspaces. " +
781
+ "Supports account, org, and project scope. " +
782
+ SCOPE_BEHAVIOR_DOC +
783
+ " Use harness_list/harness_get to discover sets, harness_create to create, and harness_update with variable_set_id to update. " +
784
+ "Create/update return the VariableSet resource (identifier, name, variables, connectors). " +
785
+ "IMPORTANT: harness_update is HTTP PUT with full-replacement collections — call harness_get first, then PUT the full desired body " +
786
+ "(omitting terraform_variables / environment_variables / connectors / terraform_variable_files clears them on the server). " +
787
+ "NOTE: IaCM variable-set RBAC permissions (iac_variableset_*) are currently Experimental in Harness — " +
788
+ "deny paths are not enforceable until iac-server activates them; MCP still forwards the caller token unchanged. " +
789
+ "See also: iacm_workspace.variable_sets for attaching sets to a workspace.",
790
+ toolset: "iacm",
791
+ scope: "project",
792
+ supportedScopes: ["account", "org", "project"],
793
+ identifierFields: ["variable_set_id"],
794
+ relatedResources: [
795
+ {
796
+ resourceType: "iacm_workspace",
797
+ relationship: "used by",
798
+ description: "Workspaces can attach this variable set via their variable_sets field",
799
+ },
800
+ ],
801
+ operations: {
802
+ list: {
803
+ method: "GET",
804
+ path: "/iacm/api/orgs/{org}/projects/{project}/variable-sets",
805
+ pathBuilder: variableSetListPath,
806
+ operationPolicy: { risk: "read", retryPolicy: "safe" },
807
+ responseExtractor: variableSetListExtract,
808
+ description: "List IaCM variable sets at the selected account/org/project scope. " +
809
+ "Response fields: items, page_count (full result size — this API is not paginated), has_more=false.",
810
+ },
811
+ get: {
812
+ method: "GET",
813
+ path: "/iacm/api/orgs/{org}/projects/{project}/variable-set/{variableSetId}",
814
+ pathBuilder: variableSetItemPath,
815
+ operationPolicy: { risk: "read", retryPolicy: "safe" },
816
+ responseExtractor: variableSetExtract,
817
+ description: "Get a variable set by identifier at the selected account/org/project scope. " +
818
+ "Pass variable_set_id via params or as resource_id. Response is the VariableSet resource.",
819
+ },
820
+ create: {
821
+ method: "POST",
822
+ path: "/iacm/api/orgs/{org}/projects/{project}/variable-set",
823
+ pathBuilder: variableSetCollectionPath,
824
+ operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
825
+ bodyBuilder: (input) => input.body,
826
+ bodySchema: variableSetCreateSchema,
827
+ skipScopeBodyInjection: true,
828
+ responseExtractor: variableSetExtract,
829
+ description: "Create an IaCM variable set. Required body fields: identifier, name. " +
830
+ "Optional: description, environment_variables, terraform_variables, terraform_variable_files, connectors. " +
831
+ "Variable maps use key → { key, value, value_type } with value_type string|secret. " +
832
+ "Response is the created VariableSet resource. " +
833
+ "MCP forwards the caller token; variable-set RBAC permissions are Experimental until iac-server enforces them.",
834
+ },
835
+ update: {
836
+ method: "PUT",
837
+ path: "/iacm/api/orgs/{org}/projects/{project}/variable-set/{variableSetId}",
838
+ pathBuilder: variableSetItemPath,
839
+ operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
840
+ bodyBuilder: (input) => input.body,
841
+ bodySchema: variableSetUpdateSchema,
842
+ skipScopeBodyInjection: true,
843
+ responseExtractor: variableSetExtract,
844
+ description: "Update an IaCM variable set by identifier (variable_set_id / resource_id). " +
845
+ "HTTP PUT with full-replacement semantics for collections (same behavior as iac-server): " +
846
+ "required body fields are name, terraform_variables, and environment_variables " +
847
+ "(send maps from harness_get, or {} to clear). " +
848
+ "connectors and terraform_variable_files are also full-replacement — omit or [] clears them; " +
849
+ "prefer get-then-put and resend current values to keep them. " +
850
+ "Response is the updated VariableSet resource. " +
851
+ "MCP forwards the caller token; variable-set RBAC permissions are Experimental until iac-server enforces them.",
852
+ },
227
853
  },
228
854
  },
229
855
  // ─── Terraform Resources / Outputs / DataSources ───────────────────────
@@ -299,14 +925,27 @@ export const iacmToolset = {
299
925
  resourceType: "iacm_module",
300
926
  displayName: "IaCM Module Registry",
301
927
  description: "Terraform modules registered in the Harness IaCM module registry. " +
302
- "Modules have a name, version, provider, source_repo, tags, and invocation_count. " +
928
+ "Modules have a name, system/provider, version metadata, source_repo, tags, and invocation_count. " +
303
929
  "PAGINATION: Results are capped at 30 per page (1-based). " +
304
930
  "Response includes has_more (true = more pages exist) and page_count (items on THIS page only). " +
305
931
  "IMPORTANT: page_count is NOT the total module count. " +
306
932
  "To find the true total, paginate until has_more=false and sum the page_counts. " +
307
- "Use harness_get with the numeric id field (from list) to fetch full module details.",
933
+ "Use harness_get with the numeric/UUID id from list (NOT the module name). " +
934
+ "Use harness_create with body.name + body.system to register a module. " +
935
+ "Use harness_update with id/resource_id plus body.name + body.system (get-then-put for optionals). " +
936
+ "Create/update return the module resource. Registry RBAC (iac_registry_view / iac_registry_edit) is Active.\n" +
937
+ "SCOPE: modules live at account, org, or project scope. Every operation (list/get/create/update) sends the same " +
938
+ "scope_org/scope_project query params, so a module is visible where it was created. " +
939
+ "Set resource_scope='account' (or pass neither org_id nor project_id) for the account registry, " +
940
+ "resource_scope='org' with org_id for an org module, or resource_scope='project' with org_id + project_id for a project module. " +
941
+ "When resource_scope is omitted, org_id/project_id are used only if explicitly passed — configured HARNESS_ORG/HARNESS_PROJECT " +
942
+ "defaults are NOT applied, so an ambient project config cannot silently turn an account module into a project one. " +
943
+ "NOTE: body.org / body.project (when present) locate the module's Git connector and are independent of this visibility scope.",
308
944
  toolset: "iacm",
309
945
  scope: "account",
946
+ supportedScopes: ["account", "org", "project"],
947
+ scopeOptional: true,
948
+ scopeParams: { org: "scope_org", project: "scope_project" },
310
949
  identifierFields: ["id"],
311
950
  listFilterFields: [
312
951
  {
@@ -340,6 +979,8 @@ export const iacmToolset = {
340
979
  operationPolicy: { risk: "read", retryPolicy: "safe" },
341
980
  responseExtractor: moduleListExtract,
342
981
  description: "List Terraform modules in the IaCM module registry (30 per page; page >= 1). " +
982
+ "Scope: defaults to the account registry; pass resource_scope='org' + org_id or resource_scope='project' + org_id/project_id " +
983
+ "(or the ids alone) to list modules registered at that scope — the same scope accepted by create/update. " +
343
984
  "Response fields: items (module objects), page_count (THIS page only), has_more, pagination_note. " +
344
985
  "CRITICAL: page_count is the count for this page only — NEVER report it as the total module count. " +
345
986
  "If has_more=true, call again with page+1. " +
@@ -350,11 +991,146 @@ export const iacmToolset = {
350
991
  path: "/iacm/api/modules/{moduleId}",
351
992
  pathParams: { id: "moduleId" },
352
993
  operationPolicy: { risk: "read", retryPolicy: "safe" },
353
- responseExtractor: (raw) => raw,
994
+ responseExtractor: moduleExtract,
354
995
  description: "Get full details for a specific IaCM module. " +
355
- "IMPORTANT: id must be the numeric id from the list response (e.g. '4640'), " +
996
+ "IMPORTANT: id must be the numeric/UUID id from the list response (e.g. '4640'), " +
356
997
  "NOT the module name (e.g. 'buha-module-v2'). " +
357
- "Always call harness_list on iacm_module first to get the numeric id, then call harness_get.",
998
+ "Always call harness_list on iacm_module first to get the id, then call harness_get. " +
999
+ "For org/project modules pass the same resource_scope (or org_id/project_id) used to list them.",
1000
+ },
1001
+ create: {
1002
+ method: "POST",
1003
+ path: "/iacm/api/modules",
1004
+ operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
1005
+ bodyBuilder: (input) => input.body,
1006
+ bodySchema: moduleCreateSchema,
1007
+ skipScopeBodyInjection: true,
1008
+ responseExtractor: moduleExtract,
1009
+ description: "Create a module in the IaCM module registry. Required body fields: name, system. " +
1010
+ "Optional body fields include repository metadata, tags, storage_type, and onboarding pipeline settings. " +
1011
+ "Scope: omit org_id/project_id (or set resource_scope='account') for an account module; pass resource_scope='org' + org_id " +
1012
+ "or resource_scope='project' + org_id/project_id to register it at org/project scope — these become scope_org/scope_project " +
1013
+ "query params, never body fields. List/get accept the same scope, so the module is discoverable where it was created. " +
1014
+ "Returns the module resource (includes id for later get/update). " +
1015
+ "Registry RBAC is Active (iac_registry_edit). medium_write — requires confirmation.",
1016
+ },
1017
+ update: {
1018
+ method: "PUT",
1019
+ path: "/iacm/api/modules/{moduleId}",
1020
+ pathParams: { id: "moduleId" },
1021
+ operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
1022
+ bodyBuilder: (input) => input.body,
1023
+ bodySchema: moduleUpdateSchema,
1024
+ skipScopeBodyInjection: true,
1025
+ responseExtractor: moduleExtract,
1026
+ description: "Update a module in the IaCM module registry by numeric/UUID id (id / resource_id). " +
1027
+ "Required body fields: name, system. " +
1028
+ "Pass the same scope you created the module at (resource_scope, or org_id/project_id) so the request targets the right registry — " +
1029
+ "these become scope_org/scope_project query params, never body fields. " +
1030
+ "Prefer harness_get then PUT the full desired module — omitting optional fields may clear them. " +
1031
+ "Returns the module resource. Registry RBAC is Active (iac_registry_edit). medium_write — requires confirmation.",
1032
+ },
1033
+ },
1034
+ },
1035
+ // ─── Provider Registry ─────────────────────────────────────────────────
1036
+ {
1037
+ resourceType: "iacm_provider",
1038
+ displayName: "IaCM Provider Registry",
1039
+ description: "Private Terraform/OpenTofu providers registered in the Harness IaCM provider registry. " +
1040
+ "SCOPE: account-only — the IaCM provider-registry API has no scope_org/scope_project params " +
1041
+ "(unlike iacm_module). Ambient org_id/project_id and HARNESS_ORG/HARNESS_PROJECT are ignored. " +
1042
+ "Providers are identified by numeric/string id (not type alone). " +
1043
+ "USER FLOW: harness_create → response is { id } only → harness_get/harness_list with that id → " +
1044
+ "harness_update for versions (not metadata). " +
1045
+ "IMPORTANT: there is no metadata PUT /providers/{id}. harness_update is version-oriented: " +
1046
+ "omit params.version to create a new version (POST), or pass params.version to update an existing version (PUT). " +
1047
+ "Version create/update may return an empty body (HarnessClient normalizes to success). " +
1048
+ "Provider-registry RBAC (iac_providerregistry_*) is Experimental. " +
1049
+ "PAGINATION: Results are capped at 30 per page (1-based). page_count is THIS page only.",
1050
+ toolset: "iacm",
1051
+ scope: "account",
1052
+ identifierFields: ["id"],
1053
+ listFilterFields: [
1054
+ { name: "search_term", description: "Filter providers by partial name match", type: "string" },
1055
+ {
1056
+ name: "sort",
1057
+ description: "Sort order",
1058
+ type: "string",
1059
+ enum: ["name,ASC", "name,DESC", "updated,ASC", "updated,DESC"],
1060
+ },
1061
+ { name: "page", description: "Page number (1-based). Default: 1.", type: "number" },
1062
+ { name: "size", description: "Page size (mapped to API limit). Default: 30.", type: "number" },
1063
+ ],
1064
+ operations: {
1065
+ list: {
1066
+ method: "GET",
1067
+ path: "/iacm/api/providers",
1068
+ queryParams: {
1069
+ page: "page",
1070
+ size: "limit",
1071
+ search_term: "searchTerm",
1072
+ sort: "sort",
1073
+ },
1074
+ pageOneIndexed: true,
1075
+ operationPolicy: { risk: "read", retryPolicy: "safe" },
1076
+ responseExtractor: providerListExtract,
1077
+ description: "List providers in the account-scoped IaCM provider registry (30 per page; page >= 1). " +
1078
+ "org_id/project_id are ignored — this API has no scope_org/scope_project. " +
1079
+ "Response fields: items, page_count (THIS page only), has_more, pagination_note.",
1080
+ },
1081
+ get: {
1082
+ method: "GET",
1083
+ path: "/iacm/api/providers/{providerId}",
1084
+ pathParams: { id: "providerId" },
1085
+ operationPolicy: { risk: "read", retryPolicy: "safe" },
1086
+ responseExtractor: providerExtract,
1087
+ description: "Get a provider by id. Use the id from harness_create (response.id) or harness_list — " +
1088
+ "not the provider type string alone. Account-scoped; org_id/project_id are ignored.",
1089
+ },
1090
+ create: {
1091
+ method: "POST",
1092
+ path: "/iacm/api/providers/{type}",
1093
+ pathBuilder: providerCreatePath,
1094
+ operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
1095
+ bodyBuilder: providerCreateBody,
1096
+ bodySchema: providerCreateSchema,
1097
+ skipScopeBodyInjection: true,
1098
+ responseExtractor: providerExtract,
1099
+ description: "Create a provider registry entry. Required body.type becomes the path segment; optional body.description is sent as JSON. " +
1100
+ "Response is { id } only — not the full provider. Follow up with harness_get " +
1101
+ "(resource_type=iacm_provider, id=<id>) or harness_list before version updates. " +
1102
+ "Account-scoped (no org/project query params). " +
1103
+ "Provider-registry RBAC is Experimental (iac_providerregistry_edit). medium_write — requires confirmation.",
1104
+ },
1105
+ update: {
1106
+ method: "PUT",
1107
+ path: "/iacm/api/providers/{providerId}/version/{version}",
1108
+ methodBuilder: providerVersionMethod,
1109
+ pathBuilder: providerVersionPath,
1110
+ operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
1111
+ bodyBuilder: providerVersionBody,
1112
+ bodySchema: providerUpdateSchema,
1113
+ skipScopeBodyInjection: true,
1114
+ responseExtractor: providerExtract,
1115
+ paramsSchema: {
1116
+ fields: [
1117
+ {
1118
+ name: "id",
1119
+ required: true,
1120
+ description: "Provider id from create/list (also accepted as resource_id) — not the type string",
1121
+ },
1122
+ {
1123
+ name: "version",
1124
+ required: false,
1125
+ description: "When set, updates that existing version via PUT. When omitted, creates a new version via POST (include body.version).",
1126
+ },
1127
+ ],
1128
+ },
1129
+ description: "Version-oriented provider update — NOT a metadata PATCH. Use the id from harness_create/harness_list (not type). " +
1130
+ "Create version: omit params.version and pass body.version + protocol + gpg_key_id (POST /providers/{id}/version). " +
1131
+ "Update version: pass params.version with body.protocol + gpg_key_id (PUT /providers/{id}/version/{version}). " +
1132
+ "Version writes may return an empty 201/204 body; HarnessClient returns { status: \"SUCCESS\", message: \"No content\" }. " +
1133
+ "Account-scoped (no org/project query params). Provider-registry RBAC is Experimental. medium_write — requires confirmation.",
358
1134
  },
359
1135
  },
360
1136
  },