@elitedcs/ghl-mcp 3.3.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/README.md +10 -6
- package/dist/index.js +494 -47
- package/package.json +3 -2
- package/templates/trigger-schemas.json +37854 -0
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.
|
|
35
|
-
description: "GoHighLevel MCP Server for Claude.
|
|
34
|
+
version: "3.4.0",
|
|
35
|
+
description: "GoHighLevel MCP Server for Claude. 175 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
|
],
|
|
@@ -868,6 +869,19 @@ var WorkflowBuilderClient = class _WorkflowBuilderClient {
|
|
|
868
869
|
getApiKey() {
|
|
869
870
|
return this.apiKey;
|
|
870
871
|
}
|
|
872
|
+
/**
|
|
873
|
+
* Probe Firebase auth by refreshing the ID token. Used by the health_check
|
|
874
|
+
* diagnostic tool. Does not touch GHL backend — just exchanges the refresh
|
|
875
|
+
* token at securetoken.googleapis.com.
|
|
876
|
+
*/
|
|
877
|
+
async checkAuth() {
|
|
878
|
+
try {
|
|
879
|
+
await this.getIdToken();
|
|
880
|
+
return { ok: true };
|
|
881
|
+
} catch (error) {
|
|
882
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
883
|
+
}
|
|
884
|
+
}
|
|
871
885
|
/**
|
|
872
886
|
* Create from environment variables, with optional token registry fallback
|
|
873
887
|
*/
|
|
@@ -5930,6 +5944,460 @@ ${errors.join("\n")}` : "\nNo errors!",
|
|
|
5930
5944
|
);
|
|
5931
5945
|
}
|
|
5932
5946
|
|
|
5947
|
+
// src/tools/validators.ts
|
|
5948
|
+
var import_zod40 = require("zod");
|
|
5949
|
+
function extractFromTrigger(trigger, refs) {
|
|
5950
|
+
const triggerName = String(trigger.name ?? trigger.type ?? "unnamed trigger");
|
|
5951
|
+
const where = `trigger "${triggerName}"`;
|
|
5952
|
+
const conditions = Array.isArray(trigger.conditions) ? trigger.conditions : [];
|
|
5953
|
+
for (let i = 0; i < conditions.length; i++) {
|
|
5954
|
+
const c = conditions[i];
|
|
5955
|
+
const field = typeof c.field === "string" ? c.field : null;
|
|
5956
|
+
const value = c.value;
|
|
5957
|
+
if (!field || value === void 0 || value === null) continue;
|
|
5958
|
+
const valueAsArray = Array.isArray(value) ? value : [value];
|
|
5959
|
+
for (const v of valueAsArray) {
|
|
5960
|
+
if (typeof v !== "string") continue;
|
|
5961
|
+
const childWhere = `${where} \u2192 conditions[${i}] (field=${field})`;
|
|
5962
|
+
switch (field) {
|
|
5963
|
+
case "opportunity.pipelineId":
|
|
5964
|
+
refs.push({ kind: "pipeline", id: v, where: childWhere });
|
|
5965
|
+
break;
|
|
5966
|
+
case "opportunity.pipelineStageId":
|
|
5967
|
+
refs.push({ kind: "stage", id: v, where: childWhere });
|
|
5968
|
+
break;
|
|
5969
|
+
case "opportunity.assignedTo":
|
|
5970
|
+
case "contact.assignedTo":
|
|
5971
|
+
refs.push({ kind: "user", id: v, where: childWhere });
|
|
5972
|
+
break;
|
|
5973
|
+
case "workflow.id":
|
|
5974
|
+
refs.push({ kind: "workflow", id: v, where: childWhere });
|
|
5975
|
+
break;
|
|
5976
|
+
case "form.id":
|
|
5977
|
+
refs.push({ kind: "form", id: v, where: childWhere });
|
|
5978
|
+
break;
|
|
5979
|
+
case "calendar.id":
|
|
5980
|
+
refs.push({ kind: "calendar", id: v, where: childWhere });
|
|
5981
|
+
break;
|
|
5982
|
+
case "survey.id":
|
|
5983
|
+
refs.push({ kind: "survey", id: v, where: childWhere });
|
|
5984
|
+
break;
|
|
5985
|
+
}
|
|
5986
|
+
}
|
|
5987
|
+
}
|
|
5988
|
+
const triggerActions = Array.isArray(trigger.actions) ? trigger.actions : [];
|
|
5989
|
+
for (let i = 0; i < triggerActions.length; i++) {
|
|
5990
|
+
const a = triggerActions[i];
|
|
5991
|
+
if (a.type === "add_to_workflow" && typeof a.workflow_id === "string") {
|
|
5992
|
+
refs.push({
|
|
5993
|
+
kind: "workflow",
|
|
5994
|
+
id: a.workflow_id,
|
|
5995
|
+
where: `${where} \u2192 actions[${i}] (add_to_workflow)`
|
|
5996
|
+
});
|
|
5997
|
+
}
|
|
5998
|
+
}
|
|
5999
|
+
}
|
|
6000
|
+
function extractFromAction(action, refs) {
|
|
6001
|
+
const type = typeof action.type === "string" ? action.type : "unknown";
|
|
6002
|
+
const name = typeof action.name === "string" ? action.name : "unnamed action";
|
|
6003
|
+
const where = `action "${name}" (${type})`;
|
|
6004
|
+
const attr = action.attributes ?? {};
|
|
6005
|
+
switch (type) {
|
|
6006
|
+
case "update_contact_field": {
|
|
6007
|
+
const fields = Array.isArray(attr.fields) ? attr.fields : [];
|
|
6008
|
+
for (let i = 0; i < fields.length; i++) {
|
|
6009
|
+
const f = fields[i];
|
|
6010
|
+
if (typeof f.field === "string") {
|
|
6011
|
+
refs.push({
|
|
6012
|
+
kind: "custom_field",
|
|
6013
|
+
id: f.field,
|
|
6014
|
+
where: `${where} \u2192 fields[${i}]`
|
|
6015
|
+
});
|
|
6016
|
+
}
|
|
6017
|
+
}
|
|
6018
|
+
break;
|
|
6019
|
+
}
|
|
6020
|
+
case "internal_notification": {
|
|
6021
|
+
const notif = attr.notification ?? {};
|
|
6022
|
+
if (typeof notif.selectedUser === "string" && notif.selectedUser.trim() !== "") {
|
|
6023
|
+
refs.push({
|
|
6024
|
+
kind: "user",
|
|
6025
|
+
id: notif.selectedUser,
|
|
6026
|
+
where: `${where} \u2192 notification.selectedUser`
|
|
6027
|
+
});
|
|
6028
|
+
}
|
|
6029
|
+
break;
|
|
6030
|
+
}
|
|
6031
|
+
case "task_notification": {
|
|
6032
|
+
if (typeof attr.assignedTo === "string" && attr.assignedTo.trim() !== "") {
|
|
6033
|
+
refs.push({
|
|
6034
|
+
kind: "user",
|
|
6035
|
+
id: attr.assignedTo,
|
|
6036
|
+
where: `${where} \u2192 assignedTo`
|
|
6037
|
+
});
|
|
6038
|
+
}
|
|
6039
|
+
break;
|
|
6040
|
+
}
|
|
6041
|
+
case "remove_from_workflow": {
|
|
6042
|
+
if (typeof attr.workflowId === "string") {
|
|
6043
|
+
refs.push({
|
|
6044
|
+
kind: "workflow",
|
|
6045
|
+
id: attr.workflowId,
|
|
6046
|
+
where: `${where} \u2192 workflowId`
|
|
6047
|
+
});
|
|
6048
|
+
}
|
|
6049
|
+
const workflowIdArr = Array.isArray(attr.workflow_id) ? attr.workflow_id : [];
|
|
6050
|
+
for (let i = 0; i < workflowIdArr.length; i++) {
|
|
6051
|
+
const v = workflowIdArr[i];
|
|
6052
|
+
if (typeof v === "string") {
|
|
6053
|
+
refs.push({
|
|
6054
|
+
kind: "workflow",
|
|
6055
|
+
id: v,
|
|
6056
|
+
where: `${where} \u2192 workflow_id[${i}]`
|
|
6057
|
+
});
|
|
6058
|
+
}
|
|
6059
|
+
}
|
|
6060
|
+
break;
|
|
6061
|
+
}
|
|
6062
|
+
case "internal_update_opportunity": {
|
|
6063
|
+
const customInputs = Array.isArray(attr.__customInputFields__) ? attr.__customInputFields__ : [];
|
|
6064
|
+
for (let i = 0; i < customInputs.length; i++) {
|
|
6065
|
+
const c = customInputs[i];
|
|
6066
|
+
if (typeof c.value !== "string") continue;
|
|
6067
|
+
if (c.filterField === "pipelineId") {
|
|
6068
|
+
refs.push({ kind: "pipeline", id: c.value, where: `${where} \u2192 __customInputFields__[${i}].pipelineId` });
|
|
6069
|
+
} else if (c.filterField === "pipelineStageId") {
|
|
6070
|
+
refs.push({ kind: "stage", id: c.value, where: `${where} \u2192 __customInputFields__[${i}].pipelineStageId` });
|
|
6071
|
+
}
|
|
6072
|
+
}
|
|
6073
|
+
break;
|
|
6074
|
+
}
|
|
6075
|
+
}
|
|
6076
|
+
}
|
|
6077
|
+
function registerValidatorTools(server2, client, builderClient) {
|
|
6078
|
+
if (!builderClient) return;
|
|
6079
|
+
server2.tool(
|
|
6080
|
+
"validate_workflow",
|
|
6081
|
+
"Pre-flight ID validation for a deployed GHL workflow. Scans every trigger and action for references to pipelines, pipeline stages, custom fields, users, workflows, forms, calendars, and surveys; 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.",
|
|
6082
|
+
{
|
|
6083
|
+
workflowId: import_zod40.z.string().describe("The workflow ID to validate.")
|
|
6084
|
+
},
|
|
6085
|
+
async ({ workflowId }) => {
|
|
6086
|
+
try {
|
|
6087
|
+
let collectIds2 = function(envelope, listKey) {
|
|
6088
|
+
const ids = /* @__PURE__ */ new Set();
|
|
6089
|
+
if (envelope && typeof envelope === "object") {
|
|
6090
|
+
const e = envelope;
|
|
6091
|
+
const arr = Array.isArray(e[listKey]) ? e[listKey] : Array.isArray(envelope) ? envelope : [];
|
|
6092
|
+
for (const item of arr) {
|
|
6093
|
+
if (typeof item === "object" && item !== null) {
|
|
6094
|
+
const o = item;
|
|
6095
|
+
const id = typeof o.id === "string" ? o.id : typeof o._id === "string" ? o._id : null;
|
|
6096
|
+
if (id) ids.add(id);
|
|
6097
|
+
}
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
6100
|
+
return ids;
|
|
6101
|
+
};
|
|
6102
|
+
var collectIds = collectIds2;
|
|
6103
|
+
const workflow = await builderClient.getWorkflow(workflowId);
|
|
6104
|
+
if (!workflow) return errorResponse(new Error(`Workflow ${workflowId} not found`));
|
|
6105
|
+
const refs = [];
|
|
6106
|
+
const triggers = Array.isArray(workflow.triggers) ? workflow.triggers : [];
|
|
6107
|
+
for (const t of triggers) {
|
|
6108
|
+
extractFromTrigger(t, refs);
|
|
6109
|
+
}
|
|
6110
|
+
const actions = workflow.workflowData?.templates ?? [];
|
|
6111
|
+
for (const a of actions) {
|
|
6112
|
+
extractFromAction(a, refs);
|
|
6113
|
+
}
|
|
6114
|
+
if (refs.length === 0) {
|
|
6115
|
+
const empty = {
|
|
6116
|
+
workflowId,
|
|
6117
|
+
workflowName: workflow.name,
|
|
6118
|
+
status: "ok",
|
|
6119
|
+
references_scanned: 0,
|
|
6120
|
+
issues_count: 0,
|
|
6121
|
+
findings: []
|
|
6122
|
+
};
|
|
6123
|
+
return jsonResponse(empty);
|
|
6124
|
+
}
|
|
6125
|
+
const refCategories = new Set(refs.map((r) => r.kind));
|
|
6126
|
+
const locationId2 = client.defaultLocationId;
|
|
6127
|
+
const fetches = {};
|
|
6128
|
+
if (refCategories.has("pipeline") || refCategories.has("stage")) {
|
|
6129
|
+
fetches.pipelines = client.get("/opportunities/pipelines", { params: { locationId: locationId2 } });
|
|
6130
|
+
}
|
|
6131
|
+
if (refCategories.has("custom_field")) {
|
|
6132
|
+
fetches.customFields = client.get(`/locations/${locationId2}/customFields`);
|
|
6133
|
+
}
|
|
6134
|
+
if (refCategories.has("user")) {
|
|
6135
|
+
fetches.users = client.get("/users/", { params: { locationId: locationId2 } });
|
|
6136
|
+
}
|
|
6137
|
+
if (refCategories.has("workflow")) {
|
|
6138
|
+
fetches.workflows = builderClient.listWorkflowsFull();
|
|
6139
|
+
}
|
|
6140
|
+
if (refCategories.has("form")) {
|
|
6141
|
+
fetches.forms = client.get("/forms/", { params: { locationId: locationId2 } });
|
|
6142
|
+
}
|
|
6143
|
+
if (refCategories.has("calendar")) {
|
|
6144
|
+
fetches.calendars = client.get("/calendars/", { params: { locationId: locationId2 } });
|
|
6145
|
+
}
|
|
6146
|
+
if (refCategories.has("survey")) {
|
|
6147
|
+
fetches.surveys = client.get("/surveys/", { params: { locationId: locationId2 } });
|
|
6148
|
+
}
|
|
6149
|
+
const results = await Promise.allSettled(Object.values(fetches));
|
|
6150
|
+
const keys = Object.keys(fetches);
|
|
6151
|
+
const data = {};
|
|
6152
|
+
for (let i = 0; i < keys.length; i++) {
|
|
6153
|
+
const r = results[i];
|
|
6154
|
+
data[keys[i]] = r.status === "fulfilled" ? r.value : null;
|
|
6155
|
+
}
|
|
6156
|
+
const validPipelineIds = /* @__PURE__ */ new Set();
|
|
6157
|
+
const validStageIds = /* @__PURE__ */ new Set();
|
|
6158
|
+
const stageToPipeline = /* @__PURE__ */ new Map();
|
|
6159
|
+
if (data.pipelines) {
|
|
6160
|
+
try {
|
|
6161
|
+
const parsed = PipelinesResponseSchema.parse(data.pipelines);
|
|
6162
|
+
for (const p of parsed.pipelines) {
|
|
6163
|
+
validPipelineIds.add(p.id);
|
|
6164
|
+
for (const s of p.stages) {
|
|
6165
|
+
validStageIds.add(s.id);
|
|
6166
|
+
stageToPipeline.set(s.id, p.id);
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
} catch {
|
|
6170
|
+
}
|
|
6171
|
+
}
|
|
6172
|
+
const validCustomFieldIds = /* @__PURE__ */ new Set();
|
|
6173
|
+
if (data.customFields && typeof data.customFields === "object") {
|
|
6174
|
+
const cf = data.customFields;
|
|
6175
|
+
const arr = Array.isArray(cf.customFields) ? cf.customFields : Array.isArray(cf) ? cf : [];
|
|
6176
|
+
for (const f of arr) {
|
|
6177
|
+
if (typeof f === "object" && f !== null && typeof f.id === "string") {
|
|
6178
|
+
validCustomFieldIds.add(f.id);
|
|
6179
|
+
}
|
|
6180
|
+
}
|
|
6181
|
+
}
|
|
6182
|
+
const validUserIds = /* @__PURE__ */ new Set();
|
|
6183
|
+
if (data.users && typeof data.users === "object") {
|
|
6184
|
+
const u = data.users;
|
|
6185
|
+
const arr = Array.isArray(u.users) ? u.users : Array.isArray(u) ? u : [];
|
|
6186
|
+
for (const user of arr) {
|
|
6187
|
+
if (typeof user === "object" && user !== null && typeof user.id === "string") {
|
|
6188
|
+
validUserIds.add(user.id);
|
|
6189
|
+
}
|
|
6190
|
+
}
|
|
6191
|
+
}
|
|
6192
|
+
const validWorkflowIds = /* @__PURE__ */ new Set();
|
|
6193
|
+
if (data.workflows && typeof data.workflows === "object") {
|
|
6194
|
+
const w = data.workflows;
|
|
6195
|
+
const arr = Array.isArray(w.rows) ? w.rows : Array.isArray(w.workflows) ? w.workflows : Array.isArray(w) ? w : [];
|
|
6196
|
+
for (const wf of arr) {
|
|
6197
|
+
if (typeof wf === "object" && wf !== null) {
|
|
6198
|
+
const o = wf;
|
|
6199
|
+
const id = typeof o._id === "string" ? o._id : typeof o.id === "string" ? o.id : null;
|
|
6200
|
+
if (id) validWorkflowIds.add(id);
|
|
6201
|
+
}
|
|
6202
|
+
}
|
|
6203
|
+
}
|
|
6204
|
+
const validFormIds = collectIds2(data.forms, "forms");
|
|
6205
|
+
const validCalendarIds = collectIds2(data.calendars, "calendars");
|
|
6206
|
+
const validSurveyIds = collectIds2(data.surveys, "surveys");
|
|
6207
|
+
const findings = [];
|
|
6208
|
+
for (const ref of refs) {
|
|
6209
|
+
let valid = false;
|
|
6210
|
+
let extraMsg = "";
|
|
6211
|
+
switch (ref.kind) {
|
|
6212
|
+
case "pipeline":
|
|
6213
|
+
valid = validPipelineIds.has(ref.id);
|
|
6214
|
+
break;
|
|
6215
|
+
case "stage":
|
|
6216
|
+
valid = validStageIds.has(ref.id);
|
|
6217
|
+
break;
|
|
6218
|
+
case "custom_field":
|
|
6219
|
+
valid = validCustomFieldIds.has(ref.id);
|
|
6220
|
+
break;
|
|
6221
|
+
case "user":
|
|
6222
|
+
valid = validUserIds.has(ref.id);
|
|
6223
|
+
break;
|
|
6224
|
+
case "workflow":
|
|
6225
|
+
valid = validWorkflowIds.has(ref.id);
|
|
6226
|
+
if (valid && ref.id === workflowId) {
|
|
6227
|
+
extraMsg = " (self-reference)";
|
|
6228
|
+
}
|
|
6229
|
+
break;
|
|
6230
|
+
case "form":
|
|
6231
|
+
valid = validFormIds.has(ref.id);
|
|
6232
|
+
break;
|
|
6233
|
+
case "calendar":
|
|
6234
|
+
valid = validCalendarIds.has(ref.id);
|
|
6235
|
+
break;
|
|
6236
|
+
case "survey":
|
|
6237
|
+
valid = validSurveyIds.has(ref.id);
|
|
6238
|
+
break;
|
|
6239
|
+
}
|
|
6240
|
+
if (!valid) {
|
|
6241
|
+
findings.push({
|
|
6242
|
+
severity: "error",
|
|
6243
|
+
category: ref.kind,
|
|
6244
|
+
id: ref.id,
|
|
6245
|
+
where: ref.where,
|
|
6246
|
+
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.`
|
|
6247
|
+
});
|
|
6248
|
+
} else if (extraMsg) {
|
|
6249
|
+
findings.push({
|
|
6250
|
+
severity: "warning",
|
|
6251
|
+
category: ref.kind,
|
|
6252
|
+
id: ref.id,
|
|
6253
|
+
where: ref.where,
|
|
6254
|
+
message: `${ref.kind} id "${ref.id}" is valid${extraMsg}.`
|
|
6255
|
+
});
|
|
6256
|
+
}
|
|
6257
|
+
}
|
|
6258
|
+
const report = {
|
|
6259
|
+
workflowId,
|
|
6260
|
+
workflowName: workflow.name,
|
|
6261
|
+
status: findings.some((f) => f.severity === "error") ? "issues_found" : "ok",
|
|
6262
|
+
references_scanned: refs.length,
|
|
6263
|
+
issues_count: findings.filter((f) => f.severity === "error").length,
|
|
6264
|
+
findings
|
|
6265
|
+
};
|
|
6266
|
+
return jsonResponse(report);
|
|
6267
|
+
} catch (error) {
|
|
6268
|
+
return errorResponse(error);
|
|
6269
|
+
}
|
|
6270
|
+
}
|
|
6271
|
+
);
|
|
6272
|
+
}
|
|
6273
|
+
|
|
6274
|
+
// src/version-check.ts
|
|
6275
|
+
var REGISTRY_URL = "https://registry.npmjs.org/@elitedcs/ghl-mcp/latest";
|
|
6276
|
+
async function fetchLatestVersion(timeoutMs = 5e3) {
|
|
6277
|
+
try {
|
|
6278
|
+
const response = await fetch(REGISTRY_URL, {
|
|
6279
|
+
headers: { Accept: "application/json" },
|
|
6280
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
6281
|
+
});
|
|
6282
|
+
if (!response.ok) return null;
|
|
6283
|
+
const body = await response.json();
|
|
6284
|
+
return typeof body.version === "string" ? body.version : null;
|
|
6285
|
+
} catch {
|
|
6286
|
+
return null;
|
|
6287
|
+
}
|
|
6288
|
+
}
|
|
6289
|
+
async function getVersionStatus(installed) {
|
|
6290
|
+
const latest = await fetchLatestVersion();
|
|
6291
|
+
if (latest === null) {
|
|
6292
|
+
return { installed, latest: null, upToDate: null, fetchError: "Could not reach npm registry" };
|
|
6293
|
+
}
|
|
6294
|
+
return { installed, latest, upToDate: installed === latest };
|
|
6295
|
+
}
|
|
6296
|
+
|
|
6297
|
+
// src/tools/diagnostics.ts
|
|
6298
|
+
function registerDiagnosticTools(server2, installedVersion, client, builderClient, registry2) {
|
|
6299
|
+
server2.tool(
|
|
6300
|
+
"health_check",
|
|
6301
|
+
"Run a full health check of the GHL Command MCP install. Reports on: npm registry + version status, GHL API key validity, default location reachability, Firebase auth status (workflow builder), and token registry. Returns a structured pass/fail/warn list with detail per check. Use this when something feels broken or after credentials change.",
|
|
6302
|
+
{},
|
|
6303
|
+
async () => {
|
|
6304
|
+
const checks = [];
|
|
6305
|
+
const versionPromise = getVersionStatus(installedVersion);
|
|
6306
|
+
const apiKeyPromise = (async () => {
|
|
6307
|
+
try {
|
|
6308
|
+
const response = await fetch("https://services.leadconnectorhq.com/locations/search", {
|
|
6309
|
+
method: "GET",
|
|
6310
|
+
headers: {
|
|
6311
|
+
Authorization: `Bearer ${client.getApiKey()}`,
|
|
6312
|
+
"Content-Type": "application/json",
|
|
6313
|
+
Accept: "application/json",
|
|
6314
|
+
Version: "2021-07-28"
|
|
6315
|
+
},
|
|
6316
|
+
signal: AbortSignal.timeout(8e3)
|
|
6317
|
+
});
|
|
6318
|
+
if (response.status === 401) {
|
|
6319
|
+
return { name: "GHL API key", status: "fail", detail: `401 Unauthorized \u2014 key ${client.getApiKeyPrefix()} is invalid or revoked. Run setup_ghl_mcp with a fresh key.` };
|
|
6320
|
+
}
|
|
6321
|
+
if (!response.ok) {
|
|
6322
|
+
return { name: "GHL API key", status: "warn", detail: `HTTP ${response.status} from /locations/search \u2014 key may be limited or GHL is having issues.` };
|
|
6323
|
+
}
|
|
6324
|
+
return { name: "GHL API key", status: "pass", detail: `Valid (key ${client.getApiKeyPrefix()}).` };
|
|
6325
|
+
} catch (error) {
|
|
6326
|
+
return { name: "GHL API key", status: "warn", detail: `Network error: ${error instanceof Error ? error.message : String(error)}. Server starts anyway; tool calls will report errors when invoked.` };
|
|
6327
|
+
}
|
|
6328
|
+
})();
|
|
6329
|
+
const locationPromise = (async () => {
|
|
6330
|
+
const locId = client.defaultLocationId;
|
|
6331
|
+
if (!locId) {
|
|
6332
|
+
return { name: "Default location", status: "warn", detail: "No default location set. Tools that don't accept locationId arg will fail." };
|
|
6333
|
+
}
|
|
6334
|
+
try {
|
|
6335
|
+
const result = await client.get(`/locations/${locId}`);
|
|
6336
|
+
const loc = result.location ?? result;
|
|
6337
|
+
return { name: "Default location", status: "pass", detail: `${loc?.name ?? "Unknown"} (${locId}) reachable.` };
|
|
6338
|
+
} catch (error) {
|
|
6339
|
+
return { name: "Default location", status: "fail", detail: `Could not read location ${locId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
6340
|
+
}
|
|
6341
|
+
})();
|
|
6342
|
+
const firebasePromise = (async () => {
|
|
6343
|
+
if (!builderClient) {
|
|
6344
|
+
return { name: "Firebase auth (workflow builder)", status: "skip", detail: "Not configured. Workflow builder tools (8 of 174) require ghl_user_id, ghl_firebase_api_key, ghl_firebase_refresh_token. Other 166 tools work fine without." };
|
|
6345
|
+
}
|
|
6346
|
+
const result = await builderClient.checkAuth();
|
|
6347
|
+
if (result.ok) {
|
|
6348
|
+
return { name: "Firebase auth (workflow builder)", status: "pass", detail: "ID token refresh succeeded. Workflow builder tools are usable." };
|
|
6349
|
+
}
|
|
6350
|
+
return { name: "Firebase auth (workflow builder)", status: "fail", detail: `Token refresh failed: ${result.error}. Re-capture Firebase values from GHL DevTools and re-run setup_ghl_mcp.` };
|
|
6351
|
+
})();
|
|
6352
|
+
const registryCheck = (() => {
|
|
6353
|
+
if (!registry2) {
|
|
6354
|
+
return { name: "Token registry", status: "skip", detail: "Not initialized \u2014 using env-var credentials only. switch_location won't auto-swap keys between sub-accounts." };
|
|
6355
|
+
}
|
|
6356
|
+
const locs = registry2.listLocations();
|
|
6357
|
+
if (locs.length === 0) {
|
|
6358
|
+
return { name: "Token registry", status: "warn", detail: "Initialized but empty \u2014 register sub-accounts via register_location for cross-account switching." };
|
|
6359
|
+
}
|
|
6360
|
+
return { name: "Token registry", status: "pass", detail: `${locs.length} sub-account(s) registered.` };
|
|
6361
|
+
})();
|
|
6362
|
+
const [versionStatus, apiKeyCheck, locationCheck, firebaseCheck] = await Promise.all([
|
|
6363
|
+
versionPromise,
|
|
6364
|
+
apiKeyPromise,
|
|
6365
|
+
locationPromise,
|
|
6366
|
+
firebasePromise
|
|
6367
|
+
]);
|
|
6368
|
+
let versionCheck;
|
|
6369
|
+
if (versionStatus.latest === null) {
|
|
6370
|
+
versionCheck = { name: "npm registry + version", status: "warn", detail: `Installed v${versionStatus.installed}. Could not reach npm registry to check for updates: ${versionStatus.fetchError ?? "unknown"}.` };
|
|
6371
|
+
} else if (versionStatus.upToDate) {
|
|
6372
|
+
versionCheck = { name: "npm registry + version", status: "pass", detail: `Up to date at v${versionStatus.installed}.` };
|
|
6373
|
+
} else {
|
|
6374
|
+
versionCheck = { name: "npm registry + version", status: "warn", detail: `Installed v${versionStatus.installed} but latest is v${versionStatus.latest}. Quit Claude (Cmd+Q) and reopen to upgrade.` };
|
|
6375
|
+
}
|
|
6376
|
+
checks.push(versionCheck, apiKeyCheck, locationCheck, firebaseCheck, registryCheck);
|
|
6377
|
+
const symbols = { pass: "\u2713", fail: "\u2717", warn: "!", skip: "\u2014" };
|
|
6378
|
+
const lines = [];
|
|
6379
|
+
lines.push("GHL Command \u2014 Health Check");
|
|
6380
|
+
lines.push("\u2501".repeat(50));
|
|
6381
|
+
for (const c of checks) {
|
|
6382
|
+
lines.push(`${symbols[c.status]} ${c.name.padEnd(35)} ${c.status.toUpperCase()}`);
|
|
6383
|
+
lines.push(` ${c.detail}`);
|
|
6384
|
+
}
|
|
6385
|
+
lines.push("\u2501".repeat(50));
|
|
6386
|
+
const fails = checks.filter((c) => c.status === "fail").length;
|
|
6387
|
+
const warns = checks.filter((c) => c.status === "warn").length;
|
|
6388
|
+
if (fails === 0 && warns === 0) {
|
|
6389
|
+
lines.push("All systems go.");
|
|
6390
|
+
} else {
|
|
6391
|
+
const parts = [];
|
|
6392
|
+
if (fails > 0) parts.push(`${fails} failure(s)`);
|
|
6393
|
+
if (warns > 0) parts.push(`${warns} warning(s)`);
|
|
6394
|
+
lines.push(parts.join(", ") + " \u2014 see details above.");
|
|
6395
|
+
}
|
|
6396
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
6397
|
+
}
|
|
6398
|
+
);
|
|
6399
|
+
}
|
|
6400
|
+
|
|
5933
6401
|
// src/tools/index.ts
|
|
5934
6402
|
var publicApiTools = [
|
|
5935
6403
|
[registerContactTools, "contacts"],
|
|
@@ -5972,6 +6440,8 @@ function registerAllTools(server2, client, registry2, mcpVersion) {
|
|
|
5972
6440
|
registerFormBuilderTools(server2, builderClient);
|
|
5973
6441
|
registerPipelineBuilderTools(server2, builderClient);
|
|
5974
6442
|
registerWorkflowClonerTools(server2, builderClient);
|
|
6443
|
+
registerValidatorTools(server2, client, builderClient);
|
|
6444
|
+
registerDiagnosticTools(server2, mcpVersion ?? "unknown", client, builderClient, registry2 ?? null);
|
|
5975
6445
|
registerLocationSwitcherTools(server2, client, builderClient, registry2, mcpVersion);
|
|
5976
6446
|
}
|
|
5977
6447
|
|
|
@@ -5979,18 +6449,18 @@ function registerAllTools(server2, client, registry2, mcpVersion) {
|
|
|
5979
6449
|
var fs4 = __toESM(require("fs"));
|
|
5980
6450
|
var path4 = __toESM(require("path"));
|
|
5981
6451
|
var os = __toESM(require("os"));
|
|
5982
|
-
var
|
|
6452
|
+
var import_zod41 = require("zod");
|
|
5983
6453
|
var APP_NAME = "elitedcs-ghl-mcp";
|
|
5984
|
-
var CredentialsSchema =
|
|
5985
|
-
license_key:
|
|
5986
|
-
email:
|
|
5987
|
-
verified_at:
|
|
5988
|
-
ghl_api_key:
|
|
5989
|
-
ghl_location_id:
|
|
5990
|
-
ghl_company_id:
|
|
5991
|
-
ghl_user_id:
|
|
5992
|
-
ghl_firebase_api_key:
|
|
5993
|
-
ghl_firebase_refresh_token:
|
|
6454
|
+
var CredentialsSchema = import_zod41.z.object({
|
|
6455
|
+
license_key: import_zod41.z.string().min(1),
|
|
6456
|
+
email: import_zod41.z.string().email(),
|
|
6457
|
+
verified_at: import_zod41.z.string().min(1),
|
|
6458
|
+
ghl_api_key: import_zod41.z.string().min(1),
|
|
6459
|
+
ghl_location_id: import_zod41.z.string().min(1),
|
|
6460
|
+
ghl_company_id: import_zod41.z.string().optional(),
|
|
6461
|
+
ghl_user_id: import_zod41.z.string().optional(),
|
|
6462
|
+
ghl_firebase_api_key: import_zod41.z.string().optional(),
|
|
6463
|
+
ghl_firebase_refresh_token: import_zod41.z.string().optional()
|
|
5994
6464
|
});
|
|
5995
6465
|
function appDataDir() {
|
|
5996
6466
|
const home = os.homedir();
|
|
@@ -6040,7 +6510,7 @@ function writeCredentials(creds) {
|
|
|
6040
6510
|
// src/setup-tool.ts
|
|
6041
6511
|
var os2 = __toESM(require("os"));
|
|
6042
6512
|
var crypto3 = __toESM(require("crypto"));
|
|
6043
|
-
var
|
|
6513
|
+
var import_zod42 = require("zod");
|
|
6044
6514
|
var LICENSE_API = "https://elitedcs.com/api/validate-license";
|
|
6045
6515
|
var GHL_API = "https://services.leadconnectorhq.com";
|
|
6046
6516
|
var FIREBASE_TOKEN_API = "https://securetoken.googleapis.com/v1/token";
|
|
@@ -6109,16 +6579,16 @@ async function validateFirebase(firebaseKey, refreshToken) {
|
|
|
6109
6579
|
function registerSetupTool(server2) {
|
|
6110
6580
|
server2.tool(
|
|
6111
6581
|
"setup_ghl_mcp",
|
|
6112
|
-
"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
|
|
6582
|
+
"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 175 tools.",
|
|
6113
6583
|
{
|
|
6114
|
-
email:
|
|
6115
|
-
license_key:
|
|
6116
|
-
ghl_api_key:
|
|
6117
|
-
ghl_location_id:
|
|
6118
|
-
ghl_company_id:
|
|
6119
|
-
ghl_user_id:
|
|
6120
|
-
ghl_firebase_api_key:
|
|
6121
|
-
ghl_firebase_refresh_token:
|
|
6584
|
+
email: import_zod42.z.string().email().describe("Email used at purchase."),
|
|
6585
|
+
license_key: import_zod42.z.string().min(20).describe("License key from your purchase email."),
|
|
6586
|
+
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."),
|
|
6587
|
+
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."),
|
|
6588
|
+
ghl_company_id: import_zod42.z.string().optional().describe("(Agency only) Company ID for multi-location access."),
|
|
6589
|
+
ghl_user_id: import_zod42.z.string().optional().describe("(Workflow Builder, optional) Firebase User ID. See README for browser capture instructions."),
|
|
6590
|
+
ghl_firebase_api_key: import_zod42.z.string().optional().describe("(Workflow Builder, optional) Firebase API Key starting with 'AIza'."),
|
|
6591
|
+
ghl_firebase_refresh_token: import_zod42.z.string().optional().describe("(Workflow Builder, optional) Firebase refresh token starting with 'AMf-'.")
|
|
6122
6592
|
},
|
|
6123
6593
|
async (args) => {
|
|
6124
6594
|
const lic = await validateLicense(args.email, args.license_key);
|
|
@@ -6164,7 +6634,7 @@ Note: Firebase credentials rejected (${fb.error}). Saved without Workflow Builde
|
|
|
6164
6634
|
ghl_firebase_api_key: workflowBuilderEnabled ? args.ghl_firebase_api_key?.trim() : void 0,
|
|
6165
6635
|
ghl_firebase_refresh_token: workflowBuilderEnabled ? args.ghl_firebase_refresh_token?.trim() : void 0
|
|
6166
6636
|
});
|
|
6167
|
-
const toolCount = workflowBuilderEnabled ? "
|
|
6637
|
+
const toolCount = workflowBuilderEnabled ? "175" : "167";
|
|
6168
6638
|
const wfLine = workflowBuilderEnabled ? "Workflow Builder: enabled." : "Workflow Builder: not configured (optional).";
|
|
6169
6639
|
return {
|
|
6170
6640
|
content: [{
|
|
@@ -6189,29 +6659,6 @@ Note: Firebase credentials rejected (${fb.error}). Saved without Workflow Builde
|
|
|
6189
6659
|
);
|
|
6190
6660
|
}
|
|
6191
6661
|
|
|
6192
|
-
// src/version-check.ts
|
|
6193
|
-
var REGISTRY_URL = "https://registry.npmjs.org/@elitedcs/ghl-mcp/latest";
|
|
6194
|
-
async function fetchLatestVersion(timeoutMs = 5e3) {
|
|
6195
|
-
try {
|
|
6196
|
-
const response = await fetch(REGISTRY_URL, {
|
|
6197
|
-
headers: { Accept: "application/json" },
|
|
6198
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
6199
|
-
});
|
|
6200
|
-
if (!response.ok) return null;
|
|
6201
|
-
const body = await response.json();
|
|
6202
|
-
return typeof body.version === "string" ? body.version : null;
|
|
6203
|
-
} catch {
|
|
6204
|
-
return null;
|
|
6205
|
-
}
|
|
6206
|
-
}
|
|
6207
|
-
async function getVersionStatus(installed) {
|
|
6208
|
-
const latest = await fetchLatestVersion();
|
|
6209
|
-
if (latest === null) {
|
|
6210
|
-
return { installed, latest: null, upToDate: null, fetchError: "Could not reach npm registry" };
|
|
6211
|
-
}
|
|
6212
|
-
return { installed, latest, upToDate: installed === latest };
|
|
6213
|
-
}
|
|
6214
|
-
|
|
6215
6662
|
// src/tools/meta.ts
|
|
6216
6663
|
function registerMetaTools(server2, installedVersion) {
|
|
6217
6664
|
server2.tool(
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elitedcs/ghl-mcp",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "GoHighLevel MCP Server for Claude.
|
|
3
|
+
"version": "3.4.0",
|
|
4
|
+
"description": "GoHighLevel MCP Server for Claude. 175 tools — full CRM, automation, marketing control, and the only programmatic GHL workflow builder.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ghl-mcp": "dist/index.js"
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"dist/index.js",
|
|
11
11
|
"templates/action-schemas.json",
|
|
12
12
|
"templates/clinic-medspa.json",
|
|
13
|
+
"templates/trigger-schemas.json",
|
|
13
14
|
"README.md",
|
|
14
15
|
"CHANGELOG.md"
|
|
15
16
|
],
|