@elitedcs/ghl-mcp 3.2.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -31,8 +31,8 @@ var require_package = __commonJS({
31
31
  "package.json"(exports2, module2) {
32
32
  module2.exports = {
33
33
  name: "@elitedcs/ghl-mcp",
34
- version: "3.2.0",
35
- description: "GoHighLevel MCP Server for Claude. 173 tools \u2014 full CRM, automation, marketing control, and the only programmatic GHL workflow builder.",
34
+ version: "3.3.1",
35
+ description: "GoHighLevel MCP Server for Claude. 174 tools \u2014 full CRM, automation, marketing control, and the only programmatic GHL workflow builder.",
36
36
  main: "dist/index.js",
37
37
  bin: {
38
38
  "ghl-mcp": "dist/index.js"
@@ -41,6 +41,7 @@ var require_package = __commonJS({
41
41
  "dist/index.js",
42
42
  "templates/action-schemas.json",
43
43
  "templates/clinic-medspa.json",
44
+ "templates/trigger-schemas.json",
44
45
  "README.md",
45
46
  "CHANGELOG.md"
46
47
  ],
@@ -405,12 +406,24 @@ var TriggerCommonSchema = import_zod2.z.object({
405
406
  schedule_config: import_zod2.z.record(import_zod2.z.unknown()).optional(),
406
407
  date_updated: import_zod2.z.string().optional()
407
408
  }).passthrough();
408
- function typedTrigger(typeLiteral, knownFields) {
409
- const fieldDesc = knownFields.length === 0 ? "No filter fields \u2014 trigger matches any event of this type." : `Known field paths: ${knownFields.join(", ")}.`;
409
+ function describe(status, knownFields) {
410
+ switch (status) {
411
+ case "fieldless":
412
+ return "No filter fields \u2014 trigger fires on any event of this type.";
413
+ case "documented":
414
+ return `Known field paths: ${knownFields.join(", ")}.`;
415
+ case "partial":
416
+ return knownFields.length > 0 ? `Known field paths: ${knownFields.join(", ")}. Note: GHL exposes additional filter fields that aren't yet captured; pass them through if known.` : "GHL exposes one or more filter fields for this trigger; the exact field paths are not yet captured. Pass through whatever you know from the GHL UI.";
417
+ case "uncaptured":
418
+ return "Filter field paths not yet captured. Pass through whatever fields you know from the GHL UI; reads will accept any.";
419
+ }
420
+ }
421
+ function typedTrigger(typeLiteral, knownFields, status = "documented") {
422
+ const fieldDesc = describe(status, knownFields);
410
423
  return TriggerCommonSchema.extend({
411
424
  type: import_zod2.z.literal(typeLiteral),
412
425
  conditions: import_zod2.z.array(import_zod2.z.object({
413
- operator: import_zod2.z.string().describe("Comparison operator (equals, greater_than, less_than, contains, index-of-true, etc.)."),
426
+ operator: import_zod2.z.string().describe("Comparison operator (equals, greater_than, less_than, contains, index-of-true, is-any-of, etc.)."),
414
427
  field: import_zod2.z.string().describe(fieldDesc),
415
428
  value: import_zod2.z.unknown().optional(),
416
429
  title: import_zod2.z.string().optional(),
@@ -430,9 +443,6 @@ var ContactTagTriggerSchema = TriggerCommonSchema.extend({
430
443
  id: import_zod2.z.string().optional()
431
444
  }).passthrough())
432
445
  });
433
- var CustomerReplyTriggerSchema = typedTrigger("customer_reply", [
434
- // No fields in the catalogue — Customer Reply matches any inbound message.
435
- ]);
436
446
  var AppointmentTriggerSchema = typedTrigger("appointment", [
437
447
  "calendar.id",
438
448
  "appointment.status",
@@ -441,15 +451,105 @@ var AppointmentTriggerSchema = typedTrigger("appointment", [
441
451
  "appointment.eventType",
442
452
  "appointment.modifiedBy"
443
453
  ]);
444
- var PipelineStageUpdatedTriggerSchema = typedTrigger("pipeline_stage_updated", [
445
- "opportunity.pipelineId",
446
- "opportunity.pipelineStageId",
454
+ var BirthdayReminderTriggerSchema = typedTrigger("birthday_reminder", [
455
+ "contact.birthMonth",
456
+ "contact.birthDay",
457
+ "contact.dateOfBirth"
458
+ ]);
459
+ var CallStatusTriggerSchema = typedTrigger("call_status", [
460
+ "call_status",
461
+ "custom_disposition",
462
+ "message.direction",
463
+ "workflow.id"
464
+ ]);
465
+ var CategoryCompletedTriggerSchema = typedTrigger("category_completed", [
466
+ "membership.product.id"
467
+ ]);
468
+ var CategoryStartedTriggerSchema = typedTrigger("category_started", [
469
+ "membership.product.id"
470
+ ]);
471
+ var ContactChangedTriggerSchema = typedTrigger("contact_changed", [
472
+ "contact.tags",
473
+ "contact.dnd",
474
+ "contact.assignedTo",
475
+ "contact.phone",
476
+ "contact.email",
477
+ "contact.type",
478
+ "contact.address1",
479
+ "contact.city",
480
+ "contact.state",
481
+ "contact.country",
482
+ "contact.postalCode",
483
+ "contact.website"
484
+ ]);
485
+ var ContactCreatedTriggerSchema = typedTrigger("contact_created", [
486
+ "tagsAdded",
487
+ "contact.phone",
488
+ "contact.email",
489
+ "contact.type"
490
+ ]);
491
+ var CustomerAppointmentTriggerSchema = typedTrigger("customer_appointment", [
492
+ "calendar.id",
493
+ "contact.tags"
494
+ ]);
495
+ var CustomerReplyTriggerSchema = typedTrigger("customer_reply", [
496
+ "workflow.id",
497
+ "message.type",
498
+ "message.body",
499
+ "contact.tags"
500
+ ]);
501
+ var DndContactTriggerSchema = typedTrigger("dnd_contact", [
502
+ "contact.dnd_direction",
503
+ "contact.dnd",
447
504
  "contact.tags"
448
505
  ]);
449
506
  var FormSubmissionTriggerSchema = typedTrigger("form_submission", [
450
507
  "form.id",
451
508
  "formData.termsAndConditions"
452
509
  ]);
510
+ var InvoiceTriggerSchema = typedTrigger("invoice", [
511
+ "invoice.status",
512
+ "contact.tags"
513
+ ]);
514
+ var IvrIncomingCallTriggerSchema = typedTrigger("ivr_incoming_call", [
515
+ "inbound_number"
516
+ ]);
517
+ var LessonCompletedTriggerSchema = typedTrigger("lesson_completed", [
518
+ "membership.product.id"
519
+ ]);
520
+ var LessonStartedTriggerSchema = typedTrigger("lesson_started", [
521
+ "membership.product.id"
522
+ ]);
523
+ var MailgunEmailEventTriggerSchema = typedTrigger("mailgun_email_event", [
524
+ "workflow.id",
525
+ "mailgun.event"
526
+ // opened | clicked | bounced | delivered | etc.
527
+ ]);
528
+ var MembershipContactCreatedTriggerSchema = typedTrigger("membership_contact_created", [
529
+ "offer.id"
530
+ ]);
531
+ var NoteAddTriggerSchema = typedTrigger("note_add", [
532
+ "contact.tags"
533
+ ]);
534
+ var NoteChangedTriggerSchema = typedTrigger("note_changed", [
535
+ "contact.tags"
536
+ ]);
537
+ var OfferAccessGrantedTriggerSchema = typedTrigger("offer_access_granted", [
538
+ "offer.id"
539
+ ]);
540
+ var OfferAccessRemovedTriggerSchema = typedTrigger("offer_access_removed", [
541
+ "offer.id"
542
+ ]);
543
+ var OpportunityChangedTriggerSchema = typedTrigger("opportunity_changed", [
544
+ "opportunity.pipelineId",
545
+ "contact.tags",
546
+ "opportunity.assignedTo",
547
+ "opportunity.monetaryValue",
548
+ "opportunity.forecastExpectedCloseDate",
549
+ "opportunity.forecastProbability",
550
+ "opportunity.status",
551
+ "opportunity.lostReasonId"
552
+ ]);
453
553
  var OpportunityCreatedTriggerSchema = typedTrigger("opportunity_created", [
454
554
  "opportunity.pipelineId",
455
555
  "contact.tags",
@@ -460,8 +560,9 @@ var OpportunityCreatedTriggerSchema = typedTrigger("opportunity_created", [
460
560
  "opportunity.status",
461
561
  "opportunity.lostReasonId"
462
562
  ]);
463
- var OpportunityChangedTriggerSchema = typedTrigger("opportunity_changed", [
563
+ var OpportunityDecayTriggerSchema = typedTrigger("opportunity_decay", [
464
564
  "opportunity.pipelineId",
565
+ "opportunity.lastActionDate",
465
566
  "contact.tags",
466
567
  "opportunity.assignedTo",
467
568
  "opportunity.monetaryValue",
@@ -481,44 +582,146 @@ var OpportunityStatusChangedTriggerSchema = typedTrigger("opportunity_status_cha
481
582
  "opportunity.forecastProbability",
482
583
  "opportunity.lostReasonId"
483
584
  ]);
484
- var PaymentReceivedTriggerSchema = typedTrigger("payment_received", [
485
- // No filter fields — fires on any payment received.
585
+ var PipelineStageUpdatedTriggerSchema = typedTrigger("pipeline_stage_updated", [
586
+ "opportunity.pipelineId",
587
+ "opportunity.pipelineStageId",
588
+ // not in catalogue but central to this trigger by name
589
+ "contact.tags",
590
+ "opportunity.assignedTo",
591
+ "opportunity.monetaryValue",
592
+ "opportunity.forecastExpectedCloseDate",
593
+ "opportunity.forecastProbability",
594
+ "opportunity.status",
595
+ "opportunity.lostReasonId"
486
596
  ]);
487
- var InboundWebhookTriggerSchema = typedTrigger("inbound_webhook", [
488
- // No filter fields — fires on any inbound webhook hit.
597
+ var ProductAccessGrantedTriggerSchema = typedTrigger("product_access_granted", [
598
+ "product.id"
489
599
  ]);
490
- var MailgunEmailEventTriggerSchema = typedTrigger("mailgun_email_event", [
491
- "workflow.id",
492
- "mailgun.event"
493
- // opened | clicked | bounced | delivered | etc.
600
+ var ProductAccessRemovedTriggerSchema = typedTrigger("product_access_removed", [
601
+ "product.id"
494
602
  ]);
495
- var NoteAddTriggerSchema = typedTrigger("note_add", [
496
- "contact.tags"
603
+ var ProductCompletedTriggerSchema = typedTrigger("product_completed", [
604
+ "membership.product.id"
605
+ ]);
606
+ var ProductStartedTriggerSchema = typedTrigger("product_started", [
607
+ "membership.product.id"
608
+ ]);
609
+ var ShopifyAbandonedCartTriggerSchema = typedTrigger("shopify_abandoned_cart", [
610
+ "duration",
611
+ "cart_value"
612
+ ]);
613
+ var ShopifyOrderFulfilledTriggerSchema = typedTrigger("shopify_order_fulfilled", [
614
+ "cart_value"
615
+ ]);
616
+ var ShopifyOrderPlacedTriggerSchema = typedTrigger("shopify_order_placed", [
617
+ "cart_value"
618
+ ]);
619
+ var SurveySubmissionTriggerSchema = typedTrigger("survey_submission", [
620
+ "survey.id",
621
+ "surveySubmission.disqualified",
622
+ "surveyData.termsAndConditions"
497
623
  ]);
498
624
  var TaskAddedTriggerSchema = typedTrigger("task_added", [
499
625
  "task.assignedTo"
500
626
  ]);
627
+ var TaskDueDateReminderTriggerSchema = typedTrigger("task_due_date_reminder", [
628
+ "task.dueDate"
629
+ ]);
630
+ var TikTokFormSubmittedTriggerSchema = typedTrigger("tik_tok_form_submitted", [
631
+ "tikTok.formId"
632
+ ]);
633
+ var TriggerLinkTriggerSchema = typedTrigger("trigger_link", [
634
+ "link.id"
635
+ ]);
636
+ var TwoStepFormSubmissionTriggerSchema = typedTrigger("two_step_form_submission", [
637
+ "twoStepOrderForm.funnelId",
638
+ "twoStepOrderForm.submissionType"
639
+ ]);
640
+ var ValidationErrorTriggerSchema = typedTrigger("validation_error", [
641
+ "contact.phoneInfo"
642
+ ]);
643
+ var VideoEventTriggerSchema = typedTrigger("video_event", [
644
+ "video.funnelId",
645
+ "video.videoId",
646
+ "video.duration"
647
+ ]);
648
+ var CustomDateReminderTriggerSchema = typedTrigger("custom_date_reminder", [], "partial");
649
+ var InboundTriggerTriggerSchema = typedTrigger("inbound_trigger", ["contact.tags"], "partial");
650
+ var FacebookCommentOnPostTriggerSchema = typedTrigger("facebook_comment_on_post", [], "partial");
651
+ var IgCommentOnPostTriggerSchema = typedTrigger("ig_comment_on_post", [], "partial");
652
+ var InboundWebhookTriggerSchema = typedTrigger("inbound_webhook", [], "fieldless");
653
+ var PaymentReceivedTriggerSchema = typedTrigger("payment_received", [], "fieldless");
654
+ var AffiliateCreatedTriggerSchema = typedTrigger("affiliate_created", [], "uncaptured");
655
+ var SchedulerTriggerTriggerSchema = typedTrigger("scheduler_trigger", [], "uncaptured");
656
+ var UserLogInTriggerSchema = typedTrigger("user_log_in", [], "uncaptured");
657
+ var OrderSubmissionTriggerSchema = typedTrigger("order_submission", [], "uncaptured");
658
+ var ConvAiTriggerTriggerSchema = typedTrigger("conv_ai_trigger", [], "uncaptured");
659
+ var ConvAiAutonomousTriggerTriggerSchema = typedTrigger("conv_ai_autonomous_trigger", [], "uncaptured");
660
+ var CustomObjectCreatedTriggerSchema = typedTrigger("custom_object_created", [], "uncaptured");
661
+ var CustomObjectChangedTriggerSchema = typedTrigger("custom_object_changed", [], "uncaptured");
662
+ var FacebookLeadGenTriggerSchema = typedTrigger("facebook_lead_gen", [], "uncaptured");
501
663
  var UnknownTriggerSchema = TriggerCommonSchema.extend({
502
664
  type: import_zod2.z.string(),
503
665
  conditions: import_zod2.z.array(import_zod2.z.record(import_zod2.z.unknown())).optional()
504
666
  });
505
667
  var WorkflowTriggerSchema = import_zod2.z.union([
506
- // Originally typed (v3.0.x)
507
668
  ContactTagTriggerSchema,
508
- CustomerReplyTriggerSchema,
509
669
  AppointmentTriggerSchema,
510
- PipelineStageUpdatedTriggerSchema,
511
- // Added in v3.2.0
670
+ BirthdayReminderTriggerSchema,
671
+ CallStatusTriggerSchema,
672
+ CategoryCompletedTriggerSchema,
673
+ CategoryStartedTriggerSchema,
674
+ ContactChangedTriggerSchema,
675
+ ContactCreatedTriggerSchema,
676
+ CustomerAppointmentTriggerSchema,
677
+ CustomerReplyTriggerSchema,
678
+ DndContactTriggerSchema,
512
679
  FormSubmissionTriggerSchema,
513
- OpportunityCreatedTriggerSchema,
514
- OpportunityChangedTriggerSchema,
515
- OpportunityStatusChangedTriggerSchema,
516
- PaymentReceivedTriggerSchema,
517
- InboundWebhookTriggerSchema,
680
+ InvoiceTriggerSchema,
681
+ IvrIncomingCallTriggerSchema,
682
+ LessonCompletedTriggerSchema,
683
+ LessonStartedTriggerSchema,
518
684
  MailgunEmailEventTriggerSchema,
685
+ MembershipContactCreatedTriggerSchema,
519
686
  NoteAddTriggerSchema,
687
+ NoteChangedTriggerSchema,
688
+ OfferAccessGrantedTriggerSchema,
689
+ OfferAccessRemovedTriggerSchema,
690
+ OpportunityChangedTriggerSchema,
691
+ OpportunityCreatedTriggerSchema,
692
+ OpportunityDecayTriggerSchema,
693
+ OpportunityStatusChangedTriggerSchema,
694
+ PipelineStageUpdatedTriggerSchema,
695
+ ProductAccessGrantedTriggerSchema,
696
+ ProductAccessRemovedTriggerSchema,
697
+ ProductCompletedTriggerSchema,
698
+ ProductStartedTriggerSchema,
699
+ ShopifyAbandonedCartTriggerSchema,
700
+ ShopifyOrderFulfilledTriggerSchema,
701
+ ShopifyOrderPlacedTriggerSchema,
702
+ SurveySubmissionTriggerSchema,
520
703
  TaskAddedTriggerSchema,
521
- // Fallback for the remaining 44 native trigger types + any future ones
704
+ TaskDueDateReminderTriggerSchema,
705
+ TikTokFormSubmittedTriggerSchema,
706
+ TriggerLinkTriggerSchema,
707
+ TwoStepFormSubmissionTriggerSchema,
708
+ ValidationErrorTriggerSchema,
709
+ VideoEventTriggerSchema,
710
+ CustomDateReminderTriggerSchema,
711
+ InboundTriggerTriggerSchema,
712
+ FacebookCommentOnPostTriggerSchema,
713
+ IgCommentOnPostTriggerSchema,
714
+ InboundWebhookTriggerSchema,
715
+ PaymentReceivedTriggerSchema,
716
+ AffiliateCreatedTriggerSchema,
717
+ SchedulerTriggerTriggerSchema,
718
+ UserLogInTriggerSchema,
719
+ OrderSubmissionTriggerSchema,
720
+ ConvAiTriggerTriggerSchema,
721
+ ConvAiAutonomousTriggerTriggerSchema,
722
+ CustomObjectCreatedTriggerSchema,
723
+ CustomObjectChangedTriggerSchema,
724
+ FacebookLeadGenTriggerSchema,
522
725
  UnknownTriggerSchema
523
726
  ]);
524
727
 
@@ -5728,6 +5931,287 @@ ${errors.join("\n")}` : "\nNo errors!",
5728
5931
  );
5729
5932
  }
5730
5933
 
5934
+ // src/tools/validators.ts
5935
+ var import_zod40 = require("zod");
5936
+ function extractFromTrigger(trigger, refs) {
5937
+ const triggerName = String(trigger.name ?? trigger.type ?? "unnamed trigger");
5938
+ const where = `trigger "${triggerName}"`;
5939
+ const conditions = Array.isArray(trigger.conditions) ? trigger.conditions : [];
5940
+ for (let i = 0; i < conditions.length; i++) {
5941
+ const c = conditions[i];
5942
+ const field = typeof c.field === "string" ? c.field : null;
5943
+ const value = c.value;
5944
+ if (!field || value === void 0 || value === null) continue;
5945
+ const valueAsArray = Array.isArray(value) ? value : [value];
5946
+ for (const v of valueAsArray) {
5947
+ if (typeof v !== "string") continue;
5948
+ const childWhere = `${where} \u2192 conditions[${i}] (field=${field})`;
5949
+ switch (field) {
5950
+ case "opportunity.pipelineId":
5951
+ refs.push({ kind: "pipeline", id: v, where: childWhere });
5952
+ break;
5953
+ case "opportunity.pipelineStageId":
5954
+ refs.push({ kind: "stage", id: v, where: childWhere });
5955
+ break;
5956
+ case "opportunity.assignedTo":
5957
+ case "contact.assignedTo":
5958
+ refs.push({ kind: "user", id: v, where: childWhere });
5959
+ break;
5960
+ case "workflow.id":
5961
+ refs.push({ kind: "workflow", id: v, where: childWhere });
5962
+ break;
5963
+ }
5964
+ }
5965
+ }
5966
+ const triggerActions = Array.isArray(trigger.actions) ? trigger.actions : [];
5967
+ for (let i = 0; i < triggerActions.length; i++) {
5968
+ const a = triggerActions[i];
5969
+ if (a.type === "add_to_workflow" && typeof a.workflow_id === "string") {
5970
+ refs.push({
5971
+ kind: "workflow",
5972
+ id: a.workflow_id,
5973
+ where: `${where} \u2192 actions[${i}] (add_to_workflow)`
5974
+ });
5975
+ }
5976
+ }
5977
+ }
5978
+ function extractFromAction(action, refs) {
5979
+ const type = typeof action.type === "string" ? action.type : "unknown";
5980
+ const name = typeof action.name === "string" ? action.name : "unnamed action";
5981
+ const where = `action "${name}" (${type})`;
5982
+ const attr = action.attributes ?? {};
5983
+ switch (type) {
5984
+ case "update_contact_field": {
5985
+ const fields = Array.isArray(attr.fields) ? attr.fields : [];
5986
+ for (let i = 0; i < fields.length; i++) {
5987
+ const f = fields[i];
5988
+ if (typeof f.field === "string") {
5989
+ refs.push({
5990
+ kind: "custom_field",
5991
+ id: f.field,
5992
+ where: `${where} \u2192 fields[${i}]`
5993
+ });
5994
+ }
5995
+ }
5996
+ break;
5997
+ }
5998
+ case "internal_notification": {
5999
+ const notif = attr.notification ?? {};
6000
+ if (typeof notif.selectedUser === "string" && notif.selectedUser.trim() !== "") {
6001
+ refs.push({
6002
+ kind: "user",
6003
+ id: notif.selectedUser,
6004
+ where: `${where} \u2192 notification.selectedUser`
6005
+ });
6006
+ }
6007
+ break;
6008
+ }
6009
+ case "task_notification": {
6010
+ if (typeof attr.assignedTo === "string" && attr.assignedTo.trim() !== "") {
6011
+ refs.push({
6012
+ kind: "user",
6013
+ id: attr.assignedTo,
6014
+ where: `${where} \u2192 assignedTo`
6015
+ });
6016
+ }
6017
+ break;
6018
+ }
6019
+ case "remove_from_workflow": {
6020
+ if (typeof attr.workflowId === "string") {
6021
+ refs.push({
6022
+ kind: "workflow",
6023
+ id: attr.workflowId,
6024
+ where: `${where} \u2192 workflowId`
6025
+ });
6026
+ }
6027
+ const workflowIdArr = Array.isArray(attr.workflow_id) ? attr.workflow_id : [];
6028
+ for (let i = 0; i < workflowIdArr.length; i++) {
6029
+ const v = workflowIdArr[i];
6030
+ if (typeof v === "string") {
6031
+ refs.push({
6032
+ kind: "workflow",
6033
+ id: v,
6034
+ where: `${where} \u2192 workflow_id[${i}]`
6035
+ });
6036
+ }
6037
+ }
6038
+ break;
6039
+ }
6040
+ case "internal_update_opportunity": {
6041
+ const customInputs = Array.isArray(attr.__customInputFields__) ? attr.__customInputFields__ : [];
6042
+ for (let i = 0; i < customInputs.length; i++) {
6043
+ const c = customInputs[i];
6044
+ if (typeof c.value !== "string") continue;
6045
+ if (c.filterField === "pipelineId") {
6046
+ refs.push({ kind: "pipeline", id: c.value, where: `${where} \u2192 __customInputFields__[${i}].pipelineId` });
6047
+ } else if (c.filterField === "pipelineStageId") {
6048
+ refs.push({ kind: "stage", id: c.value, where: `${where} \u2192 __customInputFields__[${i}].pipelineStageId` });
6049
+ }
6050
+ }
6051
+ break;
6052
+ }
6053
+ }
6054
+ }
6055
+ function registerValidatorTools(server2, client, builderClient) {
6056
+ if (!builderClient) return;
6057
+ server2.tool(
6058
+ "validate_workflow",
6059
+ "Pre-flight ID validation for a deployed GHL workflow. Scans every trigger and action for references to pipelines, pipeline stages, custom fields, users, and other workflows; verifies each ID exists in the current location. Use this BEFORE publish_workflow when a workflow has been edited, or whenever a published workflow stops behaving as expected. Catches the silent-failure bug where invalid IDs make GHL skip all subsequent actions without warning. Returns a structured report of valid + missing references.",
6060
+ {
6061
+ workflowId: import_zod40.z.string().describe("The workflow ID to validate.")
6062
+ },
6063
+ async ({ workflowId }) => {
6064
+ try {
6065
+ const workflow = await builderClient.getWorkflow(workflowId);
6066
+ if (!workflow) return errorResponse(new Error(`Workflow ${workflowId} not found`));
6067
+ const refs = [];
6068
+ const triggers = Array.isArray(workflow.triggers) ? workflow.triggers : [];
6069
+ for (const t of triggers) {
6070
+ extractFromTrigger(t, refs);
6071
+ }
6072
+ const actions = workflow.workflowData?.templates ?? [];
6073
+ for (const a of actions) {
6074
+ extractFromAction(a, refs);
6075
+ }
6076
+ if (refs.length === 0) {
6077
+ const empty = {
6078
+ workflowId,
6079
+ workflowName: workflow.name,
6080
+ status: "ok",
6081
+ references_scanned: 0,
6082
+ issues_count: 0,
6083
+ findings: []
6084
+ };
6085
+ return jsonResponse(empty);
6086
+ }
6087
+ const refCategories = new Set(refs.map((r) => r.kind));
6088
+ const locationId2 = client.defaultLocationId;
6089
+ const fetches = {};
6090
+ if (refCategories.has("pipeline") || refCategories.has("stage")) {
6091
+ fetches.pipelines = client.get("/opportunities/pipelines", { params: { locationId: locationId2 } });
6092
+ }
6093
+ if (refCategories.has("custom_field")) {
6094
+ fetches.customFields = client.get(`/locations/${locationId2}/customFields`);
6095
+ }
6096
+ if (refCategories.has("user")) {
6097
+ fetches.users = client.get("/users/", { params: { locationId: locationId2 } });
6098
+ }
6099
+ if (refCategories.has("workflow")) {
6100
+ fetches.workflows = builderClient.listWorkflowsFull();
6101
+ }
6102
+ const results = await Promise.allSettled(Object.values(fetches));
6103
+ const keys = Object.keys(fetches);
6104
+ const data = {};
6105
+ for (let i = 0; i < keys.length; i++) {
6106
+ const r = results[i];
6107
+ data[keys[i]] = r.status === "fulfilled" ? r.value : null;
6108
+ }
6109
+ const validPipelineIds = /* @__PURE__ */ new Set();
6110
+ const validStageIds = /* @__PURE__ */ new Set();
6111
+ const stageToPipeline = /* @__PURE__ */ new Map();
6112
+ if (data.pipelines) {
6113
+ try {
6114
+ const parsed = PipelinesResponseSchema.parse(data.pipelines);
6115
+ for (const p of parsed.pipelines) {
6116
+ validPipelineIds.add(p.id);
6117
+ for (const s of p.stages) {
6118
+ validStageIds.add(s.id);
6119
+ stageToPipeline.set(s.id, p.id);
6120
+ }
6121
+ }
6122
+ } catch {
6123
+ }
6124
+ }
6125
+ const validCustomFieldIds = /* @__PURE__ */ new Set();
6126
+ if (data.customFields && typeof data.customFields === "object") {
6127
+ const cf = data.customFields;
6128
+ const arr = Array.isArray(cf.customFields) ? cf.customFields : Array.isArray(cf) ? cf : [];
6129
+ for (const f of arr) {
6130
+ if (typeof f === "object" && f !== null && typeof f.id === "string") {
6131
+ validCustomFieldIds.add(f.id);
6132
+ }
6133
+ }
6134
+ }
6135
+ const validUserIds = /* @__PURE__ */ new Set();
6136
+ if (data.users && typeof data.users === "object") {
6137
+ const u = data.users;
6138
+ const arr = Array.isArray(u.users) ? u.users : Array.isArray(u) ? u : [];
6139
+ for (const user of arr) {
6140
+ if (typeof user === "object" && user !== null && typeof user.id === "string") {
6141
+ validUserIds.add(user.id);
6142
+ }
6143
+ }
6144
+ }
6145
+ const validWorkflowIds = /* @__PURE__ */ new Set();
6146
+ if (data.workflows && typeof data.workflows === "object") {
6147
+ const w = data.workflows;
6148
+ const arr = Array.isArray(w.rows) ? w.rows : Array.isArray(w.workflows) ? w.workflows : Array.isArray(w) ? w : [];
6149
+ for (const wf of arr) {
6150
+ if (typeof wf === "object" && wf !== null) {
6151
+ const o = wf;
6152
+ const id = typeof o._id === "string" ? o._id : typeof o.id === "string" ? o.id : null;
6153
+ if (id) validWorkflowIds.add(id);
6154
+ }
6155
+ }
6156
+ }
6157
+ const findings = [];
6158
+ for (const ref of refs) {
6159
+ let valid = false;
6160
+ let extraMsg = "";
6161
+ switch (ref.kind) {
6162
+ case "pipeline":
6163
+ valid = validPipelineIds.has(ref.id);
6164
+ break;
6165
+ case "stage":
6166
+ valid = validStageIds.has(ref.id);
6167
+ break;
6168
+ case "custom_field":
6169
+ valid = validCustomFieldIds.has(ref.id);
6170
+ break;
6171
+ case "user":
6172
+ valid = validUserIds.has(ref.id);
6173
+ break;
6174
+ case "workflow":
6175
+ valid = validWorkflowIds.has(ref.id);
6176
+ if (valid && ref.id === workflowId) {
6177
+ extraMsg = " (self-reference)";
6178
+ }
6179
+ break;
6180
+ }
6181
+ if (!valid) {
6182
+ findings.push({
6183
+ severity: "error",
6184
+ category: ref.kind,
6185
+ id: ref.id,
6186
+ where: ref.where,
6187
+ message: `${ref.kind} id "${ref.id}" does not exist in this location \u2014 GHL will silently skip this action and all subsequent actions when the workflow runs.`
6188
+ });
6189
+ } else if (extraMsg) {
6190
+ findings.push({
6191
+ severity: "warning",
6192
+ category: ref.kind,
6193
+ id: ref.id,
6194
+ where: ref.where,
6195
+ message: `${ref.kind} id "${ref.id}" is valid${extraMsg}.`
6196
+ });
6197
+ }
6198
+ }
6199
+ const report = {
6200
+ workflowId,
6201
+ workflowName: workflow.name,
6202
+ status: findings.some((f) => f.severity === "error") ? "issues_found" : "ok",
6203
+ references_scanned: refs.length,
6204
+ issues_count: findings.filter((f) => f.severity === "error").length,
6205
+ findings
6206
+ };
6207
+ return jsonResponse(report);
6208
+ } catch (error) {
6209
+ return errorResponse(error);
6210
+ }
6211
+ }
6212
+ );
6213
+ }
6214
+
5731
6215
  // src/tools/index.ts
5732
6216
  var publicApiTools = [
5733
6217
  [registerContactTools, "contacts"],
@@ -5770,6 +6254,7 @@ function registerAllTools(server2, client, registry2, mcpVersion) {
5770
6254
  registerFormBuilderTools(server2, builderClient);
5771
6255
  registerPipelineBuilderTools(server2, builderClient);
5772
6256
  registerWorkflowClonerTools(server2, builderClient);
6257
+ registerValidatorTools(server2, client, builderClient);
5773
6258
  registerLocationSwitcherTools(server2, client, builderClient, registry2, mcpVersion);
5774
6259
  }
5775
6260
 
@@ -5777,18 +6262,18 @@ function registerAllTools(server2, client, registry2, mcpVersion) {
5777
6262
  var fs4 = __toESM(require("fs"));
5778
6263
  var path4 = __toESM(require("path"));
5779
6264
  var os = __toESM(require("os"));
5780
- var import_zod40 = require("zod");
6265
+ var import_zod41 = require("zod");
5781
6266
  var APP_NAME = "elitedcs-ghl-mcp";
5782
- var CredentialsSchema = import_zod40.z.object({
5783
- license_key: import_zod40.z.string().min(1),
5784
- email: import_zod40.z.string().email(),
5785
- verified_at: import_zod40.z.string().min(1),
5786
- ghl_api_key: import_zod40.z.string().min(1),
5787
- ghl_location_id: import_zod40.z.string().min(1),
5788
- ghl_company_id: import_zod40.z.string().optional(),
5789
- ghl_user_id: import_zod40.z.string().optional(),
5790
- ghl_firebase_api_key: import_zod40.z.string().optional(),
5791
- ghl_firebase_refresh_token: import_zod40.z.string().optional()
6267
+ var CredentialsSchema = import_zod41.z.object({
6268
+ license_key: import_zod41.z.string().min(1),
6269
+ email: import_zod41.z.string().email(),
6270
+ verified_at: import_zod41.z.string().min(1),
6271
+ ghl_api_key: import_zod41.z.string().min(1),
6272
+ ghl_location_id: import_zod41.z.string().min(1),
6273
+ ghl_company_id: import_zod41.z.string().optional(),
6274
+ ghl_user_id: import_zod41.z.string().optional(),
6275
+ ghl_firebase_api_key: import_zod41.z.string().optional(),
6276
+ ghl_firebase_refresh_token: import_zod41.z.string().optional()
5792
6277
  });
5793
6278
  function appDataDir() {
5794
6279
  const home = os.homedir();
@@ -5838,7 +6323,7 @@ function writeCredentials(creds) {
5838
6323
  // src/setup-tool.ts
5839
6324
  var os2 = __toESM(require("os"));
5840
6325
  var crypto3 = __toESM(require("crypto"));
5841
- var import_zod41 = require("zod");
6326
+ var import_zod42 = require("zod");
5842
6327
  var LICENSE_API = "https://elitedcs.com/api/validate-license";
5843
6328
  var GHL_API = "https://services.leadconnectorhq.com";
5844
6329
  var FIREBASE_TOKEN_API = "https://securetoken.googleapis.com/v1/token";
@@ -5909,14 +6394,14 @@ function registerSetupTool(server2) {
5909
6394
  "setup_ghl_mcp",
5910
6395
  "First-run setup for GHL Command MCP. Validates your license and GHL credentials, then writes them to a per-user credentials file. Restart Claude after this completes to load all 171 tools.",
5911
6396
  {
5912
- email: import_zod41.z.string().email().describe("Email used at purchase."),
5913
- license_key: import_zod41.z.string().min(20).describe("License key from your purchase email."),
5914
- ghl_api_key: import_zod41.z.string().min(10).describe("GHL Private Integration key (starts with 'pit-'). Created INSIDE the sub-account at Settings > Integrations > Private Integrations."),
5915
- ghl_location_id: import_zod41.z.string().min(10).describe("GHL Location ID (sub-account ID). Found in your GHL URL: /location/THIS_PART/dashboard."),
5916
- ghl_company_id: import_zod41.z.string().optional().describe("(Agency only) Company ID for multi-location access."),
5917
- ghl_user_id: import_zod41.z.string().optional().describe("(Workflow Builder, optional) Firebase User ID. See README for browser capture instructions."),
5918
- ghl_firebase_api_key: import_zod41.z.string().optional().describe("(Workflow Builder, optional) Firebase API Key starting with 'AIza'."),
5919
- ghl_firebase_refresh_token: import_zod41.z.string().optional().describe("(Workflow Builder, optional) Firebase refresh token starting with 'AMf-'.")
6397
+ email: import_zod42.z.string().email().describe("Email used at purchase."),
6398
+ license_key: import_zod42.z.string().min(20).describe("License key from your purchase email."),
6399
+ ghl_api_key: import_zod42.z.string().min(10).describe("GHL Private Integration key (starts with 'pit-'). Created INSIDE the sub-account at Settings > Integrations > Private Integrations."),
6400
+ ghl_location_id: import_zod42.z.string().min(10).describe("GHL Location ID (sub-account ID). Found in your GHL URL: /location/THIS_PART/dashboard."),
6401
+ ghl_company_id: import_zod42.z.string().optional().describe("(Agency only) Company ID for multi-location access."),
6402
+ ghl_user_id: import_zod42.z.string().optional().describe("(Workflow Builder, optional) Firebase User ID. See README for browser capture instructions."),
6403
+ ghl_firebase_api_key: import_zod42.z.string().optional().describe("(Workflow Builder, optional) Firebase API Key starting with 'AIza'."),
6404
+ ghl_firebase_refresh_token: import_zod42.z.string().optional().describe("(Workflow Builder, optional) Firebase refresh token starting with 'AMf-'.")
5920
6405
  },
5921
6406
  async (args) => {
5922
6407
  const lic = await validateLicense(args.email, args.license_key);