@elitedcs/ghl-mcp 3.34.5 → 3.34.7

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.
package/CHANGELOG.md CHANGED
@@ -1,20 +1,46 @@
1
1
  # Changelog
2
2
 
3
- ## 3.34.5Update-Opportunity actions are now creatable via the builder
4
-
5
- - **`update_workflow_actions` can now create `internal_update_opportunity`
6
- ("Update Opportunity" / move-to-stage) actions from scratch.** v3.34.4 only
7
- guarded against the cryptic "action has a corrupted type" save failure; this
8
- release fixes the root cause. The bug: `workflowsActionType: "INTERNAL"` was
9
- being placed inside `attributes`, but GHL's deserializer reads that
10
- discriminator at the **node** level — a nested one is rejected as corrupted.
11
- The builder now normalizes the action to GHL's real node shape
12
- (`workflowsActionType` hoisted to the node level; `allowBackward` + per-field
13
- `__customInputs__` scaffolding added). Captured from a real UI-built node and
14
- **proven against a live builder save**. Just pass `__customInputFields__` with
15
- a `pipelineId` and a `pipelineStageId` entry (validated both required, so it
16
- never saves a silent no-op). Round-tripped actions normalize idempotently. Tool
17
- + schema docs updated; the v3.34.4 "build it in the UI" guidance is superseded.
3
+ ## 3.34.7Audit now sees UUID pipeline-stage ids (dead-stage silent failure)
4
+
5
+ - **`audit_workflows` / `validate_workflow` now catch a dead pipeline STAGE
6
+ reference inside move/create/update-opportunity actions.** GHL pipeline **stage**
7
+ ids are hyphenated UUIDs (e.g. `ce81a710-7b63-4324-b5dc-c170ad5b8773`), but the
8
+ id-shape gate (`ID_SHAPE`) only matched hyphen-free alphanumeric ids. Every
9
+ action-level stage reference (`internal_update_opportunity`,
10
+ `internal_create_opportunity`, `create_opportunity`) was therefore silently
11
+ dropped before validation: a workflow that moved or created an opportunity into a
12
+ **deleted** stage passed the audit clean. This is exactly the silent-failure class
13
+ the tool exists to catch, and it affected every account whose pipelines use UUID
14
+ stages (the GHL norm). `validate_workflow` on such a workflow now reports both the
15
+ pipeline and the stage reference (previously only the pipeline).
16
+ - **Fix:** `ID_SHAPE` accepts both shapes — `[A-Za-z0-9]{17,}` and the canonical
17
+ UUID. The broadening cannot cause a false break: display names ("Jane Doe") and
18
+ standard field names still never match, and anything that cannot be resolved is
19
+ reported as `unverified`, never `error`. Trigger-condition stage refs were already
20
+ un-gated and unaffected.
21
+ - Tests: 4 new regression cases (dead UUID stage in each of the three opportunity
22
+ action shapes must flag; a valid UUID stage must produce zero errors and now be
23
+ extracted). Full suite 273 pass / 1 skip.
24
+
25
+ ## 3.34.6 — Revert v3.34.5 (internal_update_opportunity save-abort regression)
26
+
27
+ - **Reverts v3.34.5.** That release made `validateActionChain` throw on *every*
28
+ `internal_update_opportunity` action — including ones round-tripped from
29
+ `get_workflow_full` — unless `__customInputFields__` carried both a `pipelineId`
30
+ and a `pipelineStageId` entry, and it removed v3.34.4's id-based pass-through for
31
+ already-saved nodes. Because that check runs at the top of `buildActionChain`,
32
+ one such node aborted the **entire** `update_workflow_actions` save. Re-saving any
33
+ workflow that already contained an "Update Opportunity" / move-to-stage action
34
+ could hard-fail. The v3.34.5 normalizer was also not shape-preserving for a real
35
+ round-tripped node (it dropped extra per-field keys), risking corruption on
36
+ re-save. An independent (non-Claude) audit confirmed both failure modes.
37
+ - **v3.34.4 is retained** — its merge-tag blank-render audit and the honest
38
+ Update-Opportunity guard were audited clean. Creating an
39
+ `internal_update_opportunity` from scratch is guarded again with the clear "build
40
+ that one step in the GHL UI" message; a round-tripped one passes through untouched.
41
+ - Net effect: restores the proven v3.34.3 / v3.34.4 save behavior. A correct
42
+ from-scratch builder for Update-Opportunity will be re-attempted separately, with
43
+ live verification, before any future republish.
18
44
 
19
45
  ## 3.34.4 — Audit catches blank-render merge tags + honest guard on Update-Opportunity
