@elitedcs/ghl-mcp 3.78.0 → 3.78.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.78.1 — the template installer stops guessing which funnel it just made
4
+
5
+ Yesterday's release shipped `install_ghl_template` with the originals kept by default,
6
+ because the tool worked out which funnel it had created by comparing the account before
7
+ and after, and on a busy sub-account that comparison cannot be certain. Keeping the
8
+ originals was the safe answer to an open question.
9
+
10
+ The question now has an answer. GoHighLevel's install response names what it created: the
11
+ funnel id and its first page. The tool reads that id and waits for that funnel, so it
12
+ cannot land on one somebody else happened to be building at the same moment.
13
+
14
+ **It does not simply believe it.** The name only counts when your account agrees the
15
+ funnel is genuinely new, and the tool checks the response against a complete picture of
16
+ the account taken before the install. Two independent confirmations, because that one
17
+ field is what authorises removing anything. If the name and the account ever disagree, the
18
+ name is thrown away, nothing is removed, and the result says so.
19
+
20
+ With both confirmations in hand the originals go back to being removed once every branded
21
+ page has been read back and measured, which is how the tool was meant to work: one clean
22
+ funnel in your client's colours, not a doubled one you have to tidy. Pass
23
+ `keepOriginals: true` if you would rather see both.
24
+
25
+ Anything less and the originals stay, whatever you passed: no name in the response, a name
26
+ the account cannot corroborate, or an account with so many funnels that the before picture
27
+ was incomplete.
28
+
29
+ A split-test step, where one step holds more than one page, still stops any removal. Only
30
+ the first page of such a step is rebuilt, so removing the step would take the variants
31
+ with it.
32
+
33
+ Counts unchanged: 250 tools, 51 modules, free tier 112.
34
+
3
35
  ## 3.78.0 — GoHighLevel's own template library, repainted in your client's colours
4
36
 
5
37
  GoHighLevel ships more than 900 designed funnel templates. You could always install
package/README.md CHANGED
@@ -897,7 +897,7 @@ Built by **[Elite DCs, LLC](https://elitedcs.com)** — a digital marketing and
897
897
 
898
898
  **Tech stack:** TypeScript, Node.js, esbuild, MCP SDK, Zod, GHL API v2, Firebase Auth
899
899
 
900
- **Version:** 3.78.0
900
+ **Version:** 3.78.1
901
901
 
902
902
  ---
903
903
 
package/dist/index.js CHANGED
@@ -10713,6 +10713,15 @@ async function templateFacets(api, product = "funnels") {
10713
10713
  const names = (list2) => (list2 ?? []).map((x) => typeof x === "string" ? x : x.name ?? "").filter(Boolean);
10714
10714
  return { categories: names(out.categories), tags: names(out.tags), features: names(out.features) };
10715
10715
  }
10716
+ function installedFunnelId(loaded) {
10717
+ const r = loaded;
10718
+ if (!r || typeof r !== "object") return void 0;
10719
+ if (r.status !== "ok") return void 0;
10720
+ if (r.data?.status !== "completed") return void 0;
10721
+ const t = r.data?.target;
10722
+ if (!t || t.asset !== "funnels") return void 0;
10723
+ return typeof t.assetId === "string" && GHL_ID.test(t.assetId) ? t.assetId : void 0;
10724
+ }
10716
10725
  async function installTemplate(api, templateId) {
10717
10726
  if (!/^[A-Za-z0-9]{12,40}$/.test(templateId)) throw new Error(`Not a template id: ${JSON.stringify(templateId)}`);
10718
10727
  return call(api, "/templates/template/load", {
@@ -10737,12 +10746,13 @@ function rankForBrand(rows, brand) {
10737
10746
  return { ...t, why: parts.join("; ") };
10738
10747
  });
10739
10748
  }
10740
- var SVC;
10749
+ var SVC, GHL_ID;
10741
10750
  var init_ghl_templates = __esm({
10742
10751
  "src/command-os/design/ghl-templates.ts"() {
10743
10752
  "use strict";
10744
10753
  init_color();
10745
10754
  SVC = "https://services.leadconnectorhq.com";
10755
+ GHL_ID = /^[A-Za-z0-9]{15,40}$/;
10746
10756
  }
10747
10757
  });
10748
10758
 
@@ -13550,7 +13560,7 @@ function installedFunnelTracker(before) {
13550
13560
  };
13551
13561
  }
13552
13562
  function removalHoldBack(opts) {
13553
- if (opts.truncated) return `this sub-account has at least ${opts.funnelPageSize} funnels, so "which funnel is new" could not be established with certainty`;
13563
+ if (opts.truncated && !opts.identifiedFromResponse) return `this sub-account's funnels could not all be read back before the install, so "which funnel is new" could not be established with certainty`;
13554
13564
  if (opts.multiPageStepNames.length > 0) return `${opts.multiPageStepNames.length} step(s) carry more than one page (${opts.multiPageStepNames.join(", ")}) and only the first of each was rebuilt`;
13555
13565
  return null;
13556
13566
  }
@@ -13563,12 +13573,23 @@ function registerPageStudioTools(server2, builderClient) {
13563
13573
  locationId: () => client.locationId
13564
13574
  });
