@elitedcs/ghl-mcp 3.7.0 → 3.8.1

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,5 +1,105 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.8.1 — Vitest test suite + pre-publish gate
4
+
5
+ **178 tools across 38 modules. Bundle: 291.3 KB (unchanged).**
6
+
7
+ Strict safety patch. No new features, no behavior changes — just tests covering the high-risk code paths so regressions get caught at build time instead of in production.
8
+
9
+ ### New: Vitest test suite (87 tests, 154ms)
10
+ Four test files added under `src/`:
11
+
12
+ - `retry.test.ts` (15 tests) — `computeRetryDelay`: Retry-After delta-seconds + HTTP-date parsing + clamping + full-jitter backoff math. Catches the "5junk" mixed-string regression and the "doubles per attempt" backoff guarantee.
13
+ - `trigger-schemas.test.ts` (60 tests) — Every one of the 57 native trigger types parses through its typed variant. Unknown future types fall through to the permissive fallback. Real-world `form_submission` payload preserves passthrough fields.
14
+ - `workflow-builder-client.test.ts` (7 tests) — `normalizeRemoveFromWorkflowAction` covers all branches (no-op when both fields present, synthesize string from array, synthesize array from string, multi-element array picks first, empty array no-op, non-target action type no-op, missing attributes no-op).
15
+ - `version-check.test.ts` (5 + tests) — `fetchLatestVersion` + `getVersionStatus` with mocked `fetch`: 200/non-2xx/network-error/missing-version-field/non-string-version cases.
16
+
17
+ ### CI gate
18
+ The npm publish workflow now runs `npm test` before the tag-vs-version check. **Tests must pass for a release to ship.** Combined with the post-publish smoke test that verifies the package boots cleanly on a fresh machine, both surfaces are now covered:
19
+
20
+ - Pre-publish: schema correctness, normalization logic, retry math
21
+ - Post-publish: server boots, tool registry includes required tools
22
+
23
+ ### New npm scripts
24
+ - `npm test` — run once, exit
25
+ - `npm run test:watch` — re-run on file change during development
26
+
27
+ ### Dev-only dep added
28
+ - `vitest@^4.1.6` (devDependencies — not shipped to consumers; the npm tarball is unchanged size)
29
+
30
+ ### `normalizeRemoveFromWorkflowAction` now exported
31
+ Was private in v3.8.0. Made public so tests can exercise it directly. Still not exposed via MCP — it's a runtime detail used by `getWorkflow` (read) and `buildActionChain` (write).
32
+
33
+ ### Files changed
34
+ - `src/retry.test.ts` (NEW)
35
+ - `src/trigger-schemas.test.ts` (NEW)
36
+ - `src/workflow-builder-client.test.ts` (NEW)
37
+ - `src/version-check.test.ts` (NEW)
38
+ - `src/workflow-builder-client.ts` — `normalizeRemoveFromWorkflowAction` now exported
39
+ - `package.json` — `vitest` dev dep + `test` / `test:watch` scripts
40
+ - `.github/workflows/publish.yml` — added pre-publish test step
41
+
42
+ ## 3.8.0 — Bundle re-extraction wins (triggers + goal events) + correctness fixes
43
+
44
+ **178 tools across 38 modules. Bundle: 291.3 KB.**
45
+
46
+ Three improvements that all flow from one bundle dive into `client-app-automation-workflows.leadconnectorhq.com`:
47
+
48
+ ### Goal-event catalogue — all 10 conditions documented (was 1)
49
+ v3.6.0 shipped `build_goal_event` with one verified `goal_condition` (`review_request_clicked`) and a permissive string for everything else. The bundle's `GoalCondition` enum reveals the full set:
50
+
51
+ - `email_event` — extras: `{ stepIds: [] }`
52
+ - `link_click` — extras: `{ linkIds: [] }`
53
+ - `add_contact_tag` — extras: `{ tags: [] }` (inferred)
54
+ - `remove_contact_tag` — extras: `{ tags: [] }` (inferred)
55
+ - `appointment_status` — extras: `{ calendarId }`
56
+ - `payment_received` — extras: `{ globalProductIds: [] }`
57
+ - `form_submission` — extras: `{ formIds: [] }`
58
+ - `document_status` — extras: `{ templateId }`
59
+ - `invoice_paid` — extras: `{ invoiceStepId }`
60
+ - `review_request_clicked` — extras: `{ reviewTypes, reviewLinkId }`
61
+
62
+ `build_goal_event`'s `goal_condition` is now a `z.enum` of these 10 values (was permissive `z.string()`). The per-condition extras shapes are documented in the tool description.
63
+
64
+ ### Goal-event action enum fixed
65
+ v3.6.0's tool advertised `action: "exit" | "continue" | "goto"`. The actual `GoalAction` enum in the bundle is `continue | wait | exit` — **no `goto`**. Fixed. Also removed the `target_node_id` parameter that paired with the bogus goto. `WorkflowGoalAttributes` type updated to match.
66
+
67
+ ### 7 of 9 previously-uncaptured triggers now documented
68
+ Trigger field paths extracted from per-trigger validator functions in the bundle:
69
+
70
+ - `affiliate_created` → `affiliate.id, contact.tags`
71
+ - `scheduler_trigger` → `scheduler.interval, scheduler.cron, scheduler.frequency`
72
+ - `user_log_in` → `product.id, category.id, lesson.id, offer.id, contact.tags` (shares membership-course validator)
73
+ - `order_submission` → `order.funnel_id, order.line_item_global_product_ids, order.line_item_funnel_product_ids, payment.calendar.id, payment.global_product_ids, payment.form.id`
74
+ - `facebook_lead_gen` → `facebook.formId, facebook.pageId, contact.tags`
75
+
76
+ Plus 2 more handled explicitly:
77
+ - `conv_ai_trigger`, `conv_ai_autonomous_trigger` — confirmed fieldless (no specific validator in the bundle)
78
+ - `custom_object_created`, `custom_object_changed` — fields are user-defined per object using the `customObject.<field>` prefix; schema now documents this dynamic shape
79
+
80
+ Coverage: **57/57 native triggers, 0 type-only-pending** (was 9 type-only-pending).
81
+
82
+ ### Bug fix: `remove_from_workflow` read/write asymmetry
83
+ Don Harris flagged at the end of his 2026-05-14 funnel audit that some GHL workflows return only the `workflow_id` array on read, while writes require both that AND the `workflowId` string. Couldn't reproduce in MCP Testing — both reads I checked returned both fields — but added defensive symmetric normalization to both paths in `workflow-builder-client.ts`. If only one form is present in either direction, the other is synthesized. No-op when both are already present.
84
+
85
+ ### Bug fix: accurate "without Firebase" tool count
86
+ Empirical audit: basic-creds mode (no Firebase) actually exposes **148 tools**, not 168 as the v3.5.1 messaging claimed. Firebase adds **30 tools** across 6 modules (workflow builder, funnel builder, form builder, pipeline builder, workflow cloner, validate_workflow). `setup_ghl_mcp`, `enable_workflow_builder`, README, and CLAUDE.md all corrected.
87
+
88
+ ### Bundle re-extraction methodology (for future audits)
89
+ 1. Fetch `https://client-app-automation-workflows.leadconnectorhq.com/` → find current `index-*.js` bundle URL
90
+ 2. Pull the bundle (~9.5 MB minified)
91
+ 3. Search for `var GoalCondition=(...)` and `var GoalAction=(...)` for enum extraction
92
+ 4. Search for `<triggerName>Validator=n=>` patterns for per-trigger field paths
93
+ 5. Watch for i18n translation pollution — filter out windows containing non-ASCII Danish/German/Swedish characters
94
+
95
+ ### Files changed
96
+ - `src/trigger-schemas.ts` — 7 uncaptured triggers now documented; 2 confirmed fieldless; 2 use dynamic `customObject.*` schema
97
+ - `src/tools/workflow-builder.ts` — `build_goal_event` enum tightened; goto removed; description lists all 10 goal_condition values + extras shapes
98
+ - `src/workflow-action-types.ts` — `WorkflowGoalAttributes.action` corrected to `continue | wait | exit`
99
+ - `src/workflow-builder-client.ts` — `normalizeRemoveFromWorkflowAction()` helper applied symmetrically on read + write
100
+ - `src/setup-tool.ts` — 168 → 148; "9 workflow builder tools" → "30 tools across 6 modules"
101
+ - `README.md`, `CLAUDE.md` — synced
102
+
3
103
  ## 3.7.0 — Funnel-builder fixes (Don Harris audit)
