@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 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.31.0",
34
+ version: "3.32.0",
35
35
  mcpName: "io.github.drjerryrelth/ghl-command",
36
- description: "GoHighLevel MCP Server for Claude. 214 tools \u2014 full CRM, automation, marketing control, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
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.31.0",
3
+ "version": "3.32.0",
4
4
  "mcpName": "io.github.drjerryrelth/ghl-command",
5
- "description": "GoHighLevel MCP Server for Claude. 214 tools — full CRM, automation, marketing control, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
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"