20
46
 
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ var require_package = __commonJS({
31
31
  "package.json"(exports2, module2) {
32
32
  module2.exports = {
33
33
  name: "@elitedcs/ghl-mcp",
34
- version: "3.34.5",
34
+ version: "3.34.7",
35
35
  mcpName: "io.github.drjerryrelth/ghl-command",
36
36
  description: "GoHighLevel MCP Server for Claude. 218 tools \u2014 full CRM, automation, marketing control, account-wide workflow audit, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
37
37
  main: "dist/index.js",
@@ -1170,30 +1170,6 @@ function normalizeRemoveFromWorkflowAction(action) {
1170
1170
  }
1171
1171
  return { ...action, attributes: next };
1172
1172
  }
1173
- function normalizeInternalUpdateOpportunityAction(action) {
1174
- if (action.type !== "internal_update_opportunity") return action;
1175
- const attrIn = action.attributes && typeof action.attributes === "object" ? action.attributes : {};
1176
- const fieldsIn = Array.isArray(attrIn.__customInputFields__) ? attrIn.__customInputFields__ : [];
1177
- const __customInputFields__ = fieldsIn.map((raw) => {
1178
- const f = raw && typeof raw === "object" ? raw : {};
1179
- return {
1180
- __customInputs__: f.__customInputs__ && typeof f.__customInputs__ === "object" ? f.__customInputs__ : {},
1181
- dataType: typeof f.dataType === "string" ? f.dataType : "SINGLE_OPTIONS",
1182
- filterField: f.filterField,
1183
- value: f.value,
1184
- valueFieldType: typeof f.valueFieldType === "string" ? f.valueFieldType : "select"
1185
- };
1186
- });
1187
- const attributes = {
1188
- ...attrIn,
1189
- type: "internal_update_opportunity",
1190
- allowBackward: typeof attrIn.allowBackward === "boolean" ? attrIn.allowBackward : false,
1191
- __customInputs__: attrIn.__customInputs__ && typeof attrIn.__customInputs__ === "object" ? attrIn.__customInputs__ : {},
1192
- __customInputFields__
1193
- };
1194
- delete attributes.workflowsActionType;
1195
- return { ...action, workflowsActionType: "INTERNAL", attributes };
1196
- }
1197
1173
  function hasId(action) {
1198
1174
  return typeof action.id === "string" && action.id.length > 0;
1199
1175
  }
@@ -1231,10 +1207,12 @@ function validateActionChain(actions) {
1231
1207
  if (!attr.startAfter) throw new Error(`Wait action "${action.name}" missing required 'startAfter' in attributes.`);
1232
1208
  break;
1233
1209
  case "internal_update_opportunity": {
1234
- const cif = Array.isArray(attr.__customInputFields__) ? attr.__customInputFields__ : null;
1235
- if (!cif) throw new Error(`Internal update opportunity action "${action.name}" missing '__customInputFields__' array (needs pipelineId + pipelineStageId entries).`);
1236
- const hasTarget = (ff) => cif.some((f) => f && f.filterField === ff && typeof f.value === "string" && f.value.length > 0);
1237
- if (!hasTarget("pipelineId") || !hasTarget("pipelineStageId")) throw new Error(`Internal update opportunity action "${action.name}" needs both a pipelineId and a pipelineStageId entry (each with a value) in '__customInputFields__'. Use get_pipelines to find the IDs.`);
1210
+ if (!hasId(action)) {
1211
+ throw new Error(
1212
+ `Action "${action.name}" (internal_update_opportunity / "Update Opportunity" / move-to-stage) cannot be created via the API yet: GHL's workflow builder rejects a synthesized node with "action has a corrupted type", which silently fails the whole save. Build this one step in the GHL UI (Workflow > add action > Update Opportunity), or pass through an existing one read via get_workflow_full (it keeps its id). Every other action type in this call works.`
1213
+ );
1214
+ }
1215
+ if (!Array.isArray(attr.__customInputFields__)) throw new Error(`Internal update opportunity action "${action.name}" missing '__customInputFields__' array.`);
1238
1216
  break;
1239
1217
  }
1240
1218
  case "remove_from_workflow":
@@ -1855,7 +1833,7 @@ ${errorBody}`
1855
1833
  */
1856
1834
  buildActionChain(actions) {
1857
1835
  validateActionChain(actions);
1858
- const linked = actions.map(normalizeRemoveFromWorkflowAction).map(normalizeInternalUpdateOpportunityAction).map((action, i) => {
1836
+ const linked = actions.map(normalizeRemoveFromWorkflowAction).map((action, i) => {
1859
1837
  const copy = { ...action };
1860
1838
  if (!copy.id) {
1861
1839
  copy.id = crypto.randomUUID();
@@ -5422,7 +5400,7 @@ function registerWorkflowBuilderTools(server2, client) {
5422
5400
  );
5423
5401
  server2.tool(
5424
5402
  "update_workflow_actions",
5425
- "Update a workflow's actions (steps), triggers, name, or status. IMPORTANT: Call get_workflow_full first to see the current state before updating. Handles version tracking automatically. Uses the internal builder API (requires Firebase auth). Action types: sms, email, add_contact_tag, remove_contact_tag, wait, webhook, internal_update_opportunity, custom_code, update_contact_field, add_notes, internal_notification, task_notification, remove_from_workflow, add_to_workflow, goto, transition, workflow_goal. NOTE: internal_update_opportunity (GHL's 'Update Opportunity' / move-to-stage action) is creatable \u2014 pass attributes.__customInputFields__ with a pipelineId entry and a pipelineStageId entry (use get_pipelines for the IDs); the builder normalizes it to GHL's node shape (workflowsActionType is set at the node level for you). For if/else, call build_if_else_branch and include its returned nodes; if_else is a node discriminator, not a standalone action. For goal events (exit-on-condition nodes), call build_goal_event to get a correctly-shaped workflow_goal node \u2014 wire it in by setting the prior action's `next` to the goal node's id. Trigger types: all 57 native GHL trigger types have typed validation; any unknown trigger type passes through via a permissive fallback so reads never crash.",
5403
+ "Update a workflow's actions (steps), triggers, name, or status. IMPORTANT: Call get_workflow_full first to see the current state before updating. Handles version tracking automatically. Uses the internal builder API (requires Firebase auth). Action types: sms, email, add_contact_tag, remove_contact_tag, wait, webhook, internal_update_opportunity, custom_code, update_contact_field, add_notes, internal_notification, task_notification, remove_from_workflow, add_to_workflow, goto, transition, workflow_goal. NOTE: internal_update_opportunity (GHL's 'Update Opportunity' / move-to-stage action) cannot be created fresh through this API \u2014 GHL's builder rejects a synthesized node with 'action has a corrupted type' and fails the whole save; add that single step in the GHL UI, or pass through one already read via get_workflow_full (it keeps its node id). For if/else, call build_if_else_branch and include its returned nodes; if_else is a node discriminator, not a standalone action. For goal events (exit-on-condition nodes), call build_goal_event to get a correctly-shaped workflow_goal node \u2014 wire it in by setting the prior action's `next` to the goal node's id. Trigger types: all 57 native GHL trigger types have typed validation; any unknown trigger type passes through via a permissive fallback so reads never crash.",
5426
5404
  {
5427
5405
  workflowId: import_zod33.z.string().describe("The workflow ID to update."),
5428
5406
  name: import_zod33.z.string().optional().describe("New workflow name."),
@@ -8538,7 +8516,7 @@ ${errors.join("\n")}` : "\nNo errors!",
8538
8516
  // src/tools/validators.ts
8539
8517
  var import_zod47 = require("zod");
8540
8518
  var ALL_CATEGORIES = ["pipeline", "stage", "custom_field", "user", "workflow", "form", "calendar", "survey"];
8541
- var ID_SHAPE = /^[A-Za-z0-9]{17,}$/;
8519
+ var ID_SHAPE = /^([A-Za-z0-9]{17,}|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;
8542
8520
  function isIdShaped(s) {
8543
8521
  return typeof s === "string" && ID_SHAPE.test(s);
8544
8522
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.34.5",
3
+ "version": "3.34.7",
4
4
  "mcpName": "io.github.drjerryrelth/ghl-command",
5
5
  "description": "GoHighLevel MCP Server for Claude. 218 tools — full CRM, automation, marketing control, account-wide workflow audit, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
6
6
  "main": "dist/index.js",
@@ -165,7 +165,7 @@
165
165
  "workflowsActionType": "INTERNAL",
166
166
  "type": "internal_update_opportunity"
167
167
  },
168
- "notes": "CREATABLE (v3.34.5+): pass __customInputFields__ with a pipelineId entry and a pipelineStageId entry; the builder sets workflowsActionType at the NODE level for you. (workflowsActionType must NOT be nested in attributes GHL rejects a nested one as 'action has a corrupted type'. The builder hoists it automatically; the example here shows it in attributes only for backward-compat input.) Proven against a live builder save 2026-06-14. Use pipeline and stage IDs (not names). Use get_pipelines or list_pipelines_full to find IDs FIRST. CRITICAL: If the pipelineId or stageId don't exist in the target sub-account, GHL silently fails this action AND can kill subsequent actions in the workflow. Always verify IDs exist before deploying."
168
+ "notes": "CANNOT CREATE FRESH VIA THIS API: GHL's builder rejects a synthesized internal_update_opportunity node with 'action has a corrupted type' and fails the whole save. Build this step in the GHL UI, or round-trip one read via get_workflow_full (it keeps its node id). The shape below is correct for READING / round-tripping. Use pipeline and stage IDs (not names). Use get_pipelines or list_pipelines_full to find IDs FIRST. CRITICAL: If the pipelineId or stageId don't exist in the target sub-account, GHL silently fails this action AND can kill subsequent actions in the workflow. Always verify IDs exist before deploying."
169
169
  },
170
170
 
171
171
  "_if_else_branching": {