@elitedcs/ghl-mcp 3.31.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 +26 -0
- package/dist/index.js +136 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
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
|
+
|
|
3
29
|
## 3.31.0 — Snapshots: list + share-link (agency tooling)
|
|
4
30
|
|
|
5
31
|
Two new agency-level tools, the first build toward removing manual steps from the
|
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"
|
|
@@ -8926,6 +8926,137 @@ function registerSnapshotTools(server2, client, registry2) {
|
|
|
8926
8926
|
);
|
|
8927
8927
|
}
|
|
8928
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
|
+
|
|
8929
9060
|
// src/tools/index.ts
|
|
8930
9061
|
var publicApiTools = [
|
|
8931
9062
|
[registerContactTools, "contacts"],
|
|
@@ -8957,7 +9088,9 @@ var publicApiTools = [
|
|
|
8957
9088
|
[registerDocumentTools, "documents"],
|
|
8958
9089
|
[registerBulkOperationTools, "bulk-operations"],
|
|
8959
9090
|
[registerAccountExportTools, "account-export"],
|
|
8960
|
-
[registerTemplateDeployerTools, "template-deployer"]
|
|
9091
|
+
[registerTemplateDeployerTools, "template-deployer"],
|
|
9092
|
+
[registerPhoneTools, "phone"],
|
|
9093
|
+
[registerAccountHealthTools, "account-health"]
|
|
8961
9094
|
];
|
|
8962
9095
|
var internalApiTools = [
|
|
8963
9096
|
[registerWorkflowBuilderTools, "workflow-builder"],
|
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"
|