13565
13575
  const FUNNEL_PAGE = 100;
13576
+ const FUNNEL_PAGE_CAP = 20;
13566
13577
  async function funnelSummary() {
13567
13578
  const out = /* @__PURE__ */ new Map();
13568
- const r = await funnelRequest("GET", `/funnel/list?locationId=${client.locationId}&limit=${FUNNEL_PAGE}`);
13569
- const rows = r.funnels ?? [];
13570
- for (const f of rows) out.set(f._id, `${f.name} (${(f.steps ?? []).length} steps)`);
13571
- return { map: out, truncated: rows.length >= FUNNEL_PAGE };
13579
+ let truncated = false;
13580
+ for (let page = 0; page < FUNNEL_PAGE_CAP; page++) {
13581
+ const r = await funnelRequest("GET", `/funnel/list?locationId=${client.locationId}&limit=${FUNNEL_PAGE}&offset=${page * FUNNEL_PAGE}`);
13582
+ const rows = r.funnels ?? [];
13583
+ const fresh = rows.filter((f) => !out.has(f._id));
13584
+ if (page > 0 && fresh.length === 0 && rows.length > 0) {
13585
+ truncated = true;
13586
+ break;
13587
+ }
13588
+ for (const f of rows) out.set(f._id, `${f.name} (${(f.steps ?? []).length} steps)`);
13589
+ if (rows.length < FUNNEL_PAGE) return { map: out, truncated: false };
13590
+ if (page === FUNNEL_PAGE_CAP - 1) truncated = true;
13591
+ }
13592
+ return { map: out, truncated };
13572
13593
  }
13573
13594
  async function funnelRequest(method, path36, body) {
13574
13595
  const headers = await client.buildHeaders();
@@ -13723,23 +13744,34 @@ ${await response.text()}`);
13723
13744
  templateId: import_zod42.z.string().describe("From find_ghl_template."),
13724
13745
  brandSlug: import_zod42.z.string().optional().describe("A stored brand to repaint the template in. Omit to install it in the designer's own colours."),
13725
13746
  confirm: import_zod42.z.literal("INSTALL").describe("Type INSTALL. This writes into the live sub-account you are switched to."),
13726
- keepOriginals: import_zod42.z.boolean().optional().describe("With brandSlug: leave the template's original unbranded steps beside the branded ones. DEFAULT TRUE \u2014 nothing is deleted unless you pass false. Deleting means trusting that the funnel this installed into is the one the template made, and on a busy sub-account that cannot be established with certainty (see the tool's note).")
13747
+ keepOriginals: import_zod42.z.boolean().optional().describe("With brandSlug: leave the template's original unbranded steps beside the branded ones. Normally they are removed once every branded page has been read back and measured. They are KEPT automatically, whatever you pass, if GoHighLevel did not name the funnel it created in its install response and the identification had to fall back to comparing the account before and after.")
13727
13748
  },
13728
13749
  async ({ templateId, brandSlug, keepOriginals }) => {
13729
13750
  const brand = brandSlug ? houseLooks().find((x) => x.slug === brandSlug) ?? readBrand(slugify(brandSlug)) : null;
13730
13751
  const inventory = await funnelSummary();
13731
13752
  const before = inventory.map;
13732
- await installTemplate(templateApi(), templateId);
13753
+ const named = installedFunnelId(await installTemplate(templateApi(), templateId));
13754
+ const namedFunnelId = named && !before.has(named) ? named : void 0;
13755
+ const namedButNotNew = Boolean(named) && named !== namedFunnelId;
13733
13756
  let funnel;
13734
13757
  let ambiguous = [];
13735
13758
  const tracker = installedFunnelTracker(before);
13736
13759
  for (let tries = 0; tries < 8; tries++) {
13737
13760
  const list2 = await funnelRequest("GET", `/funnel/list?locationId=${client.locationId}&limit=${FUNNEL_PAGE}`);
13738
- const seen = tracker.observe(list2.funnels ?? []);
13739
- if (seen.settled) {
13740
- if ("funnel" in seen) funnel = seen.funnel;
13741
- else ambiguous = seen.ambiguous;
13742
- break;
13761
+ const rows = list2.funnels ?? [];
13762
+ if (namedFunnelId) {
13763
+ const mine = rows.find((f) => f._id === namedFunnelId);
13764
+ if (mine && (mine.steps ?? []).length > 0) {
13765
+ funnel = mine;
13766
+ break;
13767
+ }
13768
+ } else {
13769
+ const seen = tracker.observe(rows);
13770
+ if (seen.settled) {
13771
+ if ("funnel" in seen) funnel = seen.funnel;
13772
+ else ambiguous = seen.ambiguous;
13773
+ break;
13774
+ }
13743
13775
  }
13744
13776
  await new Promise((r) => setTimeout(r, 1500));
13745
13777
  }
@@ -13839,9 +13871,10 @@ ${await response.text()}`);
13839
13871
  }
