@elitedcs/ghl-mcp 3.50.0 → 3.52.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,56 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.52.0 — Fix: `update_page_content` now actually writes page content
4
+
5
+ `update_page_content` was a silent no-op — it POSTed the page envelope to GHL's
6
+ prebuilt-section template-sync route (`/funnels/builder/prebuilt-section/sync/changes`),
7
+ which accepts the request, ignores `pageData`, returns `{"prebuiltSectionTemplates":[]}`,
8
+ and persists nothing (the page stayed at version 1 with no `pageDataDownloadUrl`).
9
+ Present since v3.7.0 — this capability had never worked. Reported by a customer
10
+ (v3.47 + v3.50, custom MCP client) and reproduced.
11
+
12
+ - **Re-pointed to GHL's real builder autosave endpoint**
13
+ `POST /funnels/builder/autosave/{pageId}`, captured live from the page builder
14
+ 2026-07-09. The server uploads the page to Firebase Storage, bumps the version,
15
+ and regenerates the preview snapshot, returning the new `pageDataUrl` /
16
+ `pageDataDownloadUrl`. Verified end-to-end: a from-scratch API write bumps the
17
+ version and the content round-trips through `get_page_content`.
18
+ - **Body shape** is `{funnelId, pageVersion, pageData: {…envelope, pageVersion,
19
+ pageType, manualSave, integrations}}`. The tool auto-resolves `funnelId` and the
20
+ current `pageVersion` from the page metadata (both optional to pass), computes
21
+ the `integrations` element-type counts from the content, and sends the builder
22
+ origin/referer + `Version` header the endpoint's IAM check requires.
23
+ - `update_page_content` gains an optional `funnelId` arg (auto-resolved when
24
+ omitted). No tool-count change.
25
+
26
+ ## 3.51.0 — Agency sub-account provisioning (`create_sub_account` / `delete_sub_account`)
27
+
28
+ The missing first step of fully programmatic client provisioning: create the
29
+ sub-account itself (optionally loading a snapshot at creation), then Blueprint
30
+ builds inside it and verify_funnel proves it.
31
+
32
+ - **`create_sub_account`** — agency-API location create with optional
33
+ `snapshot_id` (see `list_snapshots`), prospect/owner info, address, timezone.
34
+ Probes whether the agency key can operate the new location's data plane and
35
+ auto-registers it in the token registry when it can (switch_location works
36
+ immediately). Non-idempotent create is retry-suppressed. Requires an
37
+ agency-level key with the locations.write scope; the error path says exactly
38
+ that when the scope is missing.
39
+ - **`delete_sub_account`** — agency-API location delete, confirm-gated,
40
+ refuses the currently active location, cleans the token registry entry.
41
+ - **Tool counts corrected: the real full count is 232 (180 core), free tier
42
+ 106 (88).** A live tools/list dump exposed that `get_mcp_version` registers
43
+ outside the counted registry in both modes — every prior claim (229, 231,
44
+ free 105/87) was one short. The drift-guard constant now documents all four
45
+ normal-mode extras.
46
+ - **Test-isolation fix: the suite can never clobber a real install again.**
47
+ credentials-store's atomicity tests wrote fixture credentials to the REAL
48
+ per-user credentials.json (no GHL_MCP_CONFIG_DIR override) — any dev running
49
+ the suite silently broke their own install on next restart. Tests now run in
50
+ a temp dir AND writeCredentials refuses to run under vitest without an
51
+ override.
52
+
53
+
3
54
  ## 3.50.0 — FREE read-only tier
4
55
 
5
56
  A free license (`ghlcommand.com/free`, key by email, no card) now runs the same
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GHL Command — GoHighLevel MCP Server
2
2
 
3
- **Full GoHighLevel API access for Claude.** 229 tools across 48 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. **Multi-tenant:** one install can run the workflow builder across multiple clients' GHL accounts.
3
+ **Full GoHighLevel API access for Claude.** 232 tools across 48 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. **Multi-tenant:** one install can run the workflow builder across multiple clients' GHL accounts.
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
 
