@elitedcs/ghl-mcp 3.30.0 → 3.32.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 +51 -0
- package/dist/index.js +251 -6
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.32.0 — Account-health summary + phone reads
|
|
4
|
+
|
|
5
|
+
Three read tools. The composite is the second roadmap build and the first
|
|
6
|
+
"how's the account doing" answer (GHL has no public reporting API, confirmed by
|
|
7
|
+
probe — so this composes existing reads).
|
|
8
|
+
|
|
9
|
+
- **`get_account_health_summary`** — one call returns, for a location: total
|
|
10
|
+
contacts + NEW contacts in a window (default 30d), total opportunities + counts
|
|
11
|
+
by status (open/won/lost/abandoned), total conversations, and phone-number
|
|
12
|
+
count.
|
|
13
|
+
- **`list_phone_numbers`** — provisioned LC Phone numbers (sid, number, label).
|
|
14
|
+
- **`list_number_pools`** — configured number pools.
|
|
15
|
+
|
|
16
|
+
**Honesty by construction.** Every metric is explicitly labeled `scope`
|
|
17
|
+
(`all_time` vs `window`, with start/end on windowed ones) so an all-time number
|
|
18
|
+
can never be read as a recent one. Any metric that can't be read returns
|
|
19
|
+
`{status:"unavailable", reason}` — never a misleading `0`. Each sub-read is
|
|
20
|
+
isolated, so one failure degrades only its own section, not the whole summary.
|
|
21
|
+
|
|
22
|
+
**Scope (verified against the live API):** windowed *new contacts* use the
|
|
23
|
+
`/contacts/search` `dateAdded` range filter; opportunity status counts use
|
|
24
|
+
filtered `meta.total`. Conversations are all-time only (the API's
|
|
25
|
+
`startAfterDate` is a cursor, not a count filter). Revenue (transactions are
|
|
26
|
+
403 for sub-account tokens) and appointments (no location-wide events endpoint)
|
|
27
|
+
are intentionally excluded.
|
|
28
|
+
|
|
29
|
+
## 3.31.0 — Snapshots: list + share-link (agency tooling)
|
|
30
|
+
|
|
31
|
+
Two new agency-level tools, the first build toward removing manual steps from the
|
|
32
|
+
client-provisioning runbook (snapshot selection + handoff for new sub-accounts).
|
|
33
|
+
|
|
34
|
+
- **`list_snapshots`** — list the agency's snapshots (`id`, `name`, `type`) so you
|
|
35
|
+
can pick the right one by name. Read-only.
|
|
36
|
+
- **`create_snapshot_share_link`** — mint a load/share link for a snapshot
|
|
37
|
+
(`gohighlevel.com/?share=…`) to import it into a sub-account. Requires an explicit
|
|
38
|
+
`share_type` (`link`, `permanent_link`, `agency_link`, `location_link`,
|
|
39
|
+
`marketplace_link`).
|
|
40
|
+
|
|
41
|
+
Both use the agency/company-scoped key (`getAgencyKey()`), not a sub-account PIT —
|
|
42
|
+
snapshots are an agency resource. The tools resolve `companyId` with strict rules
|
|
43
|
+
(explicit param > active location's company; mismatch is rejected, not guessed) so
|
|
44
|
+
a multi-tenant install can't list the wrong agency's snapshots, and they fail with
|
|
45
|
+
an actionable message when no agency key is registered.
|
|
46
|
+
|
|
47
|
+
**Notes / limits (verified against the live API):**
|
|
48
|
+
- Creating a sub-account from a snapshot is NOT exposed: `POST /locations/` returns
|
|
49
|
+
401 for PIT auth. Snapshot *apply* stays a guided GHL-UI step.
|
|
50
|
+
- `create_snapshot_share_link` is not idempotent (each call mints a new link, no
|
|
51
|
+
revoke API — remove in the UI). The HTTP client gained an internal `noRetry`
|
|
52
|
+
option so a lost-response retry can't silently create a duplicate.
|
|
53
|
+
|
|
3
54
|
## 3.30.0 — Workflow trigger stays active after edit/publish (Bug 7)
|
|
4
55
|
|
|
5
56
|
Editing a trigger on a published workflow via `update_workflow_actions` silently
|
package/dist/index.js
CHANGED
|
@@ -31,9 +31,9 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "@elitedcs/ghl-mcp",
|
|
34
|
-
version: "3.
|
|
34
|
+
version: "3.32.0",
|
|
35
35
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
36
|
-
description: "GoHighLevel MCP Server for Claude.
|
|
36
|
+
description: "GoHighLevel MCP Server for Claude. 217 tools \u2014 full CRM, automation, marketing control, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
|
|
37
37
|
main: "dist/index.js",
|
|
38
38
|
bin: {
|
|
39
39
|
"ghl-mcp": "dist/index.js"
|
|
@@ -203,7 +203,7 @@ var GHLClient = class {
|
|
|
203
203
|
if (error instanceof Error && error.name === "AbortError") {
|
|
204
204
|
throw new Error(`Request timeout (30s): ${method} ${path6}`);
|
|
205
205
|
}
|
|
206
|
-
if (attempt < MAX_RETRIES) {
|
|
206
|
+
if (!options.noRetry && attempt < MAX_RETRIES) {
|
|
207
207
|
const delay4 = computeRetryDelay(null, attempt, BASE_DELAY_MS);
|
|
208
208
|
process.stderr.write(`[ghl-mcp] Network error on ${method} ${path6}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay4}ms
|
|
209
209
|
`);
|
|
@@ -214,7 +214,7 @@ var GHLClient = class {
|
|
|
214
214
|
} finally {
|
|
215
215
|
clearTimeout(timeout);
|
|
216
216
|
}
|
|
217
|
-
if ((response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES) {
|
|
217
|
+
if (!options.noRetry && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES) {
|
|
218
218
|
const delay4 = computeRetryDelay(response.headers.get("Retry-After"), attempt, BASE_DELAY_MS);
|
|
219
219
|
process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${path6}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay4}ms
|
|
220
220
|
`);
|
|
@@ -8817,6 +8817,246 @@ function registerDiagnosticTools(server2, installedVersion, client, builderClien
|
|
|
8817
8817
|
);
|
|
8818
8818
|
}
|
|
8819
8819
|
|
|
8820
|
+
// src/tools/snapshots.ts
|
|
8821
|
+
var import_zod48 = require("zod");
|
|
8822
|
+
var SnapshotSchema = import_zod48.z.object({ id: import_zod48.z.string(), name: import_zod48.z.string(), type: import_zod48.z.string() }).passthrough();
|
|
8823
|
+
var SnapshotsResponseSchema = import_zod48.z.object({ snapshots: import_zod48.z.array(SnapshotSchema) }).passthrough();
|
|
8824
|
+
var ShareLinkResponseSchema = import_zod48.z.object({ id: import_zod48.z.string(), shareLink: import_zod48.z.string() }).passthrough();
|
|
8825
|
+
var SHARE_TYPES = [
|
|
8826
|
+
"link",
|
|
8827
|
+
"permanent_link",
|
|
8828
|
+
"agency_link",
|
|
8829
|
+
"location_link",
|
|
8830
|
+
"marketplace_link"
|
|
8831
|
+
];
|
|
8832
|
+
function resolveSnapshotAuth(client, registry2, companyIdParam) {
|
|
8833
|
+
const agencyKey = registry2?.getAgencyKey();
|
|
8834
|
+
if (!agencyKey) {
|
|
8835
|
+
throw new Error(
|
|
8836
|
+
"Snapshots require an agency/company-scoped API key, and none is registered. This install only has sub-account Private Integration keys. Add the agency key (created at the AGENCY level in GHL Settings > Integrations) to the token registry, then retry."
|
|
8837
|
+
);
|
|
8838
|
+
}
|
|
8839
|
+
const storedCompanyId = client.defaultLocationId ? registry2?.getToken(client.defaultLocationId)?.companyId : void 0;
|
|
8840
|
+
const param = companyIdParam?.trim() || void 0;
|
|
8841
|
+
if (param && storedCompanyId && param !== storedCompanyId) {
|
|
8842
|
+
throw new Error(
|
|
8843
|
+
`companyId mismatch: you passed ${param}, but the active location belongs to company ${storedCompanyId}. Refusing to guess. Switch to a location in the intended company, or pass the matching companyId.`
|
|
8844
|
+
);
|
|
8845
|
+
}
|
|
8846
|
+
const companyId = param ?? storedCompanyId;
|
|
8847
|
+
if (!companyId) {
|
|
8848
|
+
throw new Error(
|
|
8849
|
+
"Could not determine the companyId for this snapshot call. The active location has no stored company. Pass companyId explicitly, or run register_location / switch_location for the active location so its company is recorded."
|
|
8850
|
+
);
|
|
8851
|
+
}
|
|
8852
|
+
return { agencyClient: new GHLClient({ apiKey: agencyKey }), companyId };
|
|
8853
|
+
}
|
|
8854
|
+
function explainSnapshotError(error, companyId) {
|
|
8855
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8856
|
+
if (message.includes("401") || message.includes("Unauthorized") || message.includes("403")) {
|
|
8857
|
+
throw new Error(
|
|
8858
|
+
`${message}
|
|
8859
|
+
|
|
8860
|
+
The agency key is scoped to a single company and may not cover company ${companyId}. Snapshots only list for the agency that owns the key. Use a companyId belonging to the agency key, or register that agency's own key.`
|
|
8861
|
+
);
|
|
8862
|
+
}
|
|
8863
|
+
throw error instanceof Error ? error : new Error(message);
|
|
8864
|
+
}
|
|
8865
|
+
function registerSnapshotTools(server2, client, registry2) {
|
|
8866
|
+
safeTool(
|
|
8867
|
+
server2,
|
|
8868
|
+
"list_snapshots",
|
|
8869
|
+
"List the agency's GHL snapshots (id, name, type) so you can pick the right one by name before applying it to a sub-account. Reads the agency/company-scoped key (not a sub-account PIT). companyId defaults to the active location's company; pass it explicitly to target a different company you have the agency key for. Read-only.",
|
|
8870
|
+
{
|
|
8871
|
+
companyId: import_zod48.z.string().optional().describe(
|
|
8872
|
+
"Agency/company ID whose snapshots to list. Defaults to the active location's company. Must match the company your agency key is scoped to."
|
|
8873
|
+
)
|
|
8874
|
+
},
|
|
8875
|
+
async ({ companyId }) => {
|
|
8876
|
+
const { agencyClient, companyId: resolved } = resolveSnapshotAuth(client, registry2, companyId);
|
|
8877
|
+
let raw;
|
|
8878
|
+
try {
|
|
8879
|
+
raw = await agencyClient.get("/snapshots/", { params: { companyId: resolved } });
|
|
8880
|
+
} catch (error) {
|
|
8881
|
+
explainSnapshotError(error, resolved);
|
|
8882
|
+
}
|
|
8883
|
+
const parsed = SnapshotsResponseSchema.parse(raw);
|
|
8884
|
+
return {
|
|
8885
|
+
companyId: resolved,
|
|
8886
|
+
count: parsed.snapshots.length,
|
|
8887
|
+
snapshots: parsed.snapshots
|
|
8888
|
+
};
|
|
8889
|
+
}
|
|
8890
|
+
);
|
|
8891
|
+
safeTool(
|
|
8892
|
+
server2,
|
|
8893
|
+
"create_snapshot_share_link",
|
|
8894
|
+
"Create a shareable load link for one of the agency's snapshots (returns a gohighlevel.com/?share=... URL to import the snapshot into a sub-account). Uses the agency/company-scoped key. WARNING: this is NOT idempotent \u2014 each call mints a NEW link, and there is no API to list or revoke links (revoke in the GHL UI under the snapshot's share settings). Pick share_type deliberately. Use list_snapshots first to get the snapshot id.",
|
|
8895
|
+
{
|
|
8896
|
+
snapshot_id: import_zod48.z.string().describe("The snapshot id to share (from list_snapshots)."),
|
|
8897
|
+
share_type: import_zod48.z.enum(SHARE_TYPES).describe(
|
|
8898
|
+
"Share link type. 'link' = standard share link; 'permanent_link' = non-expiring; 'agency_link' = share to agencies; 'location_link' = load into a sub-account/location; 'marketplace_link' = marketplace listing. No default \u2014 choose intentionally."
|
|
8899
|
+
),
|
|
8900
|
+
companyId: import_zod48.z.string().optional().describe(
|
|
8901
|
+
"Agency/company ID that owns the snapshot. Defaults to the active location's company. Must match the company your agency key is scoped to."
|
|
8902
|
+
)
|
|
8903
|
+
},
|
|
8904
|
+
async ({ snapshot_id, share_type, companyId }) => {
|
|
8905
|
+
const { agencyClient, companyId: resolved } = resolveSnapshotAuth(client, registry2, companyId);
|
|
8906
|
+
let raw;
|
|
8907
|
+
try {
|
|
8908
|
+
raw = await agencyClient.post("/snapshots/share/link", {
|
|
8909
|
+
params: { companyId: resolved },
|
|
8910
|
+
body: { snapshot_id, share_type },
|
|
8911
|
+
noRetry: true
|
|
8912
|
+
});
|
|
8913
|
+
} catch (error) {
|
|
8914
|
+
explainSnapshotError(error, resolved);
|
|
8915
|
+
}
|
|
8916
|
+
const parsed = ShareLinkResponseSchema.parse(raw);
|
|
8917
|
+
return {
|
|
8918
|
+
companyId: resolved,
|
|
8919
|
+
snapshot_id,
|
|
8920
|
+
share_type,
|
|
8921
|
+
id: parsed.id,
|
|
8922
|
+
shareLink: parsed.shareLink,
|
|
8923
|
+
_note: "A new share link was created. There is no API to revoke it \u2014 remove it in the GHL UI (snapshot share settings) if needed."
|
|
8924
|
+
};
|
|
8925
|
+
}
|
|
8926
|
+
);
|
|
8927
|
+
}
|
|
8928
|
+
|
|
8929
|
+
// src/tools/phone.ts
|
|
8930
|
+
var import_zod49 = require("zod");
|
|
8931
|
+
var PhoneNumberSchema = import_zod49.z.object({ sid: import_zod49.z.string(), value: import_zod49.z.string(), title: import_zod49.z.string().optional() }).passthrough();
|
|
8932
|
+
var NumbersResponseSchema = import_zod49.z.object({ phoneNumbers: import_zod49.z.array(PhoneNumberSchema) }).passthrough();
|
|
8933
|
+
var PoolsResponseSchema = import_zod49.z.object({ pools: import_zod49.z.array(import_zod49.z.object({}).passthrough()) }).passthrough();
|
|
8934
|
+
function registerPhoneTools(server2, client) {
|
|
8935
|
+
safeTool(
|
|
8936
|
+
server2,
|
|
8937
|
+
"list_phone_numbers",
|
|
8938
|
+
"List the LC Phone numbers provisioned for a location (sid, number, label). Read-only. Use to verify or count purchased numbers (e.g. provisioning step 11). Number purchase is not exposed (billable write).",
|
|
8939
|
+
{
|
|
8940
|
+
locationId: import_zod49.z.string().optional().describe("Defaults to the active location.")
|
|
8941
|
+
},
|
|
8942
|
+
async ({ locationId: locationId2 }) => {
|
|
8943
|
+
const loc = client.resolveLocationId(locationId2);
|
|
8944
|
+
const raw = await client.get("/phone-system/numbers/", { params: { locationId: loc } });
|
|
8945
|
+
const parsed = NumbersResponseSchema.parse(raw);
|
|
8946
|
+
return {
|
|
8947
|
+
locationId: loc,
|
|
8948
|
+
count: parsed.phoneNumbers.length,
|
|
8949
|
+
phoneNumbers: parsed.phoneNumbers.map((n) => ({ sid: n.sid, number: n.value, label: n.title }))
|
|
8950
|
+
};
|
|
8951
|
+
}
|
|
8952
|
+
);
|
|
8953
|
+
safeTool(
|
|
8954
|
+
server2,
|
|
8955
|
+
"list_number_pools",
|
|
8956
|
+
"List LC Phone number pools configured for a location. Read-only.",
|
|
8957
|
+
{
|
|
8958
|
+
locationId: import_zod49.z.string().optional().describe("Defaults to the active location.")
|
|
8959
|
+
},
|
|
8960
|
+
async ({ locationId: locationId2 }) => {
|
|
8961
|
+
const loc = client.resolveLocationId(locationId2);
|
|
8962
|
+
const raw = await client.get("/phone-system/number-pools/", { params: { locationId: loc } });
|
|
8963
|
+
const parsed = PoolsResponseSchema.parse(raw);
|
|
8964
|
+
return { locationId: loc, count: parsed.pools.length, pools: parsed.pools };
|
|
8965
|
+
}
|
|
8966
|
+
);
|
|
8967
|
+
}
|
|
8968
|
+
|
|
8969
|
+
// src/tools/account-health.ts
|
|
8970
|
+
var import_zod50 = require("zod");
|
|
8971
|
+
var MetaTotalSchema = import_zod50.z.object({ meta: import_zod50.z.object({ total: import_zod50.z.number() }).passthrough() }).passthrough();
|
|
8972
|
+
var TotalSchema = import_zod50.z.object({ total: import_zod50.z.number() }).passthrough();
|
|
8973
|
+
var NumbersSchema = import_zod50.z.object({ phoneNumbers: import_zod50.z.array(import_zod50.z.unknown()) }).passthrough();
|
|
8974
|
+
var OPP_STATUSES = ["open", "won", "lost", "abandoned"];
|
|
8975
|
+
async function section(scope, fn) {
|
|
8976
|
+
try {
|
|
8977
|
+
return { ...await fn(), scope, status: "ok" };
|
|
8978
|
+
} catch (e) {
|
|
8979
|
+
return { status: "unavailable", scope, reason: errorMessage(e) };
|
|
8980
|
+
}
|
|
8981
|
+
}
|
|
8982
|
+
function registerAccountHealthTools(server2, client) {
|
|
8983
|
+
safeTool(
|
|
8984
|
+
server2,
|
|
8985
|
+
"get_account_health_summary",
|
|
8986
|
+
"Account-health summary for a location, composed from existing reads (GHL has no reporting API). Returns: total contacts + NEW contacts in the window; total opportunities + counts by status (open/won/lost/abandoned); total conversations; phone-number count. Every metric is explicitly labeled all_time vs window (with start/end) \u2014 windowed and all-time numbers are never conflated. Sections that can't be read return status:'unavailable' (never a misleading 0). Revenue and appointments are intentionally excluded (not reachable / too costly via the public API).",
|
|
8987
|
+
{
|
|
8988
|
+
locationId: import_zod50.z.string().optional().describe("Defaults to the active location."),
|
|
8989
|
+
windowDays: import_zod50.z.number().int().positive().max(365).optional().describe("Lookback window in days for windowed metrics (new contacts). Default 30.")
|
|
8990
|
+
},
|
|
8991
|
+
async ({ locationId: locationId2, windowDays }) => {
|
|
8992
|
+
const loc = client.resolveLocationId(locationId2);
|
|
8993
|
+
const days = windowDays ?? 30;
|
|
8994
|
+
const end = Date.now();
|
|
8995
|
+
const start = end - days * 864e5;
|
|
8996
|
+
const startISO = new Date(start).toISOString();
|
|
8997
|
+
const endISO = new Date(end).toISOString();
|
|
8998
|
+
const [
|
|
8999
|
+
contactsAllTime,
|
|
9000
|
+
contactsNewInWindow,
|
|
9001
|
+
opportunitiesTotal,
|
|
9002
|
+
opportunitiesByStatus,
|
|
9003
|
+
conversations,
|
|
9004
|
+
phoneNumbers
|
|
9005
|
+
] = await Promise.all([
|
|
9006
|
+
section("all_time", async () => {
|
|
9007
|
+
const raw = await client.get("/contacts/", { params: { locationId: loc, limit: 1 } });
|
|
9008
|
+
return { total: MetaTotalSchema.parse(raw).meta.total };
|
|
9009
|
+
}),
|
|
9010
|
+
section("window", async () => {
|
|
9011
|
+
const raw = await client.post("/contacts/search", {
|
|
9012
|
+
body: {
|
|
9013
|
+
locationId: loc,
|
|
9014
|
+
pageLimit: 1,
|
|
9015
|
+
filters: [{ field: "dateAdded", operator: "range", value: { gte: start, lte: end } }]
|
|
9016
|
+
}
|
|
9017
|
+
});
|
|
9018
|
+
return { start: startISO, end: endISO, total: TotalSchema.parse(raw).total };
|
|
9019
|
+
}),
|
|
9020
|
+
// Overall opp total in its OWN section so a failed per-status query can't
|
|
9021
|
+
// blank a count we already have.
|
|
9022
|
+
section("all_time", async () => {
|
|
9023
|
+
const raw = await client.get("/opportunities/search", { params: { location_id: loc, limit: 1 } });
|
|
9024
|
+
return { total: MetaTotalSchema.parse(raw).meta.total };
|
|
9025
|
+
}),
|
|
9026
|
+
section("all_time", async () => {
|
|
9027
|
+
const counts = await Promise.all(
|
|
9028
|
+
OPP_STATUSES.map(async (s) => {
|
|
9029
|
+
const raw = await client.get("/opportunities/search", {
|
|
9030
|
+
params: { location_id: loc, status: s, limit: 1 }
|
|
9031
|
+
});
|
|
9032
|
+
return [s, MetaTotalSchema.parse(raw).meta.total];
|
|
9033
|
+
})
|
|
9034
|
+
);
|
|
9035
|
+
return { byStatus: Object.fromEntries(counts) };
|
|
9036
|
+
}),
|
|
9037
|
+
section("all_time", async () => {
|
|
9038
|
+
const raw = await client.get("/conversations/search", { params: { locationId: loc, limit: 1 } });
|
|
9039
|
+
return { total: TotalSchema.parse(raw).total };
|
|
9040
|
+
}),
|
|
9041
|
+
section("all_time", async () => {
|
|
9042
|
+
const raw = await client.get("/phone-system/numbers/", { params: { locationId: loc } });
|
|
9043
|
+
return { count: NumbersSchema.parse(raw).phoneNumbers.length };
|
|
9044
|
+
})
|
|
9045
|
+
]);
|
|
9046
|
+
const sections = {
|
|
9047
|
+
contactsAllTime,
|
|
9048
|
+
contactsNewInWindow,
|
|
9049
|
+
opportunitiesTotal,
|
|
9050
|
+
opportunitiesByStatus,
|
|
9051
|
+
conversations,
|
|
9052
|
+
phoneNumbers
|
|
9053
|
+
};
|
|
9054
|
+
const unavailable = Object.entries(sections).filter(([, v]) => v.status === "unavailable").map(([k]) => k);
|
|
9055
|
+
return { locationId: loc, requestedWindow: { days, start: startISO, end: endISO }, sections, unavailable };
|
|
9056
|
+
}
|
|
9057
|
+
);
|
|
9058
|
+
}
|
|
9059
|
+
|
|
8820
9060
|
// src/tools/index.ts
|
|
8821
9061
|
var publicApiTools = [
|
|
8822
9062
|
[registerContactTools, "contacts"],
|
|
@@ -8848,7 +9088,9 @@ var publicApiTools = [
|
|
|
8848
9088
|
[registerDocumentTools, "documents"],
|
|
8849
9089
|
[registerBulkOperationTools, "bulk-operations"],
|
|
8850
9090
|
[registerAccountExportTools, "account-export"],
|
|
8851
|
-
[registerTemplateDeployerTools, "template-deployer"]
|
|
9091
|
+
[registerTemplateDeployerTools, "template-deployer"],
|
|
9092
|
+
[registerPhoneTools, "phone"],
|
|
9093
|
+
[registerAccountHealthTools, "account-health"]
|
|
8852
9094
|
];
|
|
8853
9095
|
var internalApiTools = [
|
|
8854
9096
|
[registerWorkflowBuilderTools, "workflow-builder"],
|
|
@@ -8865,12 +9107,14 @@ var internalApiTools = [
|
|
|
8865
9107
|
var VALIDATORS_MODULE = "validators";
|
|
8866
9108
|
var DIAGNOSTICS_MODULE = "diagnostics";
|
|
8867
9109
|
var LOCATION_SWITCHER_MODULE = "location-switcher";
|
|
9110
|
+
var SNAPSHOTS_MODULE = "snapshots";
|
|
8868
9111
|
var KNOWN_MODULES = /* @__PURE__ */ new Set([
|
|
8869
9112
|
...publicApiTools.map(([, label]) => label),
|
|
8870
9113
|
...internalApiTools.map(([, label]) => label),
|
|
8871
9114
|
VALIDATORS_MODULE,
|
|
8872
9115
|
DIAGNOSTICS_MODULE,
|
|
8873
|
-
LOCATION_SWITCHER_MODULE
|
|
9116
|
+
LOCATION_SWITCHER_MODULE,
|
|
9117
|
+
SNAPSHOTS_MODULE
|
|
8874
9118
|
]);
|
|
8875
9119
|
function registerAllTools(server2, client, registry2, mcpVersion, env = process.env) {
|
|
8876
9120
|
const config3 = parseAllowlist(env);
|
|
@@ -8892,6 +9136,7 @@ function registerAllTools(server2, client, registry2, mcpVersion, env = process.
|
|
|
8892
9136
|
builderClient,
|
|
8893
9137
|
registry2 ?? null
|
|
8894
9138
|
);
|
|
9139
|
+
registerSnapshotTools(wrap(SNAPSHOTS_MODULE), client, registry2);
|
|
8895
9140
|
registerLocationSwitcherTools(
|
|
8896
9141
|
wrap(LOCATION_SWITCHER_MODULE),
|
|
8897
9142
|
client,
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elitedcs/ghl-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.32.0",
|
|
4
4
|
"mcpName": "io.github.drjerryrelth/ghl-command",
|
|
5
|
-
"description": "GoHighLevel MCP Server for Claude.
|
|
5
|
+
"description": "GoHighLevel MCP Server for Claude. 217 tools — full CRM, automation, marketing control, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"ghl-mcp": "dist/index.js"
|