13840
13872
  }
13841
13873
  let removed = 0;
13842
- const removeOriginals = keepOriginals === false;
13874
+ const certain = Boolean(namedFunnelId) && !inventory.truncated;
13875
+ const removeOriginals = certain ? keepOriginals !== true : keepOriginals === false;
13843
13876
  const complete = problems.length === 0 && done.length === originals.length && done.length > 0;
13844
- const holdBack = removalHoldBack({ truncated: inventory.truncated, funnelPageSize: FUNNEL_PAGE, multiPageStepNames: multiPage.map((m) => m.name) });
13877
+ const holdBack = removalHoldBack({ truncated: inventory.truncated, funnelPageSize: FUNNEL_PAGE, multiPageStepNames: multiPage.map((m) => m.name), identifiedFromResponse: certain });
13845
13878
  if (complete && removeOriginals && holdBack === null) {
13846
13879
  for (const d of done) {
13847
13880
  try {
@@ -13860,6 +13893,7 @@ ${await response.text()}`);
13860
13893
  pages: done,
13861
13894
  originalsRemoved: complete && removeOriginals ? removed : 0,
13862
13895
  ...problems.length > 0 && { problems, kept: "Every original step was LEFT IN PLACE because something failed. Nothing was deleted." },
13896
+ ...namedButNotNew && { identification: "GoHighLevel named a funnel that this account ALREADY had before the install. That name was discarded and the account was compared instead, so the funnel above was identified the cautious way and originals are only removed if you asked for it explicitly. Worth reporting: it means the install response no longer means what it did." },
13863
13897
  ...holdBack !== null && { kept: `Every original step was LEFT IN PLACE on purpose: ${holdBack}. The branded pages are there beside them \u2014 remove the originals by hand once you have looked.` },
13864
13898
  warnings: rethemeEnvelope({}, brand, palette).warnings,
13865
13899
  next: done.length ? `Grade one before the client sees it: verify_design on ${String(done[0].preview)} with medium "site" and placement "ghl". GoHighLevel's own templates are designed but frequently fail contrast and tap-target rules.` : "Nothing was branded.",
@@ -38775,7 +38809,7 @@ var require_package = __commonJS({
38775
38809
  "package.json"(exports2, module2) {
38776
38810
  module2.exports = {
38777
38811
  name: "@elitedcs/ghl-mcp",
38778
- version: "3.78.0",
38812
+ version: "3.78.1",
38779
38813
  mcpName: "io.github.drjerryrelth/ghl-command",
38780
38814
  description: "GoHighLevel MCP Server for Claude. 250 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.",
38781
38815
  main: "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.78.0",
3
+ "version": "3.78.1",
4
4
  "mcpName": "io.github.drjerryrelth/ghl-command",
5
5
  "description": "GoHighLevel MCP Server for Claude. 250 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",