@elitedcs/ghl-mcp 3.49.0 → 3.50.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,59 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.50.0 — FREE read-only tier
4
+
5
+ A free license (`ghlcommand.com/free`, key by email, no card) now runs the same
6
+ npm package in read-only mode on the user's own GHL account.
7
+
8
+ - **Tier gating in the tool registry** (`tool-filter.ts`, second axis alongside
9
+ the env allowlist). Free tier: every read-only tool works for real — all
10
+ `get_*`/`list_*`/`search_*` plus `audit_workflows`, `validate_workflow`,
11
+ `get_account_health_summary`, `compare_locations`, `verify_funnel`,
12
+ `check_blog_slug`, `workflow_builder_status`. That's **105 usable tools with
13
+ the Firebase login (87 before it)**, enumerated from the registry by the
14
+ drift-guard test. Write tools still register with full schemas but their
15
+ handlers return a clean upgrade message (checkout link, same-key upgrade
16
+ note) — never a stack trace, never a half-executed write. Excluded from free
17
+ by design: `export_account`, the whole Blueprint (`intake-to-build`) module,
18
+ and the multi-location registry writes (`register_location`,
19
+ `register_agency_key`, `switch_location`, …) — free is 1 location, 1 machine.
20
+ - **Tier is cryptographically bound.** The license server now signs `tier`
21
+ inside the Ed25519 attestation payload; the MCP derives the session tier ONLY
22
+ from a verified attestation (legacy attestations without the field = 'full').
23
+ Hand-editing credentials.json cannot unlock writes. Old MCP versions ignore
24
+ the extra payload field — fully backward compatible.
25
+ - **Firebase capture stays available on free** (`capture_firebase_interactive`,
26
+ `enable_workflow_builder`, `auto_capture_firebase_script`): the one-time GHL
27
+ login is what powers the read-only auditor suite. Builder writes stay gated.
28
+ - **setup_ghl_mcp / request_license** are tier-aware: free installs see the
29
+ read-only counts and the auditor-unlock next step; request_license now offers
30
+ the free tier (`ghlcommand.com/free`) alongside purchase.
31
+ - **Drift guard extended** (`tool-count.test.ts`): free-tier counts (105/87)
32
+ are computed from the registry and asserted against README + setup-tool copy;
33
+ a classification change breaks the build until the claims are re-synced. The
34
+ ghlcommand.com `/free` page and the key-delivery email carry the same number —
35
+ re-sync those manually when this test moves (different repos).
36
+ - Server side (elite-dcs/website + Supabase, deployed separately): `licenses.status`
37
+ gains `'free'`; `register_device` returns the row status; `validate-license`
38
+ returns + signs `tier`; the Stripe webhook upgrades a free row IN PLACE on
39
+ purchase (same key, installs 1→3, `source: stripe_97mo_from_free`) — proven
40
+ against a live throwaway row and a mocked-fetch webhook harness. New
41
+ `/api/free-license` provisioning endpoint (signup → row → key email → 3NA51
42
+ tag `ghl-command-free-tier`).
43
+
44
+ ## 3.49.1 — Docs patch: one-click capture is now the primary Milestone-2 path
45
+
46
+ No behavior changes; text only.
47
+
48
+ - README "Enable the Workflow Builder": `capture_firebase_interactive` (log into a
49
+ Chrome window, zero pasting) is the supported path; the console-paste script is
50
+ demoted to a collapsed fallback (still the path for locked-down machines and for
51
+ capturing CLIENT accounts in multi-tenant). Rotation note now points at the
52
+ silent re-capture.
53
+ - `enable_workflow_builder` and `auto_capture_firebase_script` tool descriptions
54
+ point to `capture_firebase_interactive` first.
55
+
56
+
3
57
  ## 3.49.0 — One-click Workflow Builder unlock (`capture_firebase_interactive`)
4
58
 
5
59
  The buyer's whole job is now: log into GHL in a Chrome window the tool opens.
