@elitedcs/ghl-mcp 3.83.4 → 3.83.5

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
@@ -2,6 +2,35 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 3.83.5 — the brand page's buttons work, and a run tells you it is working
6
+
7
+ If you use Design Studio, every button on a brand's own page has been dead since
8
+ 3.78.0. "Make it" did nothing but load a blank page. So did "Save changes", which
9
+ means any accent, font, name or logo you edited there was thrown away without
10
+ saying so. Nine days, five releases. One piece of broken punctuation in the page's
11
+ script switched all of it off at once, and because a browser discards a script like
12
+ that in silence, nothing on screen ever said so.
13
+
14
+ Both are fixed, and a new check now reads every page the cockpit serves and refuses
15
+ to ship one whose script will not run.
16
+
17
+ A design run also used to tell you nothing at all while it worked. Claude writes the
18
+ whole file in a single answer, so the log stayed empty for ten or twenty minutes,
19
+ and there was no way to tell a working run from a dead one. The sensible thing to do
20
+ was stop it, which was the wrong thing to do. The run now shows a clock, counts the
21
+ passes, and says plainly that nothing will appear until a pass finishes.
22
+
23
+ While reading what gets sent to Claude on those runs, we found the brief was asking
24
+ for two tools that are deliberately switched off for them, and then telling Claude
25
+ in the next line that it had no tools. It no longer does that.
26
+
27
+ **A correction to 3.83.4.** That release said a form field written without its id,
28
+ key and data type "is stored and shows as raw data instead of a rendered field."
29
+ That is wrong, and we tested it after we shipped it rather than before. A field
30
+ without those still renders normally in the form editor. What they actually do is
31
+ tie the answer to the contact record, which is reason enough to set them, and
32
+ `update_form` now says only that.
33
+
5
34
  ## 3.83.4 — one door for every write, so "protected" cannot quietly stop meaning protected
6
35
 
7
36
  3.83.3 fixed the holes. This closes the shape that kept making them.
@@ -28,6 +57,11 @@ RENDER rather than only how to send one. A custom question needs the real custom
28
57
  field's id, key and data type from your account; a field written without them is
29
58
  stored and shows as raw data instead of a rendered field.
30
59
 
60
+ > **Corrected 2026-09-12, in 3.83.5.** The last sentence is wrong. It was tested
61
+ > after this release shipped, not before: a field without those keys still renders
62
+ > normally in the form editor. They tie the answer to the contact record. That is
63
+ > what `update_form` says now.
64
+
31
65
  ## 3.83.3 — an account you marked protected is now actually protected
32
66
 
33
67
  **Marking a client account protected did not stop fourteen write paths from writing to it.**
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.83.4
900
+ **Version:** 3.83.5
901
901
 
902
902
  ---
903
903
 
package/dist/index.js CHANGED
@@ -17177,7 +17177,7 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
17177
17177
  );