4
104
 
5
105
  **178 tools across 38 modules. Bundle: 288.4 KB.**
package/README.md CHANGED
@@ -138,7 +138,7 @@ https://app.gohighlevel.com/v2/location/YOUR_LOCATION_ID/dashboard
138
138
 
139
139
  ## Enable Workflow Builder (Optional)
140
140
 
141
- The 9 workflow builder tools use GHL'''s internal API and require Firebase credentials. Without them, the other 168 tools work fine — you just won'''t have workflow editing.
141
+ The 30 builder + cloner + validator tools (workflow builder, funnel builder, form builder, pipeline builder, workflow cloner, validate_workflow) use GHL'''s internal API and require Firebase credentials. Without them, the other 148 tools work fine — you just won'''t have workflow/funnel/form/pipeline editing.
142
142
 
143
143
  Grab the three values from your GHL browser session, then re-run `setup_ghl_mcp` with them:
144
144
 
@@ -420,7 +420,7 @@ Re-run setup_ghl_mcp with workflow builder:
420
420
  |---|---|
421
421
  | `get_mcp_version` | Check installed version against the latest published to npm. Confirms an upgrade landed after restarting Claude. Available even before GHL credentials are configured. |
422
422
  | `health_check` | Run a full health check: npm registry + version status, GHL API key validity, default location reachability, Firebase auth status, token registry presence. Use when something feels broken. |
