@elitedcs/ghl-mcp 3.10.0 → 3.11.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/CHANGELOG.md CHANGED
@@ -1,5 +1,81 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.11.0 — Smart Lists CRUD (gap-closure round 3)
4
+
5
+ **196 tools across 40 modules. Bundle: 315.1 KB.**
6
+
7
+ Closes the Smart Lists gap with full CRUD. Smart Lists are saved searches over contacts or opportunities — agencies use them heavily for segmentation, campaign targeting, and workflow trigger filters.
8
+
9
+ ### 5 new tools (Firebase-gated)
10
+
11
+ - **`list_smart_lists`** — paginated by `objectKey` (`contacts` or `opportunity`). Supports free-text name search.
12
+ - **`get_smart_list`** — single list with full filter spec, columns, permissions.
13
+ - **`create_smart_list`** — required: name + objectKey. Optional: filters, columns, pipelineIds, defaultInPipelines.
14
+ - **`update_smart_list`** — partial updates work. objectKey can't be changed after creation.
15
+ - **`delete_smart_list`** — confirm-gated.
16
+
17
+ ### Discovery story
18
+
19
+ The workflow-builder bundle declares `smartListBackendURL` as config but doesn't actually use it. Real path was in the same bundle under `SmartListService extends BaseService`:
20
+
21
+ ```js
22
+ new SmartListService(`${config.marketPlaceBackendURL}/lists/dynamic/`, ...)
23
+ ```
24
+
25
+ That resolves to `backend.leadconnectorhq.com/lists/dynamic/{locationId}` — verified all 5 CRUD endpoints (list / get / create / update / delete) end-to-end.
26
+
27
+ ### Why Firebase-gated, not public
28
+
29
+ Tried bearer-token auth on the same endpoints — returns 400 `"Cannot list smartlists"`. Smart Lists require the internal-API auth path (Firebase ID token via the `token-id` header), same gate as workflow-builder, funnel-builder, form-builder, and pipeline-builder.
30
+
31
+ ### Tool count impact
32
+
33
+ - Total: 191 → 196 (+5)
34
+ - With Firebase: 191 → 196 (+5 unlocked when enable_workflow_builder runs)
35
+ - Without Firebase: 161 (unchanged — these are internal-API tools)
36
+ - Modules: 39 → 40 (new smart-lists module)
37
+
38
+ ### Verified end-to-end against MCP Testing
39
+
40
+ Round-trip on `objectKey: "contacts"`: create → get → list (search) → update (rename) → delete. 5/5 success, no probe pollution left in the sub-account.
41
+
42
+ ### Files changed
43
+
44
+ - `src/tools/smart-lists.ts` — NEW
45
+ - `src/tools/index.ts` — registered `registerSmartListTools` alongside other internal-API builders
46
+ - `src/setup-tool.ts` — bumped Firebase-mode tool count (191 → 196)
47
+
48
+ ## 3.10.1 — `list_message_templates` (SMS / email-snippet / WhatsApp read)
49
+
50
+ **191 tools across 39 modules. Bundle: ~304.7 KB.**
51
+
52
+ Small patch closing the SMS template read gap.
53
+
54
+ ### New tool
55
+
56
+ - `list_message_templates(type?, limit?, skip?, locationId?)` — list snippet-style message templates. `type` filters by `sms` / `email` / `whatsapp`; omit to list all types.
57
+
58
+ ### What this is (and isn't)
59
+
60
+ GHL has **two separate template systems**:
61
+ 1. **Email builders** (drag-and-drop) at `/emails/builder` — already covered by `list_email_templates` + `create_email_template` + `update_email_template` from v3.10.0
62
+ 2. **Snippet templates** (simpler, multi-type) at `/locations/{locationId}/templates` — covered by this new tool
63
+
64
+ This patch closes the **read** side of system #2. The write side is a real wall: `POST /locations/{locationId}/templates` returns 401 *"The token is not authorized for this scope"* with a Private Integration token, regardless of body shape. GHL gates template creation behind a scope (`templates.write` or similar) that PITs don't have. Creating message templates either happens in the GHL UI or requires an OAuth integration. Honest scope documented in the tool description.
65
+
66
+ ### Endpoint discovery story
67
+
68
+ Found in the workflow-builder JS bundle (`templateEndpoint = /locations/${locationId}/templates`). Endpoint accepts `type=sms|email|whatsapp` to filter; without `type` returns all template kinds. Same endpoint serves single-template GET at `/locations/{locationId}/templates/{id}` (`get_message_template_by_id` could be added if a use case emerges; for now, the list returns enough detail).
69
+
70
+ ### Tool count impact
71
+
72
+ - Total: 190 → 191 (+1)
73
+ - Without Firebase: 160 → 161 (public API)
74
+
75
+ ### Files changed
76
+
77
+ - `src/tools/emails.ts` — added `list_message_templates`
78
+
3
79
  ## 3.10.0 — Email Templates (gap-closure round 2)
