@elitedcs/ghl-mcp 3.83.0 → 3.83.2

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/dist/index.js CHANGED
@@ -241,7 +241,7 @@ function foldCredentialsIntoEnv(creds, env = process.env) {
241
241
  ];
242
242
  for (const [credKey, envKey] of mappings) {
243
243
  const value = creds[credKey];
244
- if (value && !env[envKey]) {
244
+ if (typeof value === "string" && value && !env[envKey]) {
245
245
  env[envKey] = value;
246
246
  }
247
247
  }
@@ -740,13 +740,13 @@ var init_token_registry = __esm({
740
740
  name: import_zod2.z.string().optional()
741
741
  });
742
742
  TokenRegistryDataSchema = import_zod2.z.object({
743
- tokens: import_zod2.z.record(LocationTokenSchema),
743
+ tokens: import_zod2.z.record(import_zod2.z.string(), LocationTokenSchema),
744
744
  agencyKey: import_zod2.z.string().optional(),
745
745
  // "Home" Firebase — the company the install was set up for (credentials.json).
746
746
  firebase: FirebaseConfigSchema.optional(),
747
747
  // Additional companies' Firebase auth, keyed by GHL companyId. Lets ONE
748
748
  // process operate the workflow builder across multiple clients' GHL accounts.
749
- firebaseByCompany: import_zod2.z.record(CompanyFirebaseSchema).optional()
749
+ firebaseByCompany: import_zod2.z.record(import_zod2.z.string(), CompanyFirebaseSchema).optional()
750
750
  });
751
751
  TokenRegistry = class _TokenRegistry {
752
752
  data;
@@ -1256,7 +1256,7 @@ var init_trigger_schemas = __esm({
1256
1256
  masterType: import_zod3.z.string().optional(),
1257
1257
  name: import_zod3.z.string().optional(),
1258
1258
  actions: import_zod3.z.array(TriggerActionSchema).optional(),
1259
- schedule_config: import_zod3.z.record(import_zod3.z.unknown()).optional(),
1259
+ schedule_config: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.unknown()).optional(),
1260
1260
  date_updated: import_zod3.z.string().optional()
1261
1261
  }).passthrough();
1262
1262
  ContactTagTriggerSchema = TriggerCommonSchema.extend({
@@ -1547,7 +1547,7 @@ var init_trigger_schemas = __esm({
1547
1547
  });
1548
1548
  UnknownTriggerSchema = TriggerCommonSchema.extend({
1549
1549
  type: import_zod3.z.string(),
1550
- conditions: import_zod3.z.array(import_zod3.z.record(import_zod3.z.unknown())).optional()
1550
+ conditions: import_zod3.z.array(import_zod3.z.record(import_zod3.z.string(), import_zod3.z.unknown())).optional()
1551
1551
  });
1552
1552
  WorkflowTriggerSchema = import_zod3.z.union([
1553
1553
  ContactTagTriggerSchema,
@@ -3883,7 +3883,8 @@ function annotateListResponse(raw, listKey, limit, nextHint) {
3883
3883
  };
3884
3884
  }
3885
3885
  function safeTool(server2, name, description, schema, handler) {
3886
- server2.tool(name, description, schema, async (args) => {
3886
+ const toolServer = server2;
3887
+ toolServer.tool(name, description, schema, async (args) => {
3887
3888
  try {
3888
3889
  return jsonResponse(await handler(args));
3889
3890
  } catch (error) {
@@ -5090,7 +5091,7 @@ function registerCalendarTools(server2, client) {
5090
5091
  slug: import_zod12.z.string().optional().describe("Calendar slug"),
5091
5092
  widgetSlug: import_zod12.z.string().optional().describe("Widget slug for embedding"),
5092
5093
  calendarType: import_zod12.z.string().optional().describe("Calendar type"),
5093
- teamMembers: import_zod12.z.array(import_zod12.z.record(import_zod12.z.unknown())).optional().describe("Array of team member objects"),
5094
+ teamMembers: import_zod12.z.array(import_zod12.z.record(import_zod12.z.string(), import_zod12.z.unknown())).optional().describe("Array of team member objects"),
5094
5095
  eventTitle: import_zod12.z.string().optional().describe("Default event title"),
5095
5096
  eventColor: import_zod12.z.string().optional().describe("Event color hex code"),
5096
5097
  slotDuration: import_zod12.z.number().optional().describe("Slot duration in minutes"),
@@ -5386,7 +5387,7 @@ function registerLocationTools(server2, client) {
5386
5387
  timezone: import_zod13.z.string().optional().describe("IANA timezone (e.g., 'America/New_York')."),
5387
5388
  phone: import_zod13.z.string().optional().describe("Phone number."),
5388
5389
  email: import_zod13.z.string().optional().describe("Email address."),
5389
- settings: import_zod13.z.record(import_zod13.z.unknown()).optional().describe("Location settings object with key-value pairs."),
5390
+ settings: import_zod13.z.record(import_zod13.z.string(), import_zod13.z.unknown()).optional().describe("Location settings object with key-value pairs."),
5390
5391
  overwriteBusinessInfo: import_zod13.z.boolean().optional().describe(
5391
5392
  "Allow a CHANGE to business details on a sub-account that already has a registered A2P brand. Off by default. Only needed when the account has a brandId AND you are changing (not filling) one of name/address/city/state/postalCode/country/phone/website/email."
5392
5393
  )
@@ -6726,7 +6727,7 @@ function registerUserTools(server2, client, registry2) {
6726
6727
  role: import_zod22.z.enum(["admin", "user"]).describe("GHL role on this sub-account. 'admin' = full settings access; 'user' = day-to-day staff (contacts, conversations, calendar, opportunities)."),
6727
6728
  phone: import_zod22.z.string().optional().describe("Phone number (E.164 preferred)."),
6728
6729
  password: import_zod22.z.string().min(8).optional().describe("Only if you must set one. Omit it (recommended) so GHL sends the invite instead."),
6729
- permissions: import_zod22.z.record(import_zod22.z.boolean()).optional().describe("Override the default per-role permissions object entirely (GHL UserPermissions booleans)."),
6730
+ permissions: import_zod22.z.record(import_zod22.z.string(), import_zod22.z.boolean()).optional().describe("Override the default per-role permissions object entirely (GHL UserPermissions booleans)."),
6730
6731
  companyId: import_zod22.z.string().optional().describe("Agency/company id. Resolved from the registered location by default; pass it only when that lookup fails."),
6731
6732
  locationId: import_zod22.z.string().optional().describe("Sub-account to add the user to (defaults to the active location). The user is granted access to this ONE location.")
6732
6733
  },
@@ -7432,8 +7433,8 @@ function registerEmailTools(server2, client) {
7432
7433
  );
7433
7434
  }
7434
7435
  function registerEmailBuilderInternalTools(server2, builderClient, publicClient) {
7436
+ if (!builderClient) return;
7435
7437
  const client = builderClient;
7436
- if (!client) return;
7437
7438
  async function builderRequest(method, path39, body) {
7438
7439
  const headers = await client.buildHeaders();
7439
7440
  const response = await fetch(`${EMAIL_BUILDER_BASE}${path39}`, {
@@ -7712,7 +7713,7 @@ function registerCustomObjectTools(server2, client) {
7712
7713
  {
7713
7714
  schemaKey: import_zod30.z.string().describe("The custom object schema key."),
7714
7715
  locationId: import_zod30.z.string().optional().describe("GHL Location ID. Optional if GHL_LOCATION_ID is set in env."),
7715
- properties: import_zod30.z.record(import_zod30.z.unknown()).describe("Key-value pairs of field values for the new record.")
7716
+ properties: import_zod30.z.record(import_zod30.z.string(), import_zod30.z.unknown()).describe("Key-value pairs of field values for the new record.")
7716
7717
  },
7717
7718
  async ({ schemaKey, locationId: locationId2, properties }) => {
7718
7719
  const resolvedLocationId = client.resolveLocationId(locationId2);
@@ -7729,7 +7730,7 @@ function registerCustomObjectTools(server2, client) {
7729
7730
  schemaKey: import_zod30.z.string().describe("The custom object schema key."),
7730
7731
  recordId: import_zod30.z.string().describe("The record ID to update."),
7731
7732
  locationId: import_zod30.z.string().optional().describe("GHL Location ID. Optional if GHL_LOCATION_ID is set in env."),
7732
- properties: import_zod30.z.record(import_zod30.z.unknown()).describe("Key-value pairs of field values to update.")
7733
+ properties: import_zod30.z.record(import_zod30.z.string(), import_zod30.z.unknown()).describe("Key-value pairs of field values to update.")
7733
7734
  },
7734
7735
  async ({ schemaKey, recordId, locationId: locationId2, properties }) => {
7735
7736
  const resolvedLocationId = client.resolveLocationId(locationId2);
@@ -7990,7 +7991,7 @@ function registerEstimateTools(server2, client) {
7990
7991
  price: import_zod32.z.number().describe("Unit price, in the estimate currency."),
7991
7992
  currency: import_zod32.z.string().optional().describe("Currency code (e.g. USD).")
7992
7993
  })).optional().describe("Updated line items. Give quantity and price; they are translated to the qty/amount/type GHL requires."),
7993
- discount: import_zod32.z.record(import_zod32.z.unknown()).optional().describe("Updated discount object.")
7994
+ discount: import_zod32.z.record(import_zod32.z.string(), import_zod32.z.unknown()).optional().describe("Updated discount object.")
7994
7995
  },
7995
7996
  async ({ estimateId, locationId: locationId2, name, title: title2, items, discount }) => {
7996
7997
  const resolvedLocationId = client.resolveLocationId(locationId2);
@@ -8769,7 +8770,7 @@ function registerConversationAiTools(server2, client) {
8769
8770
  agentId: import_zod35.z.string().describe("Agent id."),
8770
8771
  type: import_zod35.z.enum(ACTION_TYPES),
8771
8772
  name: import_zod35.z.string().min(1).describe("Name the operator sees."),
8772
- details: import_zod35.z.record(import_zod35.z.unknown()).describe("Type-specific details. humanHandOver: enabled, triggerCondition, handoverType (contactRequest|lackOfInformation|failedToResolveIssue|custom), examples[], assignToUserId, finalMessage, reactivateEnabled(+sleepTime/Unit). appointmentBooking: calendarId, onlySendLink, triggerWorkflow(+workflowIds), sleepAfterBooking(+sleepTime/Unit), transferBot(+transferAgent), rescheduleEnabled, cancelEnabled. triggerWorkflow: workflowIds[], triggerCondition, triggerMessage. stopBot: stopBotDetectionType (Goodbye|Custom), stopBotTriggerCondition, stopBotExamples[] (2+), finalMessage, enabled, reactivateEnabled, tags[]. updateContactField: contactFieldId, description, contactUpdateExamples[]. transferBot: transferBotType, transferToBot, enabled. advancedFollowup: enabled, scenarioId, followupSequence[].")
8773
+ details: import_zod35.z.record(import_zod35.z.string(), import_zod35.z.unknown()).describe("Type-specific details. humanHandOver: enabled, triggerCondition, handoverType (contactRequest|lackOfInformation|failedToResolveIssue|custom), examples[], assignToUserId, finalMessage, reactivateEnabled(+sleepTime/Unit). appointmentBooking: calendarId, onlySendLink, triggerWorkflow(+workflowIds), sleepAfterBooking(+sleepTime/Unit), transferBot(+transferAgent), rescheduleEnabled, cancelEnabled. triggerWorkflow: workflowIds[], triggerCondition, triggerMessage. stopBot: stopBotDetectionType (Goodbye|Custom), stopBotTriggerCondition, stopBotExamples[] (2+), finalMessage, enabled, reactivateEnabled, tags[]. updateContactField: contactFieldId, description, contactUpdateExamples[]. transferBot: transferBotType, transferToBot, enabled. advancedFollowup: enabled, scenarioId, followupSequence[].")
8773
8774
  },
8774
8775
  async ({ agentId, type, name, details }) => {
8775
8776
  const problem = actionBodyProblem(type, details);
@@ -8787,7 +8788,7 @@ function registerConversationAiTools(server2, client) {
8787
8788
  actionId: import_zod35.z.string().describe("Action id."),
8788
8789
  type: import_zod35.z.enum(ACTION_TYPES),
8789
8790
  name: import_zod35.z.string().min(1),
8790
- details: import_zod35.z.record(import_zod35.z.unknown()).describe("The complete details for this type.")
8791
+ details: import_zod35.z.record(import_zod35.z.string(), import_zod35.z.unknown()).describe("The complete details for this type.")
8791
8792
  },
8792
8793
  async ({ agentId, actionId, type, name, details }) => {
8793
8794
  const problem = actionBodyProblem(type, details);
@@ -10024,7 +10025,7 @@ function registerVoiceAiTools(server2, shared) {
10024
10025
  locationId: LOCATION,
10025
10026
  actionType: import_zod37.z.enum(VOICE_ACTION_TYPES),
10026
10027
  name: import_zod37.z.string().min(1).describe("Name the operator sees. Letters, numbers, spaces, _ and - only."),
10027
- actionParameters: import_zod37.z.record(import_zod37.z.unknown()).describe(ACTION_PARAMS_DOC)
10028
+ actionParameters: import_zod37.z.record(import_zod37.z.string(), import_zod37.z.unknown()).describe(ACTION_PARAMS_DOC)
10028
10029
  },
10029
10030
  async ({ agentId, locationId: locationId2, actionType, name, actionParameters }) => {
10030
10031
  const client = shared.pinned();
@@ -10060,7 +10061,7 @@ function registerVoiceAiTools(server2, shared) {
10060
10061
  locationId: LOCATION,
10061
10062
  actionType: import_zod37.z.enum(VOICE_ACTION_TYPES),
10062
10063
  name: import_zod37.z.string().min(1),
10063
- actionParameters: import_zod37.z.record(import_zod37.z.unknown()).describe("The complete parameters for this type.")
10064
+ actionParameters: import_zod37.z.record(import_zod37.z.string(), import_zod37.z.unknown()).describe("The complete parameters for this type.")
10064
10065
  },
10065
10066
  async ({ agentId, actionId, locationId: locationId2, actionType, name, actionParameters }) => {
10066
10067
  const client = shared.pinned();
@@ -10522,7 +10523,7 @@ var init_webhooks = __esm({
10522
10523
  import_zod38.z.string().describe("A GHL trigger type, e.g. 'contact_created'. Fires with no filter."),
10523
10524
  import_zod38.z.object({
10524
10525
  type: import_zod38.z.string().describe("A GHL trigger type, e.g. 'contact_tag' or 'pipeline_stage_updated'."),
10525
- conditions: import_zod38.z.array(import_zod38.z.record(import_zod38.z.unknown())).optional().describe(
10526
+ conditions: import_zod38.z.array(import_zod38.z.record(import_zod38.z.string(), import_zod38.z.unknown())).optional().describe(
10526
10527
  "Filter conditions, exactly as update_workflow_actions takes them. Use get_trigger_registry to find the valid fields for a trigger type. Omit to fire on every event of that type."
10527
10528
  )
10528
10529
  })
@@ -10607,8 +10608,8 @@ function registerDocumentTools(server2, client) {
10607
10608
  sentBy: import_zod39.z.string().optional().describe("GHL user ID of the sender (required by the API; falls back to GHL_USER_ID env)."),
10608
10609
  documentName: import_zod39.z.string().optional().describe("Override the document name shown to recipients."),
10609
10610
  medium: import_zod39.z.string().optional().describe("Delivery medium, e.g. 'email'. Defaults to email."),
10610
- ccRecipients: import_zod39.z.array(import_zod39.z.record(import_zod39.z.unknown())).optional().describe("Optional CC recipients: [{id, email, contactName, firstName, lastName, imageUrl}]."),
10611
- notificationSettings: import_zod39.z.record(import_zod39.z.unknown()).optional().describe("Optional sender/receiver notification settings: {sender:{fromName,fromEmail}, receive:{subject,templateId}}."),
10611
+ ccRecipients: import_zod39.z.array(import_zod39.z.record(import_zod39.z.string(), import_zod39.z.unknown())).optional().describe("Optional CC recipients: [{id, email, contactName, firstName, lastName, imageUrl}]."),
10612
+ notificationSettings: import_zod39.z.record(import_zod39.z.string(), import_zod39.z.unknown()).optional().describe("Optional sender/receiver notification settings: {sender:{fromName,fromEmail}, receive:{subject,templateId}}."),
10612
10613
  locationId: import_zod39.z.string().optional().describe("GHL Location ID. Optional if GHL_LOCATION_ID is set in env.")
10613
10614
  },
10614
10615
  async ({ documentId, sentBy, documentName, medium, ccRecipients, notificationSettings, locationId: locationId2 }) => {
@@ -11055,7 +11056,7 @@ function registerWorkflowBuilderTools(server2, client) {
11055
11056
  "invoice_paid",
11056
11057
  "review_request_clicked"
11057
11058
  ]).describe("The goal-condition identifier. Extras shape per condition: email_event \u2192 {stepIds:[]}; link_click \u2192 {linkIds:[]}; appointment_status \u2192 {calendarId}; payment_received \u2192 {globalProductIds:[]}; form_submission \u2192 {formIds:[]}; document_status \u2192 {templateId}; invoice_paid \u2192 {invoiceStepId}; review_request_clicked \u2192 {reviewTypes:['sms'|'email'], reviewLinkId}; add_contact_tag/remove_contact_tag \u2192 {tags:[]} (uncaptured but inferred)."),
11058
- extras: import_zod40.z.record(import_zod40.z.unknown()).optional().describe("Goal-condition-specific config. See goal_condition description for the expected shape per value. Pass {} or omit for goal_conditions without extras."),
11059
+ extras: import_zod40.z.record(import_zod40.z.string(), import_zod40.z.unknown()).optional().describe("Goal-condition-specific config. See goal_condition description for the expected shape per value. Pass {} or omit for goal_conditions without extras."),
11059
11060
  action: import_zod40.z.enum(["exit", "continue", "wait"]).default("exit").describe("What happens when the goal fires (per GHL's GoalAction enum). 'exit' terminates the workflow path (verified). 'continue' lets it proceed to a downstream node. 'wait' pauses on the goal."),
11060
11061
  name: import_zod40.z.string().default("Goal").describe("Display name in the GHL UI."),
11061
11062
  op: import_zod40.z.enum(["or", "and"]).default("or").describe("Top-level boolean operator across segments. Default 'or'."),
@@ -11134,7 +11135,7 @@ var init_workflow_builder = __esm({
11134
11135
  id: import_zod40.z.string().optional().describe("Action ID (auto-generated if omitted)."),
11135
11136
  name: import_zod40.z.string().describe("Display name for this action step."),
11136
11137
  type: import_zod40.z.string().describe("Action type."),
11137
- attributes: import_zod40.z.record(import_zod40.z.unknown()).optional().describe("Action-specific configuration."),
11138
+ attributes: import_zod40.z.record(import_zod40.z.string(), import_zod40.z.unknown()).optional().describe("Action-specific configuration."),
11138
11139
  next: import_zod40.z.union([import_zod40.z.string(), import_zod40.z.array(import_zod40.z.string())]).optional().describe("Next action id, or branch id array for condition nodes."),
11139
11140
  parent: import_zod40.z.string().optional().describe("Branch parent node id."),
11140
11141
  parentKey: import_zod40.z.string().optional().describe("Previous action id or branch parent id."),
@@ -11190,8 +11191,7 @@ var init_page_reads = __esm({
11190
11191
 
11191
11192
  // src/tools/funnel-builder.ts
11192
11193
  function registerFunnelBuilderTools(server2, builderClient) {
11193
- const client = builderClient;
11194
- if (!client) {
11194
+ if (!builderClient) {
11195
11195
  server2.tool(
11196
11196
  "funnel_builder_status",
11197
11197
  "Check whether the funnel / page builder is configured. It needs Firebase credentials, added via enable_workflow_builder.",
@@ -11219,6 +11219,7 @@ function registerFunnelBuilderTools(server2, builderClient) {
11219
11219
  );
11220
11220
  return;
11221
11221
  }
11222
+ const client = builderClient;
11222
11223
  async function internalGet(path39) {
11223
11224
  return client.request("GET", path39);
11224
11225
  }
@@ -11524,7 +11525,7 @@ ${await response.text()}`);
11524
11525
  "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 and returns the new pageDataUrl. \u26A0\uFE0F GHL accepts a save onto a page that already has content but may never make it the visible version (live-tested 2026-07-28 and again 2026-08-31). The tool now VERIFIES this for you: every response carries `save_check`, and `save_check.visible: false` means the content was stored but visitors still see the previous page \u2014 rebuild on a fresh step (create_funnel_page) and save onto that. Never report a page as updated without reading save_check. Saves as a draft unless isPublished is true.",
11525
11526
  {
11526
11527
  pageId: import_zod41.z.string().describe("The page ID to update."),
11527
- content: import_zod41.z.record(import_zod41.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."),
11528
+ content: import_zod41.z.record(import_zod41.z.string(), import_zod41.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."),
11528
11529
  funnelId: import_zod41.z.string().optional().describe("Funnel ID that owns the page. Optional \u2014 auto-resolved from the page metadata if omitted."),
11529
11530
  isPublished: import_zod41.z.boolean().optional().describe("Mark the page published after the save. Defaults to false (draft).")
11530
11531
  },
@@ -16437,8 +16438,8 @@ function removalHoldBack(opts) {
16437
16438
  return null;
16438
16439
  }
16439
16440
  function registerPageStudioTools(server2, builderClient) {
16441
+ if (!builderClient) return;
16440
16442
  const client = builderClient;
16441
- if (!client) return;
16442
16443
  const templateApi = () => ({
16443
16444
  headers: () => client.buildHeaders(),
16444
16445
  companyId: () => client.getIntendedCompanyId() ?? client.getCurrentCompanyId(),
@@ -17148,7 +17149,7 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
17148
17149
  "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).",
17149
17150
  {
17150
17151
  formId: import_zod44.z.string().describe("The form ID to update."),
17151
- formData: import_zod44.z.record(import_zod44.z.unknown()).describe("The updated formData object containing form fields, settings, autoResponder config, etc."),
17152
+ 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."),
17152
17153
  name: import_zod44.z.string().optional().describe("Updated form name. If omitted, the current name is preserved.")
17153
17154
  },
17154
17155
  async ({ formId, formData, name }) => {
@@ -17407,7 +17408,7 @@ function registerFunnelQaTools(server2, client, builderClient) {
17407
17408
  funnelUrl: import_zod45.z.string().describe("The live URL the test submission POSTs to (your external lead-bridge Worker URL, or any endpoint that creates the GHL contact). POSTed as JSON."),
17408
17409
  sentinelEmail: import_zod45.z.string().optional().describe("Override the minted sentinel email. Default: a unique blueprint-qa.<id>@example.com (won't deliver; used only to find the contact)."),
17409
17410
  testPhone: import_zod45.z.string().optional().describe("Include a phone on the submission (enables SMS-path checks). Must be a test-safe number you control \u2014 real SMS may send + bill."),
17410
- submitBody: import_zod45.z.record(import_zod45.z.unknown()).optional().describe("Full JSON body to POST (advanced; for non-bridge endpoints). The sentinel email is injected if absent. Default body is the lead-bridge contract {first_name,last_name,email,phone,custom,_hp}."),
17411
+ submitBody: import_zod45.z.record(import_zod45.z.string(), import_zod45.z.unknown()).optional().describe("Full JSON body to POST (advanced; for non-bridge endpoints). The sentinel email is injected if absent. Default body is the lead-bridge contract {first_name,last_name,email,phone,custom,_hp}."),
17411
17412
  expectCustom: import_zod45.z.array(customFieldInput).optional().describe("Custom fields the form sends \u2014 each is asserted to PERSIST with the sent value (field-value fidelity, Carlos #3)."),
17412
17413
  expectStandard: import_zod45.z.array(import_zod45.z.string()).optional().describe("Standard contact keys to assert saved (e.g. ['phone','first_name']). email is always asserted."),
17413
17414
  triggerTag: import_zod45.z.string().optional().describe("The tag the form/bridge should add (the speed-to-lead trigger). Asserted to land on the contact."),
@@ -17665,8 +17666,8 @@ var init_funnel_qa = __esm({
17665
17666
 
17666
17667
  // src/tools/pipeline-builder.ts
17667
17668
  function registerPipelineBuilderTools(server2, builderClient) {
17669
+ if (!builderClient) return;
17668
17670
  const client = builderClient;
17669
- if (!client) return;
17670
17671
  async function pipelineRequest(method, path39, body) {
17671
17672
  const headers = await client.buildHeaders();
17672
17673
  const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path39}`;
@@ -18580,19 +18581,20 @@ function deviceFingerprint(opts) {
18580
18581
  function base64urlDecode(s) {
18581
18582
  const b64 = s.replace(/-/g, "+").replace(/_/g, "/");
18582
18583
  const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
18583
- return new Uint8Array(Buffer.from(padded, "base64"));
18584
+ return new Uint8Array(new Uint8Array(Buffer.from(padded, "base64")).buffer);
18584
18585
  }
18585
18586
  async function getPublicKey() {
18586
18587
  if (cachedPublicKey) return cachedPublicKey;
18587
18588
  const raw = Buffer.from(ATTESTATION_PUBLIC_KEY_B64, "base64");
18588
- cachedPublicKey = await import_node_crypto3.webcrypto.subtle.importKey(
18589
+ const key = await import_node_crypto3.webcrypto.subtle.importKey(
18589
18590
  "raw",
18590
18591
  raw,
18591
18592
  { name: "Ed25519" },
18592
18593
  false,
18593
18594
  ["verify"]
18594
18595
  );
18595
- return cachedPublicKey;
18596
+ cachedPublicKey = key;
18597
+ return key;
18596
18598
  }
18597
18599
  function parseAttestationPayload(token2) {
18598
18600
  try {
@@ -20007,7 +20009,8 @@ function registerValidatorTools(server2, client, builderClient) {
20007
20009
  const workflow = await builderClient.getWorkflow(workflowId);
20008
20010
  if (!workflow) return errorResponse(new Error(`Workflow ${workflowId} not found`));
20009
20011
  const refs = [];
20010
- const socialPlatforms = await connectedSocialPlatforms(client, client.defaultLocationId);
20012
+ const locationId2 = client.resolveLocationId();
20013
+ const socialPlatforms = await connectedSocialPlatforms(client, locationId2);
20011
20014
  const shape = [
20012
20015
  ...checkActionShapes(workflow),
20013
20016
  ...checkSaveBlockers(workflow),
@@ -20020,7 +20023,7 @@ function registerValidatorTools(server2, client, builderClient) {
20020
20023
  return jsonResponse({ workflowId, workflowName: workflow.name, ...summarizeFindings(shape), references_scanned: 0, actions_checked: actionsChecked, findings: shape });
20021
20024
  const needWorkflows = refs.some((r) => r.kind === "workflow");
20022
20025
  const catalog2 = needWorkflows ? await fullWorkflowCatalog(builderClient) : { ids: /* @__PURE__ */ new Set(), complete: true };
20023
- const lookups = await fetchAndBuildLookups(client, builderClient, client.defaultLocationId, { ids: catalog2.ids, complete: catalog2.complete });
20026
+ const lookups = await fetchAndBuildLookups(client, builderClient, locationId2, { ids: catalog2.ids, complete: catalog2.complete });
20024
20027
  const findings = [...shape, ...checkRefs(refs, workflowId, lookups)];
20025
20028
  const report2 = {
20026
20029
  workflowId,
@@ -20042,7 +20045,7 @@ function registerValidatorTools(server2, client, builderClient) {
20042
20045
  {},
20043
20046
  async () => {
20044
20047
  try {
20045
- const locationId2 = client.defaultLocationId;
20048
+ const locationId2 = client.resolveLocationId();
20046
20049
  const catalog2 = await fullWorkflowCatalog(builderClient);
20047
20050
  if (catalog2.rows.length === 0)
20048
20051
  return jsonResponse({
@@ -24836,7 +24839,7 @@ var init_plan = __esm({
24836
24839
  workflows: import_zod51.z.array(workflowSchema).optional(),
24837
24840
  handoffs: import_zod51.z.array(handoffSchema).optional(),
24838
24841
  buildOrder: import_zod51.z.array(import_zod51.z.string()).optional(),
24839
- idMap: import_zod51.z.record(import_zod51.z.string()).optional()
24842
+ idMap: import_zod51.z.record(import_zod51.z.string(), import_zod51.z.string()).optional()
24840
24843
  }).strict();
24841
24844
  PLAN_ERROR_CODES = {
24842
24845
  /** A workflow named like /nurture/i whose waits add up to < 30 days. */
@@ -26374,15 +26377,29 @@ function newRunId(now = /* @__PURE__ */ new Date(), rand = defaultRand) {
26374
26377
  function defaultRand() {
26375
26378
  return Math.floor(Math.random() * 65536).toString(16).padStart(4, "0");
26376
26379
  }
26380
+ function legacyFunnelParentage(plan) {
26381
+ const funnels = plan.funnels;
26382
+ if (!Array.isArray(funnels)) return [];
26383
+ const pairs = [];
26384
+ for (const funnel of funnels) {
26385
+ if (typeof funnel !== "object" || funnel === null) continue;
26386
+ const { ref, pages } = funnel;
26387
+ if (typeof ref !== "string" || !Array.isArray(pages)) continue;
26388
+ for (const page of pages) {
26389
+ if (typeof page !== "object" || page === null) continue;
26390
+ const pageRef = page.ref;
26391
+ if (typeof pageRef === "string") pairs.push([pageRef, ref]);
26392
+ }
26393
+ }
26394
+ return pairs;
26395
+ }
26377
26396
  function parentRefIndex(plan) {
26378
26397
  const parents = /* @__PURE__ */ new Map();
26379
26398
  if (!plan) return parents;
26380
26399
  for (const p of plan.pipelines ?? []) {
26381
26400
  for (const st of p.stages ?? []) parents.set(st.ref, p.ref);
26382
26401
  }
26383
- for (const fn of plan.funnels ?? []) {
26384
- for (const pg of fn.pages ?? []) parents.set(pg.ref, fn.ref);
26385
- }
26402
+ for (const [pageRef, funnelRef] of legacyFunnelParentage(plan)) parents.set(pageRef, funnelRef);
26386
26403
  return parents;
26387
26404
  }
26388
26405
  function buildRunRecord(input) {
@@ -28540,7 +28557,7 @@ var require_package = __commonJS({
28540
28557
  "package.json"(exports2, module2) {
28541
28558
  module2.exports = {
28542
28559
  name: "@elitedcs/ghl-mcp",
28543
- version: "3.83.0",
28560
+ version: "3.83.2",
28544
28561
  mcpName: "io.github.drjerryrelth/ghl-command",
28545
28562
  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.",
28546
28563
  main: "dist/index.js",
@@ -28555,11 +28572,6 @@ var require_package = __commonJS({
28555
28572
  "src/command-os/design/rules/anti-default.json",
28556
28573
  "src/command-os/design/rules/web.json",
28557
28574
  "src/command-os/design/rules/anti-default-contract.mjs",
28558
- "src/command-os/design/rules/check-static.mjs",
28559
- "src/command-os/design/rules/fixtures/claude-default.html",
28560
- "src/command-os/design/rules/fixtures/house-clean.html",
28561
- "src/command-os/design/rules/fixtures/generator-run-1.html",
28562
- "src/command-os/design/rules/fixtures/external-css.html",
28563
28575
  "dist/index.js",
28564
28576
  "dist/capture-helper.js",
28565
28577
  "templates/action-schemas.json",
@@ -28577,6 +28589,7 @@ var require_package = __commonJS({
28577
28589
  ],
28578
28590
  scripts: {
28579
28591
  build: "node src/command-os/assessment/build-app.mjs && esbuild src/index.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/index.js --packages=external && esbuild src/capture-helper.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/capture-helper.js --packages=external",
28592
+ typecheck: "tsc --noEmit",
28580
28593
  setup: "node setup-wizard.mjs",
28581
28594
  catalogue: "node scripts/export-command-os-catalogue.mjs",
28582
28595
  "manifest:write": "node scripts/write-package-manifest.mjs",
@@ -29001,7 +29014,8 @@ function writeClientEngagement(locationId2, patch, file2 = clientEngagementsPath
29001
29014
  throw new Error("Something else saved this record while you were editing. Nothing was changed \u2014 reload and enter it again.");
29002
29015
  }
29003
29016
  function floorFor(e, profile) {
29004
- const offer = e.offer && profile.offers?.find((o) => o.name.trim().toLowerCase() === e.offer.trim().toLowerCase());
29017
+ const wanted = e.offer?.trim().toLowerCase();
29018
+ const offer = wanted ? profile.offers?.find((o) => o.name.trim().toLowerCase() === wanted) : void 0;
29005
29019
  if (offer?.priceFloor !== void 0) return offer.priceFloor;
29006
29020
  if (e.cadence === "monthly") return profile.pricing?.minMonthly;
29007
29021
  if (e.cadence === "one-time") return profile.pricing?.minSetup;
@@ -30024,13 +30038,14 @@ function pickLatest(notes) {
30024
30038
  }
30025
30039
  return null;
30026
30040
  }
30027
- var PIPELINE_NAME, NOTE_TITLE, MACHINE_MARKER, MARKER_LINE, NOTE_LIMIT;
30041
+ var PIPELINE_NAME, NOTE_TITLE, MACHINE_MARKER, STAGES, MARKER_LINE, NOTE_LIMIT;
30028
30042
  var init_store2 = __esm({
30029
30043
  "src/command-os/assessment/store.ts"() {
30030
30044
  "use strict";
30031
30045
  PIPELINE_NAME = "Assessments";
30032
30046
  NOTE_TITLE = "GHL COMMAND ASSESSMENT";
30033
30047
  MACHINE_MARKER = "--- machine block, do not edit below this line ---";
30048
+ STAGES = ["Gathering", "Findings", "Proposal sent", "Won", "Lost"];
30034
30049
  MARKER_LINE = /^--- machine block, do not edit below this line ---(?: \((\d+)\/(\d+)(?: s=([a-z0-9]{6}))?\))?[ \t]*$/gm;
30035
30050
  NOTE_LIMIT = 2e4;
30036
30051
  }
@@ -30044,7 +30059,8 @@ async function loadAssessmentRecord(reader, contactId) {
30044
30059
  }
30045
30060
  async function findAssessmentPipeline(reader, locationId2) {
30046
30061
  const raw = await reader.get("/opportunities/pipelines", { params: { locationId: locationId2 } });
30047
- return (raw?.pipelines ?? []).find((p) => String(p.name).trim().toLowerCase() === PIPELINE_NAME.toLowerCase());
30062
+ const pipelines = Array.isArray(raw?.pipelines) ? raw.pipelines : [];
30063
+ return pipelines.find((p) => String(p.name).trim().toLowerCase() === PIPELINE_NAME.toLowerCase());
30048
30064
  }
30049
30065
  async function listAssessmentRecords(reader, locationId2) {
30050
30066
  const pipeline = await findAssessmentPipeline(reader, locationId2);
@@ -33394,7 +33410,7 @@ function registerAssessmentTools(server2, client) {
33394
33410
  quote: import_zod52.z.string().describe("The owner's own words, VERBATIM from the transcript. Do not tidy it up \u2014 if it is not in the transcript the answer is thrown out."),
33395
33411
  reasoning: import_zod52.z.string().optional().describe("How you got from the quote to the value, when they are not the same.")
33396
33412
  })).describe("One entry per question you could answer. Leave out anything the conversation did not cover."),
33397
- existing: import_zod52.z.record(import_zod52.z.union([import_zod52.z.string(), import_zod52.z.number()])).optional().describe("Answers already captured, so this adds to them rather than replacing them.")
33413
+ existing: import_zod52.z.record(import_zod52.z.string(), import_zod52.z.union([import_zod52.z.string(), import_zod52.z.number()])).optional().describe("Answers already captured, so this adds to them rather than replacing them.")
33398
33414
  },
33399
33415
  async ({ transcript, proposed, existing }) => {
33400
33416
  const gated = gateExtraction(transcript, proposed, QUESTION_UNITS);
@@ -33517,9 +33533,9 @@ var init_assessment = __esm({
33517
33533
  StateSchema = import_zod52.z.object({
33518
33534
  biz: import_zod52.z.string().min(1).describe("The prospect's business name."),
33519
33535
  trade: import_zod52.z.string().optional().describe("Their trade. Decides which benchmarks may be applied."),
33520
- ans: import_zod52.z.record(import_zod52.z.union([import_zod52.z.string(), import_zod52.z.number()])).describe("questionId -> answer, exactly as typed."),
33521
- skip: import_zod52.z.record(import_zod52.z.boolean()).optional().describe("questionId -> true where they did not know."),
33522
- asm: import_zod52.z.record(import_zod52.z.number()).optional().describe("assumption id -> rate as a decimal."),
33536
+ ans: import_zod52.z.record(import_zod52.z.string(), import_zod52.z.union([import_zod52.z.string(), import_zod52.z.number()])).describe("questionId -> answer, exactly as typed."),
33537
+ skip: import_zod52.z.record(import_zod52.z.string(), import_zod52.z.boolean()).optional().describe("questionId -> true where they did not know."),
33538
+ asm: import_zod52.z.record(import_zod52.z.string(), import_zod52.z.number()).optional().describe("assumption id -> rate as a decimal."),
33523
33539
  client: import_zod52.z.object({ name: import_zod52.z.string().max(120), role: import_zod52.z.string().max(80), email: import_zod52.z.string().max(200), phone: import_zod52.z.string().max(40), website: import_zod52.z.string().max(300), city: import_zod52.z.string().max(120) }).partial().optional().describe("The person and place this is for, as typed on The client step. Rides with the answers so a load brings it back.")
33524
33540
  });
33525
33541
  }
@@ -34096,7 +34112,7 @@ function startRun(locationId2, stage, onDone, meta = {}) {
34096
34112
  const obligations = (context.manualObligations ?? []).filter((o) => !deps.some((d) => d.ref === o.ref) && !(outcome.manualChecks ?? []).some((c) => c.ref === o.ref && c.type === o.type && c.ok && c.evidence.trim()));
34097
34113
  for (const h of plan?.plan?.handoffs ?? []) if (!obligations.some((o) => o.ref === h.ref) && !(outcome.manualChecks ?? []).some((c) => c.ref === h.ref && c.ok && c.evidence.trim())) obligations.push({ ref: h.ref, type: "handoff", name: h.title || h.name || h.ref, reason: h.instruction || "Finish this manual action." });
34098
34114
  outcome.ok = verifyBuildPassed(outcome, readback, plan?.plan);
34099
- verifyRaw = { summary: outcome.summary, issues: outcome.issues, verification: { deps, checks, readback, obligations, buildOk: outcome.ok, liveProofApplicable: Boolean(plan?.plan?.funnels?.length), liveProof: outcome.liveProof ?? { ok: false, evidence: outcome.error || "No test-lead result was returned." } } };
34115
+ verifyRaw = { summary: outcome.summary, issues: outcome.issues, verification: { deps, checks, readback, obligations, buildOk: outcome.ok, liveProofApplicable: Boolean(plan?.plan && typeof plan.plan === "object" && "funnels" in plan.plan && Array.isArray(plan.plan.funnels) && plan.plan.funnels.length), liveProof: outcome.liveProof ?? { ok: false, evidence: outcome.error || "No test-lead result was returned." } } };
34100
34116
  if (!plan?.plan) {
34101
34117
  outcome.ok = false;
34102
34118
  verifyRaw.issues = [...verifyRaw.issues ?? [], "Run Build before verifying."];
@@ -36987,8 +37003,9 @@ async function runDashboard(argv) {
36987
37003
  state = setAccountType(state, outcome.locationId, "client");
36988
37004
  if (args.industry) state = setAccountIndustry(state, outcome.locationId, args.industry);
36989
37005
  if (!outcome.registered) {
36990
- state = setPending(state, outcome.locationId, args.name, args.industry || void 0);
36991
- if (outcome.saveFailed) state.pending[outcome.locationId].saveFailed = true;
37006
+ const pending = setPending(state, outcome.locationId, args.name, args.industry || void 0);
37007
+ if (outcome.saveFailed) pending.pending[outcome.locationId].saveFailed = true;
37008
+ state = pending;
36992
37009
  }
36993
37010
  writeCockpitState(state);
36994
37011
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -37996,7 +38013,7 @@ function registerBulkOperationTools(server2, client) {
37996
38013
  "Update the same field(s) on multiple contacts at once. Rate-limited. Example: set a custom field value, change source, update address for a batch of contacts.",
37997
38014
  {
37998
38015
  contactIds: import_zod54.z.array(import_zod54.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs to update."),
37999
- fields: import_zod54.z.record(import_zod54.z.unknown()).describe("Fields to set on each contact (e.g. {customField: {id: 'xxx', value: 'yyy'}}, {source: 'Import'}).")
38016
+ fields: import_zod54.z.record(import_zod54.z.string(), import_zod54.z.unknown()).describe("Fields to set on each contact (e.g. {customField: {id: 'xxx', value: 'yyy'}}, {source: 'Import'}).")
38000
38017
  },
38001
38018
  async ({ contactIds, fields }) => {
38002
38019
  if (Object.keys(fields).length === 0) {
@@ -39054,10 +39071,12 @@ function registerWorkflowClonerTools(server2, builderClient) {
39054
39071
  workflowId: void 0
39055
39072
  // Will be set by the new workflow
39056
39073
  };
39057
- if (cloned.data && typeof cloned.data === "object" && "targetActionId" in cloned.data) {
39074
+ const clonedData = cloned.data;
39075
+ if (clonedData && typeof clonedData === "object" && "targetActionId" in clonedData) {
39076
+ const targetActionId = clonedData.targetActionId;
39058
39077
  cloned.data = {
39059
- ...cloned.data,
39060
- targetActionId: remap(cloned.data.targetActionId)
39078
+ ...clonedData,
39079
+ targetActionId: typeof targetActionId === "string" ? remap(targetActionId) : targetActionId
39061
39080
  };
39062
39081
  }
39063
39082
  return cloned;
@@ -39170,8 +39189,8 @@ function buildContactSmartListBody(args) {
39170
39189
  };
39171
39190
  }
39172
39191
  function registerSmartListTools(server2, builderClient) {
39192
+ if (!builderClient) return;
39173
39193
  const client = builderClient;
39174
- if (!client) return;
39175
39194
  async function smartListRequest(method, path39, body) {
39176
39195
  const headers = await client.buildHeaders();
39177
39196
  const url = `${SMARTLIST_BASE}${path39}`;
@@ -39232,7 +39251,7 @@ ${await res.text()}`);
39232
39251
  {
39233
39252
  name: import_zod57.z.string().describe("Display name for the smart list."),
39234
39253
  tagFilters: import_zod57.z.array(import_zod57.z.array(import_zod57.z.string())).optional().describe('Tag conditions. Each inner array is one filter requiring ALL those tags, e.g. [["escalated"]] for "has the escalated tag".'),
39235
- rawFilters: import_zod57.z.array(import_zod57.z.record(import_zod57.z.unknown())).optional().describe("Advanced: raw filter leaves {field, operator, value, options}. Combined with tagFilters into one AND group."),
39254
+ rawFilters: import_zod57.z.array(import_zod57.z.record(import_zod57.z.string(), import_zod57.z.unknown())).optional().describe("Advanced: raw filter leaves {field, operator, value, options}. Combined with tagFilters into one AND group."),
39236
39255
  locationId: import_zod57.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
39237
39256
  },
39238
39257
  async ({ name, tagFilters, rawFilters, locationId: locationId2 }) => {
@@ -39317,8 +39336,8 @@ ${await res.text()}`);
39317
39336
  {
39318
39337
  name: import_zod57.z.string().describe("Display name for the smart list."),
39319
39338
  objectKey: import_zod57.z.enum(OBJECT_KEYS).describe("Object type the list segments over. 'contacts' or 'opportunity'."),
39320
- filters: import_zod57.z.array(import_zod57.z.record(import_zod57.z.unknown())).optional().describe("Array of filter objects. Each object has fields like {field, operator, value} \u2014 exact shape varies by filter type. See get_smart_list on an existing list to learn the format."),
39321
- columns: import_zod57.z.array(import_zod57.z.record(import_zod57.z.unknown())).optional().describe("Array of column definitions for the smart list view in GHL UI. Each defines which contact/opportunity field shows as a column. Defaults to GHL's standard columns if omitted."),
39339
+ filters: import_zod57.z.array(import_zod57.z.record(import_zod57.z.string(), import_zod57.z.unknown())).optional().describe("Array of filter objects. Each object has fields like {field, operator, value} \u2014 exact shape varies by filter type. See get_smart_list on an existing list to learn the format."),
39340
+ columns: import_zod57.z.array(import_zod57.z.record(import_zod57.z.string(), import_zod57.z.unknown())).optional().describe("Array of column definitions for the smart list view in GHL UI. Each defines which contact/opportunity field shows as a column. Defaults to GHL's standard columns if omitted."),
39322
39341
  pipelineIds: import_zod57.z.array(import_zod57.z.string()).optional().describe("(opportunity objectKey only) Pipeline IDs to restrict this smart list to. Empty array = all pipelines."),
39323
39342
  defaultInPipelines: import_zod57.z.array(import_zod57.z.string()).optional().describe("(opportunity objectKey only) Pipeline IDs where this list is the default view."),
39324
39343
  locationId: import_zod57.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
@@ -39344,8 +39363,8 @@ ${await res.text()}`);
39344
39363
  {
39345
39364
  listId: import_zod57.z.string().describe("The smart list ID to update."),
39346
39365
  name: import_zod57.z.string().optional().describe("New display name."),
39347
- filters: import_zod57.z.array(import_zod57.z.record(import_zod57.z.unknown())).optional().describe("Replace the filter array entirely. To add a filter, fetch the current list, append, and pass the new array."),
39348
- columns: import_zod57.z.array(import_zod57.z.record(import_zod57.z.unknown())).optional().describe("Replace the column array entirely."),
39366
+ filters: import_zod57.z.array(import_zod57.z.record(import_zod57.z.string(), import_zod57.z.unknown())).optional().describe("Replace the filter array entirely. To add a filter, fetch the current list, append, and pass the new array."),
39367
+ columns: import_zod57.z.array(import_zod57.z.record(import_zod57.z.string(), import_zod57.z.unknown())).optional().describe("Replace the column array entirely."),
39349
39368
  pipelineIds: import_zod57.z.array(import_zod57.z.string()).optional().describe("(opportunity only) Update the pipeline scope."),
39350
39369
  defaultInPipelines: import_zod57.z.array(import_zod57.z.string()).optional().describe("(opportunity only) Update the default-in-pipelines list."),
39351
39370
  locationId: import_zod57.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
@@ -39412,8 +39431,8 @@ var init_smart_lists = __esm({
39412
39431
 
39413
39432
  // src/tools/reputation.ts
39414
39433
  function registerReputationTools(server2, builderClient) {
39434
+ if (!builderClient) return;
39415
39435
  const client = builderClient;
39416
- if (!client) return;
39417
39436
  async function reputationRequest(method, path39) {
39418
39437
  const headers = await client.buildHeaders();
39419
39438
  const response = await fetch(`${REPUTATION_BASE}${path39}`, { method, headers });
@@ -39558,8 +39577,8 @@ var init_email_campaigns = __esm({
39558
39577
 
39559
39578
  // src/tools/memberships.ts
39560
39579
  function registerMembershipTools(server2, builderClient) {
39580
+ if (!builderClient) return;
39561
39581
  const client = builderClient;
39562
- if (!client) return;
39563
39582
  async function membershipRequest(path39, method = "GET", body) {
39564
39583
  const headers = await client.buildHeaders();
39565
39584
  const response = await fetch(`${MEMBERSHIP_BASE2}${path39}`, {
@@ -39875,7 +39894,7 @@ function registerTemplateDeployerTools(server2, client) {
39875
39894
  "Deploy a template to set up a GHL sub-account. Creates tags, custom fields, pipelines with stages, calendars, workflows, and forms based on the template and the user's questionnaire answers. This is the main setup automation tool.",
39876
39895
  {
39877
39896
  templateFile: import_zod61.z.string().describe("Path to the template JSON file."),
39878
- answers: import_zod61.z.record(import_zod61.z.unknown()).describe("Questionnaire answers keyed by question ID (e.g. {business_name: 'My Clinic', business_phone: '+15551234567', ...})."),
39897
+ answers: import_zod61.z.record(import_zod61.z.string(), import_zod61.z.unknown()).describe("Questionnaire answers keyed by question ID (e.g. {business_name: 'My Clinic', business_phone: '+15551234567', ...})."),
39879
39898
  locationId: import_zod61.z.string().optional().describe("Location ID to deploy to. Uses default if not specified."),
39880
39899
  dryRun: import_zod61.z.boolean().optional().describe("If true, shows what would be created without actually creating anything. Defaults to false."),
39881
39900
  overwriteBusinessInfo: import_zod61.z.boolean().optional().describe(
@@ -40281,7 +40300,7 @@ var init_template_deployer = __esm({
40281
40300
  required: import_zod61.z.boolean().optional(),
40282
40301
  placeholder: import_zod61.z.string().optional()
40283
40302
  })).optional().default([]),
40284
- location: import_zod61.z.record(import_zod61.z.unknown()).optional(),
40303
+ location: import_zod61.z.record(import_zod61.z.string(), import_zod61.z.unknown()).optional(),
40285
40304
  tags: import_zod61.z.array(import_zod61.z.string()).optional(),
40286
40305
  customFields: import_zod61.z.array(import_zod61.z.object({
40287
40306
  name: import_zod61.z.string(),
@@ -40294,7 +40313,7 @@ var init_template_deployer = __esm({
40294
40313
  workflows: import_zod61.z.array(import_zod61.z.object({
40295
40314
  name: import_zod61.z.string(),
40296
40315
  condition: import_zod61.z.string().optional(),
40297
- actions: import_zod61.z.array(import_zod61.z.record(import_zod61.z.unknown())).optional().default([])
40316
+ actions: import_zod61.z.array(import_zod61.z.record(import_zod61.z.string(), import_zod61.z.unknown())).optional().default([])
40298
40317
  })).optional(),
40299
40318
  calendars: import_zod61.z.array(import_zod61.z.object({
40300
40319
  name: import_zod61.z.string(),
@@ -40334,13 +40353,17 @@ var init_version_check = __esm({
40334
40353
  });
40335
40354
 
40336
40355
  // src/tools/diagnostics.ts
40356
+ function anthropicKeyCheckStatus(verdict) {
40357
+ const severity = ANTHROPIC_KEY_STATUS[verdict];
40358
+ return severity === "ok" ? "pass" : severity;
40359
+ }
40337
40360
  async function collectInstallChecks(installedVersion, client, builderClient, registry2, getGatingReport, plan = "mcp", probeAnthropic = probeAnthropicKey) {
40338
40361
  const checks = [];
40339
40362
  const anthropicPromise = plan === "command-os" ? (async () => {
40340
40363
  const r = await probeAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
40341
40364
  return {
40342
40365
  name: "Anthropic key (Command OS demo builder)",
40343
- status: ANTHROPIC_KEY_STATUS[r.verdict],
40366
+ status: anthropicKeyCheckStatus(r.verdict),
40344
40367
  detail: r.raw ? `${r.detail} Anthropic said: "${r.raw}"` : r.detail
40345
40368
  };
40346
40369
  })() : null;
@@ -46256,7 +46279,7 @@ function registerAgencyBuildTools(server2, knownTools) {
46256
46279
  why: import_zod72.z.string().min(3).describe("Plain English. This is what the operator reads before approving."),
46257
46280
  targets: import_zod72.z.array(targetSchema).default([]),
46258
46281
  creates: import_zod72.z.array(import_zod72.z.object({ ref: import_zod72.z.string(), kind: import_zod72.z.string() })).optional().describe("What this step brings into existence, for later steps to point at."),
46259
- args: import_zod72.z.record(import_zod72.z.unknown()).optional().describe("The arguments, kept verbatim \u2014 the approval covers these, so changing one voids it.")
46282
+ args: import_zod72.z.record(import_zod72.z.string(), import_zod72.z.unknown()).optional().describe("The arguments, kept verbatim \u2014 the approval covers these, so changing one voids it.")
46260
46283
  })).min(1)
46261
46284
  },
46262
46285
  async ({ goal, findings, steps }) => {
@@ -46326,7 +46349,7 @@ function registerAgencyBuildTools(server2, knownTools) {
46326
46349
  undo: import_zod72.z.string().optional().describe("Required on 'applied'. How to reverse this exact write, specifically enough to act on months later."),
46327
46350
  because: import_zod72.z.string().optional().describe("Required on 'failed'. What the account actually said."),
46328
46351
  result: import_zod72.z.unknown().optional().describe("What came back, kept so a stall can be diagnosed without re-running anything."),
46329
- resolved: import_zod72.z.record(import_zod72.z.string()).optional().describe("The real ids this step created, keyed by the ref later steps use."),
46352
+ resolved: import_zod72.z.record(import_zod72.z.string(), import_zod72.z.string()).optional().describe("The real ids this step created, keyed by the ref later steps use."),
46330
46353
  retry: import_zod72.z.boolean().optional().describe("This is a second run of a step that failed. Both attempts are kept, and the earlier failure stops blocking what came after it.")
46331
46354
  },
46332
46355
  async ({ buildId, stepId, state, undo, because, result, resolved: resolved2, retry }) => {
@@ -47191,7 +47214,7 @@ function registerAuditReportTools(server2) {
47191
47214
  "build_audit_report",
47192
47215
  "Turn the output of `audit_workflows` into a client-ready assessment: plain-English findings with money attached where it can honestly be attached, ranked worst-first, plus an explicit list of what could NOT be checked. Call `audit_workflows` first and pass its result here \u2014 this does not re-read the account. Give `leadsPerMonth`, `closeRate` and `monthlyValuePerClient` if you know them and the findings get costed in dollars; leave them out and it says which input is missing instead of guessing. `monthlyValuePerClient` defaults to your saved minimum monthly fee from your agency profile. Use this to produce the document you send or present, not to diagnose \u2014 the diagnosing already happened.",
47193
47216
  {
47194
- audit: import_zod74.z.record(import_zod74.z.unknown()).describe("The full JSON result returned by audit_workflows."),
47217
+ audit: import_zod74.z.record(import_zod74.z.string(), import_zod74.z.unknown()).describe("The full JSON result returned by audit_workflows."),
47195
47218
  audience: import_zod74.z.enum(["internal", "prospect"]).optional().describe("Who reads it. 'internal' (default) is a report about an account you already run, and may use your own minimum monthly as what a client is worth. 'prospect' is a document about somebody else's business: your numbers are NEVER used, so findings stay uncosted unless you supply THEIR lead volume, close rate and client value."),
47196
47219
  accountName: import_zod74.z.string().describe("The client's name as they should see it on the document."),
47197
47220
  locationId: import_zod74.z.string().optional().describe("Sub-account id. Recorded on the document, never shown in the body."),
@@ -47256,7 +47279,7 @@ function registerAuditReportTools(server2) {
47256
47279
  "build_site_audit_report",
47257
47280
  "Turn a website read-through into the assessment document you put in front of a PROSPECT \u2014 someone whose GoHighLevel account you do not have. Pass the verified observations from a site crawl and it returns plain-English findings, ranked, each one carrying the quote and the page it came from. Use it before a sales call, or alongside a receptionist demo, so the same read of their site fills both. It never invents money: a website cannot tell you what a lead is worth to that business, so findings come back uncosted unless you supply THEIR figures, and the document says why. It also never repeats a claim the crawl could not stand up \u2014 anything refused is returned separately, so you can see what was thrown out rather than wonder what was missed.",
47258
47281
  {
47259
- observations: import_zod74.z.record(import_zod74.z.unknown()).describe("The verified observations payload from the site read: site, pagesRead, observations, limits. Passed through as-is."),
47282
+ observations: import_zod74.z.record(import_zod74.z.string(), import_zod74.z.unknown()).describe("The verified observations payload from the site read: site, pagesRead, observations, limits. Passed through as-is."),
47260
47283
  businessName: import_zod74.z.string().describe("The business's name as it should appear on the document."),
47261
47284
  leadsPerMonth: import_zod74.z.number().min(0).optional().describe("THEIR leads per month, if they told you. Never your own figure."),
47262
47285
  closeRate: import_zod74.z.number().min(0).max(1).optional().describe("THEIR close rate, 0-1, if they told you. Never guessed."),
@@ -48138,7 +48161,7 @@ var init_brief2 = __esm({
48138
48161
  preset: presetSchema,
48139
48162
  briefSource: briefSourceSchema,
48140
48163
  /** Partner-OS deep structures (ICA / offer / brand-DNA), carried verbatim. */
48141
- extended: import_zod79.z.record(import_zod79.z.unknown()).optional(),
48164
+ extended: import_zod79.z.record(import_zod79.z.string(), import_zod79.z.unknown()).optional(),
48142
48165
  business: import_zod79.z.object({
48143
48166
  name: import_zod79.z.string(),
48144
48167
  type: import_zod79.z.string().optional(),
@@ -49913,7 +49936,7 @@ async function executeBackbone(plan, deps, opts = {}) {
49913
49936
  pendStaff(u, `Staff member "${name}" (${u.email}) was not created: no agency key is registered (creating users is an agency-level write). Run register_agency_key with an agency-level Private Integration key that has users.write, then re-run \u2014 or add them in Settings \u2192 Team and re-run to bind them by email.`);
49914
49937
  continue;
49915
49938
  }
49916
- let outcome;
49939
+ let outcome = void 0;
49917
49940
  let recoveredUser;
49918
49941
  try {
49919
49942
  outcome = await deps.createUser({ firstName: u.firstName, lastName: u.lastName, email: u.email, role: u.role, phone: u.phone });
@@ -51353,14 +51376,14 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
51353
51376
  "validate_brief",
51354
51377
  "Validate an Intake-to-Build Brief object against the \xA74 schema. Returns {valid, errors}. Use to check a normalized brief before handing it to the plan-generation skill.",
51355
51378
  {
51356
- brief: import_zod80.z.record(import_zod80.z.unknown()).describe("The Brief object to validate.")
51379
+ brief: import_zod80.z.record(import_zod80.z.string(), import_zod80.z.unknown()).describe("The Brief object to validate.")
51357
51380
  },
51358
51381
  async ({ brief }) => validateBrief(brief)
51359
51382
  );
51360
51383
  const buildPlanSchema2 = {
51361
- plan: import_zod80.z.record(import_zod80.z.unknown()).optional().describe("The approved \xA75 Build Plan object. Omit it and pass useSavedPlan:true to build from the plan already saved for this account."),
51384
+ plan: import_zod80.z.record(import_zod80.z.string(), import_zod80.z.unknown()).optional().describe("The approved \xA75 Build Plan object. Omit it and pass useSavedPlan:true to build from the plan already saved for this account."),
51362
51385
  fromPreset: import_zod80.z.boolean().optional().describe("PLAN-FROM-PRESET (PRD row 4d): compose the \xA75 plan SERVER-SIDE from the industry preset + the \xA74 brief, deterministically, in milliseconds \u2014 never author workflow actions by hand when a preset covers the brief. Pass `brief` (required) and optionally `preset` and `copy`; omit `plan`. The composed plan carries the preset's full structure (workflows, templates with complete send-ready copy token-filled from the brief, staff users, calendars with the brief's appointment lengths, pipeline stages, notification targets) and then flows through the exact same validate \u2192 dry_run/execute path as a passed plan, including being saved on execute. The dry_run response lists copySlots \u2014 the template refs whose subject/body the model MAY refine via `copy` on the execute call. If the preset cannot cover the brief the tool answers phase:\"compose\" with the reasons; only then author a plan by hand."),
51363
- brief: import_zod80.z.record(import_zod80.z.unknown()).optional().describe("fromPreset only: the \xA74 Brief (from normalize_submission_to_brief, or assembled from the cockpit intake answers)."),
51386
+ brief: import_zod80.z.record(import_zod80.z.string(), import_zod80.z.unknown()).optional().describe("fromPreset only: the \xA74 Brief (from normalize_submission_to_brief, or assembled from the cockpit intake answers)."),
51364
51387
  preset: import_zod80.z.string().optional().describe("fromPreset only: which preset to compose from \u2014 a preset id (med_spa, clinic, coach, local_service, ecom, generic), an alias (dental, medspa, home_services\u2026), or an industry slug (med-spa, local-service, ecommerce\u2026). Defaults to the brief's own preset field; an unknown value falls back to the generic preset (noted in the response)."),
51365
51388
  copy: import_zod80.z.array(
51366
51389
  import_zod80.z.object({
@@ -52013,7 +52036,7 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
52013
52036
  {
52014
52037
  formId: import_zod80.z.string().describe("The intake form ID (from install_intake_form)."),
52015
52038
  submissionId: import_zod80.z.string().optional().describe("Specific submission to normalize. Defaults to the most recent."),
52016
- fieldMap: import_zod80.z.record(import_zod80.z.string()).optional().describe("intakeKey -> customFieldId map from install_intake_form. Reconstructed from the form if omitted."),
52039
+ fieldMap: import_zod80.z.record(import_zod80.z.string(), import_zod80.z.string()).optional().describe("intakeKey -> customFieldId map from install_intake_form. Reconstructed from the form if omitted."),
52017
52040
  preset: import_zod80.z.string().optional().describe("Override the preset. Defaults to one derived from business_type.")
52018
52041
  },
52019
52042
  async ({ formId, submissionId, fieldMap, preset }) => {
@@ -52145,7 +52168,7 @@ function registerAllTools(server2, client, registry2, mcpVersion, env = process.
52145
52168
  registerConnectorTools(wrap(CONNECTORS_MODULE));
52146
52169
  registerGoogleCalendarTools(wrap(GOOGLE_CALENDAR_MODULE));
52147
52170
  registerDeliveryAuditTools(wrap(DELIVERY_AUDIT_MODULE));
52148
- registerDesignStudioTools(wrap(DESIGN_STUDIO_MODULE), builderClient, client);
52171
+ registerDesignStudioTools(wrap(DESIGN_STUDIO_MODULE));
52149
52172
  registerAgencyBuildTools(wrap(AGENCY_BUILD_MODULE), () => registeredTools);
52150
52173
  registerClientEngagementTools(wrap(CLIENT_ENGAGEMENTS_MODULE), registry2);
52151
52174
  registerAuditReportTools(wrap(AUDIT_REPORT_MODULE));
@@ -52291,9 +52314,16 @@ var init_tools = __esm({
52291
52314
  [registerAssociationTools, "associations"],
52292
52315
  [registerEstimateTools, "estimates"],
52293
52316
  [registerCouponTools, "coupons"],
52294
- [registerKnowledgeBaseTools, "knowledge-base"],
52295
- [registerConversationAiTools, "conversation-ai"],
52296
- [registerAiEmployeeDemoTools, "ai-employee-demo"],
52317
+ [(server2, client) => registerKnowledgeBaseTools(server2, client), "knowledge-base"],
52318
+ // Wrapped, not listed bare: this registrar's third parameter is a `deps`
52319
+ // object, not the TokenRegistry the loop below passes to everything in this
52320
+ // list. Today that is harmless — every key of `deps` is optional and a
52321
+ // TokenRegistry has none of them, so every read falls back to its default —
52322
+ // but it is harmless by luck, and stops being harmless the day TokenRegistry
52323
+ // grows a `sleep`, `now` or `journalDir`. The wrapper passes nothing, which
52324
+ // is what the registrar effectively receives now.
52325
+ [(server2, client) => registerConversationAiTools(server2, client), "conversation-ai"],
52326
+ [(server2, client) => registerAiEmployeeDemoTools(server2, client), "ai-employee-demo"],
52297
52327
  [registerVoiceAiTools, "voice-ai"],
52298
52328
  [registerDocumentTools, "documents"],
52299
52329
  [registerBulkOperationTools, "bulk-operations"],
@@ -52542,6 +52572,12 @@ var init_cli2 = __esm({
52542
52572
  });
52543
52573
 
52544
52574
  // src/index.ts
52575
+ var index_exports = {};
52576
+ __export(index_exports, {
52577
+ TRANSITIONAL_ATTESTATION_DEADLINE: () => TRANSITIONAL_ATTESTATION_DEADLINE,
52578
+ licenseGateFailureMessage: () => licenseGateFailureMessage
52579
+ });
52580
+ module.exports = __toCommonJS(index_exports);
52545
52581
  var dotenv2 = __toESM(require("dotenv"));
52546
52582
  var path38 = __toESM(require("path"));
52547
52583
  var fs39 = __toESM(require("fs"));
@@ -52909,6 +52945,12 @@ registerMetaTools(server, pkg.version);
52909
52945
  var inBootstrapMode = true;
52910
52946
  var telemetryTier = "none";
52911
52947
  var TRANSITIONAL_ATTESTATION_DEADLINE = Date.parse("2026-06-15T00:00:00.000Z");
52948
+ function licenseGateFailureMessage(lic, deadline = TRANSITIONAL_ATTESTATION_DEADLINE) {
52949
+ if (!lic.ok) return `[ghl-mcp] License check failed: ${lic.error}
52950
+ `;
52951
+ return `[ghl-mcp] Your license is valid, but our license server did not return a signed attestation, which has been required since ${new Date(deadline).toISOString().slice(0, 10)}. This is on our side, not your install. Please write support@ghlcommand.com.
52952
+ `;
52953
+ }
52912
52954
  async function renewAttestation(creds) {
52913
52955
  if (!creds.email || !creds.license_key) return "unauthorized";
52914
52956
  let lic;
@@ -52991,7 +53033,9 @@ async function resolveAccessAndRegister() {
52991
53033
  attestationStatus = "renewed-from-legacy";
52992
53034
  } else {
52993
53035
  attestationStatus = "needs-reset";
52994
- if (renewed === "cancelled" || renewed === "unauthorized") {
53036
+ if (renewed === "install_limit") {
53037
+ process.stderr.write("[ghl-mcp] Bootstrap mode. Free a device on your license, or write support@ghlcommand.com.\n");
53038
+ } else if (renewed === "cancelled" || renewed === "unauthorized") {
52995
53039
  process.stderr.write(`[ghl-mcp] Server says license is ${renewed}. Bootstrap mode.
52996
53040
  `);
52997
53041
  } else if (renewed === "renewed-unsigned") {
@@ -53028,8 +53072,7 @@ async function resolveAccessAndRegister() {
53028
53072
  } catch {
53029
53073
  }
53030
53074
  } else {
53031
- process.stderr.write(`[ghl-mcp] License check failed: ${lic.error}
53032
- `);
53075
+ process.stderr.write(licenseGateFailureMessage(lic));
53033
53076
  }
53034
53077
  } else {
53035
53078
  process.stderr.write(
@@ -53231,3 +53274,8 @@ main().catch((error) => {
53231
53274
  process.stderr.write(`[ghl-mcp] Failed to start: ${error}
53232
53275
  `);
53233
53276
  });
53277
+ // Annotate the CommonJS export names for ESM import in node:
53278
+ 0 && (module.exports = {
53279
+ TRANSITIONAL_ATTESTATION_DEADLINE,
53280
+ licenseGateFailureMessage
53281
+ });