17178
17178
  server2.tool(
17179
17179
  "update_form",
17180
- "Update a form's structure \u2014 fields, labels, conditional logic, auto-responder, email notifications, styling. Use get_form_full first to read the current form, modify the formData object, then pass it here. If name is omitted, the current name is preserved (one extra GET). IMPORTANT \u2014 a NEW field will not render in GoHighLevel's form editor unless it carries the binding GHL needs: a custom-question field must include the real custom field's `id`, `fieldKey` and `dataType` from THIS account (read them with get_custom_fields first; do not invent them), and a standard field (first_name, last_name, email, phone) needs `standard: true` with its `tag`. A field written without that binding is stored but shows as raw data instead of a rendered field. These shapes were reverse-engineered from live UI-built forms; copy an existing field from get_form_full rather than composing one from memory.",
17180
+ "Update a form's structure \u2014 fields, labels, conditional logic, auto-responder, email notifications, styling. Use get_form_full first to read the current form, modify the formData object, then pass it here. If name is omitted, the current name is preserved (one extra GET). IMPORTANT \u2014 a custom-question field should carry the real custom field's `id`, `fieldKey` and `dataType` from THIS account (read them with get_custom_fields; do not invent them), and a standard field (first_name, last_name, email, phone) needs `standard: true` with its `tag`. That binding is what ties the answer to the contact record. A field WITHOUT it still renders in the form editor \u2014 verified live on 2026-09-12, so do not expect a missing binding to show up as a broken field. Copy an existing field from get_form_full rather than composing one from memory; these shapes were reverse-engineered from live UI-built forms.",
17181
17181
  {
17182
17182
  formId: import_zod44.z.string().describe("The form ID to update."),
17183
17183
  formData: import_zod44.z.record(import_zod44.z.string(), import_zod44.z.unknown()).describe("The updated formData object containing form fields, settings, autoResponder config, etc. Fields live at formData.form.fields; each one needs the binding described above or the editor cannot render it."),
@@ -21368,7 +21368,7 @@ function tokensCss(d, theme) {
21368
21368
  ];
21369
21369
  return [...light, "", ...dark].join("\n");
21370
21370
  }
21371
- function composeBrief(d, brief) {
21371
+ function composeBrief(d, brief, packOpts = {}) {
21372
21372
  const conversionMode = brief.options.pageMode === "conversion" && brief.conversion;
21373
21373
  const { design, antiDefault } = rulesFor(brief.medium, "html", conversionMode ? "conversion" : "standard");
21374
21374
  const opts = brief.options;
@@ -21485,7 +21485,7 @@ ${cleanLogo.slice(0, 6e3)}` : d.logo?.dataUri ? `Logo markup: <img src="${d.logo
21485
21485
  "## Before you finish",
21486
21486
  ...checklist.map((c) => `- [ ] ${c}`),
21487
21487
  "",
21488
- `Write the whole file to \`${fileName}\`. Then run lint_design_contract, then verify_design; fix what they name; stop after three passes and report.`
21488
+ ...packOpts.tools === false ? [] : [`Write the whole file to \`${fileName}\`. Then run lint_design_contract, then verify_design; fix what they name; stop after three passes and report.`]
21489
21489
  ].join("\n");
21490
21490
  return { fileName, markdown, tokensCss: css2, rules: design, antiDefault, contract: chosenContract, checklist, bannedTells };
21491
21491
  }
@@ -28591,7 +28591,7 @@ var require_package = __commonJS({
28591
28591
  "package.json"(exports2, module2) {
28592
28592
  module2.exports = {
28593
28593
  name: "@elitedcs/ghl-mcp",
28594
- version: "3.83.4",
28594
+ version: "3.83.5",
28595
28595
  mcpName: "io.github.drjerryrelth/ghl-command",
28596
28596
  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.",
28597
28597
  main: "dist/index.js",
@@ -32397,8 +32397,18 @@ ${d.provenance.source ? `<p class="small">Source: ${esc5(d.provenance.source.url
32397
32397
  ${opts.dir ? `<p class="small">Stored at ${esc5(opts.dir)}</p>` : ""}
32398
32398
  ${foot(SHELL_JS(opts.seatToken ?? "") + `
32399
32399
  (function(){
32400
- var poll=null,runId=null;
32401
- function stopPolling(){if(poll){clearInterval(poll);poll=null}}
32400
+ var poll=null,runId=null,started=0,ticker=null,passes=0;
32401
+ function stopPolling(){if(poll){clearInterval(poll);poll=null}if(ticker){clearInterval(ticker);ticker=null}}
32402
+ function elapsed(ms){var s=Math.floor(ms/1000),m=Math.floor(s/60);s=s%60;return m?(m+"m "+(s<10?"0":"")+s+"s"):(s+"s")}
32403
+ /* The run prints NOTHING while Claude writes - it answers in one go, so the log
32404
+ box sits still for ten minutes or more. Silence used to be indistinguishable
32405
+ from a dead run, and the rational move was to press Stop on a working one
32406
+ (BUG-2026-09-12-03). So the cockpit keeps its own clock: it needs nothing
32407
+ from the child process, and it ticks every second, not every poll. */
32408
+ function tick(){
32409
+ if(!started)return;
32410
+ say("mk-status","Working on your Claude: "+elapsed(Date.now()-started)+" so far"+(passes?(", on pass "+(passes+1)):"")+". Claude writes the whole file in one answer, so nothing appears below until a pass finishes. Ten to twenty minutes is normal. Closing this tab does not stop it.");
32411
+ }
32402
32412
  function show(t){var el=document.getElementById("mk-log");el.hidden=false;el.textContent=t}
32403
32413
  document.getElementById("make").addEventListener("submit",async function(e){
32404
32414
  e.preventDefault();
@@ -32415,24 +32425,25 @@ document.getElementById("make").addEventListener("submit",async function(e){
32415
32425
  var r=await post("/api/design/run",body);
32416
32426
  runId=r.id;
32417
32427
  document.getElementById("mk-cancel").hidden=false;
32418
- say("mk-status","Running on your Claude. This keeps going if you close the tab.");
32428
+ started=Date.now();passes=0;tick();ticker=setInterval(tick,1000);
32419
32429
  poll=setInterval(async function(){
32420
32430
  var s=await (await fetch("/api/design/run?id="+encodeURIComponent(runId))).json();
32421
- show((s.tail||[]).join("
32422
- "));
32431
+ var lines=s.tail||[];
32432
+ show(lines.join("\\n"));
32433
+ passes=lines.filter(function(l){return l.trim().slice(0,5)==="pass "}).length;
32423
32434
  if(s.status!=="running"){
32424
32435
  stopPolling();go.disabled=false;document.getElementById("mk-cancel").hidden=true;
32425
32436
  var run=s.run||{};
32426
32437
  var design=run.ok?"design PASS on pass "+run.greenAt:"design NOT GREEN";
32427
- var copy=run.copy?(run.copy.error?" \xB7 copy not graded":" \xB7 copy "+run.copy.verdict+(run.copy.firstBreakdown?" (first breakdown: "+run.copy.firstBreakdown+")"):"")):"";
32438
+ var copy=run.copy?(run.copy.error?" \xB7 copy not graded":" \xB7 copy "+run.copy.verdict+(run.copy.firstBreakdown?" (first breakdown: "+run.copy.firstBreakdown+")":"")):"";
32428
32439
  say("mk-status",s.status==="done"?design+copy+" \xB7 "+(run.file||s.outDir):"The run stopped: "+design+copy,s.status==="done"&&run.ok?"ok":"err");
32429
32440
  }
32430
32441
  },4000);
32431
- }catch(err){say("mk-status",err.message,"err");go.disabled=false}
32442
+ }catch(err){stopPolling();started=0;say("mk-status",err.message,"err");go.disabled=false}
32432
32443
  });
32433
32444
  document.getElementById("mk-cancel").addEventListener("click",async function(){
32434
32445
  if(!runId)return;
32435
- try{await post("/api/design/cancel",{id:runId});stopPolling();say("mk-status","Stopped.");document.getElementById("mk-go").disabled=false;this.hidden=true}catch(e){say("mk-status",e.message,"err")}
32446
+ try{await post("/api/design/cancel",{id:runId});stopPolling();started=0;say("mk-status","Stopped.");document.getElementById("mk-go").disabled=false;this.hidden=true}catch(e){say("mk-status",e.message,"err")}
32436
32447
  });
32437
32448
  })();
32438
32449
  document.getElementById("override").addEventListener("submit",async(e)=>{e.preventDefault();const g=(id)=>document.getElementById(id).value.trim();const body={slug:${scriptJson(d.slug)}};if(g("accent"))body.accent=g("accent");if(g("displayFont"))body.displayFont=g("displayFont");if(g("bodyFont"))body.bodyFont=g("bodyFont");if(g("brandName"))body.name=g("brandName");if(g("logoSvg"))body.logoSvg=g("logoSvg");if(Object.keys(body).length===1){say("save-status","Nothing to change.");return}const b=document.getElementById("save");b.disabled=true;try{await post("/api/brand/set",body);say("save-status","Saved.","ok");location.reload()}catch(err){say("save-status",err.message,"err");b.disabled=false}});
@@ -43689,7 +43700,7 @@ async function runDesign(opts) {
43689
43700
  const medium = brief.medium;
43690
43701
  const outDir = (0, import_node_path7.resolve)(opts.outDir);
43691
43702
  (0, import_node_fs7.mkdirSync)(outDir, { recursive: true });
43692
- const pack = composeBrief(dna, brief);
43703
+ const pack = composeBrief(dna, brief, { tools: false });
43693
43704
  const maxPasses = Math.max(1, Math.min(3, opts.maxPasses ?? 3));
43694
43705
  const generate = opts.generate ?? claudeGenerate(opts.model ?? DEFAULT_MODEL);
43695
43706
  const conversionRun = brief.options.pageMode === "conversion" && brief.conversion ? { brief: brief.conversion, gate: opts.gate ?? "ship" } : void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.83.4",
3
+ "version": "3.83.5",
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",