4
80
 
5
81
  **190 tools across 39 modules. Bundle: 304.6 KB.**
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GHL Command — GoHighLevel MCP Server
2
2
 
3
- **Full GoHighLevel API access for Claude.** 190 tools across 39 modules — manage contacts, conversations, pipelines, calendars, funnels, workflows, invoices, custom objects, webhooks, and more. **Includes full workflow builder, funnel/page editor, form builder, pipeline builder, bulk operations, account export, and workflow cloning** — capabilities no other GHL tool offers.
3
+ **Full GoHighLevel API access for Claude.** 196 tools across 40 modules — manage contacts, conversations, pipelines, calendars, funnels, workflows, invoices, custom objects, webhooks, and more. **Includes full workflow builder, funnel/page editor, form builder, pipeline builder, bulk operations, account export, and workflow cloning** — capabilities no other GHL tool offers.
4
4
 
5
5
  **Distributed via npm as [`@elitedcs/ghl-mcp`](https://www.npmjs.com/package/@elitedcs/ghl-mcp).** Buyers install with one config block — no git, no Node.js setup, no terminal commands. Updates flow automatically (`npx @latest` re-resolves on every Claude restart).
6
6
 
@@ -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 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 160 tools work fine — you just won'''t have workflow/funnel/form/pipeline 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 161 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
 
package/dist/index.js CHANGED
@@ -31,8 +31,8 @@ var require_package = __commonJS({
31
31
  "package.json"(exports2, module2) {
32
32
  module2.exports = {
33
33
  name: "@elitedcs/ghl-mcp",
34
- version: "3.10.0",
35
- description: "GoHighLevel MCP Server for Claude. 190 tools \u2014 full CRM, automation, marketing control, and the only programmatic GHL workflow builder.",
34
+ version: "3.11.0",
35
+ description: "GoHighLevel MCP Server for Claude. 196 tools \u2014 full CRM, automation, marketing control, and the only programmatic GHL workflow builder.",
36
36
  main: "dist/index.js",
37
37
  bin: {
38
38
  "ghl-mcp": "dist/index.js"
@@ -3624,6 +3624,25 @@ function registerEmailTools(server2, client) {
3624
3624
  });
3625
3625
  }
3626
3626
  );
3627
+ safeTool(
3628
+ server2,
3629
+ "list_message_templates",
3630
+ "List snippet-style message templates in a location. Covers SMS, email snippets, and WhatsApp templates \u2014 pass the `type` param to filter. Different from `list_email_templates` (which returns drag-and-drop email-builder templates from a separate system). NOTE: This tool is READ-ONLY by design. Creating/updating/deleting templates at this endpoint requires a scope (`templates.write`) that GHL doesn't grant to Private Integration tokens. Buyers create message templates via the GHL UI (Conversations \u2192 Templates) or via an OAuth integration. This list endpoint works on a standard sub-account Private Integration key.",
3631
+ {
3632
+ type: import_zod24.z.enum(["sms", "email", "whatsapp"]).optional().describe("Filter by template type. Omit to list all template types in one response. SMS templates are quick-reply text snippets used in the conversations composer. Email here means HTML-snippet templates (NOT email-builder drag-and-drop templates \u2014 those are listed via `list_email_templates`). WhatsApp templates are pre-approved message templates registered with Meta."),
3633
+ limit: import_zod24.z.number().optional().describe("Max templates to return. Defaults to 1000."),
3634
+ skip: import_zod24.z.number().optional().describe("Skip this many for pagination."),
3635
+ locationId: import_zod24.z.string().optional().describe("Location ID. Falls back to GHL_LOCATION_ID env var.")
3636
+ },
3637
+ async ({ type, limit, skip, locationId: locationId2 }) => {
3638
+ const resolvedLocationId = client.resolveLocationId(locationId2);
3639
+ const params = {};
3640
+ if (type) params.type = type;
3641
+ if (limit !== void 0) params.limit = limit;
3642
+ if (skip !== void 0) params.skip = skip;
3643
+ return client.get(`/locations/${resolvedLocationId}/templates`, { params });
3644
+ }
3645
+ );
3627
3646
  safeTool(
3628
3647
  server2,
3629
3648
  "update_email_template",
@@ -5991,42 +6010,185 @@ function registerWorkflowClonerTools(server2, builderClient) {
5991
6010
  );
5992
6011
  }
5993
6012
 
5994
- // src/tools/template-deployer.ts
6013
+ // src/tools/smart-lists.ts
5995
6014
  var import_zod40 = require("zod");
6015
+ var SMARTLIST_BASE = "https://backend.leadconnectorhq.com/lists/dynamic";
6016
+ var OBJECT_KEYS = ["contacts", "opportunity"];
6017
+ function registerSmartListTools(server2, builderClient) {
6018
+ const client = builderClient;
6019
+ if (!client) return;
6020
+ async function smartListRequest(method, path6, body) {
6021
+ const headers = await client.buildHeaders();
6022
+ const url = `${SMARTLIST_BASE}${path6}`;
6023
+ const options = { method, headers };
6024
+ if (body && (method === "POST" || method === "PUT")) {
6025
+ options.body = JSON.stringify(body);
6026
+ }
6027
+ const response = await fetch(url, options);
6028
+ if (!response.ok) {
6029
+ const text2 = await response.text();
6030
+ throw new Error(`Smart Lists API Error ${response.status}: ${method} ${path6}
6031
+ ${text2}`);
6032
+ }
6033
+ const text = await response.text();
6034
+ if (!text) return {};
6035
+ return JSON.parse(text);
6036
+ }
6037
+ server2.tool(
6038
+ "list_smart_lists",
6039
+ "List smart lists (dynamic / saved-filter lists) in a location. Smart Lists are saved searches over contacts or opportunities \u2014 agencies use them to segment by complex criteria. Filters and columns aren't returned in the list view; use get_smart_list for the full filter spec.",
6040
+ {
6041
+ objectKey: import_zod40.z.enum(OBJECT_KEYS).describe("The object type the lists segment over. 'contacts' for contact-segments, 'opportunity' for opportunity-segments. Required \u2014 GHL rejects requests without it."),
6042
+ query: import_zod40.z.string().optional().describe("Free-text search across smart list names."),
6043
+ limit: import_zod40.z.number().optional().describe("Max smart lists per page. Defaults to 20 on GHL's side."),
6044
+ startAfter: import_zod40.z.string().optional().describe("Cursor for pagination \u2014 pass the last list's id from the previous page."),
6045
+ locationId: import_zod40.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
6046
+ },
6047
+ async ({ objectKey, query, limit, startAfter, locationId: locationId2 }) => {
6048
+ try {
6049
+ const loc = locationId2 ?? client.locationId;
6050
+ const params = new URLSearchParams({ objectKey });
6051
+ if (query) params.set("query", query);
6052
+ if (limit !== void 0) params.set("limit", String(limit));
6053
+ if (startAfter) params.set("startAfter", startAfter);
6054
+ const result = await smartListRequest("GET", `/${loc}?${params.toString()}`);
6055
+ return jsonResponse(result);
6056
+ } catch (error) {
6057
+ return errorResponse(error);
6058
+ }
6059
+ }
6060
+ );
6061
+ server2.tool(
6062
+ "get_smart_list",
6063
+ "Get a single smart list by ID with its full configuration: filters, columns, permissions, and metadata. The filters array is what defines who/what is in the list.",
6064
+ {
6065
+ listId: import_zod40.z.string().describe("The smart list ID (from list_smart_lists or a previous create_smart_list response)."),
6066
+ locationId: import_zod40.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
6067
+ },
6068
+ async ({ listId, locationId: locationId2 }) => {
6069
+ try {
6070
+ const loc = locationId2 ?? client.locationId;
6071
+ const result = await smartListRequest("GET", `/${loc}/${listId}`);
6072
+ return jsonResponse(result);
6073
+ } catch (error) {
6074
+ return errorResponse(error);
6075
+ }
6076
+ }
6077
+ );
6078
+ server2.tool(
6079
+ "create_smart_list",
6080
+ "Create a new smart list (dynamic filter list). Required: name + objectKey. Filters define the saved-search criteria \u2014 pass an empty array to create an empty list and add filters later via update_smart_list. The shape of filters/columns is opaque here; query an existing smart list with get_smart_list to see the format GHL expects.",
6081
+ {
6082
+ name: import_zod40.z.string().describe("Display name for the smart list."),
6083
+ objectKey: import_zod40.z.enum(OBJECT_KEYS).describe("Object type the list segments over. 'contacts' or 'opportunity'."),
6084
+ filters: import_zod40.z.array(import_zod40.z.record(import_zod40.z.unknown())).optional().describe("Array of filter objects. Each object has fields like {field, operator, value} \u2014 exact shape varies by filter type. See get_smart_list on an existing list to learn the format."),
6085
+ columns: import_zod40.z.array(import_zod40.z.record(import_zod40.z.unknown())).optional().describe("Array of column definitions for the smart list view in GHL UI. Each defines which contact/opportunity field shows as a column. Defaults to GHL's standard columns if omitted."),
6086
+ pipelineIds: import_zod40.z.array(import_zod40.z.string()).optional().describe("(opportunity objectKey only) Pipeline IDs to restrict this smart list to. Empty array = all pipelines."),
6087
+ defaultInPipelines: import_zod40.z.array(import_zod40.z.string()).optional().describe("(opportunity objectKey only) Pipeline IDs where this list is the default view."),
6088
+ locationId: import_zod40.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
6089
+ },
6090
+ async ({ name, objectKey, filters, columns, pipelineIds, defaultInPipelines, locationId: locationId2 }) => {
6091
+ try {
6092
+ const loc = locationId2 ?? client.locationId;
6093
+ const body = { name, objectKey };
6094
+ if (filters !== void 0) body.filters = filters;
6095
+ if (columns !== void 0) body.columns = columns;
6096
+ if (pipelineIds !== void 0) body.pipelineIds = pipelineIds;
6097
+ if (defaultInPipelines !== void 0) body.defaultInPipelines = defaultInPipelines;
6098
+ const result = await smartListRequest("POST", `/${loc}`, body);
6099
+ return jsonResponse(result);
6100
+ } catch (error) {
6101
+ return errorResponse(error);
6102
+ }
6103
+ }
6104
+ );
6105
+ server2.tool(
6106
+ "update_smart_list",
6107
+ "Update an existing smart list's name, filters, or columns. The objectKey CANNOT be changed after creation (GHL rejects with 422 if you try). Use get_smart_list first to inspect the current filters; partial updates work \u2014 pass only the fields you want to change.",
6108
+ {
6109
+ listId: import_zod40.z.string().describe("The smart list ID to update."),
6110
+ name: import_zod40.z.string().optional().describe("New display name."),
6111
+ filters: import_zod40.z.array(import_zod40.z.record(import_zod40.z.unknown())).optional().describe("Replace the filter array entirely. To add a filter, fetch the current list, append, and pass the new array."),
6112
+ columns: import_zod40.z.array(import_zod40.z.record(import_zod40.z.unknown())).optional().describe("Replace the column array entirely."),
6113
+ pipelineIds: import_zod40.z.array(import_zod40.z.string()).optional().describe("(opportunity only) Update the pipeline scope."),
6114
+ defaultInPipelines: import_zod40.z.array(import_zod40.z.string()).optional().describe("(opportunity only) Update the default-in-pipelines list."),
6115
+ locationId: import_zod40.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
6116
+ },
6117
+ async ({ listId, name, filters, columns, pipelineIds, defaultInPipelines, locationId: locationId2 }) => {
6118
+ try {
6119
+ const loc = locationId2 ?? client.locationId;
6120
+ const body = {};
6121
+ if (name !== void 0) body.name = name;
6122
+ if (filters !== void 0) body.filters = filters;
6123
+ if (columns !== void 0) body.columns = columns;
6124
+ if (pipelineIds !== void 0) body.pipelineIds = pipelineIds;
6125
+ if (defaultInPipelines !== void 0) body.defaultInPipelines = defaultInPipelines;
6126
+ if (Object.keys(body).length === 0) {
6127
+ throw new Error("Pass at least one of: name, filters, columns, pipelineIds, defaultInPipelines. Nothing to update.");
6128
+ }
6129
+ const result = await smartListRequest("PUT", `/${loc}/${listId}`, body);
6130
+ return jsonResponse(result);
6131
+ } catch (error) {
6132
+ return errorResponse(error);
6133
+ }
6134
+ }
6135
+ );
6136
+ server2.tool(
6137
+ "delete_smart_list",
6138
+ "Permanently delete a smart list. IRREVERSIBLE. The list configuration is removed but the contacts/opportunities themselves are NOT touched \u2014 smart lists are just saved filters. Any workflow trigger / dashboard / report that referenced this list by ID will stop working.",
6139
+ {
6140
+ listId: import_zod40.z.string().describe("The smart list ID to delete."),
6141
+ confirm: import_zod40.z.literal("DELETE").describe("Must pass 'DELETE' to confirm this destructive action."),
6142
+ locationId: import_zod40.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
6143
+ },
6144
+ async ({ listId, locationId: locationId2 }) => {
6145
+ try {
6146
+ const loc = locationId2 ?? client.locationId;
6147
+ const result = await smartListRequest("DELETE", `/${loc}/${listId}`);
6148
+ return jsonResponse(result);
6149
+ } catch (error) {
6150
+ return errorResponse(error);
6151
+ }
6152
+ }
6153
+ );
6154
+ }
6155
+
6156
+ // src/tools/template-deployer.ts
6157
+ var import_zod41 = require("zod");
5996
6158
  var fs3 = __toESM(require("fs"));
5997
6159
  var path3 = __toESM(require("path"));
5998
6160
  function delay3(ms) {
5999
6161
  return new Promise((resolve5) => setTimeout(resolve5, ms));
6000
6162
  }
6001
- var TemplateSchema = import_zod40.z.object({
6002
- templateName: import_zod40.z.string(),
6003
- templateVersion: import_zod40.z.string().optional(),
6004
- description: import_zod40.z.string().optional().default(""),
6005
- questionnaire: import_zod40.z.array(import_zod40.z.object({
6006
- id: import_zod40.z.string(),
6007
- question: import_zod40.z.string(),
6008
- type: import_zod40.z.string(),
6009
- required: import_zod40.z.boolean().optional(),
6010
- placeholder: import_zod40.z.string().optional()
6163
+ var TemplateSchema = import_zod41.z.object({
6164
+ templateName: import_zod41.z.string(),
6165
+ templateVersion: import_zod41.z.string().optional(),
6166
+ description: import_zod41.z.string().optional().default(""),
6167
+ questionnaire: import_zod41.z.array(import_zod41.z.object({
6168
+ id: import_zod41.z.string(),
6169
+ question: import_zod41.z.string(),
6170
+ type: import_zod41.z.string(),
6171
+ required: import_zod41.z.boolean().optional(),
6172
+ placeholder: import_zod41.z.string().optional()
6011
6173
  })).optional().default([]),
6012
- location: import_zod40.z.record(import_zod40.z.unknown()).optional(),
6013
- tags: import_zod40.z.array(import_zod40.z.string()).optional(),
6014
- customFields: import_zod40.z.array(import_zod40.z.object({
6015
- name: import_zod40.z.string(),
6016
- dataType: import_zod40.z.string()
6174
+ location: import_zod41.z.record(import_zod41.z.unknown()).optional(),
6175
+ tags: import_zod41.z.array(import_zod41.z.string()).optional(),
6176
+ customFields: import_zod41.z.array(import_zod41.z.object({
6177
+ name: import_zod41.z.string(),
6178
+ dataType: import_zod41.z.string()
6017
6179
  })).optional(),
6018
- pipelines: import_zod40.z.array(import_zod40.z.object({
6019
- name: import_zod40.z.string(),
6020
- stages: import_zod40.z.array(import_zod40.z.object({ position: import_zod40.z.number(), name: import_zod40.z.string() }))
6180
+ pipelines: import_zod41.z.array(import_zod41.z.object({
6181
+ name: import_zod41.z.string(),
6182
+ stages: import_zod41.z.array(import_zod41.z.object({ position: import_zod41.z.number(), name: import_zod41.z.string() }))
6021
6183
  })).optional(),
6022
- workflows: import_zod40.z.array(import_zod40.z.object({
6023
- name: import_zod40.z.string(),
6024
- condition: import_zod40.z.string().optional(),
6025
- actions: import_zod40.z.array(import_zod40.z.record(import_zod40.z.unknown())).optional().default([])
6184
+ workflows: import_zod41.z.array(import_zod41.z.object({
6185
+ name: import_zod41.z.string(),
6186
+ condition: import_zod41.z.string().optional(),
6187
+ actions: import_zod41.z.array(import_zod41.z.record(import_zod41.z.unknown())).optional().default([])
6026
6188
  })).optional(),
6027
- calendars: import_zod40.z.array(import_zod40.z.object({
6028
- name: import_zod40.z.string(),
6029
- description: import_zod40.z.string().optional()
6189
+ calendars: import_zod41.z.array(import_zod41.z.object({
6190
+ name: import_zod41.z.string(),
6191
+ description: import_zod41.z.string().optional()
6030
6192
  })).optional()
6031
6193
  });
6032
6194
  function registerTemplateDeployerTools(server2, client) {
@@ -6097,7 +6259,7 @@ function registerTemplateDeployerTools(server2, client) {
6097
6259
  "get_template_questionnaire",
6098
6260
  "Get the questionnaire for a specific template. Returns all the questions that need to be answered before deploying. Present these to the user one at a time in a conversational style.",
6099
6261
  {
6100
- templateFile: import_zod40.z.string().describe("Path to the template JSON file (from list_templates).")
6262
+ templateFile: import_zod41.z.string().describe("Path to the template JSON file (from list_templates).")
6101
6263
  },
6102
6264
  async ({ templateFile }) => {
6103
6265
  try {
@@ -6130,10 +6292,10 @@ function registerTemplateDeployerTools(server2, client) {
6130
6292
  "deploy_template",
6131
6293
  "Deploy a template to set up a GHL sub-account. Creates tags, custom fields, pipelines with stages, calendars, workflows, and forms based on the template and the user's questionnaire answers. This is the main setup automation tool.",
6132
6294
  {
6133
- templateFile: import_zod40.z.string().describe("Path to the template JSON file."),
6134
- answers: import_zod40.z.record(import_zod40.z.unknown()).describe("Questionnaire answers keyed by question ID (e.g. {business_name: 'My Clinic', business_phone: '+15551234567', ...})."),
6135
- locationId: import_zod40.z.string().optional().describe("Location ID to deploy to. Uses default if not specified."),
6136
- dryRun: import_zod40.z.boolean().optional().describe("If true, shows what would be created without actually creating anything. Defaults to false.")
6295
+ templateFile: import_zod41.z.string().describe("Path to the template JSON file."),
6296
+ answers: import_zod41.z.record(import_zod41.z.unknown()).describe("Questionnaire answers keyed by question ID (e.g. {business_name: 'My Clinic', business_phone: '+15551234567', ...})."),
6297
+ locationId: import_zod41.z.string().optional().describe("Location ID to deploy to. Uses default if not specified."),
6298
+ dryRun: import_zod41.z.boolean().optional().describe("If true, shows what would be created without actually creating anything. Defaults to false.")
6137
6299
  },
6138
6300
  async ({ templateFile, answers, locationId: locationId2, dryRun }) => {
6139
6301
  try {
@@ -6379,7 +6541,7 @@ ${errors.join("\n")}` : "\nNo errors!",
6379
6541
  }
6380
6542
 
6381
6543
  // src/tools/validators.ts
6382
- var import_zod41 = require("zod");
6544
+ var import_zod42 = require("zod");
6383
6545
  function extractFromTrigger(trigger, refs) {
6384
6546
  const triggerName = String(trigger.name ?? trigger.type ?? "unnamed trigger");
6385
6547
  const where = `trigger "${triggerName}"`;
@@ -6514,7 +6676,7 @@ function registerValidatorTools(server2, client, builderClient) {
6514
6676
  "validate_workflow",
6515
6677
  "Pre-flight ID validation for a deployed GHL workflow. Scans every trigger and action for references to pipelines, pipeline stages, custom fields, users, workflows, forms, calendars, and surveys; verifies each ID exists in the current location. Use this BEFORE publish_workflow when a workflow has been edited, or whenever a published workflow stops behaving as expected. Catches the silent-failure bug where invalid IDs make GHL skip all subsequent actions without warning. Returns a structured report of valid + missing references.",
6516
6678
  {
6517
- workflowId: import_zod41.z.string().describe("The workflow ID to validate.")
6679
+ workflowId: import_zod42.z.string().describe("The workflow ID to validate.")
6518
6680
  },
6519
6681
  async ({ workflowId }) => {
6520
6682
  try {
@@ -6882,6 +7044,7 @@ function registerAllTools(server2, client, registry2, mcpVersion) {
6882
7044
  registerFormBuilderTools(server2, builderClient);
6883
7045
  registerPipelineBuilderTools(server2, builderClient);
6884
7046
  registerWorkflowClonerTools(server2, builderClient);
7047
+ registerSmartListTools(server2, builderClient);
6885
7048
  registerValidatorTools(server2, client, builderClient);
6886
7049
  registerDiagnosticTools(server2, mcpVersion ?? "unknown", client, builderClient, registry2 ?? null);
6887
7050
  registerLocationSwitcherTools(server2, client, builderClient, registry2, mcpVersion);
@@ -6891,18 +7054,18 @@ function registerAllTools(server2, client, registry2, mcpVersion) {
6891
7054
  var fs4 = __toESM(require("fs"));
6892
7055
  var path4 = __toESM(require("path"));
6893
7056
  var os = __toESM(require("os"));
6894
- var import_zod42 = require("zod");
7057
+ var import_zod43 = require("zod");
6895
7058
  var APP_NAME = "elitedcs-ghl-mcp";
6896
- var CredentialsSchema = import_zod42.z.object({
6897
- license_key: import_zod42.z.string().min(1),
6898
- email: import_zod42.z.string().email(),
6899
- verified_at: import_zod42.z.string().min(1),
6900
- ghl_api_key: import_zod42.z.string().min(1),
6901
- ghl_location_id: import_zod42.z.string().min(1),
6902
- ghl_company_id: import_zod42.z.string().optional(),
6903
- ghl_user_id: import_zod42.z.string().optional(),
6904
- ghl_firebase_api_key: import_zod42.z.string().optional(),
6905
- ghl_firebase_refresh_token: import_zod42.z.string().optional()
7059
+ var CredentialsSchema = import_zod43.z.object({
7060
+ license_key: import_zod43.z.string().min(1),
7061
+ email: import_zod43.z.string().email(),
7062
+ verified_at: import_zod43.z.string().min(1),
7063
+ ghl_api_key: import_zod43.z.string().min(1),
7064
+ ghl_location_id: import_zod43.z.string().min(1),
7065
+ ghl_company_id: import_zod43.z.string().optional(),
7066
+ ghl_user_id: import_zod43.z.string().optional(),
7067
+ ghl_firebase_api_key: import_zod43.z.string().optional(),
7068
+ ghl_firebase_refresh_token: import_zod43.z.string().optional()
6906
7069
  });
6907
7070
  function appDataDir() {
6908
7071
  const home = os.homedir();
@@ -6952,7 +7115,7 @@ function writeCredentials(creds) {
6952
7115
  // src/setup-tool.ts
6953
7116
  var os2 = __toESM(require("os"));
6954
7117
  var crypto3 = __toESM(require("crypto"));
6955
- var import_zod43 = require("zod");
7118
+ var import_zod44 = require("zod");
6956
7119
  var LICENSE_API = "https://elitedcs.com/api/validate-license";
6957
7120
  var GHL_API = "https://services.leadconnectorhq.com";
6958
7121
  var FIREBASE_TOKEN_API = "https://securetoken.googleapis.com/v1/token";
@@ -7021,16 +7184,16 @@ async function validateFirebase(firebaseKey, refreshToken) {
7021
7184
  function registerSetupTool(server2) {
7022
7185
  server2.tool(
7023
7186
  "setup_ghl_mcp",
7024
- "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 190 tools (160 if you skip the optional Firebase fields; add Firebase later with enable_workflow_builder).",
7187
+ "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 196 tools (161 if you skip the optional Firebase fields; add Firebase later with enable_workflow_builder).",
7025
7188
  {
7026
- email: import_zod43.z.string().email().describe("Email used at purchase."),
7027
- license_key: import_zod43.z.string().min(20).describe("License key from your purchase email."),
7028
- ghl_api_key: import_zod43.z.string().min(10).describe("GHL Private Integration key (starts with 'pit-'). Created INSIDE the sub-account at Settings > Integrations > Private Integrations."),
7029
- ghl_location_id: import_zod43.z.string().min(10).describe("GHL Location ID (sub-account ID). Found in your GHL URL: /location/THIS_PART/dashboard."),
7030
- ghl_company_id: import_zod43.z.string().optional().describe("(Agency only) Company ID for multi-location access."),
7031
- ghl_user_id: import_zod43.z.string().optional().describe("(Workflow Builder, optional) Firebase User ID. See README for browser capture instructions."),
7032
- ghl_firebase_api_key: import_zod43.z.string().optional().describe("(Workflow Builder, optional) Firebase API Key starting with 'AIza'."),
7033
- ghl_firebase_refresh_token: import_zod43.z.string().optional().describe("(Workflow Builder, optional) Firebase refresh token starting with 'AMf-'.")
7189
+ email: import_zod44.z.string().email().describe("Email used at purchase."),
7190
+ license_key: import_zod44.z.string().min(20).describe("License key from your purchase email."),
7191
+ ghl_api_key: import_zod44.z.string().min(10).describe("GHL Private Integration key (starts with 'pit-'). Created INSIDE the sub-account at Settings > Integrations > Private Integrations."),
7192
+ ghl_location_id: import_zod44.z.string().min(10).describe("GHL Location ID (sub-account ID). Found in your GHL URL: /location/THIS_PART/dashboard."),
7193
+ ghl_company_id: import_zod44.z.string().optional().describe("(Agency only) Company ID for multi-location access."),
7194
+ ghl_user_id: import_zod44.z.string().optional().describe("(Workflow Builder, optional) Firebase User ID. See README for browser capture instructions."),
7195
+ ghl_firebase_api_key: import_zod44.z.string().optional().describe("(Workflow Builder, optional) Firebase API Key starting with 'AIza'."),
7196
+ ghl_firebase_refresh_token: import_zod44.z.string().optional().describe("(Workflow Builder, optional) Firebase refresh token starting with 'AMf-'.")
7034
7197
  },
7035
7198
  async (args) => {
7036
7199
  const lic = await validateLicense(args.email, args.license_key);
@@ -7076,7 +7239,7 @@ Note: Firebase credentials rejected (${fb.error}). Saved without Workflow Builde
7076
7239
  ghl_firebase_api_key: workflowBuilderEnabled ? args.ghl_firebase_api_key?.trim() : void 0,
7077
7240
  ghl_firebase_refresh_token: workflowBuilderEnabled ? args.ghl_firebase_refresh_token?.trim() : void 0
7078
7241
  });
7079
- const toolCount = workflowBuilderEnabled ? "190" : "160";
7242
+ const toolCount = workflowBuilderEnabled ? "196" : "161";
7080
7243
  const wfLine = workflowBuilderEnabled ? "Workflow Builder: enabled." : "Workflow Builder: not configured (optional).";
7081
7244
  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.";
7082
7245
  return {
@@ -7104,11 +7267,11 @@ Note: Firebase credentials rejected (${fb.error}). Saved without Workflow Builde
7104
7267
  function registerEnableWorkflowBuilderTool(server2) {
7105
7268
  server2.tool(
7106
7269
  "enable_workflow_builder",
7107
- "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 160 to 190 after the next Claude restart.",
7270
+ "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 161 to 196 after the next Claude restart.",
7108
7271
  {
7109
- ghl_user_id: import_zod43.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."),
7110
- ghl_firebase_api_key: import_zod43.z.string().min(10).describe("Firebase API Key starting with 'AIza'. The string between 'firebase:authUser:' and ':[DEFAULT]' in the row's Key column."),
7111
- ghl_firebase_refresh_token: import_zod43.z.string().min(10).describe("Firebase refresh token. value.stsTokenManager.refreshToken in the firebase:authUser row.")
7272
+ ghl_user_id: import_zod44.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."),
7273
+ ghl_firebase_api_key: import_zod44.z.string().min(10).describe("Firebase API Key starting with 'AIza'. The string between 'firebase:authUser:' and ':[DEFAULT]' in the row's Key column."),
7274
+ ghl_firebase_refresh_token: import_zod44.z.string().min(10).describe("Firebase refresh token. value.stsTokenManager.refreshToken in the firebase:authUser row.")
7112
7275
  },
7113
7276
  async (args) => {
7114
7277
  const existing = readCredentials();
@@ -7151,7 +7314,7 @@ DevTools steps: https://elitedcs.com/ghl-mcp-firebase`
7151
7314
  "",
7152
7315
  "Firebase credentials verified and saved.",
7153
7316
  "",
7154
- "**Restart Claude (quit fully and reopen) to load the workflow builder + funnel builder + pipeline builder + form builder + workflow cloner tools (190 total).**",
7317
+ "**Restart Claude (quit fully and reopen) to load the workflow builder + funnel builder + pipeline builder + form builder + workflow cloner tools (196 total).**",
7155
7318
  "",
7156
7319
  'After restart, try: "List my workflows in full detail" or "Validate workflow <id>".',
7157
7320
  "",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.10.0",
4
- "description": "GoHighLevel MCP Server for Claude. 190 tools — full CRM, automation, marketing control, and the only programmatic GHL workflow builder.",
3
+ "version": "3.11.0",
4
+ "description": "GoHighLevel MCP Server for Claude. 196 tools — full CRM, automation, marketing control, and the only programmatic GHL workflow builder.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "ghl-mcp": "dist/index.js"