package/README.md CHANGED
@@ -8,6 +8,8 @@ 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.
12
+
11
13
  Built by [Elite DCs, LLC](https://elitedcs.com).
12
14
 
13
15
  ---
@@ -158,7 +160,16 @@ https://app.gohighlevel.com/v2/location/YOUR_LOCATION_ID/dashboard
158
160
 
159
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.
160
162
 
161
- **One-paste capture (the supported path):**
163
+ **One-click capture (v3.49.0+, the supported path):**
164
+
165
+ 1. In Claude, ask: `Run capture_firebase_interactive`. Your installed Chrome opens a dedicated window on the GHL login page (no browser download; it never touches your daily browsing profile).
166
+ 2. Log into your GHL account in that window. That's the whole job — the tool reads the three Firebase values itself, validates them against Firebase, and saves them. If the call says it's still waiting, finish logging in and run the same tool again.
167
+ 3. Restart Claude. The Firebase-gated tools are live.
168
+
169
+ > Firebase refresh tokens can rotate. If workflow tools stop working after a few weeks (`health_check` shows Firebase auth: FAIL), just re-run `capture_firebase_interactive` — after your first login it re-captures silently, no window at all.
170
+
171
+ <details>
172
+ <summary><strong>Console-paste fallback</strong> (no Chrome/Edge installed, the window path fails, or you're capturing a CLIENT account for multi-tenant)</summary>
162
173
 
163
174
  1. In Claude, ask: `Run auto_capture_firebase_script`. It returns a small browser-console script.
164
175
  2. In Chrome, open a tab logged into your GHL account. Press **F12** (Windows) or **Cmd+Option+I** (Mac), click the **Console** tab, paste the script, press Enter. If Chrome blocks the paste, type `allow pasting` in the Console first, then paste again.
@@ -168,7 +179,7 @@ The builder + cloner + validator tools (workflow builder, funnel builder, form b
168
179
 
169
180
  Screenshots and the same script behind a copy button: [elitedcs.com/ghl-mcp-firebase](https://elitedcs.com/ghl-mcp-firebase)
170
181
 
171
- > Firebase refresh tokens can rotate. If workflow tools stop working after a few weeks (`health_check` shows Firebase auth: FAIL), re-run `auto_capture_firebase_script` for fresh values and pass them to `enable_workflow_builder` again.
182
+ </details>
172
183
 
173
184
  <details>
174
185
  <summary><strong>Manual fallback</strong> (locked-down browsers that block console pasting or clipboard access)</summary>
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.49.0",
34
+ version: "3.50.0",
35
35
  mcpName: "io.github.drjerryrelth/ghl-command",
36
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.",
37
37
  main: "dist/index.js",
@@ -1947,6 +1947,47 @@ var ALWAYS_ON_TOOL_NAMES = /* @__PURE__ */ new Set([
1947
1947
  "enable_workflow_builder",
1948
1948
  "health_check"
1949
1949
  ]);
1950
+ var FREE_TIER_EXTRA_READ_TOOLS = /* @__PURE__ */ new Set([
1951
+ "audit_workflows",
1952
+ // account-wide silent-failure audit — the free tier's wow moment
1953
+ "validate_workflow",
1954
+ // single-workflow pre-flight validation (read-only)
1955
+ "compare_locations",
1956
+ // cross-location config diff (reads only)
1957
+ "verify_funnel",
1958
+ // live funnel QA (reads + public URL probes)
1959
+ "check_blog_slug",
1960
+ // slug availability lookup
1961
+ "workflow_builder_status"
1962
+ // no-Firebase fallback diagnostic (pure status read)
1963
+ ]);
1964
+ var FREE_TIER_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
1965
+ "export_account"
1966
+ // full-account export is a paid capability
1967
+ ]);
1968
+ var FREE_TIER_EXCLUDED_MODULES = /* @__PURE__ */ new Set([
1969
+ "intake-to-build"
1970
+ // Blueprint lane is paid end-to-end
1971
+ ]);
1972
+ var READ_PREFIXES = ["get_", "list_", "search_"];
1973
+ function isFreeTierTool(toolName, moduleName) {
1974
+ if (ALWAYS_ON_TOOL_NAMES.has(toolName)) return true;
1975
+ if (FREE_TIER_EXCLUDED_MODULES.has(moduleName.toLowerCase())) return false;
1976
+ if (FREE_TIER_EXCLUDED_TOOLS.has(toolName)) return false;
1977
+ if (FREE_TIER_EXTRA_READ_TOOLS.has(toolName)) return true;
1978
+ return READ_PREFIXES.some((p) => toolName.startsWith(p));
1979
+ }
1980
+ var FREE_TIER_DESC_SUFFIX = " [PAID TIER \u2014 this free license is read-only; calling this returns upgrade info, it will NOT perform the action.]";
1981
+ function freeTierUpgradeMessage(toolName) {
1982
+ return [
1983
+ `\`${toolName}\` needs the full GHL Command license \u2014 your free key is the read-only tier.`,
1984
+ "",
1985
+ "Free tier (what you have): every read \u2014 audits, account health, pipelines, contacts, conversations, funnels, workflows \u2014 on your own account.",
1986
+ "Full tier ($97/mo founding rate): Claude actually builds and fixes things \u2014 workflows, funnels, forms, pipelines, campaigns \u2014 plus the Workflow Builder, multi-location, and 3-machine activation.",
1987
+ "",
1988
+ "Upgrade at https://ghlcommand.com with the SAME email you used for the free key \u2014 your license upgrades in place (no reinstall, same key). Then fully restart Claude and this tool goes live."
1989
+ ].join("\n");
1990
+ }
1950
1991
  function parseList(raw) {
1951
1992
  if (raw === void 0) return null;
1952
1993
  const items = raw.split(/[,\s]+/).map((x) => x.trim().toLowerCase()).filter((x) => x.length > 0);
@@ -1974,7 +2015,7 @@ function shouldRegister(toolName, moduleName, config3) {
1974
2015
  const toolEnabled = config3.enabledTools?.has(toolLower) ?? false;
1975
2016
  return moduleEnabled || toolEnabled;
1976
2017
  }
1977
- function wrapServerForModule(realServer, moduleName, config3, attemptedTools, registeredTools) {
2018
+ function wrapServerForModule(realServer, moduleName, config3, attemptedTools, registeredTools, tier = "full", gatedTools) {
1978
2019
  return new Proxy(realServer, {
1979
2020
  get(target, prop, receiver) {
1980
2021
  if (prop === "tool") {
@@ -1984,7 +2025,21 @@ function wrapServerForModule(realServer, moduleName, config3, attemptedTools, re
1984
2025
  return void 0;
1985
2026
  }
1986
2027
  registeredTools.add(name);
1987
- return target.tool(name, ...rest);
2028
+ let args = rest;
2029
+ if (tier === "free" && !isFreeTierTool(name, moduleName)) {
2030
+ gatedTools?.add(name);
2031
+ args = [...rest];
2032
+ const last = args.length - 1;
2033
+ if (last >= 0 && typeof args[last] === "function") {
2034
+ args[last] = async () => ({
2035
+ content: [{ type: "text", text: freeTierUpgradeMessage(name) }]
2036
+ });
2037
+ }
2038
+ if (typeof args[0] === "string") {
2039
+ args[0] = args[0] + FREE_TIER_DESC_SUFFIX;
2040
+ }
2041
+ }
2042
+ return target.tool(name, ...args);
1988
2043
  };
1989
2044
  }
1990
2045
  return Reflect.get(target, prop, receiver);
@@ -7146,6 +7201,7 @@ async function validateLicense(email, licenseKey) {
7146
7201
  return {
7147
7202
  ok: true,
7148
7203
  installs: `${data.installs_used}/${data.installs_max}`,
7204
+ tier: data.tier === "free" ? "free" : data.tier === "full" ? "full" : void 0,
7149
7205
  signedAttestation: typeof data.signed_attestation === "string" ? data.signed_attestation : void 0
7150
7206
  };
7151
7207
  }
@@ -7279,24 +7335,27 @@ Note: Firebase credentials rejected (${fb.error}). Saved without Workflow Builde
7279
7335
  // verified on every MCP startup. Closes the hand-crafted creds bypass.
7280
7336
  signed_attestation: lic.signedAttestation
7281
7337
  });
7282
- const toolCount = workflowBuilderEnabled ? "229" : "177";
7338
+ const isFree = lic.tier === "free";
7339
+ const toolCount = isFree ? workflowBuilderEnabled ? "105" : "87" : workflowBuilderEnabled ? "229" : "177";
7283
7340
  const wfLine = workflowBuilderEnabled ? "Workflow Builder: enabled." : "Workflow Builder: not configured (optional).";
7284
- const wfTip = workflowBuilderEnabled ? "" : "\nTo enable Workflow Builder later (49 extra Firebase-gated tools): run enable_workflow_builder with your three Firebase values. No need to re-enter license/API key/location ID.";
7341
+ const wfTip = workflowBuilderEnabled ? "" : isFree ? `
7342
+ 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" : "";
7285
7344
  return {
7286
7345
  content: [{
7287
7346
  type: "text",
7288
7347
  text: [
7289
7348
  `Setup complete!`,
7290
7349
  ``,
7291
- `License: verified (installs ${lic.installs}).`,
7350
+ `License: verified (installs ${lic.installs})${isFree ? " \u2014 FREE read-only tier" : ""}.`,
7292
7351
  `GHL: connected to "${ghl.locationName}".`,
7293
7352
  wfLine,
7294
7353
  ``,
7295
7354
  `Credentials saved to: ${credentialsPath()}`,
7296
7355
  ``,
7297
- `**Restart Claude (quit fully and reopen) to load all ${toolCount} tools.**`,
7356
+ `**Restart Claude (quit fully and reopen) to load all ${toolCount} ${isFree ? "read-only " : ""}tools.**`,
7298
7357
  ``,
7299
- `After restart, try: "List my GHL contacts" or "Show my pipelines".${wfTip}`,
7358
+ `After restart, try: "List my GHL contacts" or "Show my pipelines".${wfTip}${freeTip}`,
7300
7359
  workflowBuilderNote
7301
7360
  ].join("\n")
7302
7361
  }]
@@ -7307,7 +7366,7 @@ Note: Firebase credentials rejected (${fb.error}). Saved without Workflow Builde
7307
7366
  function registerEnableWorkflowBuilderTool(server2) {
7308
7367
  server2.tool(
7309
7368
  "enable_workflow_builder",
7310
- "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. Requires you've already run setup_ghl_mcp. EASIEST PATH: run `auto_capture_firebase_script` first, paste the JSON output into `firebase_paste` here. MANUAL PATH: capture the three Firebase fields via DevTools and pass them individually. Tool count goes from 177 to 229 after the next Claude restart.",
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.",
7311
7370
  {
7312
7371
  // v3.25.0: one-paste path. Tool runs `auto_capture_firebase_script` to
7313
7372
  // get the console script; the script returns a JSON object that pastes
@@ -7400,7 +7459,7 @@ DevTools steps: https://elitedcs.com/ghl-mcp-firebase`
7400
7459
  function registerFirebaseCaptureScriptTool(server2) {
7401
7460
  server2.tool(
7402
7461
  "auto_capture_firebase_script",
7403
- "Get the browser-console script that auto-extracts the 3 Firebase fields needed to enable the Workflow Builder. Run this, copy the script, paste it into Chrome DevTools Console on a tab logged into GHL, press Enter, and the result lands in your clipboard. Then paste the JSON into setup_ghl_mcp's firebase_paste field (or enable_workflow_builder's). No manual IndexedDB digging.",
7462
+ "Get the browser-console script that auto-extracts the 3 Firebase fields needed to enable the Workflow Builder. PREFER `capture_firebase_interactive` when Chrome/Edge is installed (one-click, zero pasting); this script is the fallback for locked-down machines and the path for capturing a CLIENT account's Firebase (multi-tenant). Run this, copy the script, paste it into Chrome DevTools Console on a tab logged into GHL, press Enter, and the result lands in your clipboard. Then paste the JSON into setup_ghl_mcp's firebase_paste field (or enable_workflow_builder's).",
7404
7463
  {},
7405
7464
  async () => {
7406
7465
  const steps = [
@@ -7437,7 +7496,7 @@ function registerFirebaseCaptureScriptTool(server2) {
7437
7496
  function registerInteractiveCaptureTool(server2) {
7438
7497
  server2.tool(
7439
7498
  "capture_firebase_interactive",
7440
- "One-click Workflow Builder unlock (EASIEST PATH \u2014 try this before auto_capture_firebase_script). Opens a Chrome window where you just log into GHL; the MCP captures the Firebase credentials itself and saves them. No DevTools, no pasting. Re-run the same tool if the first call says it's still waiting for your login. After the first successful run, future re-captures (refresh-token rotations) happen silently with no window at all. Falls back cleanly: if no Chrome/Edge is installed, use auto_capture_firebase_script instead.",
7499
+ "One-click Workflow Builder unlock (EASIEST PATH \u2014 try this before auto_capture_firebase_script). Also the unlock for the FREE tier's account auditor (audit_workflows, validate_workflow) \u2014 same one-time login. Opens a Chrome window where you just log into GHL; the MCP captures the Firebase credentials itself and saves them. No DevTools, no pasting. Re-run the same tool if the first call says it's still waiting for your login. After the first successful run, future re-captures (refresh-token rotations) happen silently with no window at all. Falls back cleanly: if no Chrome/Edge is installed, use auto_capture_firebase_script instead.",
7441
7500
  {},
7442
7501
  async () => {
7443
7502
  const existing = readCredentials();
@@ -7568,7 +7627,7 @@ ${fallbackNote}`
7568
7627
  function registerLeadCaptureTool(server2) {
7569
7628
  server2.tool(
7570
7629
  "request_license",
7571
- "Get a GHL Command license. Use this if you installed from npm but don't have a license yet (or setup_ghl_mcp says your license is missing/invalid). GHL Command is $97/mo \u2014 every sub-account you manage, 3-machine activation, includes the only programmatic GHL workflow builder. Leave your email and we'll send the purchase link + setup help; the tool also returns where to buy right now.",
7630
+ "Get a GHL Command license. Use this if you installed from npm but don't have a license yet (or setup_ghl_mcp says your license is missing/invalid). GHL Command is $97/mo \u2014 every sub-account you manage, 3-machine activation, includes the only programmatic GHL workflow builder. There's also a FREE read-only tier (account audits + all reads on your own account) \u2014 instant key at https://ghlcommand.com/free. Leave your email and we'll send the purchase link + setup help; the tool also returns where to buy right now.",
7572
7631
  {
7573
7632
  email: import_zod39.z.string().email().describe("Your email \u2014 where to send the purchase link and setup help."),
7574
7633
  name: import_zod39.z.string().optional().describe("Your name (optional).")
@@ -7592,6 +7651,7 @@ function registerLeadCaptureTool(server2) {
7592
7651
  "GHL Command is $97/mo: every sub-account you manage, 3 machines, including the only programmatic GHL workflow builder. Cancel anytime.",
7593
7652
  "",
7594
7653
  `Buy now: ${url}`,
7654
+ "Want to try it first? FREE read-only tier (account audits + every read tool on your own account): https://ghlcommand.com/free",
7595
7655
  "Your license key arrives by email within a couple minutes. Then run setup_ghl_mcp with your email + key."
7596
7656
  ].join("\n")
7597
7657
  }]
@@ -13986,11 +14046,12 @@ var KNOWN_MODULES = /* @__PURE__ */ new Set([
13986
14046
  LOCATION_SWITCHER_MODULE,
13987
14047
  SNAPSHOTS_MODULE
13988
14048
  ]);
13989
- function registerAllTools(server2, client, registry2, mcpVersion, env = process.env) {
14049
+ function registerAllTools(server2, client, registry2, mcpVersion, env = process.env, tier = "full") {
13990
14050
  const config3 = parseAllowlist(env);
13991
14051
  const attemptedTools = /* @__PURE__ */ new Set();
13992
14052
  const registeredTools = /* @__PURE__ */ new Set();
13993
- const wrap = (moduleName) => wrapServerForModule(server2, moduleName, config3, attemptedTools, registeredTools);
14053
+ const gatedTools = /* @__PURE__ */ new Set();
14054
+ const wrap = (moduleName) => wrapServerForModule(server2, moduleName, config3, attemptedTools, registeredTools, tier, gatedTools);
13994
14055
  for (const [register, moduleName] of publicApiTools) {
13995
14056
  register(wrap(moduleName), client);
13996
14057
  }
@@ -14020,6 +14081,13 @@ function registerAllTools(server2, client, registry2, mcpVersion, env = process.
14020
14081
  if (isAllowlistActive(config3)) {
14021
14082
  validateAndLog(config3, KNOWN_MODULES, attemptedTools, registeredTools);
14022
14083
  }
14084
+ if (tier === "free") {
14085
+ process.stderr.write(
14086
+ `[ghl-mcp] FREE tier: ${registeredTools.size - gatedTools.size} read-only tools live; ${gatedTools.size} write tools answer with upgrade info. Full version: https://ghlcommand.com
14087
+ `
14088
+ );
14089
+ }
14090
+ return { registeredTools, gatedTools };
14023
14091
  }
14024
14092
 
14025
14093
  // src/attestation.ts
@@ -14027,6 +14095,9 @@ var import_node_crypto = require("node:crypto");
14027
14095
  var ATTESTATION_PUBLIC_KEY_B64 = "8KJo8V3Z7ngeToIQfOXpKdlr/EA34hXJVEIpW0A7wqs=";
14028
14096
  var ATTESTATION_VERSION = 1;
14029
14097
  var ATTESTATION_GRACE_DAYS = 14;
14098
+ function tierFromPayload(payload) {
14099
+ return payload?.tier === "free" ? "free" : "full";
14100
+ }
14030
14101
  function deviceFingerprint2() {
14031
14102
  const os3 = require("node:os");
14032
14103
  const crypto4 = require("node:crypto");
@@ -14051,6 +14122,20 @@ async function getPublicKey() {
14051
14122
  );
14052
14123
  return cachedPublicKey;
14053
14124
  }
14125
+ function parseAttestationPayload(token) {
14126
+ try {
14127
+ const dot = token.indexOf(".");
14128
+ if (dot <= 0 || dot === token.length - 1) return null;
14129
+ const payloadJson = new TextDecoder().decode(base64urlDecode(token.slice(0, dot)));
14130
+ const payload = JSON.parse(payloadJson);
14131
+ if (typeof payload.v !== "number" || typeof payload.email !== "string" || typeof payload.license_key !== "string" || typeof payload.device_fingerprint !== "string" || typeof payload.installs_max !== "number" || typeof payload.issued_at !== "string" || typeof payload.expires_at !== "string") {
14132
+ return null;
14133
+ }
14134
+ return payload;
14135
+ } catch {
14136
+ return null;
14137
+ }
14138
+ }
14054
14139
  async function verifyAttestation(token, bindings, now = /* @__PURE__ */ new Date()) {
14055
14140
  if (typeof token !== "string" || !token) return { ok: false, reason: "malformed" };
14056
14141
  const dot = token.indexOf(".");
@@ -14538,6 +14623,8 @@ function renewAttestationInBackground(creds) {
14538
14623
  async function resolveAccessAndRegister() {
14539
14624
  let licenseVerified = false;
14540
14625
  let attestationStatus = "no-creds";
14626
+ let sessionTier = "full";
14627
+ const tierFromDisk = () => tierFromPayload(parseAttestationPayload(readCredentials()?.signed_attestation ?? ""));
14541
14628
  if (fileCreds?.email && fileCreds?.license_key && fileCreds.signed_attestation) {
14542
14629
  const verify = await verifyAttestation(fileCreds.signed_attestation, {
14543
14630
  email: fileCreds.email,
@@ -14546,6 +14633,7 @@ async function resolveAccessAndRegister() {
14546
14633
  if (verify.ok) {
14547
14634
  licenseVerified = true;
14548
14635
  attestationStatus = verify.reason;
14636
+ sessionTier = tierFromPayload(verify.payload);
14549
14637
  void renewAttestationInBackground(fileCreds);
14550
14638
  } else {
14551
14639
  process.stderr.write(`[ghl-mcp] Stored attestation rejected: ${verify.reason}. Re-validating online.
@@ -14554,6 +14642,7 @@ async function resolveAccessAndRegister() {
14554
14642
  if (renewed === "renewed") {
14555
14643
  licenseVerified = true;
14556
14644
  attestationStatus = "renewed-after-tamper";
14645
+ sessionTier = tierFromDisk();
14557
14646
  } else if (renewed === "renewed-unsigned" && Date.now() < TRANSITIONAL_ATTESTATION_DEADLINE) {
14558
14647
  licenseVerified = true;
14559
14648
  attestationStatus = "renewed-after-tamper";
@@ -14567,6 +14656,7 @@ async function resolveAccessAndRegister() {
14567
14656
  if (renewed === "renewed") {
14568
14657
  licenseVerified = true;
14569
14658
  attestationStatus = "renewed-from-legacy";
14659
+ sessionTier = tierFromDisk();
14570
14660
  } else if (renewed === "renewed-unsigned" && Date.now() < TRANSITIONAL_ATTESTATION_DEADLINE) {
14571
14661
  licenseVerified = true;
14572
14662
  attestationStatus = "renewed-from-legacy";
@@ -14591,6 +14681,7 @@ async function resolveAccessAndRegister() {
14591
14681
  if (lic.ok && acceptUnsigned) {
14592
14682
  licenseVerified = true;
14593
14683
  attestationStatus = "renewed";
14684
+ sessionTier = lic.signedAttestation ? tierFromPayload(parseAttestationPayload(lic.signedAttestation)) : lic.tier === "free" ? "free" : "full";
14594
14685
  try {
14595
14686
  writeCredentials({
14596
14687
  license_key: licKey,
@@ -14618,7 +14709,7 @@ async function resolveAccessAndRegister() {
14618
14709
  }
14619
14710
  }
14620
14711
  if (licenseVerified) {
14621
- process.stderr.write(`[ghl-mcp] License gate: ${attestationStatus}.
14712
+ process.stderr.write(`[ghl-mcp] License gate: ${attestationStatus}${sessionTier === "free" ? " (FREE read-only tier)" : ""}.
14622
14713
  `);
14623
14714
  }
14624
14715
  inBootstrapMode = !apiKey || !locationId || !licenseVerified;
@@ -14634,7 +14725,7 @@ async function resolveAccessAndRegister() {
14634
14725
  registerFirebaseCaptureScriptTool(server);
14635
14726
  } else {
14636
14727
  const client = new GHLClient({ apiKey, locationId });
14637
- registerAllTools(server, client, registry, pkg.version);
14728
+ registerAllTools(server, client, registry, pkg.version, process.env, sessionTier);
14638
14729
  registerEnableWorkflowBuilderTool(server);
14639
14730
  registerFirebaseCaptureScriptTool(server);
14640
14731
  registerInteractiveCaptureTool(server);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.49.0",
3
+ "version": "3.50.0",
4
4
  "mcpName": "io.github.drjerryrelth/ghl-command",
5
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.",
6
6
  "main": "dist/index.js",