@elitedcs/ghl-mcp 3.42.0 → 3.44.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.
Files changed (2) hide show
  1. package/dist/index.js +55 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ var require_package = __commonJS({
31
31
  "package.json"(exports2, module2) {
32
32
  module2.exports = {
33
33
  name: "@elitedcs/ghl-mcp",
34
- version: "3.42.0",
34
+ version: "3.44.0",
35
35
  mcpName: "io.github.drjerryrelth/ghl-command",
36
36
  description: "GoHighLevel MCP Server for Claude. 218 tools \u2014 full CRM, automation, marketing control, account-wide workflow audit, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
37
37
  main: "dist/index.js",
@@ -10704,9 +10704,21 @@ var pageSchema = import_zod52.z.object({
10704
10704
  formRef: nsRef("form").optional(),
10705
10705
  calendarRef: nsRef("calendar").optional()
10706
10706
  });
10707
+ var FUNNEL_TARGETS = ["ghl", "external"];
10708
+ var FUNNEL_HOSTS = ["cloudflare", "vercel"];
10707
10709
  var funnelSchema = import_zod52.z.object({
10708
10710
  ref: nsRef("funnel"),
10709
10711
  name: import_zod52.z.string(),
10712
+ // Where the funnel is built. "ghl" (default) = funnel + named steps in GHL.
10713
+ // "external" = the subscriber builds + hosts the site themselves (Cloudflare/
10714
+ // Vercel) and wires its form back to this GHL sub-account (POWER-USER path —
10715
+ // see blueprint-funnel-targets-spec.md §9). The executor does NOT build or
10716
+ // deploy an external funnel; it surfaces the GHL-side wiring info.
10717
+ target: import_zod52.z.enum(FUNNEL_TARGETS).optional(),
10718
+ host: import_zod52.z.enum(FUNNEL_HOSTS).optional(),
10719
+ // external only
10720
+ domain: import_zod52.z.string().optional(),
10721
+ // external only
10710
10722
  pages: import_zod52.z.array(pageSchema)
10711
10723
  });
10712
10724
  var emailAssetSchema = import_zod52.z.object({
@@ -10786,9 +10798,14 @@ var workflowSchema = import_zod52.z.object({
10786
10798
  actions: import_zod52.z.array(actionSchema).max(40)
10787
10799
  // house rule: <=40 actions/workflow
10788
10800
  });
10801
+ var HANDOFF_OWNER_LEGACY = {
10802
+ "JERRY-UI": "OPERATOR-UI",
10803
+ "JERRY-EXT": "OPERATOR-EXT",
10804
+ "SASHA": "TEAM"
10805
+ };
10789
10806
  var handoffSchema = import_zod52.z.object({
10790
10807
  ref: nsRef("handoff"),
10791
- owner: import_zod52.z.enum(["JERRY-UI", "JERRY-EXT", "SASHA"]),
10808
+ owner: import_zod52.z.enum(["OPERATOR-UI", "OPERATOR-EXT", "TEAM", "JERRY-UI", "JERRY-EXT", "SASHA"]).transform((o) => HANDOFF_OWNER_LEGACY[o] ?? o),
10792
10809
  title: import_zod52.z.string(),
10793
10810
  trigger: import_zod52.z.string().optional(),
10794
10811
  instruction: import_zod52.z.string(),
@@ -11006,6 +11023,14 @@ function validateBuildPlan(input) {
11006
11023
  }
11007
11024
  }
11008
11025
  }
11026
+ for (const fn of plan.funnels ?? []) {
11027
+ const isExternal = fn.target === "external";
11028
+ if (!isExternal && (fn.host !== void 0 || fn.domain !== void 0)) {
11029
+ warnings.push(
11030
+ `funnel "${fn.ref}" sets host/domain but target is not "external" \u2014 those are ignored for a GHL-built funnel`
11031
+ );
11032
+ }
11033
+ }
11009
11034
  for (const p of plan.pipelines ?? []) {
11010
11035
  const positions = p.stages.map((s) => s.position).sort((a, b) => a - b);
11011
11036
  const expected = positions.every((pos, idx) => pos === idx);
@@ -11446,7 +11471,10 @@ var SECTION_OBJECTS = {
11446
11471
  customValues: (p) => (p.customValues ?? []).map((x) => ({ ref: x.ref, name: x.name })),
11447
11472
  calendars: (p) => (p.calendars ?? []).map((x) => ({ ref: x.ref, name: x.name })),
11448
11473
  forms: (p) => (p.forms ?? []).map((x) => ({ ref: x.ref, name: x.name })),
11449
- funnels: (p) => (p.funnels ?? []).map((x) => ({ ref: x.ref, name: x.name })),
11474
+ // EXTERNAL funnels are NOT built in GHL (the user hosts them) exclude them
11475
+ // from the scan so dry-run matches execute: they aren't counted as would-create,
11476
+ // don't seed idMap, and can't trip onConflict on a GHL-funnel name collision.
11477
+ funnels: (p) => (p.funnels ?? []).filter((x) => x.target !== "external").map((x) => ({ ref: x.ref, name: x.name })),
11450
11478
  emails: (p) => (p.emails ?? []).map((x) => ({ ref: x.ref, name: x.name })),
11451
11479
  sms: (p) => (p.sms ?? []).map((x) => ({ ref: x.ref, name: x.name })),
11452
11480
  workflows: (p) => (p.workflows ?? []).map((x) => ({ ref: x.ref, name: x.name }))
@@ -11534,12 +11562,13 @@ function renderReport(plan, result, ctx) {
11534
11562
  );
11535
11563
  L.push("");
11536
11564
  const manualCalRefs = new Set(result.calendarsManual.map((c) => c.ref));
11565
+ const externalFunnelRefs = new Set((plan.funnels ?? []).filter((f) => f.target === "external").map((f) => f.ref));
11537
11566
  L.push("\u2500\u2500 Blueprint builds automatically \u2500\u2500");
11538
11567
  for (const section2 of EXECUTION_ORDER) {
11539
11568
  const secItems = result.items.filter((i) => i.type === section2);
11540
11569
  if (secItems.length === 0) continue;
11541
11570
  for (const it of secItems) {
11542
- if (manualCalRefs.has(it.ref)) continue;
11571
+ if (manualCalRefs.has(it.ref) || externalFunnelRefs.has(it.ref)) continue;
11543
11572
  const mark = it.status === "existing" ? "skip (exists)" : ctx.mode === "dry_run" ? "would create" : "create";
11544
11573
  L.push(` [${section2}] ${it.name} \u2014 ${mark}${it.existingId ? ` \u2192 ${it.existingId}` : ""}`);
11545
11574
  }
@@ -11558,7 +11587,11 @@ function renderReport(plan, result, ctx) {
11558
11587
  }
11559
11588
  for (const fn of plan.funnels ?? []) {
11560
11589
  any = true;
11561
- L.push(` \u2022 [funnel] Design + populate the pages of funnel "${fn.name}" (Blueprint builds the funnel + steps; page content/design is manual).`);
11590
+ if (fn.target === "external") {
11591
+ L.push(` \u2022 [funnel] "${fn.name}" is EXTERNAL (host: ${fn.host ?? "cloudflare"}) \u2014 build + host the site yourself and wire its form to this GHL sub-account (contacts API + the workflow trigger tag); booking \u2192 the GHL calendar URL. Power-user path.`);
11592
+ } else {
11593
+ L.push(` \u2022 [funnel] Design + populate the pages of funnel "${fn.name}" (Blueprint builds the funnel + steps; page content/design is manual).`);
11594
+ }
11562
11595
  }
11563
11596
  for (const w of result.workflows) {
11564
11597
  if (w.triggerManual) {
@@ -11808,7 +11841,21 @@ async function executeBackbone(plan, deps, opts = {}) {
11808
11841
  });
11809
11842
  return `Design + publish the pages of funnel "${fn.name}": ${pageBits.join("; ")}. (Blueprint built the funnel + steps; page content/design is manual.)`;
11810
11843
  };
11844
+ const externalFunnelReason = (fn) => {
11845
+ const host = fn.host ?? "cloudflare";
11846
+ const bits = fn.pages.map((pg) => {
11847
+ const hosts = [];
11848
+ if (pg.formRef) hosts.push(`form "${formNameByRef.get(pg.formRef) ?? pg.formRef}"`);
11849
+ if (pg.calendarRef) hosts.push(`booking \u2192 calendar "${calNameByRef.get(pg.calendarRef) ?? pg.calendarRef}"`);
11850
+ return `page "${pg.name}"${hosts.length ? ` (${hosts.join(", ")})` : ""}`;
11851
+ });
11852
+ return `Funnel "${fn.name}" is EXTERNAL (host: ${host}${fn.domain ? `, domain: ${fn.domain}` : ""}) \u2014 Blueprint does NOT build or deploy it. Build + host the site yourself (technically-capable path), then wire its form to THIS GHL sub-account: upsert the contact via the GHL contacts API (your Private Integration token as a host-side secret) with the plan's custom fields, then add the tag that triggers your speed-to-lead workflow; ${bits.join("; ")}.`;
11853
+ };
11811
11854
  for (const fn of plan.funnels ?? []) {
11855
+ if (fn.target === "external") {
11856
+ manual.push({ ref: fn.ref, type: "funnel-external", name: fn.name, reason: externalFunnelReason(fn) });
11857
+ continue;
11858
+ }
11812
11859
  let funnels;
11813
11860
  try {
11814
11861
  funnels = await deps.listFunnels();
@@ -12415,7 +12462,7 @@ function registerIntakeToBuildTools(server2, client, builderClient) {
12415
12462
  );
12416
12463
  server2.tool(
12417
12464
  "apply_build_plan",
12418
- `Take an APPROVED Blueprint \xA75 build plan + the CURRENT confirmed sub-account and build it. mode:"dry_run" (default) writes NOTHING \u2014 it resolves refs, expands each workflow's logical actions to native GHL JSON, runs the NEVER-CLOBBER existing-asset scan, and returns a two-part report. Run it FIRST. mode:"execute" performs LIVE writes for the CRM backbone (pipelines+stages, custom fields, tags, custom values), calendars, AND forms: never clobbers (same-named objects are bound to their existing id, never modified), verifies each create by read-back before resolving its ref, halts on the first failure returning the partial idMap, and is idempotent (re-run = no-op). Staff-requiring calendars (round_robin etc.) auto-build only when you are the sole account user (auto-assigns you as the team member); with 0 or 2+ users they're surfaced as a manual step, not auto-staffed to a guess. Forms build with their standard + custom fields (custom fieldRefs resolve to the real fields created earlier in the run). Funnels build structurally (funnel + named steps; page content/HTML is a manual step \u2014 plans carry outlines). Workflows build as DRAFT with all their logical actions expanded to native GHL JSON (incl. opportunity create/move steps, re-enabled v3.41.0) and chained; a contact_tag trigger is built automatically, other trigger types are surfaced as a manual step; the operator reviews + publishes. Always confirms the active location and validates the plan before any write.`,
12465
+ `Take an APPROVED Blueprint \xA75 build plan + the CURRENT confirmed sub-account and build it. mode:"dry_run" (default) writes NOTHING \u2014 it resolves refs, expands each workflow's logical actions to native GHL JSON, runs the NEVER-CLOBBER existing-asset scan, and returns a two-part report. Run it FIRST. mode:"execute" performs LIVE writes for the CRM backbone (pipelines+stages, custom fields, tags, custom values), calendars, AND forms: never clobbers (same-named objects are bound to their existing id, never modified), verifies each create by read-back before resolving its ref, halts on the first failure returning the partial idMap, and is idempotent (re-run = no-op). Staff-requiring calendars (round_robin etc.) auto-build only when you are the sole account user (auto-assigns you as the team member); with 0 or 2+ users they're surfaced as a manual step, not auto-staffed to a guess. Forms build with their standard + custom fields (custom fieldRefs resolve to the real fields created earlier in the run). Funnels: a GHL funnel (target:"ghl", default) builds structurally (funnel + named steps; page content/HTML is a manual step \u2014 plans carry outlines); a funnel with target:"external" is NOT built or deployed here \u2014 the user builds + hosts the site themselves (Cloudflare/Vercel) and wires its form back to this GHL sub-account (surfaced as a manual wiring step). Workflows build as DRAFT with all their logical actions expanded to native GHL JSON (incl. opportunity create/move steps, re-enabled v3.41.0) and chained; a contact_tag trigger is built automatically, other trigger types are surfaced as a manual step; the operator reviews + publishes. Always confirms the active location and validates the plan before any write.`,
12419
12466
  {
12420
12467
  plan: import_zod53.z.record(import_zod53.z.unknown()).describe("The approved \xA75 Build Plan object."),
12421
12468
  mode: import_zod53.z.enum(["dry_run", "execute"]).optional().describe("dry_run (default) = resolve/expand/scan/report, no writes. execute = live writes (not yet enabled)."),
@@ -12508,7 +12555,7 @@ function registerIntakeToBuildTools(server2, client, builderClient) {
12508
12555
  manual: exec.manual,
12509
12556
  idMap: exec.idMap,
12510
12557
  deferred: exec.deferred,
12511
- deferredNote: "execute builds the WHOLE plan live: CRM backbone (pipelines, custom fields, tags, custom values), calendars, forms, funnels (funnel + named steps), and workflows (DRAFT, with all steps incl. opportunity create/move). Remaining manual steps are surfaced per item: staff-requiring calendars in multi-user accounts, funnel page content/design, workflow triggers other than contact_tag (set by hand), and publishing the DRAFT workflows.",
12558
+ deferredNote: 'execute builds the WHOLE plan live: CRM backbone (pipelines, custom fields, tags, custom values), calendars, forms, funnels (GHL-built = funnel + named steps), and workflows (DRAFT, with all steps incl. opportunity create/move). Remaining manual steps are surfaced per item: staff-requiring calendars in multi-user accounts, GHL funnel page content/design, EXTERNAL funnels (target:"external" \u2014 the user builds + hosts the site themselves and wires it back; not built here), workflow triggers other than contact_tag, and publishing the DRAFT workflows.',
12512
12559
  nextManualSteps: [...execManualLines, ...manualLines, ...handoffLines],
12513
12560
  summary: exec.ok ? `Built ${exec.built.filter((b) => b.status === "created").length} new object(s), bound ${exec.built.filter((b) => b.status === "existing").length} existing.${exec.manual.length ? ` ${exec.manual.length} item(s) need a manual step (see nextManualSteps).` : ""}${exec.deferred.length ? " Deferred: " + exec.deferred.map((d) => `${d.count} ${d.section}`).join(", ") + " (manual)." : ""}` : `HALTED at ${exec.halted?.atRef} (${exec.halted?.reason}). ${exec.built.length} object(s) were created before the halt \u2014 see idMap to resume or clean up. NEVER-CLOBBER means a re-run will bind those, not duplicate them.`
12514
12561
  });
@@ -12550,7 +12597,7 @@ function registerIntakeToBuildTools(server2, client, builderClient) {
12550
12597
  calendarsManual: result.calendarsManual,
12551
12598
  handoffs: result.handoffs,
12552
12599
  report,
12553
- next: 'Review the report. When it looks right, re-run with mode:"execute" to build it all live: CRM backbone (pipelines, fields, tags, custom values), calendars, forms, funnels (funnel + named steps), and workflows (DRAFT, with all steps incl. opportunity moves). Manual next steps: funnel page content/design, contact-tag-triggers build automatically but other trigger types are set by hand, then publish the workflows.'
12600
+ next: `Review the report. When it looks right, re-run with mode:"execute" to build it all live: CRM backbone (pipelines, fields, tags, custom values), calendars, forms, GHL funnels (funnel + named steps), and workflows (DRAFT, with all steps incl. opportunity moves). Manual next steps (see the report's Part 2): GHL funnel page content/design; EXTERNAL funnels (target:"external") which you build + host yourself and wire back; workflow triggers other than contact_tag; then publish the workflows.`
12554
12601
  });
12555
12602
  } catch (error) {
12556
12603
  return errorResponse(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.42.0",
3
+ "version": "3.44.0",
4
4
  "mcpName": "io.github.drjerryrelth/ghl-command",
5
5
  "description": "GoHighLevel MCP Server for Claude. 218 tools — full CRM, automation, marketing control, account-wide workflow audit, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
6
6
  "main": "dist/index.js",