@elitedcs/ghl-mcp 3.40.0 → 3.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +56 -16
- package/package.json +1 -1
- package/templates/action-schemas.json +1 -1
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
|
+
version: "3.41.0",
|
|
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",
|
|
@@ -705,6 +705,12 @@ function decodeFirebaseClaims(idToken) {
|
|
|
705
705
|
}
|
|
706
706
|
}
|
|
707
707
|
|
|
708
|
+
// src/id-shape.ts
|
|
709
|
+
var GHL_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})$/;
|
|
710
|
+
function isIdShaped(s) {
|
|
711
|
+
return typeof s === "string" && GHL_ID_SHAPE.test(s);
|
|
712
|
+
}
|
|
713
|
+
|
|
708
714
|
// src/trigger-schemas.ts
|
|
709
715
|
var import_zod3 = require("zod");
|
|
710
716
|
var TriggerActionSchema = import_zod3.z.object({
|
|
@@ -1170,6 +1176,31 @@ function normalizeRemoveFromWorkflowAction(action) {
|
|
|
1170
1176
|
}
|
|
1171
1177
|
return { ...action, attributes: next };
|
|
1172
1178
|
}
|
|
1179
|
+
function normalizeInternalUpdateOpportunityAction(action) {
|
|
1180
|
+
if (action.type !== "internal_update_opportunity") return action;
|
|
1181
|
+
const attrIn = action.attributes && typeof action.attributes === "object" ? action.attributes : {};
|
|
1182
|
+
const fieldsIn = Array.isArray(attrIn.__customInputFields__) ? attrIn.__customInputFields__ : [];
|
|
1183
|
+
const __customInputFields__ = fieldsIn.map((raw) => {
|
|
1184
|
+
const f = raw && typeof raw === "object" ? raw : {};
|
|
1185
|
+
return {
|
|
1186
|
+
...f,
|
|
1187
|
+
// shape-preserving — keep every key a real node carries
|
|
1188
|
+
__customInputs__: f.__customInputs__ && typeof f.__customInputs__ === "object" ? f.__customInputs__ : {},
|
|
1189
|
+
dataType: typeof f.dataType === "string" ? f.dataType : "SINGLE_OPTIONS",
|
|
1190
|
+
valueFieldType: typeof f.valueFieldType === "string" ? f.valueFieldType : "select"
|
|
1191
|
+
};
|
|
1192
|
+
});
|
|
1193
|
+
const attributes = {
|
|
1194
|
+
...attrIn,
|
|
1195
|
+
// shape-preserving
|
|
1196
|
+
type: "internal_update_opportunity",
|
|
1197
|
+
allowBackward: typeof attrIn.allowBackward === "boolean" ? attrIn.allowBackward : false,
|
|
1198
|
+
__customInputs__: attrIn.__customInputs__ && typeof attrIn.__customInputs__ === "object" ? attrIn.__customInputs__ : {},
|
|
1199
|
+
__customInputFields__
|
|
1200
|
+
};
|
|
1201
|
+
delete attributes.workflowsActionType;
|
|
1202
|
+
return { ...action, workflowsActionType: "INTERNAL", attributes };
|
|
1203
|
+
}
|
|
1173
1204
|
function hasId(action) {
|
|
1174
1205
|
return typeof action.id === "string" && action.id.length > 0;
|
|
1175
1206
|
}
|
|
@@ -1185,7 +1216,7 @@ function getStringArray(value) {
|
|
|
1185
1216
|
}
|
|
1186
1217
|
return value;
|
|
1187
1218
|
}
|
|
1188
|
-
function validateActionChain(actions) {
|
|
1219
|
+
function validateActionChain(actions, existingIds) {
|
|
1189
1220
|
const byId = new Map(actions.filter(hasId).map((action) => [action.id, action]));
|
|
1190
1221
|
for (const action of actions) {
|
|
1191
1222
|
const attr = isRecord(action.attributes) ? action.attributes : void 0;
|
|
@@ -1207,12 +1238,24 @@ function validateActionChain(actions) {
|
|
|
1207
1238
|
if (!attr.startAfter) throw new Error(`Wait action "${action.name}" missing required 'startAfter' in attributes.`);
|
|
1208
1239
|
break;
|
|
1209
1240
|
case "internal_update_opportunity": {
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
)
|
|
1241
|
+
const isRoundTripped = hasId(action) && (existingIds ? existingIds.has(action.id) : true);
|
|
1242
|
+
if (!isRoundTripped) {
|
|
1243
|
+
const cif = Array.isArray(attr.__customInputFields__) ? attr.__customInputFields__ : null;
|
|
1244
|
+
if (!cif) {
|
|
1245
|
+
throw new Error(
|
|
1246
|
+
`Internal update opportunity action "${action.name}" missing '__customInputFields__' array (needs a pipelineId entry and a pipelineStageId entry). Use get_pipelines to find the IDs.`
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
const target = (ff) => cif.find((f) => f && f.filterField === ff);
|
|
1250
|
+
for (const ff of ["pipelineId", "pipelineStageId"]) {
|
|
1251
|
+
const t = target(ff);
|
|
1252
|
+
if (!t || !isIdShaped(t.value)) {
|
|
1253
|
+
throw new Error(
|
|
1254
|
+
`Internal update opportunity action "${action.name}" needs a valid ${ff} entry (an id, not a name) in '__customInputFields__'. Use get_pipelines / list_pipelines_full to find the IDs. (A missing or non-existent id makes GHL silently fail this action and can kill the rest.)`
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1214
1258
|
}
|
|
1215
|
-
if (!Array.isArray(attr.__customInputFields__)) throw new Error(`Internal update opportunity action "${action.name}" missing '__customInputFields__' array.`);
|
|
1216
1259
|
break;
|
|
1217
1260
|
}
|
|
1218
1261
|
case "remove_from_workflow":
|
|
@@ -1686,7 +1729,8 @@ ${errorBody}`
|
|
|
1686
1729
|
}
|
|
1687
1730
|
const currentActions = current.workflowData?.templates || [];
|
|
1688
1731
|
const newActions = updates.actions ?? currentActions;
|
|
1689
|
-
const
|
|
1732
|
+
const existingActionIds = new Set(currentActions.filter(hasId).map((a) => a.id));
|
|
1733
|
+
const linkedActions = this.buildActionChain(newActions, existingActionIds);
|
|
1690
1734
|
const currentIds = new Set(currentActions.filter(hasId).map((a) => a.id));
|
|
1691
1735
|
const newIds = new Set(linkedActions.filter(hasId).map((a) => a.id));
|
|
1692
1736
|
const createdSteps = linkedActions.filter(hasId).filter((a) => !currentIds.has(a.id)).map((a) => a.id);
|
|
@@ -1831,9 +1875,9 @@ ${errorBody}`
|
|
|
1831
1875
|
* saves correctly but subsequent actions get skipped during execution.
|
|
1832
1876
|
* Only use arrays for branching nodes (if/else) that point to multiple targets.
|
|
1833
1877
|
*/
|
|
1834
|
-
buildActionChain(actions) {
|
|
1835
|
-
validateActionChain(actions);
|
|
1836
|
-
const linked = actions.map(normalizeRemoveFromWorkflowAction).map((action, i) => {
|
|
1878
|
+
buildActionChain(actions, existingIds) {
|
|
1879
|
+
validateActionChain(actions, existingIds);
|
|
1880
|
+
const linked = actions.map(normalizeRemoveFromWorkflowAction).map(normalizeInternalUpdateOpportunityAction).map((action, i) => {
|
|
1837
1881
|
const copy = { ...action };
|
|
1838
1882
|
if (!copy.id) {
|
|
1839
1883
|
copy.id = crypto.randomUUID();
|
|
@@ -5512,7 +5556,7 @@ function registerWorkflowBuilderTools(server2, client) {
|
|
|
5512
5556
|
);
|
|
5513
5557
|
server2.tool(
|
|
5514
5558
|
"update_workflow_actions",
|
|
5515
|
-
"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)
|
|
5559
|
+
"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 'Create/Update Opportunity' / move-to-stage action) IS creatable from scratch \u2014 this tool normalizes it to GHL's exact node shape (workflowsActionType:'INTERNAL' hoisted to the NODE level; a nested copy is what makes GHL reject the node as 'action has a corrupted type'). A synthesized node needs both a pipelineId and a pipelineStageId entry in attributes.__customInputFields__ (use get_pipelines for the IDs); a node round-tripped via get_workflow_full keeps its id and passes through unchanged. 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.",
|
|
5516
5560
|
{
|
|
5517
5561
|
workflowId: import_zod33.z.string().describe("The workflow ID to update."),
|
|
5518
5562
|
name: import_zod33.z.string().optional().describe("New workflow name."),
|
|
@@ -8667,10 +8711,6 @@ ${errors.join("\n")}` : "\nNo errors!",
|
|
|
8667
8711
|
// src/tools/validators.ts
|
|
8668
8712
|
var import_zod47 = require("zod");
|
|
8669
8713
|
var ALL_CATEGORIES = ["pipeline", "stage", "custom_field", "user", "workflow", "form", "calendar", "survey"];
|
|
8670
|
-
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})$/;
|
|
8671
|
-
function isIdShaped(s) {
|
|
8672
|
-
return typeof s === "string" && ID_SHAPE.test(s);
|
|
8673
|
-
}
|
|
8674
8714
|
var STANDARD_CONTACT_FIELDS = /* @__PURE__ */ new Set([
|
|
8675
8715
|
"first_name",
|
|
8676
8716
|
"firstname",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elitedcs/ghl-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.41.0",
|
|
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": "
|
|
168
|
+
"notes": "CREATABLE from scratch (re-enabled v3.41.0). The discriminator workflowsActionType:'INTERNAL' MUST sit at the NODE level, never nested in attributes — a nested copy makes GHL reject the node as 'action has a corrupted type' and silently fail the whole save. update_workflow_actions normalizes this for you (hoists workflowsActionType to the node level, scaffolds allowBackward + __customInputs__, gives each __customInputFields__ entry an __customInputs__). The shape below (workflowsActionType at the node level alongside type/name/attributes) is correct for both creating and round-tripping. Use pipeline and stage IDs (not names) — get_pipelines / list_pipelines_full to find them FIRST. CRITICAL: if the pipelineId or pipelineStageId don't exist in the target sub-account, GHL silently fails this action AND can kill subsequent actions. A synthesized node needs BOTH a pipelineId and a pipelineStageId entry; a node round-tripped via get_workflow_full keeps its id and passes through unchanged."
|
|
169
169
|
},
|
|
170
170
|
|
|
171
171
|
"_if_else_branching": {
|