@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 +51 -0
- package/README.md +5 -5
- package/dist/index.js +223 -18
- package/package.json +2 -2
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.**
|
|
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) —
|
|
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 (
|
|
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
|
|
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
|
|
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.
|
|
34
|
+
version: "3.52.0",
|
|
35
35
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
36
|
-
description: "GoHighLevel MCP Server for Claude.
|
|
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
|
|
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
|
|
6179
|
-
|
|
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
|
-
|
|
6184
|
-
|
|
6185
|
-
pageId
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
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
|
|
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 ? "
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
3
|
+
"version": "3.52.0",
|
|
4
4
|
"mcpName": "io.github.drjerryrelth/ghl-command",
|
|
5
|
-
"description": "GoHighLevel MCP Server for Claude.
|
|
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"
|