@@ -8,7 +8,7 @@ Works with **both the Claude Desktop App and Claude Code terminal** — your cho
8
8
 
9
9
  **License required.** Get it at [ghlcommand.com](https://ghlcommand.com?utm_source=npm&utm_medium=readme) — $97/mo, covering UNLIMITED GoHighLevel sub-accounts on up to 3 machines (never billed per account). **30-day time-back guarantee**: save 5+ hours on one client build or your first month back.
10
10
 
11
- **Try it FREE (read-only tier).** Grab an instant free key at [ghlcommand.com/free](https://ghlcommand.com/free?utm_source=npm&utm_medium=readme) — 105 read-only tools on your own GHL account (87 before the one-login auditor unlock): the account-wide workflow audit (`audit_workflows` finds the silently broken references GHL never warns you about), account health, and every read across contacts, pipelines, funnels, and workflows. Write tools stay visible and answer with upgrade info. Buying later with the same email upgrades your key in place — 1 machine on free, 3 when you upgrade.
11
+ **Try it FREE (read-only tier).** Grab an instant free key at [ghlcommand.com/free](https://ghlcommand.com/free?utm_source=npm&utm_medium=readme) — 106 read-only tools on your own GHL account (88 before the one-login auditor unlock): the account-wide workflow audit (`audit_workflows` finds the silently broken references GHL never warns you about), account health, and every read across contacts, pipelines, funnels, and workflows. Write tools stay visible and answer with upgrade info. Buying later with the same email upgrades your key in place — 1 machine on free, 3 when you upgrade.
12
12
 
13
13
  Built by [Elite DCs, LLC](https://elitedcs.com).
14
14
 
@@ -118,7 +118,7 @@ Run setup_ghl_mcp to activate GHL Command:
118
118
  ghl_location_id: YOUR_LOCATION_ID
119
119
  ```
120
120
 
121
- Approve the tool call. Server validates your license, verifies your GHL credentials, writes them to a per-user config file. **Quit Claude one more time and reopen** — the full core toolset is now unlocked (229 tools total with the optional Workflow Builder Firebase add-on).
121
+ Approve the tool call. Server validates your license, verifies your GHL credentials, writes them to a per-user config file. **Quit Claude one more time and reopen** — the full core toolset is now unlocked (232 tools total with the optional Workflow Builder Firebase add-on).
122
122
 
123
123
  ### 4. Try it
124
124
 
@@ -158,7 +158,7 @@ https://app.gohighlevel.com/v2/location/YOUR_LOCATION_ID/dashboard
158
158
 
159
159
  ## Enable the Workflow Builder
160
160
 
161
- The 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 177 tools work fine — you just won't have workflow/funnel/form/pipeline editing. This is the flagship feature: do this step.
161
+ The 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 180 tools work fine — you just won't have workflow/funnel/form/pipeline editing. This is the flagship feature: do this step.
162
162
 
163
163
  **One-click capture (v3.49.0+, the supported path):**
164
164
 
@@ -747,7 +747,7 @@ Source repo is private. Contributors need an invitation from `drjerryrelth`. The
747
747
 
748
748
  ### Reducing context / token usage
749
749
 
750
- Every registered MCP tool's schema is shipped to the model on every message. With 229 tools that's a meaningful per-message context cost even in chats that never touch GHL. If you only use a slice of GHL Command, restrict the tool surface with `GHL_ENABLED_MODULES` and/or `GHL_ENABLED_TOOLS`:
750
+ Every registered MCP tool's schema is shipped to the model on every message. With 232 tools that's a meaningful per-message context cost even in chats that never touch GHL. If you only use a slice of GHL Command, restrict the tool surface with `GHL_ENABLED_MODULES` and/or `GHL_ENABLED_TOOLS`:
751
751
 
752
752
  ```jsonc
753
753
  // Claude Desktop config — enable whole modules
package/dist/index.js CHANGED
@@ -31,9 +31,9 @@ var require_package = __commonJS({
31
31
  "package.json"(exports2, module2) {
32
32
  module2.exports = {
33
33
  name: "@elitedcs/ghl-mcp",
34
- version: "3.50.0",
34
+ version: "3.52.0",
35
35
  mcpName: "io.github.drjerryrelth/ghl-command",
36
- description: "GoHighLevel MCP Server for Claude. 229 tools \u2014 full CRM, automation, marketing control, account-wide workflow audit, live funnel-capture verification, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
36
+ description: "GoHighLevel MCP Server for Claude. 232 tools \u2014 full CRM, automation, marketing control, account-wide workflow audit, live funnel-capture verification, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
37
37
  main: "dist/index.js",
38
38
  bin: {
39
39
  "ghl-mcp": "dist/index.js"
@@ -370,6 +370,11 @@ function foldCredentialsIntoEnv(creds, env = process.env) {
370
370
  }
371
371
  }
372
372
  function writeCredentials(creds) {
373
+ if (process.env.VITEST && !process.env.GHL_MCP_CONFIG_DIR) {
374
+ throw new Error(
375
+ "writeCredentials under vitest without GHL_MCP_CONFIG_DIR \u2014 set it to a temp dir in the test (see interactive-capture.test.ts) so the real credentials.json is never touched."
376
+ );
377
+ }
373
378
  ensureAppDataDir();
374
379
  const file = credentialsPath();
375
380
  const tmp = `${file}.tmp.${process.pid}.${Date.now()}`;
@@ -6170,22 +6175,85 @@ ${text2}`);
6170
6175
  }
6171
6176
  }
6172
6177
  );
6178
+ function toCamel(meta) {
6179
+ return meta.replace(/-([a-z])/g, (_m, c) => c.toUpperCase());
6180
+ }
6181
+ function computeIntegrations(pageData) {
6182
+ const counts = {};
6183
+ let videoBackground = false;
6184
+ const sections = Array.isArray(pageData.sections) ? pageData.sections : [];
6185
+ for (const sec of sections) {
6186
+ const bg = sec.metaData?.extra?.bgImage?.value;
6187
+ if (bg && bg.mediaType === "video" && bg.videoUrl) videoBackground = true;
6188
+ const elements = Array.isArray(sec.elements) ? sec.elements : [];
6189
+ for (const el of elements) {
6190
+ if (el.type !== "element") continue;
6191
+ const meta = typeof el.meta === "string" ? el.meta : null;
6192
+ if (!meta) continue;
6193
+ const key = toCamel(meta);
6194
+ counts[key] = (counts[key] ?? 0) + 1;
6195
+ }
6196
+ }
6197
+ const popupsList = pageData.popupsList;
6198
+ const popup = Array.isArray(popupsList) && popupsList.length > 0;
6199
+ return {
6200
+ videoBackground,
6201
+ blogMeta: { selectedBlogCategories: [], categoryNavigationList: [] },
6202
+ ...counts,
6203
+ popup
6204
+ };
6205
+ }
6206
+ async function autosaveRequest(pageId, body) {
6207
+ const headers = await client.buildHeaders();
6208
+ headers.Origin = "https://page-builder.leadconnectorhq.com";
6209
+ headers.Referer = "https://page-builder.leadconnectorhq.com/";
6210
+ const url = `https://backend.leadconnectorhq.com/funnels/builder/autosave/${pageId}`;
6211
+ const response = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) });
6212
+ if (!response.ok) {
6213
+ throw new Error(`Funnel API Error ${response.status}: POST /builder/autosave/${pageId}
6214
+ ${await response.text()}`);
6215
+ }
6216
+ const text = await response.text();
6217
+ if (!text) return {};
6218
+ try {
6219
+ return JSON.parse(text);
6220
+ } catch {
6221
+ return JSON.parse(text.replace(/[\x00-\x1F\x7F]/g, ""));
6222
+ }
6223
+ }
6173
6224
  server2.tool(
6174
6225
  "update_page_content",
6175
- "Update a page's builder content \u2014 sections, elements, settings, pageStyles, trackingCode, popups, popupsList, fontsForPreview. Use get_page_content first to see the current structure, modify it, then pass the full 8-field envelope here. GHL's UI fires three POSTs to this endpoint per save (one collaborative-edit broadcast + two commits); for MCP use, one POST with write=true is enough.",
6226
+ "Update a funnel/website page's builder content \u2014 sections, elements, settings, pageStyles, trackingCode, popups, popupsList, fontsForPreview. Use get_page_content first to see the current structure, modify it, then pass the full envelope here. Writes via GHL's builder autosave: the server uploads the page to storage, bumps the version, and regenerates the preview snapshot, then returns the new pageDataUrl. The funnelId and current version are resolved automatically from the page. Saves as a draft unless isPublished is true.",
6176
6227
  {
6177
6228
  pageId: import_zod34.z.string().describe("The page ID to update."),
6178
- content: import_zod34.z.record(import_zod34.z.unknown()).describe("The full page content JSON. Expected fields: sections, settings, general, pageStyles, trackingCode, popups, popupsList, fontsForPreview."),
6179
- isPublished: import_zod34.z.boolean().optional().describe("Whether the page should be marked published after the save. Defaults to false (draft).")
6229
+ content: import_zod34.z.record(import_zod34.z.unknown()).describe("The full page content JSON \u2014 the 8-field envelope: sections, settings, general, pageStyles, trackingCode, fontsForPreview, popups, popupsList. Start from get_page_content and modify it."),
6230
+ funnelId: import_zod34.z.string().optional().describe("Funnel ID that owns the page. Optional \u2014 auto-resolved from the page metadata if omitted."),
6231
+ isPublished: import_zod34.z.boolean().optional().describe("Mark the page published after the save. Defaults to false (draft).")
6180
6232
  },
6181
- async ({ pageId, content, isPublished }) => {
6233
+ async ({ pageId, content, funnelId, isPublished }) => {
6182
6234
  try {
6183
- const result = await funnelRequest("POST", `/builder/prebuilt-section/sync/changes`, {
6184
- locationId: client.locationId,
6185
- pageId,
6186
- pageData: content,
6187
- write: true,
6188
- isPublished: isPublished ?? false
6235
+ let meta = {};
6236
+ try {
6237
+ meta = await funnelRequest("GET", `/page/${pageId}?locationId=${client.locationId}`);
6238
+ } catch (metaErr) {
6239
+ if (!funnelId) throw metaErr;
6240
+ }
6241
+ const resolvedFunnelId = funnelId ?? (typeof meta.funnelId === "string" ? meta.funnelId : void 0);
6242
+ if (!resolvedFunnelId) {
6243
+ throw new Error(`Could not resolve funnelId for page ${pageId}. Pass funnelId explicitly.`);
6244
+ }
6245
+ const pageVersion = typeof meta.pageVersion === "number" ? meta.pageVersion : typeof meta.version === "number" ? meta.version : 1;
6246
+ const pageData = {
6247
+ ...content,
6248
+ pageVersion,
6249
+ pageType: isPublished ? "published" : "draft",
6250
+ manualSave: true,
6251
+ integrations: computeIntegrations(content)
6252
+ };
6253
+ const result = await autosaveRequest(pageId, {
6254
+ funnelId: resolvedFunnelId,
6255
+ pageVersion,
6256
+ pageData
6189
6257
  });
6190
6258
  return jsonResponse(result);
6191
6259
  } catch (error) {
@@ -7254,7 +7322,7 @@ async function validateFirebase(firebaseKey, refreshToken) {
7254
7322
  function registerSetupTool(server2) {
7255
7323
  server2.tool(
7256
7324
  "setup_ghl_mcp",
7257
- "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 229 tools (177 if you skip the optional Firebase fields; add Firebase later with enable_workflow_builder).",
7325
+ "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 232 tools (180 if you skip the optional Firebase fields; add Firebase later with enable_workflow_builder).",
7258
7326
  {
7259
7327
  email: import_zod39.z.string().email().describe("Email used at purchase."),
7260
7328
  license_key: import_zod39.z.string().min(20).describe("License key from your purchase email."),
@@ -7336,11 +7404,11 @@ Note: Firebase credentials rejected (${fb.error}). Saved without Workflow Builde
7336
7404
  signed_attestation: lic.signedAttestation
7337
7405
  });
7338
7406
  const isFree = lic.tier === "free";
7339
- const toolCount = isFree ? workflowBuilderEnabled ? "105" : "87" : workflowBuilderEnabled ? "229" : "177";
7407
+ const toolCount = isFree ? workflowBuilderEnabled ? "106" : "88" : workflowBuilderEnabled ? "232" : "180";
7340
7408
  const wfLine = workflowBuilderEnabled ? "Workflow Builder: enabled." : "Workflow Builder: not configured (optional).";
7341
7409
  const wfTip = workflowBuilderEnabled ? "" : isFree ? `
7342
7410
  Unlock the account auditor next (the free tier's best tool): say "Unlock the Workflow Builder" \u2014 one browser login enables audit_workflows, validate_workflow, and the full-detail reads.` : "\nTo enable Workflow Builder later (52 extra Firebase-gated tools): run enable_workflow_builder with your three Firebase values. No need to re-enter license/API key/location ID.";
7343
- const freeTip = isFree ? "\n\nFree tier: 105 read-only tools (87 before the one-login auditor unlock). Write tools stay visible but answer with upgrade info instead of acting. Full version ($97/mo founding rate, same key upgrades in place): https://ghlcommand.com" : "";
7411
+ const freeTip = isFree ? "\n\nFree tier: 106 read-only tools (88 before the one-login auditor unlock). Write tools stay visible but answer with upgrade info instead of acting. Full version ($97/mo founding rate, same key upgrades in place): https://ghlcommand.com" : "";
7344
7412
  return {
7345
7413
  content: [{
7346
7414
  type: "text",
@@ -7366,7 +7434,7 @@ Unlock the account auditor next (the free tier's best tool): say "Unlock the Wor
7366
7434
  function registerEnableWorkflowBuilderTool(server2) {
7367
7435
  server2.tool(
7368
7436
  "enable_workflow_builder",
7369
- "Add Firebase credentials to an existing GHL Command install to unlock 52 additional tools across the internal-API modules: workflow builder (create/edit/clone/delete/publish/validate workflows, build_if_else_branch, build_goal_event, get_trigger_registry), funnel + page builder, form builder, pipeline builder, workflow cloner, smart lists, reputation, email campaigns, email templates, and memberships, plus the pre-deploy validator. On the FREE tier this same login unlocks the read-only auditor suite (audit_workflows, validate_workflow, full-detail workflow/funnel/pipeline reads). Requires you've already run setup_ghl_mcp. EASIEST PATH: run `capture_firebase_interactive` instead \u2014 a Chrome window opens, you log into GHL, zero pasting. Use THIS tool when you have JSON from `auto_capture_firebase_script` (console-paste path) to put in `firebase_paste`, or the three manual DevTools fields. Tool count goes from 177 to 229 after the next Claude restart.",
7437
+ "Add Firebase credentials to an existing GHL Command install to unlock 52 additional tools across the internal-API modules: workflow builder (create/edit/clone/delete/publish/validate workflows, build_if_else_branch, build_goal_event, get_trigger_registry), funnel + page builder, form builder, pipeline builder, workflow cloner, smart lists, reputation, email campaigns, email templates, and memberships, plus the pre-deploy validator. On the FREE tier this same login unlocks the read-only auditor suite (audit_workflows, validate_workflow, full-detail workflow/funnel/pipeline reads). Requires you've already run setup_ghl_mcp. EASIEST PATH: run `capture_firebase_interactive` instead \u2014 a Chrome window opens, you log into GHL, zero pasting. Use THIS tool when you have JSON from `auto_capture_firebase_script` (console-paste path) to put in `firebase_paste`, or the three manual DevTools fields. Tool count goes from 180 to 232 after the next Claude restart.",
7370
7438
  {
7371
7439
  // v3.25.0: one-paste path. Tool runs `auto_capture_firebase_script` to
7372
7440
  // get the console script; the script returns a JSON object that pastes
@@ -7447,7 +7515,7 @@ DevTools steps: https://elitedcs.com/ghl-mcp-firebase`
7447
7515
  "",
7448
7516
  "**You MUST restart Claude before using any workflow-builder tool.** Quit Claude completely (Cmd+Q on Mac, full exit on Windows) and reopen. Without a restart, the workflow builder tools will keep using the OLD Firebase auth from before this call and fail with 401 errors \u2014 even though this tool reported success.",
7449
7517
  "",
7450
- 'After restart, all 229 tools load. Try: "List my workflows in full detail" or "Validate workflow <id>".',
7518
+ 'After restart, all 232 tools load. Try: "List my workflows in full detail" or "Validate workflow <id>".',
7451
7519
  "",
7452
7520
  "Note: Firebase refresh tokens rotate every few weeks. If workflow tools stop working in a few weeks (run `health_check` to confirm Firebase auth: FAIL), run `auto_capture_firebase_script` for fresh values and re-run this tool with the new firebase_paste."
7453
7521
  ].join("\n")
@@ -7543,7 +7611,7 @@ The login in the capture window may belong to the wrong GHL account, or the sess
7543
7611
  "",
7544
7612
  "**You MUST restart Claude before using any workflow-builder tool.** Quit Claude completely (Cmd+Q on Mac, full exit on Windows) and reopen.",
7545
7613
  "",
7546
- 'After restart, all 229 tools load. Try: "List my workflows in full detail".',
7614
+ 'After restart, all 232 tools load. Try: "List my workflows in full detail".',
7547
7615
  "",
7548
7616
  "Future token rotations re-capture silently \u2014 if workflow tools ever 401, just run capture_firebase_interactive again; no window should appear."
7549
7617
  ].join("\n")
@@ -8180,6 +8248,143 @@ ${lines.join("\n")}
8180
8248
  }
8181
8249
  }
8182
8250
  );
8251
+ server2.tool(
8252
+ "create_sub_account",
8253
+ "Create a NEW GHL sub-account (location) under the agency, optionally loading a snapshot at creation \u2014 the first step of fully programmatic client provisioning (create \u2192 Blueprint build \u2192 verify_funnel). Requires an agency-level API key with the locations.write scope (add via register_agency_key). After creating, this probes whether the agency key can operate the new location and auto-registers it in the token registry when it can (switch_location then works immediately); if the probe fails, create a Private Integration Token inside the new sub-account and run register_location. Company Firebase (register_company_firebase) covers the internal-API builder tools in the new location either way.",
8254
+ {
8255
+ name: import_zod40.z.string().min(1).describe("Business / sub-account name."),
8256
+ snapshot_id: import_zod40.z.string().optional().describe("Snapshot ID to load at creation (see list_snapshots)."),
8257
+ company_id: import_zod40.z.string().optional().describe("Agency company ID. Defaults to the active location's registered companyId."),
8258
+ first_name: import_zod40.z.string().optional().describe("Prospect/owner first name."),
8259
+ last_name: import_zod40.z.string().optional().describe("Prospect/owner last name."),
8260
+ email: import_zod40.z.string().email().optional().describe("Prospect/owner email."),
8261
+ phone: import_zod40.z.string().optional().describe("Business phone (E.164, e.g. +15551234567)."),
8262
+ address: import_zod40.z.string().optional().describe("Street address."),
8263
+ city: import_zod40.z.string().optional(),
8264
+ state: import_zod40.z.string().optional(),
8265
+ postal_code: import_zod40.z.string().optional(),
8266
+ country: import_zod40.z.string().optional().describe("Two-letter country code. Defaults to US."),
8267
+ website: import_zod40.z.string().optional(),
8268
+ timezone: import_zod40.z.string().optional().describe("IANA timezone, e.g. America/Los_Angeles."),
8269
+ auto_register: import_zod40.z.boolean().optional().describe("Probe the new location with the agency key and add it to the token registry on success. Default true.")
8270
+ },
8271
+ async (args) => {
8272
+ const agencyKey = registry2?.getAgencyKey();
8273
+ if (!agencyKey) {
8274
+ return {
8275
+ content: [{ type: "text", text: "No agency key registered. Run register_agency_key with an agency-level Private Integration key (it needs the locations.write scope to create sub-accounts)." }],
8276
+ isError: true
8277
+ };
8278
+ }
8279
+ const companyId = args.company_id || (client.defaultLocationId ? registry2?.getToken(client.defaultLocationId)?.companyId : void 0) || process.env.GHL_COMPANY_ID;
8280
+ if (!companyId) {
8281
+ return {
8282
+ content: [{ type: "text", text: "Couldn't resolve the agency company ID \u2014 pass company_id explicitly (the GHL agency/company ID, not a location ID)." }],
8283
+ isError: true
8284
+ };
8285
+ }
8286
+ try {
8287
+ const agencyClient = new GHLClient({ apiKey: agencyKey });
8288
+ const prospect = args.first_name || args.last_name || args.email ? { prospectInfo: { ...args.first_name && { firstName: args.first_name }, ...args.last_name && { lastName: args.last_name }, ...args.email && { email: args.email } } } : {};
8289
+ const body = {
8290
+ name: args.name,
8291
+ companyId,
8292
+ country: args.country || "US",
8293
+ ...args.snapshot_id && { snapshotId: args.snapshot_id },
8294
+ ...args.phone && { phone: args.phone },
8295
+ ...args.address && { address: args.address },
8296
+ ...args.city && { city: args.city },
8297
+ ...args.state && { state: args.state },
8298
+ ...args.postal_code && { postalCode: args.postal_code },
8299
+ ...args.website && { website: args.website },
8300
+ ...args.timezone && { timezone: args.timezone },
8301
+ ...prospect
8302
+ };
8303
+ const created = await agencyClient.post("/locations/", { body, noRetry: true });
8304
+ const loc = created.location ?? created;
8305
+ const locationId2 = loc.id || loc._id;
8306
+ if (!locationId2) {
8307
+ return {
8308
+ content: [{ type: "text", text: `Sub-account create returned no location id. Raw response:
8309
+ ${JSON.stringify(created, null, 2)}` }],
8310
+ isError: true
8311
+ };
8312
+ }
8313
+ let probeOk = false;
8314
+ let probeNote = "";
8315
+ try {
8316
+ await agencyClient.get("/contacts/", { params: { locationId: locationId2, limit: 1 } });
8317
+ probeOk = true;
8318
+ } catch (e) {
8319
+ probeNote = e instanceof Error ? e.message : String(e);
8320
+ }
8321
+ let registered = false;
8322
+ if (probeOk && args.auto_register !== false && registry2) {
8323
+ registry2.registerLocation(locationId2, args.name, agencyKey, companyId);
8324
+ registered = true;
8325
+ }
8326
+ return {
8327
+ content: [{
8328
+ type: "text",
8329
+ text: [
8330
+ `Sub-account created: "${args.name}"`,
8331
+ `Location ID: ${locationId2}`,
8332
+ args.snapshot_id ? `Snapshot ${args.snapshot_id} is loading (GHL applies snapshots asynchronously \u2014 assets appear over the next minute or two).` : "No snapshot loaded.",
8333
+ probeOk ? registered ? "Agency key CAN operate this location \u2014 added to the token registry. Run switch_location to start building in it." : "Agency key CAN operate this location (auto_register was off \u2014 run register_location to add it)." : `Agency key can NOT read this location's data (${probeNote.slice(0, 120)}). Create a Private Integration Token inside the new sub-account, then run register_location. The company Firebase still covers the workflow/funnel/form builder tools there.`
8334
+ ].join("\n")
8335
+ }]
8336
+ };
8337
+ } catch (error) {
8338
+ const message = error instanceof Error ? error.message : String(error);
8339
+ const scopeHint = /401|403|scope|unauthorized/i.test(message) ? "\n\nThis usually means the registered agency key lacks the locations.write scope. Edit the agency Private Integration in GHL (Agency Settings \u2192 Private Integrations) and enable it." : "";
8340
+ return {
8341
+ content: [{ type: "text", text: `Error creating sub-account: ${message}${scopeHint}` }],
8342
+ isError: true
8343
+ };
8344
+ }
8345
+ }
8346
+ );
8347
+ server2.tool(
8348
+ "delete_sub_account",
8349
+ "Permanently delete a GHL sub-account (location) via the agency API. IRREVERSIBLE \u2014 every contact, workflow, funnel, and setting inside the sub-account is destroyed. Refuses to delete the currently active location. Also removes the location from the token registry. Requires an agency-level key with locations.write.",
8350
+ {
8351
+ location_id: import_zod40.z.string().min(1).describe("The location ID to delete."),
8352
+ confirm: import_zod40.z.literal("DELETE").describe("Must pass 'DELETE' to confirm this destructive action."),
8353
+ delete_twilio: import_zod40.z.boolean().optional().describe("Also delete the location's Twilio sub-account. Default false.")
8354
+ },
8355
+ async ({ location_id, delete_twilio }) => {
8356
+ const agencyKey = registry2?.getAgencyKey();
8357
+ if (!agencyKey) {
8358
+ return {
8359
+ content: [{ type: "text", text: "No agency key registered. Run register_agency_key first." }],
8360
+ isError: true
8361
+ };
8362
+ }
8363
+ if (client.defaultLocationId === location_id) {
8364
+ return {
8365
+ content: [{ type: "text", text: `Refusing to delete the currently ACTIVE location (${location_id}). switch_location to a different sub-account first.` }],
8366
+ isError: true
8367
+ };
8368
+ }
8369
+ try {
8370
+ const agencyClient = new GHLClient({ apiKey: agencyKey });
8371
+ await agencyClient.delete(`/locations/${location_id}`, { params: { deleteTwilioAccount: delete_twilio ?? false } });
8372
+ const wasRegistered = registry2?.removeLocation(location_id) ?? false;
8373
+ return {
8374
+ content: [{
8375
+ type: "text",
8376
+ text: `Sub-account ${location_id} permanently deleted.${wasRegistered ? " Removed from the token registry." : ""}`
8377
+ }]
8378
+ };
8379
+ } catch (error) {
8380
+ const message = error instanceof Error ? error.message : String(error);
8381
+ return {
8382
+ content: [{ type: "text", text: `Error deleting sub-account: ${message}` }],
8383
+ isError: true
8384
+ };
8385
+ }
8386
+ }
8387
+ );
8183
8388
  }
8184
8389
 
8185
8390
  // src/tools/bulk-operations.ts
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.50.0",
3
+ "version": "3.52.0",
4
4
  "mcpName": "io.github.drjerryrelth/ghl-command",
5
- "description": "GoHighLevel MCP Server for Claude. 229 tools — full CRM, automation, marketing control, account-wide workflow audit, live funnel-capture verification, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
5
+ "description": "GoHighLevel MCP Server for Claude. 232 tools — full CRM, automation, marketing control, account-wide workflow audit, live funnel-capture verification, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "ghl-mcp": "dist/index.js"