423
- | `enable_workflow_builder` | Add Firebase credentials to an existing install to unlock the 9 workflow builder tools. No need to re-enter license / API key / location ID. Run this any time after the basic setup, on the buyer's schedule. |
423
+ | `enable_workflow_builder` | Add Firebase credentials to an existing install to unlock 30 additional tools across 6 modules (workflow builder, funnel builder, form builder, pipeline builder, workflow cloner, validate_workflow). No need to re-enter license / API key / location ID. Run this any time after the basic setup, on the buyer's schedule. |
424
424
 
425
425
  ### Other Modules
426
426
 
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.7.0",
34
+ version: "3.8.1",
35
35
  description: "GoHighLevel MCP Server for Claude. 178 tools \u2014 full CRM, automation, marketing control, and the only programmatic GHL workflow builder.",
36
36
  main: "dist/index.js",
37
37
  bin: {
@@ -50,6 +50,8 @@ var require_package = __commonJS({
50
50
  setup: "node setup-wizard.mjs",
51
51
  start: "node dist/index.js",
52
52
  dev: "tsc --watch",
53
+ test: "vitest run",
54
+ "test:watch": "vitest",
53
55
  prepublishOnly: "npm run build"
54
56
  },
55
57
  keywords: [
@@ -87,7 +89,8 @@ var require_package = __commonJS({
87
89
  "@types/node": "^22.15.3",
88
90
  esbuild: "^0.27.4",
89
91
  open: "^11.0.0",
90
- typescript: "^5.8.3"
92
+ typescript: "^5.8.3",
93
+ vitest: "^4.1.6"
91
94
  }
92
95
  };
93
96
  }
@@ -651,15 +654,60 @@ var FacebookCommentOnPostTriggerSchema = typedTrigger("facebook_comment_on_post"
651
654
  var IgCommentOnPostTriggerSchema = typedTrigger("ig_comment_on_post", [], "partial");
652
655
  var InboundWebhookTriggerSchema = typedTrigger("inbound_webhook", [], "fieldless");
653
656
  var PaymentReceivedTriggerSchema = typedTrigger("payment_received", [], "fieldless");
654
- var AffiliateCreatedTriggerSchema = typedTrigger("affiliate_created", [], "uncaptured");
655
- var SchedulerTriggerTriggerSchema = typedTrigger("scheduler_trigger", [], "uncaptured");
656
- var UserLogInTriggerSchema = typedTrigger("user_log_in", [], "uncaptured");
657
- var OrderSubmissionTriggerSchema = typedTrigger("order_submission", [], "uncaptured");
658
- var ConvAiTriggerTriggerSchema = typedTrigger("conv_ai_trigger", [], "uncaptured");
659
- var ConvAiAutonomousTriggerTriggerSchema = typedTrigger("conv_ai_autonomous_trigger", [], "uncaptured");
660
- var CustomObjectCreatedTriggerSchema = typedTrigger("custom_object_created", [], "uncaptured");
661
- var CustomObjectChangedTriggerSchema = typedTrigger("custom_object_changed", [], "uncaptured");
662
- var FacebookLeadGenTriggerSchema = typedTrigger("facebook_lead_gen", [], "uncaptured");
657
+ var AffiliateCreatedTriggerSchema = typedTrigger("affiliate_created", [
658
+ "affiliate.id",
659
+ "contact.tags"
660
+ ]);
661
+ var SchedulerTriggerTriggerSchema = typedTrigger("scheduler_trigger", [
662
+ "scheduler.interval",
663
+ "scheduler.cron",
664
+ "scheduler.frequency"
665
+ ]);
666
+ var UserLogInTriggerSchema = typedTrigger("user_log_in", [
667
+ // Shares membershipCourseValidator with course/lesson/category triggers.
668
+ "product.id",
669
+ "category.id",
670
+ "lesson.id",
671
+ "offer.id",
672
+ "contact.tags"
673
+ ]);
674
+ var OrderSubmissionTriggerSchema = typedTrigger("order_submission", [
675
+ "order.funnel_id",
676
+ "order.line_item_global_product_ids",
677
+ "order.line_item_funnel_product_ids",
678
+ "payment.calendar.id",
679
+ "payment.global_product_ids",
680
+ "payment.form.id"
681
+ ]);
682
+ var FacebookLeadGenTriggerSchema = typedTrigger("facebook_lead_gen", [
683
+ "facebook.formId",
684
+ "facebook.pageId",
685
+ "contact.tags"
686
+ ]);
687
+ var CustomObjectCreatedTriggerSchema = TriggerCommonSchema.extend({
688
+ type: import_zod2.z.literal("custom_object_created"),
689
+ conditions: import_zod2.z.array(import_zod2.z.object({
690
+ operator: import_zod2.z.string(),
691
+ field: import_zod2.z.string().describe("Custom-object field paths use the `customObject.<your_field_name>` prefix. Field names are user-defined per custom object \u2014 query the location's custom objects to discover valid field paths."),
692
+ value: import_zod2.z.unknown().optional(),
693
+ title: import_zod2.z.string().optional(),
694
+ type: import_zod2.z.string().optional(),
695
+ id: import_zod2.z.string().optional()
696
+ }).passthrough()).optional()
697
+ });
698
+ var CustomObjectChangedTriggerSchema = TriggerCommonSchema.extend({
699
+ type: import_zod2.z.literal("custom_object_changed"),
700
+ conditions: import_zod2.z.array(import_zod2.z.object({
701
+ operator: import_zod2.z.string(),
702
+ field: import_zod2.z.string().describe("Custom-object field paths use the `customObject.<your_field_name>` prefix. Field names are user-defined per custom object \u2014 query the location's custom objects to discover valid field paths."),
703
+ value: import_zod2.z.unknown().optional(),
704
+ title: import_zod2.z.string().optional(),
705
+ type: import_zod2.z.string().optional(),
706
+ id: import_zod2.z.string().optional()
707
+ }).passthrough()).optional()
708
+ });
709
+ var ConvAiTriggerTriggerSchema = typedTrigger("conv_ai_trigger", [], "fieldless");
710
+ var ConvAiAutonomousTriggerTriggerSchema = typedTrigger("conv_ai_autonomous_trigger", [], "fieldless");
663
711
  var UnknownTriggerSchema = TriggerCommonSchema.extend({
664
712
  type: import_zod2.z.string(),
665
713
  conditions: import_zod2.z.array(import_zod2.z.record(import_zod2.z.unknown())).optional()
@@ -770,6 +818,23 @@ var CreateWorkflowResponseSchema = import_zod3.z.union([
770
818
  version: typeof data.version === "number" ? data.version : 1
771
819
  });
772
820
  });
821
+ function normalizeRemoveFromWorkflowAction(action) {
822
+ if (action.type !== "remove_from_workflow") return action;
823
+ const attrs = action.attributes;
824
+ if (!attrs || typeof attrs !== "object") return action;
825
+ const a = attrs;
826
+ const hasArr = Array.isArray(a.workflow_id) && a.workflow_id.length > 0;
827
+ const hasStr = typeof a.workflowId === "string" && a.workflowId.length > 0;
828
+ if (hasArr && hasStr) return action;
829
+ if (!hasArr && !hasStr) return action;
830
+ const next = { ...a };
831
+ if (hasArr && !hasStr) {
832
+ next.workflowId = a.workflow_id[0];
833
+ } else if (hasStr && !hasArr) {
834
+ next.workflow_id = [a.workflowId];
835
+ }
836
+ return { ...action, attributes: next };
837
+ }
773
838
  function hasId(action) {
774
839
  return typeof action.id === "string" && action.id.length > 0;
775
840
  }
@@ -1074,7 +1139,11 @@ ${errorBody}`
1074
1139
  workflowData: isRecord(raw.workflowData.workflowData) ? raw.workflowData.workflowData : { templates: [] },
1075
1140
  triggers: raw.triggers
1076
1141
  } : raw;
1077
- return WorkflowFullSchema.parse(flat);
1142
+ const parsed = WorkflowFullSchema.parse(flat);
1143
+ if (parsed.workflowData?.templates) {
1144
+ parsed.workflowData.templates = parsed.workflowData.templates.map(normalizeRemoveFromWorkflowAction);
1145
+ }
1146
+ return parsed;
1078
1147
  }
1079
1148
  /**
1080
1149
  * Create a new empty workflow
@@ -1235,7 +1304,7 @@ ${errorBody}`
1235
1304
  */
1236
1305
  buildActionChain(actions) {
1237
1306
  validateActionChain(actions);
1238
- const linked = actions.map((action, i) => {
1307
+ const linked = actions.map(normalizeRemoveFromWorkflowAction).map((action, i) => {
1239
1308
  const copy = { ...action };
1240
1309
  if (!copy.id) {
1241
1310
  copy.id = crypto.randomUUID();
@@ -4308,21 +4377,28 @@ function registerWorkflowBuilderTools(server2, client) {
4308
4377
  );
4309
4378
  server2.tool(
4310
4379
  "build_goal_event",
4311
- "Build a correctly-shaped GHL workflow goal-event node. Goal events sit inline in the action chain \u2014 when the goal condition fires during workflow execution, the configured action runs (default: exit the workflow). The previous action's `next` should point to the goal node's id; the goal node itself does not have a `next`. Verified goal_condition value (Jerry's UI sample): 'review_request_clicked' with extras { reviewTypes: ['sms', 'email'], reviewLinkId: '' }. Other goal_condition values that GHL ships are passed through verbatim \u2014 the catalogue of all goal conditions has not been fully captured yet, but the schema accepts any string value.",
4312
- {
4313
- goal_condition: import_zod31.z.string().describe("The goal-condition identifier \u2014 what the goal is watching for. Verified value: 'review_request_clicked'. Other goal_condition strings that GHL's UI exposes (e.g., tag-related, appointment-related) pass through unchanged."),
4314
- extras: import_zod31.z.record(import_zod31.z.unknown()).optional().describe("Goal-condition-specific config. Example for review_request_clicked: { reviewTypes: ['sms', 'email'], reviewLinkId: '' }. Pass an empty object {} or omit if the goal_condition has no extras."),
4315
- action: import_zod31.z.enum(["exit", "continue", "goto"]).default("exit").describe("What happens when the goal fires. 'exit' terminates the workflow path (verified). 'continue' and 'goto' are GHL UI options; 'goto' likely needs an extra targetNodeId field, not yet captured."),
4316
- target_node_id: import_zod31.z.string().optional().describe("Required when action === 'goto'. The action node id to jump to."),
4380
+ "Build a correctly-shaped GHL workflow goal-event node. Goal events sit inline in the action chain \u2014 when the goal condition fires during workflow execution, the configured action runs (default: exit the workflow). The previous action's `next` should point to the goal node's id; the goal node itself does not have a `next`. All 10 goal_condition values are catalogued (from GHL's workflow-builder JS bundle, extracted 2026-05-18): email_event, link_click, add_contact_tag, remove_contact_tag, appointment_status, payment_received, form_submission, document_status, invoice_paid, review_request_clicked. Each one has a different `extras` shape (see the `goal_condition` field's description for details).",
4381
+ {
4382
+ goal_condition: import_zod31.z.enum([
4383
+ "email_event",
4384
+ "link_click",
4385
+ "add_contact_tag",
4386
+ "remove_contact_tag",
4387
+ "appointment_status",
4388
+ "payment_received",
4389
+ "form_submission",
4390
+ "document_status",
4391
+ "invoice_paid",
4392
+ "review_request_clicked"
4393
+ ]).describe("The goal-condition identifier. Extras shape per condition: email_event \u2192 {stepIds:[]}; link_click \u2192 {linkIds:[]}; appointment_status \u2192 {calendarId}; payment_received \u2192 {globalProductIds:[]}; form_submission \u2192 {formIds:[]}; document_status \u2192 {templateId}; invoice_paid \u2192 {invoiceStepId}; review_request_clicked \u2192 {reviewTypes:['sms'|'email'], reviewLinkId}; add_contact_tag/remove_contact_tag \u2192 {tags:[]} (uncaptured but inferred)."),
4394
+ extras: import_zod31.z.record(import_zod31.z.unknown()).optional().describe("Goal-condition-specific config. See goal_condition description for the expected shape per value. Pass {} or omit for goal_conditions without extras."),
4395
+ action: import_zod31.z.enum(["exit", "continue", "wait"]).default("exit").describe("What happens when the goal fires (per GHL's GoalAction enum). 'exit' terminates the workflow path (verified). 'continue' lets it proceed to a downstream node. 'wait' pauses on the goal."),
4317
4396
  name: import_zod31.z.string().default("Goal").describe("Display name in the GHL UI."),
4318
4397
  op: import_zod31.z.enum(["or", "and"]).default("or").describe("Top-level boolean operator across segments. Default 'or'."),
4319
4398
  inner_op: import_zod31.z.enum(["or", "and"]).default("or").describe("Boolean operator across conditions within a single segment. Default 'or'.")
4320
4399
  },
4321
- async ({ goal_condition, extras, action, target_node_id, name, op, inner_op }) => {
4400
+ async ({ goal_condition, extras, action, name, op, inner_op }) => {
4322
4401
  try {
4323
- if (action === "goto" && !target_node_id) {
4324
- throw new Error("action='goto' requires target_node_id to identify which workflow action to jump to.");
4325
- }
4326
4402
  const nodeId = crypto.randomUUID();
4327
4403
  const conditionId = crypto.randomUUID();
4328
4404
  const attributes = {
@@ -4338,9 +4414,6 @@ function registerWorkflowBuilderTools(server2, client) {
4338
4414
  type: "workflow_goal",
4339
4415
  action
4340
4416
  };
4341
- if (action === "goto" && target_node_id) {
4342
- attributes.targetNodeId = target_node_id;
4343
- }
4344
4417
  const goalNode = {
4345
4418
  id: nodeId,
4346
4419
  name,
@@ -6724,7 +6797,7 @@ async function validateFirebase(firebaseKey, refreshToken) {
6724
6797
  function registerSetupTool(server2) {
6725
6798
  server2.tool(
6726
6799
  "setup_ghl_mcp",
6727
- "First-run setup for GHL Command MCP. Validates your license and GHL credentials, then writes them to a per-user credentials file. Restart Claude after this completes to load all 178 tools (168 if you skip the optional Firebase fields; add Firebase later with enable_workflow_builder).",
6800
+ "First-run setup for GHL Command MCP. Validates your license and GHL credentials, then writes them to a per-user credentials file. Restart Claude after this completes to load all 178 tools (148 if you skip the optional Firebase fields; add Firebase later with enable_workflow_builder).",
6728
6801
  {
6729
6802
  email: import_zod42.z.string().email().describe("Email used at purchase."),
6730
6803
  license_key: import_zod42.z.string().min(20).describe("License key from your purchase email."),
@@ -6779,7 +6852,7 @@ Note: Firebase credentials rejected (${fb.error}). Saved without Workflow Builde
6779
6852
  ghl_firebase_api_key: workflowBuilderEnabled ? args.ghl_firebase_api_key?.trim() : void 0,
6780
6853
  ghl_firebase_refresh_token: workflowBuilderEnabled ? args.ghl_firebase_refresh_token?.trim() : void 0
6781
6854
  });
6782
- const toolCount = workflowBuilderEnabled ? "178" : "168";
6855
+ const toolCount = workflowBuilderEnabled ? "178" : "148";
6783
6856
  const wfLine = workflowBuilderEnabled ? "Workflow Builder: enabled." : "Workflow Builder: not configured (optional).";
6784
6857
  const wfTip = workflowBuilderEnabled ? "" : "\nTo enable Workflow Builder later (8 extra tools): run enable_workflow_builder with your three Firebase values. No need to re-enter license/API key/location ID.";
6785
6858
  return {
@@ -6807,7 +6880,7 @@ Note: Firebase credentials rejected (${fb.error}). Saved without Workflow Builde
6807
6880
  function registerEnableWorkflowBuilderTool(server2) {
6808
6881
  server2.tool(
6809
6882
  "enable_workflow_builder",
6810
- "Add Firebase credentials to an existing GHL Command install to unlock the 9 workflow builder tools (create/edit/clone/delete/publish workflows, validate_workflow, get_trigger_registry). Requires you've already run setup_ghl_mcp. Capture the three Firebase values from your GHL browser session \u2014 see elitedcs.com/ghl-mcp-firebase for step-by-step DevTools instructions. Tool count goes from 168 to 178 after the next Claude restart.",
6883
+ "Add Firebase credentials to an existing GHL Command install to unlock 30 additional tools across 6 modules: workflow builder (create/edit/clone/delete/publish/validate workflows, build_if_else_branch, build_goal_event, get_trigger_registry), funnel + page builder (10 tools), form builder (5 tools), pipeline builder (5 tools), and workflow cloning. Requires you've already run setup_ghl_mcp. Capture the three Firebase values from your GHL browser session \u2014 see elitedcs.com/ghl-mcp-firebase for step-by-step DevTools instructions. Tool count goes from 148 to 178 after the next Claude restart.",
6811
6884
  {
6812
6885
  ghl_user_id: import_zod42.z.string().min(10).describe("Firebase User ID (uid). DevTools \u2192 Application \u2192 IndexedDB \u2192 firebaseLocalStorageDb \u2192 firebaseLocalStorage \u2192 the value.uid field of the firebase:authUser row."),
6813
6886
  ghl_firebase_api_key: import_zod42.z.string().min(10).describe("Firebase API Key starting with 'AIza'. The string between 'firebase:authUser:' and ':[DEFAULT]' in the row's Key column."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.7.0",
3
+ "version": "3.8.1",
4
4
  "description": "GoHighLevel MCP Server for Claude. 178 tools — full CRM, automation, marketing control, and the only programmatic GHL workflow builder.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -19,6 +19,8 @@
19
19
  "setup": "node setup-wizard.mjs",
20
20
  "start": "node dist/index.js",
21
21
  "dev": "tsc --watch",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
22
24
  "prepublishOnly": "npm run build"
23
25
  },
24
26
  "keywords": [
@@ -56,6 +58,7 @@
56
58
  "@types/node": "^22.15.3",
57
59
  "esbuild": "^0.27.4",
58
60
  "open": "^11.0.0",
59
- "typescript": "^5.8.3"
61
+ "typescript": "^5.8.3",
62
+ "vitest": "^4.1.6"
60
63
  }
61
64
  }