@elitedcs/ghl-mcp 3.19.1 → 3.23.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 +64 -0
- package/dist/index.js +114 -36
- package/package.json +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.23.0 — `update_calendar` can assign team members again (Henry fix, part 2)
|
|
4
|
+
|
|
5
|
+
**Critical fix. Tool count unchanged (212 across 43 modules). The `teamMembers` parameter is back on `update_calendar` — and actually persists this time — with a typed schema that surfaces GHL's strict validation up front.**
|
|
6
|
+
|
|
7
|
+
Reported by Henry Boulton, 2026-05-26.
|
|
8
|
+
|
|
9
|
+
**Root cause.** Same UI-vs-API pattern we hit in v3.22.0 (forms) and v3.15.0 (email templates). v3.21.0's diagnosis ("GHL silently drops `teamMembers` on PUT, no public-API path exists") was based on testing against an Event calendar — that calendar TYPE doesn't have team members, so GHL accepts the call and ignores the field. On round-robin and class-booking calendars the field is meaningful, and PUT validates it strictly: `priority` must be exactly `0`, `0.5`, or `1` (anything else 422s), `locationConfigurations` needs at least one entry, and `userId` / `selected` / `isPrimary` must be present. Henry's original attempt likely either targeted the wrong calendar type or passed a non-enum priority.
|
|
10
|
+
|
|
11
|
+
**Fix.** `teamMembers` is back on `update_calendar`, typed via an exported `CalendarTeamMemberSchema` that mirrors GHL's validation:
|
|
12
|
+
|
|
13
|
+
- `priority` constrained to `z.union([z.literal(0), z.literal(0.5), z.literal(1)])` — invalid values fail at the MCP layer with a clear error instead of an opaque GHL 422.
|
|
14
|
+
- `locationConfigurations` requires `min(1)` and a non-negative integer `position`.
|
|
15
|
+
- Tool description spells out that team-member assignment only applies to calendar types that support it (round_robin, class_booking); on Event calendars the field is silently ignored by GHL — that's the type, not us.
|
|
16
|
+
|
|
17
|
+
End-to-end verification 2026-05-26 against MCP Testing: create round_robin → assign user → update via PUT (priority 0.5 → 1, location string change) → GET back the new shape → cleanup.
|
|
18
|
+
|
|
19
|
+
Five new tests in `src/tools/tool-payloads.test.ts` lock the schema in.
|
|
20
|
+
|
|
21
|
+
## 3.22.0 — `update_form` works again (Henry fix)
|
|
22
|
+
|
|
23
|
+
**Critical fix. Tool count unchanged (212 across 43 modules). The `update_form` tool, which v3.21.0 marked "currently unavailable," is back — wired to the live save endpoint and verified end-to-end against MCP Testing.**
|
|
24
|
+
|
|
25
|
+
Reported by Henry Boulton, 2026-05-26.
|
|
26
|
+
|
|
27
|
+
**Root cause.** v3.21.0's diagnosis was half right and half wrong. The half that was right: the v2 form builder UI does run in a cross-origin iframe (`leadgen-apps-form-survey-builder.leadconnectorhq.com`) and the UI does read form state through Firestore — top-frame network capture only sees a Firestore `Listen` channel, never a REST save. That's exactly what made the route look gone. The half that was wrong: we concluded the REST save route therefore *didn't exist* and the tool needed a Firestore-client integration. It does exist. The new UI just doesn't drive it. Same UI-vs-API divergence we saw with email templates in v3.15.0 ([reference_email_template_firestore](https://github.com/drjerryrelth/ghl-command-mcp/blob/main/CHANGELOG.md#3150)) — the REST routes survive even when the editor moves to Firestore.
|
|
28
|
+
|
|
29
|
+
**Fix.** `update_form` now calls `POST /forms/{formId}?locationId={loc}` with body exactly `{name, formData}`. Verified shape constraints (server rejects everything else with 422 "property X should not exist"):
|
|
30
|
+
|
|
31
|
+
- `locationId` belongs in the query string only — putting it in the body fails.
|
|
32
|
+
- `name` and `formData` are both required together — one without the other 422s.
|
|
33
|
+
- `_id`, `deleted`, `productType`, `dateAdded`, `dateUpdated`, `source`, `updatedBy`, `version`, `updatedAt`, `versionHistory` must not appear in the body — `get_form_full` → pass through fails, so callers should pass only the `formData` they want to write.
|
|
34
|
+
|
|
35
|
+
If `name` is omitted, the tool pre-fetches the current form (one extra GET) so the existing name is preserved. Body and path are factored into pure `buildUpdateFormBody` / `buildUpdateFormPath` helpers and locked in by `src/tools/tool-payloads.test.ts` (3 new tests) — a refactor can't silently regress the tool back to v3.21.0's "unavailable" state.
|
|
36
|
+
|
|
37
|
+
End-to-end verification 2026-05-26: read form (version 5) → mutate first field's `placeholder` → save (201) → re-read (placeholder persisted) → restore. MCP Testing sandbox left clean.
|
|
38
|
+
|
|
39
|
+
## 3.21.0 — Workflow Builder now loads for npm-install buyers (Henry + Ryan fix)
|
|
40
|
+
|
|
41
|
+
**Critical fix. Tool count unchanged (212 across 43 modules), but for `npx`-install buyers the 49 Firebase-gated tools (workflow builder, funnel builder, form builder, pipeline builder, smart lists, reputation, email campaigns, memberships, email-builder internal, plus the pre-deploy validator) now actually register after `enable_workflow_builder` — previously they stayed "Not configured" no matter how many times you restarted.**
|
|
42
|
+
|
|
43
|
+
Reported independently by Henry Boulton and Ryan Thomas, 2026-05-26.
|
|
44
|
+
|
|
45
|
+
**Root cause.** Index startup folded `GHL_USER_ID` + the Firebase trio from `credentials.json` into `process.env`, but never folded `GHL_API_KEY` or `GHL_LOCATION_ID`. `WorkflowBuilderClient.fromEnv()` reads all five directly from `process.env`, so for the recommended `npx -y @elitedcs/ghl-mcp@latest` install path — where no env vars are set anywhere — `fromEnv()` returned `null` and every Firebase-gated tool was skipped. Wrapper-script installs (e.g. `start-mcp.sh`) were unaffected because they exported all the vars manually. Henry's workaround of passing the values as `--env` flags on `claude mcp add` "fixed" it by route, which is why this looked like a credentials-file persistence bug.
|
|
46
|
+
|
|
47
|
+
**Fix.** All credentials-file values now fold into `process.env` at startup (env still wins when already set, so wrapper-script + headless paths are unchanged). Pulled the fold into a single `foldCredentialsIntoEnv()` helper with regression tests that lock in all five Firebase-gated fields.
|
|
48
|
+
|
|
49
|
+
Also in this release:
|
|
50
|
+
|
|
51
|
+
- **`create_form`** now uses the correct path. GHL silently retired `POST /forms?locationId=…` (returns 404); the working path is `POST /forms/?locationId=…` (trailing slash). Verified against MCP Testing.
|
|
52
|
+
- **`update_form`** is honestly marked unavailable, with the actual root cause documented for the first time: the GHL form builder runs in a cross-origin iframe (`leadgen-apps-form-survey-builder.leadconnectorhq.com`) and writes saves **directly to Firestore via the Firebase JS SDK** — `firestore.googleapis.com/.../databases/(default)` against the `highlevel-backend` project — rather than any REST endpoint. That's why every REST probe returns 404 or "Form does not exist or is deleted." Wiring this up needs a Firestore-client integration (separate from the workflow-builder's REST-over-Firebase-token pattern). Tracked for a focused future release. Workaround: `delete_form` + `create_form` (both work).
|
|
53
|
+
- **`update_calendar`** removed `teamMembers` from the input. GHL's public API silently drops the field on PUT (200 OK, nothing persists) across every shape and API version we tested. The tool description now tells the caller to set the assignee in the GHL UI instead of returning a fake success.
|
|
54
|
+
|
|
55
|
+
## 3.20.0 — License gate for headless / env-var installs
|
|
56
|
+
|
|
57
|
+
**Security + headless support. No tool changes (212 across 43 modules).**
|
|
58
|
+
|
|
59
|
+
Closes a gap where setting `GHL_API_KEY` + `GHL_LOCATION_ID` via environment variables loaded the full tool set **without ever validating a license**. The server now requires a verified license before exposing the full tools:
|
|
60
|
+
|
|
61
|
+
- A `credentials.json` written by `setup_ghl_mcp` carries `verified_at` (validated at setup) and is trusted as before — **no change for normal Desktop/Claude Code installs.**
|
|
62
|
+
- The env-var / headless path must now supply `GHL_LICENSE_EMAIL` + `GHL_LICENSE_KEY`. These are validated once at boot and the verified result is cached to `credentials.json`, so there's no per-restart phone-home and device activations aren't re-counted on a stable machine.
|
|
63
|
+
- Without a verified license, the server stays in bootstrap mode (only `setup_ghl_mcp`, `request_license`, `get_mcp_version`) with a clear message.
|
|
64
|
+
|
|
65
|
+
This makes headless/server installs first-class: set `GHL_LICENSE_EMAIL`, `GHL_LICENSE_KEY`, `GHL_API_KEY`, `GHL_LOCATION_ID` (plus the Firebase vars for the workflow builder) and the server boots into the full tool set.
|
|
66
|
+
|
|
3
67
|
## 3.19.1 — MCP registry prep + README UTM tracking
|
|
4
68
|
|
|
5
69
|
- Added `mcpName` (`io.github.drjerryrelth/ghl-command`) to package.json so the package can be published to the official MCP registry (many directories pull from it).
|
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
|
+
version: "3.23.0",
|
|
35
35
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
36
36
|
description: "GoHighLevel MCP Server for Claude. 212 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",
|
|
@@ -60,9 +60,15 @@ var require_package = __commonJS({
|
|
|
60
60
|
"model-context-protocol",
|
|
61
61
|
"gohighlevel",
|
|
62
62
|
"ghl",
|
|
63
|
+
"gohighlevel-mcp",
|
|
64
|
+
"ghl-automation",
|
|
63
65
|
"crm",
|
|
64
66
|
"claude",
|
|
65
|
-
"anthropic"
|
|
67
|
+
"anthropic",
|
|
68
|
+
"workflow-builder",
|
|
69
|
+
"marketing-automation",
|
|
70
|
+
"ai-agent",
|
|
71
|
+
"agency"
|
|
66
72
|
],
|
|
67
73
|
author: "Elite DCs, LLC",
|
|
68
74
|
license: "MIT",
|
|
@@ -326,6 +332,23 @@ function readCredentials() {
|
|
|
326
332
|
return null;
|
|
327
333
|
}
|
|
328
334
|
}
|
|
335
|
+
function foldCredentialsIntoEnv(creds, env = process.env) {
|
|
336
|
+
if (!creds) return;
|
|
337
|
+
const mappings = [
|
|
338
|
+
["ghl_api_key", "GHL_API_KEY"],
|
|
339
|
+
["ghl_location_id", "GHL_LOCATION_ID"],
|
|
340
|
+
["ghl_user_id", "GHL_USER_ID"],
|
|
341
|
+
["ghl_firebase_api_key", "GHL_FIREBASE_API_KEY"],
|
|
342
|
+
["ghl_firebase_refresh_token", "GHL_FIREBASE_REFRESH_TOKEN"],
|
|
343
|
+
["ghl_company_id", "GHL_COMPANY_ID"]
|
|
344
|
+
];
|
|
345
|
+
for (const [credKey, envKey] of mappings) {
|
|
346
|
+
const value = creds[credKey];
|
|
347
|
+
if (value && !env[envKey]) {
|
|
348
|
+
env[envKey] = value;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
329
352
|
function writeCredentials(creds) {
|
|
330
353
|
ensureAppDataDir();
|
|
331
354
|
const file = credentialsPath();
|
|
@@ -2364,6 +2387,18 @@ function registerOpportunityTools(server2, client) {
|
|
|
2364
2387
|
|
|
2365
2388
|
// src/tools/calendars.ts
|
|
2366
2389
|
var import_zod9 = require("zod");
|
|
2390
|
+
var TeamMemberLocationConfigSchema = import_zod9.z.object({
|
|
2391
|
+
kind: import_zod9.z.string().describe("Meeting location kind. Common values: 'custom', 'zoom_conference', 'google_meet'."),
|
|
2392
|
+
position: import_zod9.z.number().int().nonnegative().describe("Position index for ordering (0-based)."),
|
|
2393
|
+
location: import_zod9.z.string().optional().describe("Display string for the meeting location (URL or address). Optional for kind='custom'.")
|
|
2394
|
+
});
|
|
2395
|
+
var CalendarTeamMemberSchema = import_zod9.z.object({
|
|
2396
|
+
userId: import_zod9.z.string().describe("GHL user ID to assign. Must exist in this sub-account."),
|
|
2397
|
+
priority: import_zod9.z.union([import_zod9.z.literal(0), import_zod9.z.literal(0.5), import_zod9.z.literal(1)]).describe("Booking priority. Exactly 0, 0.5, or 1 (anything else 422s from GHL). Higher = preferred for round-robin."),
|
|
2398
|
+
isPrimary: import_zod9.z.boolean().describe("Whether this user is the primary assignee."),
|
|
2399
|
+
selected: import_zod9.z.boolean().describe("Whether this user is currently active on the calendar (true to enable)."),
|
|
2400
|
+
locationConfigurations: import_zod9.z.array(TeamMemberLocationConfigSchema).min(1).describe("At least one meeting-location config. Typical shape: [{kind:'custom', position:0, location:''}].")
|
|
2401
|
+
});
|
|
2367
2402
|
function registerCalendarTools(server2, client) {
|
|
2368
2403
|
safeTool(
|
|
2369
2404
|
server2,
|
|
@@ -2433,7 +2468,7 @@ function registerCalendarTools(server2, client) {
|
|
|
2433
2468
|
safeTool(
|
|
2434
2469
|
server2,
|
|
2435
2470
|
"update_calendar",
|
|
2436
|
-
"Update an existing calendar",
|
|
2471
|
+
"Update an existing calendar's settings. Supports name, description, slug, event title/color, slot durations, capacity, AND team-member assignment via `teamMembers`. Team members only apply to calendar types that use them (round_robin, class_booking, etc.) \u2014 on event calendars GHL accepts the call but silently ignores the field. priority MUST be 0, 0.5, or 1.",
|
|
2437
2472
|
{
|
|
2438
2473
|
calendarId: import_zod9.z.string().describe("The calendar ID to update"),
|
|
2439
2474
|
name: import_zod9.z.string().optional().describe("Calendar name"),
|
|
@@ -2441,7 +2476,7 @@ function registerCalendarTools(server2, client) {
|
|
|
2441
2476
|
slug: import_zod9.z.string().optional().describe("Calendar slug"),
|
|
2442
2477
|
widgetSlug: import_zod9.z.string().optional().describe("Widget slug for embedding"),
|
|
2443
2478
|
calendarType: import_zod9.z.string().optional().describe("Calendar type"),
|
|
2444
|
-
teamMembers: import_zod9.z.array(
|
|
2479
|
+
teamMembers: import_zod9.z.array(CalendarTeamMemberSchema).optional().describe("Replace the calendar's team-member assignments. Pass [] to clear (round-robin calendars require at least one). Use get_calendar to inspect the current shape first."),
|
|
2445
2480
|
eventTitle: import_zod9.z.string().optional().describe("Default event title"),
|
|
2446
2481
|
eventColor: import_zod9.z.string().optional().describe("Event color hex code"),
|
|
2447
2482
|
slotDuration: import_zod9.z.number().optional().describe("Slot duration in minutes"),
|
|
@@ -5476,6 +5511,12 @@ ${text2}`);
|
|
|
5476
5511
|
|
|
5477
5512
|
// src/tools/form-builder.ts
|
|
5478
5513
|
var import_zod35 = require("zod");
|
|
5514
|
+
function buildUpdateFormPath(formId, locationId2) {
|
|
5515
|
+
return `/${formId}?locationId=${locationId2}`;
|
|
5516
|
+
}
|
|
5517
|
+
function buildUpdateFormBody(name, formData) {
|
|
5518
|
+
return { name, formData };
|
|
5519
|
+
}
|
|
5479
5520
|
function registerFormBuilderTools(server2, builderClient) {
|
|
5480
5521
|
const client = builderClient;
|
|
5481
5522
|
if (!client) return;
|
|
@@ -5519,17 +5560,28 @@ ${text2}`);
|
|
|
5519
5560
|
);
|
|
5520
5561
|
server2.tool(
|
|
5521
5562
|
"update_form",
|
|
5522
|
-
"Update a form's structure \u2014 fields, labels, conditional logic, auto-responder, email notifications, styling. Use get_form_full first to
|
|
5563
|
+
"Update a form's structure \u2014 fields, labels, conditional logic, auto-responder, email notifications, styling. Use get_form_full first to read the current form, modify the formData object, then pass it here. If name is omitted, the current name is preserved (one extra GET).",
|
|
5523
5564
|
{
|
|
5524
5565
|
formId: import_zod35.z.string().describe("The form ID to update."),
|
|
5525
5566
|
formData: import_zod35.z.record(import_zod35.z.unknown()).describe("The updated formData object containing form fields, settings, autoResponder config, etc."),
|
|
5526
|
-
name: import_zod35.z.string().optional().describe("Updated form name.")
|
|
5567
|
+
name: import_zod35.z.string().optional().describe("Updated form name. If omitted, the current name is preserved.")
|
|
5527
5568
|
},
|
|
5528
5569
|
async ({ formId, formData, name }) => {
|
|
5529
5570
|
try {
|
|
5530
|
-
|
|
5531
|
-
if (
|
|
5532
|
-
|
|
5571
|
+
let resolvedName = name;
|
|
5572
|
+
if (resolvedName === void 0) {
|
|
5573
|
+
const current = await formRequest("GET", `/${formId}?locationId=${client.locationId}`);
|
|
5574
|
+
const currentName = typeof current === "object" && current !== null && "form" in current ? current.form?.name : void 0;
|
|
5575
|
+
if (typeof currentName !== "string") {
|
|
5576
|
+
throw new Error(`Could not resolve current form name for ${formId} (GET /forms/${formId} returned no string name).`);
|
|
5577
|
+
}
|
|
5578
|
+
resolvedName = currentName;
|
|
5579
|
+
}
|
|
5580
|
+
const result = await formRequest(
|
|
5581
|
+
"POST",
|
|
5582
|
+
buildUpdateFormPath(formId, client.locationId),
|
|
5583
|
+
buildUpdateFormBody(resolvedName, formData)
|
|
5584
|
+
);
|
|
5533
5585
|
return {
|
|
5534
5586
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
5535
5587
|
};
|
|
@@ -5546,7 +5598,7 @@ ${text2}`);
|
|
|
5546
5598
|
},
|
|
5547
5599
|
async ({ name }) => {
|
|
5548
5600
|
try {
|
|
5549
|
-
const result = await formRequest("POST",
|
|
5601
|
+
const result = await formRequest("POST", `/?locationId=${client.locationId}`, {
|
|
5550
5602
|
name,
|
|
5551
5603
|
locationId: client.locationId,
|
|
5552
5604
|
formData: {
|
|
@@ -8297,18 +8349,7 @@ var registry = new TokenRegistry();
|
|
|
8297
8349
|
var fileCreds = readCredentials();
|
|
8298
8350
|
var locationId = process.env.GHL_LOCATION_ID || fileCreds?.ghl_location_id || void 0;
|
|
8299
8351
|
var apiKey = process.env.GHL_API_KEY || fileCreds?.ghl_api_key || void 0;
|
|
8300
|
-
|
|
8301
|
-
process.env.GHL_USER_ID = fileCreds.ghl_user_id;
|
|
8302
|
-
}
|
|
8303
|
-
if (fileCreds?.ghl_firebase_api_key && !process.env.GHL_FIREBASE_API_KEY) {
|
|
8304
|
-
process.env.GHL_FIREBASE_API_KEY = fileCreds.ghl_firebase_api_key;
|
|
8305
|
-
}
|
|
8306
|
-
if (fileCreds?.ghl_firebase_refresh_token && !process.env.GHL_FIREBASE_REFRESH_TOKEN) {
|
|
8307
|
-
process.env.GHL_FIREBASE_REFRESH_TOKEN = fileCreds.ghl_firebase_refresh_token;
|
|
8308
|
-
}
|
|
8309
|
-
if (fileCreds?.ghl_company_id && !process.env.GHL_COMPANY_ID) {
|
|
8310
|
-
process.env.GHL_COMPANY_ID = fileCreds.ghl_company_id;
|
|
8311
|
-
}
|
|
8352
|
+
foldCredentialsIntoEnv(fileCreds);
|
|
8312
8353
|
if (locationId && registry.hasTokens()) {
|
|
8313
8354
|
const token = registry.getToken(locationId);
|
|
8314
8355
|
if (token) {
|
|
@@ -8321,24 +8362,60 @@ var server = new import_mcp.McpServer({
|
|
|
8321
8362
|
name: "elite-dcs-ghl",
|
|
8322
8363
|
version: pkg.version
|
|
8323
8364
|
});
|
|
8324
|
-
var inBootstrapMode = !apiKey || !locationId;
|
|
8325
8365
|
registerMetaTools(server, pkg.version);
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8366
|
+
var inBootstrapMode = true;
|
|
8367
|
+
async function resolveAccessAndRegister() {
|
|
8368
|
+
let licenseVerified = Boolean(fileCreds?.verified_at && fileCreds?.license_key);
|
|
8369
|
+
if (!licenseVerified && apiKey && locationId) {
|
|
8370
|
+
const licEmail = (process.env.GHL_LICENSE_EMAIL || fileCreds?.email)?.trim();
|
|
8371
|
+
const licKey = (process.env.GHL_LICENSE_KEY || fileCreds?.license_key)?.trim();
|
|
8372
|
+
if (licEmail && licKey) {
|
|
8373
|
+
const lic = await validateLicense(licEmail, licKey);
|
|
8374
|
+
if (lic.ok) {
|
|
8375
|
+
licenseVerified = true;
|
|
8376
|
+
try {
|
|
8377
|
+
writeCredentials({
|
|
8378
|
+
license_key: licKey,
|
|
8379
|
+
email: licEmail,
|
|
8380
|
+
verified_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8381
|
+
ghl_api_key: apiKey,
|
|
8382
|
+
ghl_location_id: locationId,
|
|
8383
|
+
...process.env.GHL_COMPANY_ID ? { ghl_company_id: process.env.GHL_COMPANY_ID } : {},
|
|
8384
|
+
...process.env.GHL_USER_ID ? { ghl_user_id: process.env.GHL_USER_ID } : {},
|
|
8385
|
+
...process.env.GHL_FIREBASE_API_KEY ? { ghl_firebase_api_key: process.env.GHL_FIREBASE_API_KEY } : {},
|
|
8386
|
+
...process.env.GHL_FIREBASE_REFRESH_TOKEN ? { ghl_firebase_refresh_token: process.env.GHL_FIREBASE_REFRESH_TOKEN } : {}
|
|
8387
|
+
});
|
|
8388
|
+
process.stderr.write("[ghl-mcp] License validated and cached.\n");
|
|
8389
|
+
} catch {
|
|
8390
|
+
}
|
|
8391
|
+
} else {
|
|
8392
|
+
process.stderr.write(`[ghl-mcp] License check failed: ${lic.error}
|
|
8393
|
+
`);
|
|
8394
|
+
}
|
|
8395
|
+
} else {
|
|
8396
|
+
process.stderr.write(
|
|
8397
|
+
"[ghl-mcp] No verified license. For a headless/server install set GHL_LICENSE_EMAIL + GHL_LICENSE_KEY alongside GHL_API_KEY + GHL_LOCATION_ID; otherwise run setup_ghl_mcp.\n"
|
|
8398
|
+
);
|
|
8399
|
+
}
|
|
8400
|
+
}
|
|
8401
|
+
inBootstrapMode = !apiKey || !locationId || !licenseVerified;
|
|
8402
|
+
if (inBootstrapMode) {
|
|
8403
|
+
process.stderr.write(
|
|
8404
|
+
`[ghl-mcp] Bootstrap mode.
|
|
8405
|
+
[ghl-mcp] Need a valid license plus GHL_API_KEY + GHL_LOCATION_ID (env vars or credentials file at ${credentialsPath()}).
|
|
8330
8406
|
[ghl-mcp] Only setup_ghl_mcp, request_license, and get_mcp_version are available. Run setup_ghl_mcp with your license + GHL credentials \u2014 or request_license if you don't have a license yet.
|
|
8331
8407
|
`
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
} else {
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8408
|
+
);
|
|
8409
|
+
registerSetupTool(server);
|
|
8410
|
+
registerLeadCaptureTool(server);
|
|
8411
|
+
} else {
|
|
8412
|
+
const client = new GHLClient({ apiKey, locationId });
|
|
8413
|
+
registerAllTools(server, client, registry, pkg.version);
|
|
8414
|
+
registerEnableWorkflowBuilderTool(server);
|
|
8415
|
+
if (fileCreds && !process.env.GHL_API_KEY) {
|
|
8416
|
+
process.stderr.write(`[ghl-mcp] Loaded credentials from ${credentialsPath()}
|
|
8341
8417
|
`);
|
|
8418
|
+
}
|
|
8342
8419
|
}
|
|
8343
8420
|
}
|
|
8344
8421
|
async function validateApiKey() {
|
|
@@ -8375,6 +8452,7 @@ async function checkForUpdates() {
|
|
|
8375
8452
|
}
|
|
8376
8453
|
}
|
|
8377
8454
|
async function main() {
|
|
8455
|
+
await resolveAccessAndRegister();
|
|
8378
8456
|
const transport = new import_stdio.StdioServerTransport();
|
|
8379
8457
|
await server.connect(transport);
|
|
8380
8458
|
if (inBootstrapMode) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elitedcs/ghl-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.23.0",
|
|
4
4
|
"mcpName": "io.github.drjerryrelth/ghl-command",
|
|
5
5
|
"description": "GoHighLevel MCP Server for Claude. 212 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",
|
|
@@ -29,9 +29,15 @@
|
|
|
29
29
|
"model-context-protocol",
|
|
30
30
|
"gohighlevel",
|
|
31
31
|
"ghl",
|
|
32
|
+
"gohighlevel-mcp",
|
|
33
|
+
"ghl-automation",
|
|
32
34
|
"crm",
|
|
33
35
|
"claude",
|
|
34
|
-
"anthropic"
|
|
36
|
+
"anthropic",
|
|
37
|
+
"workflow-builder",
|
|
38
|
+
"marketing-automation",
|
|
39
|
+
"ai-agent",
|
|
40
|
+
"agency"
|
|
35
41
|
],
|
|
36
42
|
"author": "Elite DCs, LLC",
|
|
37
43
|
"license": "MIT",
|