@elitedcs/ghl-mcp 3.34.9 → 3.34.10
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 +25 -0
- package/dist/index.js +105 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.34.10 — `get_free_slots` accepts ISO dates; `update_form` name fallback for fresh forms
|
|
4
|
+
|
|
5
|
+
Two small fixes that close rough edges found while live-verifying v3.34.9 through
|
|
6
|
+
the MCP tools. Neither changes a proven capability; both have trivial workarounds.
|
|
7
|
+
|
|
8
|
+
- **`get_free_slots` now accepts ISO dates (its documented input) — not just epoch
|
|
9
|
+
milliseconds.** GHL's `GET /calendars/{id}/free-slots` requires `startDate` /
|
|
10
|
+
`endDate` as epoch **milliseconds** and 422s (`startDate must be a number…`) on an
|
|
11
|
+
ISO date, yet the tool's own schema documented `YYYY-MM-DD` and forwarded it
|
|
12
|
+
unconverted — so the documented input always failed. The tool now accepts a bare
|
|
13
|
+
`YYYY-MM-DD` (taken as start-of-day for `startDate`, end-of-day for `endDate`, in
|
|
14
|
+
the supplied `timezone`, default UTC), a full ISO datetime (`Date.parse`), or an
|
|
15
|
+
already-numeric epoch-millis value (passed through, back-compat) and converts to
|
|
16
|
+
the epoch ms GHL needs. Logic extracted to `buildFreeSlotsParams` / `toEpochMillis`
|
|
17
|
+
with tests (incl. the live Phoenix anchor `2026-06-15 → 1781506800000`).
|
|
18
|
+
- **`update_form` no longer throws on the natural `create_form` → `update_form`
|
|
19
|
+
sequence.** When `name` is omitted, `update_form` reads the current name to
|
|
20
|
+
preserve it. A form freshly created by `create_form` has no `name` in its builder
|
|
21
|
+
doc until its first save, so that read returned none and the call threw
|
|
22
|
+
(`Could not resolve current form name`). It now falls back to the public forms list
|
|
23
|
+
(which carries the name) and only errors — with actionable guidance to pass `name`
|
|
24
|
+
— if both sources come up empty. The form-builder tools now also receive the public
|
|
25
|
+
`GHLClient` for this lookup (it already had Firebase auth for the builder routes).
|
|
26
|
+
Helpers `pickFormName` / `findFormNameInList` extracted, with tests.
|
|
27
|
+
|
|
3
28
|
## 3.34.9 — Calendar availability: create_calendar / update_calendar now set `openHours`
|
|
4
29
|
|
|
5
30
|
Closes the on-camera "assign availability hours" gap. `create_calendar` and
|
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "@elitedcs/ghl-mcp",
|
|
34
|
-
version: "3.34.
|
|
34
|
+
version: "3.34.10",
|
|
35
35
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
36
36
|
description: "GoHighLevel MCP Server for Claude. 218 tools \u2014 full CRM, automation, marketing control, account-wide workflow audit, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
|
|
37
37
|
main: "dist/index.js",
|
|
@@ -2779,6 +2779,61 @@ function buildUpdateCalendarBody(args) {
|
|
|
2779
2779
|
applyCalendarFields(body, args);
|
|
2780
2780
|
return body;
|
|
2781
2781
|
}
|
|
2782
|
+
function tzOffsetMillis(utcMillis, timeZone) {
|
|
2783
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
2784
|
+
timeZone,
|
|
2785
|
+
hourCycle: "h23",
|
|
2786
|
+
year: "numeric",
|
|
2787
|
+
month: "2-digit",
|
|
2788
|
+
day: "2-digit",
|
|
2789
|
+
hour: "2-digit",
|
|
2790
|
+
minute: "2-digit",
|
|
2791
|
+
second: "2-digit"
|
|
2792
|
+
});
|
|
2793
|
+
const parts = dtf.formatToParts(new Date(utcMillis));
|
|
2794
|
+
const get = (type) => Number(parts.find((p) => p.type === type)?.value);
|
|
2795
|
+
const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour"), get("minute"), get("second"));
|
|
2796
|
+
return asUTC - utcMillis;
|
|
2797
|
+
}
|
|
2798
|
+
function zonedStartOfDayMillis(year, month, day, timeZone) {
|
|
2799
|
+
const utcGuess = Date.UTC(year, month - 1, day, 0, 0, 0, 0);
|
|
2800
|
+
if (!timeZone) return utcGuess;
|
|
2801
|
+
return utcGuess - tzOffsetMillis(utcGuess, timeZone);
|
|
2802
|
+
}
|
|
2803
|
+
function zonedDayBoundaryMillis(year, month, day, endOfDay, timeZone) {
|
|
2804
|
+
if (endOfDay) return zonedStartOfDayMillis(year, month, day + 1, timeZone) - 1;
|
|
2805
|
+
return zonedStartOfDayMillis(year, month, day, timeZone);
|
|
2806
|
+
}
|
|
2807
|
+
function toEpochMillis(value, opts = {}) {
|
|
2808
|
+
const trimmed = value.trim();
|
|
2809
|
+
if (/^\d+$/.test(trimmed)) {
|
|
2810
|
+
if (trimmed.length === 13) return trimmed;
|
|
2811
|
+
throw new Error(`Ambiguous numeric date "${value}" for free-slots \u2014 pass epoch MILLISECONDS (13 digits), a YYYY-MM-DD date, or a full ISO datetime.`);
|
|
2812
|
+
}
|
|
2813
|
+
const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(trimmed);
|
|
2814
|
+
if (dateOnly) {
|
|
2815
|
+
const millis = zonedDayBoundaryMillis(
|
|
2816
|
+
Number(dateOnly[1]),
|
|
2817
|
+
Number(dateOnly[2]),
|
|
2818
|
+
Number(dateOnly[3]),
|
|
2819
|
+
Boolean(opts.endOfDay),
|
|
2820
|
+
opts.timeZone
|
|
2821
|
+
);
|
|
2822
|
+
return String(millis);
|
|
2823
|
+
}
|
|
2824
|
+
const parsed = Date.parse(trimmed);
|
|
2825
|
+
if (!Number.isNaN(parsed)) return String(parsed);
|
|
2826
|
+
throw new Error(`Invalid date "${value}" for free-slots \u2014 use YYYY-MM-DD, a full ISO datetime, or epoch milliseconds.`);
|
|
2827
|
+
}
|
|
2828
|
+
function buildFreeSlotsParams(args) {
|
|
2829
|
+
const params = {
|
|
2830
|
+
startDate: toEpochMillis(args.startDate, { timeZone: args.timezone }),
|
|
2831
|
+
endDate: toEpochMillis(args.endDate, { endOfDay: true, timeZone: args.timezone })
|
|
2832
|
+
};
|
|
2833
|
+
if (args.timezone !== void 0) params.timezone = args.timezone;
|
|
2834
|
+
if (args.userId !== void 0) params.userId = args.userId;
|
|
2835
|
+
return params;
|
|
2836
|
+
}
|
|
2782
2837
|
function registerCalendarTools(server2, client) {
|
|
2783
2838
|
safeTool(
|
|
2784
2839
|
server2,
|
|
@@ -2881,15 +2936,13 @@ function registerCalendarTools(server2, client) {
|
|
|
2881
2936
|
"Get available free slots for a calendar",
|
|
2882
2937
|
{
|
|
2883
2938
|
calendarId: import_zod9.z.string().describe("The calendar ID"),
|
|
2884
|
-
startDate: import_zod9.z.string().describe("Start
|
|
2885
|
-
endDate: import_zod9.z.string().describe("End
|
|
2939
|
+
startDate: import_zod9.z.string().describe("Start of the window. Accepts a date (YYYY-MM-DD), a full ISO datetime, or epoch milliseconds. A bare date is taken as start-of-day in `timezone` (UTC if unset). GHL's API needs epoch ms; the tool converts for you."),
|
|
2940
|
+
endDate: import_zod9.z.string().describe("End of the window. Accepts a date (YYYY-MM-DD), a full ISO datetime, or epoch milliseconds. A bare date is taken as end-of-day in `timezone` (UTC if unset)."),
|
|
2886
2941
|
timezone: import_zod9.z.string().optional().describe("Timezone (e.g. America/New_York)"),
|
|
2887
2942
|
userId: import_zod9.z.string().optional().describe("Filter by user ID")
|
|
2888
2943
|
},
|
|
2889
2944
|
async ({ calendarId, startDate, endDate, timezone, userId }) => {
|
|
2890
|
-
const params = { startDate, endDate };
|
|
2891
|
-
if (timezone !== void 0) params.timezone = timezone;
|
|
2892
|
-
if (userId !== void 0) params.userId = userId;
|
|
2945
|
+
const params = buildFreeSlotsParams({ startDate, endDate, timezone, userId });
|
|
2893
2946
|
return await client.get(`/calendars/${calendarId}/free-slots`, {
|
|
2894
2947
|
params
|
|
2895
2948
|
});
|
|
@@ -6007,13 +6060,34 @@ ${text2}`);
|
|
|
6007
6060
|
|
|
6008
6061
|
// src/tools/form-builder.ts
|
|
6009
6062
|
var import_zod35 = require("zod");
|
|
6063
|
+
function pickFormName(builderForm) {
|
|
6064
|
+
if (builderForm && typeof builderForm === "object" && "form" in builderForm) {
|
|
6065
|
+
const name = builderForm.form?.name;
|
|
6066
|
+
if (typeof name === "string" && name.length > 0) return name;
|
|
6067
|
+
}
|
|
6068
|
+
return void 0;
|
|
6069
|
+
}
|
|
6070
|
+
function findFormNameInList(list, formId) {
|
|
6071
|
+
const forms = list?.forms;
|
|
6072
|
+
if (Array.isArray(forms)) {
|
|
6073
|
+
for (const f of forms) {
|
|
6074
|
+
if (f && typeof f === "object") {
|
|
6075
|
+
const entry = f;
|
|
6076
|
+
if ((entry.id === formId || entry._id === formId) && typeof entry.name === "string" && entry.name.length > 0) {
|
|
6077
|
+
return entry.name;
|
|
6078
|
+
}
|
|
6079
|
+
}
|
|
6080
|
+
}
|
|
6081
|
+
}
|
|
6082
|
+
return void 0;
|
|
6083
|
+
}
|
|
6010
6084
|
function buildUpdateFormPath(formId, locationId2) {
|
|
6011
6085
|
return `/${formId}?locationId=${locationId2}`;
|
|
6012
6086
|
}
|
|
6013
6087
|
function buildUpdateFormBody(name, formData) {
|
|
6014
6088
|
return { name, formData };
|
|
6015
6089
|
}
|
|
6016
|
-
function registerFormBuilderTools(server2, builderClient) {
|
|
6090
|
+
function registerFormBuilderTools(server2, builderClient, publicClient) {
|
|
6017
6091
|
const client = builderClient;
|
|
6018
6092
|
if (!client) return;
|
|
6019
6093
|
async function formRequest(method, path7, body) {
|
|
@@ -6067,11 +6141,28 @@ ${text2}`);
|
|
|
6067
6141
|
let resolvedName = name;
|
|
6068
6142
|
if (resolvedName === void 0) {
|
|
6069
6143
|
const current = await formRequest("GET", `/${formId}?locationId=${client.locationId}`);
|
|
6070
|
-
|
|
6071
|
-
if (
|
|
6072
|
-
|
|
6144
|
+
resolvedName = pickFormName(current);
|
|
6145
|
+
if (resolvedName === void 0 && publicClient) {
|
|
6146
|
+
try {
|
|
6147
|
+
const pageSize = 100;
|
|
6148
|
+
const maxForms = 5e3;
|
|
6149
|
+
for (let skip = 0; skip < maxForms; skip += pageSize) {
|
|
6150
|
+
const list = await publicClient.get("/forms/", {
|
|
6151
|
+
params: { locationId: client.locationId, limit: pageSize, skip }
|
|
6152
|
+
});
|
|
6153
|
+
resolvedName = findFormNameInList(list, formId);
|
|
6154
|
+
if (resolvedName !== void 0) break;
|
|
6155
|
+
const forms = list?.forms;
|
|
6156
|
+
if (!Array.isArray(forms) || forms.length < pageSize) break;
|
|
6157
|
+
}
|
|
6158
|
+
} catch {
|
|
6159
|
+
}
|
|
6160
|
+
}
|
|
6161
|
+
if (resolvedName === void 0) {
|
|
6162
|
+
throw new Error(
|
|
6163
|
+
`Could not resolve the current name for form ${formId}. Pass the \`name\` argument explicitly \u2014 a form created via create_form has no builder-doc name until its first save.`
|
|
6164
|
+
);
|
|
6073
6165
|
}
|
|
6074
|
-
resolvedName = currentName;
|
|
6075
6166
|
}
|
|
6076
6167
|
const result = await formRequest(
|
|
6077
6168
|
"POST",
|
|
@@ -9545,7 +9636,6 @@ var publicApiTools = [
|
|
|
9545
9636
|
var internalApiTools = [
|
|
9546
9637
|
[registerWorkflowBuilderTools, "workflow-builder"],
|
|
9547
9638
|
[registerFunnelBuilderTools, "funnel-builder"],
|
|
9548
|
-
[registerFormBuilderTools, "form-builder"],
|
|
9549
9639
|
[registerPipelineBuilderTools, "pipeline-builder"],
|
|
9550
9640
|
[registerWorkflowClonerTools, "workflow-cloner"],
|
|
9551
9641
|
[registerSmartListTools, "smart-lists"],
|
|
@@ -9558,9 +9648,11 @@ var VALIDATORS_MODULE = "validators";
|
|
|
9558
9648
|
var DIAGNOSTICS_MODULE = "diagnostics";
|
|
9559
9649
|
var LOCATION_SWITCHER_MODULE = "location-switcher";
|
|
9560
9650
|
var SNAPSHOTS_MODULE = "snapshots";
|
|
9651
|
+
var FORM_BUILDER_MODULE = "form-builder";
|
|
9561
9652
|
var KNOWN_MODULES = /* @__PURE__ */ new Set([
|
|
9562
9653
|
...publicApiTools.map(([, label]) => label),
|
|
9563
9654
|
...internalApiTools.map(([, label]) => label),
|
|
9655
|
+
FORM_BUILDER_MODULE,
|
|
9564
9656
|
VALIDATORS_MODULE,
|
|
9565
9657
|
DIAGNOSTICS_MODULE,
|
|
9566
9658
|
LOCATION_SWITCHER_MODULE,
|
|
@@ -9578,6 +9670,7 @@ function registerAllTools(server2, client, registry2, mcpVersion, env = process.
|
|
|
9578
9670
|
for (const [register, moduleName] of internalApiTools) {
|
|
9579
9671
|
register(wrap(moduleName), builderClient);
|
|
9580
9672
|
}
|
|
9673
|
+
registerFormBuilderTools(wrap(FORM_BUILDER_MODULE), builderClient, client);
|
|
9581
9674
|
registerValidatorTools(wrap(VALIDATORS_MODULE), client, builderClient);
|
|
9582
9675
|
registerDiagnosticTools(
|
|
9583
9676
|
wrap(DIAGNOSTICS_MODULE),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elitedcs/ghl-mcp",
|
|
3
|
-
"version": "3.34.
|
|
3
|
+
"version": "3.34.10",
|
|
4
4
|
"mcpName": "io.github.drjerryrelth/ghl-command",
|
|
5
5
|
"description": "GoHighLevel MCP Server for Claude. 218 tools — full CRM, automation, marketing control, account-wide workflow audit, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
|
|
6
6
|
"main": "dist/index.js",
|