@elitedcs/ghl-mcp 3.14.0 → 3.16.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 +35 -0
- package/README.md +1 -1
- package/dist/index.js +286 -8
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.16.0 — Membership/course creates, SMS templates, reviews list (Firebase-gated)
|
|
4
|
+
|
|
5
|
+
**212 tools across 43 modules (+6). Build courses and SMS templates from Claude; read the reviews that come back.**
|
|
6
|
+
|
|
7
|
+
Six new write/read tools, all reverse-engineered from DevTools captures of the live GHL UI against the MCP Testing sandbox and verified end-to-end (each returns 2xx with the exact request shape these tools send). All require Firebase auth — the public bearer key 401s on each — so the no-Firebase tool count stays 163.
|
|
8
|
+
|
|
9
|
+
### New tools (6)
|
|
10
|
+
|
|
11
|
+
- **`create_course`** — create a membership course (a "product") with title + description.
|
|
12
|
+
- **`create_membership_category`** — add a category/module inside a course (groups lessons; supports drip days).
|
|
13
|
+
- **`create_membership_lesson`** — add a lesson (a "post") inside a category, with HTML body and content type.
|
|
14
|
+
- **`create_membership_offer`** — create the access grant that enrolls contacts into one or more courses. Free by default; supports recurring/one-time pricing.
|
|
15
|
+
- **`create_sms_template`** — create an SMS/text template (a "snippet") for the composer and SMS workflow actions; merge fields supported. Also creates email snippets via `type: "email"`.
|
|
16
|
+
- **`list_reviews`** — list the reviews a location has received (Google, Facebook, etc.) with rating, author, text, and reply status. **Fixes the long-standing "No Location Found" error:** the reviews endpoint resolves location through nested `filterParams[locationId][0][value]`, not a flat `locationId`. Responding to a review is still pending (needs a live review to capture).
|
|
17
|
+
|
|
18
|
+
Course/category/lesson/offer hit `backend.leadconnectorhq.com/membership/locations/{loc}/*`; reviews hit `backend.leadconnectorhq.com/reputation/reviews`; SMS templates hit `services.leadconnectorhq.com/snippets/{loc}`. All with the workflow-builder Firebase token.
|
|
19
|
+
|
|
20
|
+
### Still pending
|
|
21
|
+
|
|
22
|
+
Email-campaign delete/send/schedule live in a cross-origin iframe (`email-home-prod.leadconnectorhq.com`) that can't be black-box captured and whose REST paths 404 on the public host — deferred until a capture from inside that iframe.
|
|
23
|
+
|
|
24
|
+
## 3.15.0 — Email-template delete, rename, archive (Firebase-gated)
|
|
25
|
+
|
|
26
|
+
**206 tools across 43 modules (+3). Manage email templates without leaving Claude.**
|
|
27
|
+
|
|
28
|
+
Until now you could create and update email templates through the public API, but deleting or renaming one meant clicking through the GHL UI — the public bearer API 404s on both. Those mutations live on the internal API behind Firebase auth. Confirmed working 2026-05-24 against the MCP Testing sandbox and verified end-to-end (create → rename → archive → unarchive → delete, round-trip clean).
|
|
29
|
+
|
|
30
|
+
### New tools (3, all require Firebase configured)
|
|
31
|
+
|
|
32
|
+
- **`delete_email_template`** — hard delete a template by id. Irreversible. Breaks any workflow email action or draft campaign that references it.
|
|
33
|
+
- **`rename_email_template`** — change a template's display title only; HTML content and sender settings untouched.
|
|
34
|
+
- **`archive_email_template`** — archive (`archived: true`) or restore (`archived: false`) a template. Removes it from the active list without deleting it — the reversible alternative to delete.
|
|
35
|
+
|
|
36
|
+
All three hit `backend.leadconnectorhq.com/emails/builder` with the workflow-builder Firebase token (the GHL UI persists via Firestore, but the REST routes exist server-side). Registered by `registerEmailBuilderInternalTools` in the internal-API block. `update_email_template`'s description now points at `rename_email_template` / `delete_email_template` instead of telling you to use the UI.
|
|
37
|
+
|
|
3
38
|
## 3.14.0 — Multi-tenant Firebase: run the workflow builder in clients' accounts
|
|
4
39
|
|
|
5
40
|
**203 tools across 43 modules (+2). The workflow builder is no longer single-company.**
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GHL Command — GoHighLevel MCP Server
|
|
2
2
|
|
|
3
|
-
**Full GoHighLevel API access for Claude.**
|
|
3
|
+
**Full GoHighLevel API access for Claude.** 212 tools across 43 modules — manage contacts, conversations, pipelines, calendars, funnels, workflows, invoices, custom objects, webhooks, and more. **Includes full workflow builder, funnel/page editor, form builder, pipeline builder, bulk operations, account export, and workflow cloning** — capabilities no other GHL tool offers. **Multi-tenant:** one install can run the workflow builder across multiple clients' GHL accounts.
|
|
4
4
|
|
|
5
5
|
**Distributed via npm as [`@elitedcs/ghl-mcp`](https://www.npmjs.com/package/@elitedcs/ghl-mcp).** Buyers install with one config block — no git, no Node.js setup, no terminal commands. Updates flow automatically (`npx @latest` re-resolves on every Claude restart).
|
|
6
6
|
|
package/dist/index.js
CHANGED
|
@@ -31,8 +31,8 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "@elitedcs/ghl-mcp",
|
|
34
|
-
version: "3.
|
|
35
|
-
description: "GoHighLevel MCP Server for Claude.
|
|
34
|
+
version: "3.16.0",
|
|
35
|
+
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.",
|
|
36
36
|
main: "dist/index.js",
|
|
37
37
|
bin: {
|
|
38
38
|
"ghl-mcp": "dist/index.js"
|
|
@@ -3860,6 +3860,8 @@ function registerBlogTools(server2, client) {
|
|
|
3860
3860
|
|
|
3861
3861
|
// src/tools/emails.ts
|
|
3862
3862
|
var import_zod25 = require("zod");
|
|
3863
|
+
var EMAIL_BUILDER_BASE = "https://backend.leadconnectorhq.com/emails/builder";
|
|
3864
|
+
var SNIPPETS_BASE = "https://services.leadconnectorhq.com/snippets";
|
|
3863
3865
|
var TEMPLATE_TYPES = ["html", "folder", "import", "builder", "blank", "ai_template", "vibe-editor"];
|
|
3864
3866
|
var EDITOR_TYPES = ["html", "builder"];
|
|
3865
3867
|
function registerEmailTools(server2, client) {
|
|
@@ -3930,7 +3932,7 @@ function registerEmailTools(server2, client) {
|
|
|
3930
3932
|
safeTool(
|
|
3931
3933
|
server2,
|
|
3932
3934
|
"update_email_template",
|
|
3933
|
-
"Save HTML content into an existing email template. Use this to update the body of a template after `create_email_template`. The `updatedBy` field is required by GHL; defaults to 'mcp' if not provided. Note: this updates CONTENT only.
|
|
3935
|
+
"Save HTML content into an existing email template. Use this to update the body of a template after `create_email_template`. The `updatedBy` field is required by GHL; defaults to 'mcp' if not provided. Note: this updates CONTENT only. To rename a template use `rename_email_template`; to delete it use `delete_email_template` (both Firebase-gated).",
|
|
3934
3936
|
{
|
|
3935
3937
|
templateId: import_zod25.z.string().describe("The template ID to update (from create_email_template or list_email_templates)."),
|
|
3936
3938
|
html: import_zod25.z.string().describe("The full HTML body of the email. Can include merge fields like {{contact.first_name}}."),
|
|
@@ -3952,6 +3954,116 @@ function registerEmailTools(server2, client) {
|
|
|
3952
3954
|
}
|
|
3953
3955
|
);
|
|
3954
3956
|
}
|
|
3957
|
+
function registerEmailBuilderInternalTools(server2, builderClient) {
|
|
3958
|
+
const client = builderClient;
|
|
3959
|
+
if (!client) return;
|
|
3960
|
+
async function builderRequest(method, path6, body) {
|
|
3961
|
+
const headers = await client.buildHeaders();
|
|
3962
|
+
const response = await fetch(`${EMAIL_BUILDER_BASE}${path6}`, {
|
|
3963
|
+
method,
|
|
3964
|
+
headers,
|
|
3965
|
+
body: body ? JSON.stringify(body) : void 0
|
|
3966
|
+
});
|
|
3967
|
+
if (!response.ok) {
|
|
3968
|
+
const text2 = await response.text();
|
|
3969
|
+
throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${path6}
|
|
3970
|
+
${text2}`);
|
|
3971
|
+
}
|
|
3972
|
+
const text = await response.text();
|
|
3973
|
+
return text ? JSON.parse(text) : { ok: true };
|
|
3974
|
+
}
|
|
3975
|
+
server2.tool(
|
|
3976
|
+
"delete_email_template",
|
|
3977
|
+
"Permanently delete an email template ('builder') by id. This is a HARD delete \u2014 the template is removed, not archived (use archive_email_template if you want a reversible remove). Requires Firebase auth; the public bearer API can't do this. Get the templateId from list_email_templates. WARNING: irreversible. If the template is referenced by a workflow email action or a draft campaign, deleting it will break that reference.",
|
|
3978
|
+
{
|
|
3979
|
+
templateId: import_zod25.z.string().describe("The template id to delete (from list_email_templates, the `id` field)."),
|
|
3980
|
+
locationId: import_zod25.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
3981
|
+
},
|
|
3982
|
+
async ({ templateId, locationId: locationId2 }) => {
|
|
3983
|
+
try {
|
|
3984
|
+
const loc = locationId2 ?? client.locationId;
|
|
3985
|
+
const result = await builderRequest("DELETE", `/data/${templateId}?locationId=${loc}`);
|
|
3986
|
+
return jsonResponse(result);
|
|
3987
|
+
} catch (error) {
|
|
3988
|
+
return errorResponse(error);
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
);
|
|
3992
|
+
server2.tool(
|
|
3993
|
+
"rename_email_template",
|
|
3994
|
+
"Rename an existing email template ('builder'). Updates the display title only; the HTML content and sender settings are left untouched. Requires Firebase auth; the public bearer API can't do this. Get the templateId from list_email_templates.",
|
|
3995
|
+
{
|
|
3996
|
+
templateId: import_zod25.z.string().describe("The template id to rename (from list_email_templates, the `id` field)."),
|
|
3997
|
+
name: import_zod25.z.string().describe("The new display name for the template (e.g., 'May Newsletter \u2014 Final')."),
|
|
3998
|
+
locationId: import_zod25.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
3999
|
+
},
|
|
4000
|
+
async ({ templateId, name, locationId: locationId2 }) => {
|
|
4001
|
+
try {
|
|
4002
|
+
const loc = locationId2 ?? client.locationId;
|
|
4003
|
+
const result = await builderRequest("PATCH", `/${templateId}`, { locationId: loc, name });
|
|
4004
|
+
return jsonResponse(result);
|
|
4005
|
+
} catch (error) {
|
|
4006
|
+
return errorResponse(error);
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
);
|
|
4010
|
+
server2.tool(
|
|
4011
|
+
"archive_email_template",
|
|
4012
|
+
"Archive (or unarchive) an email template ('builder'). Archiving removes it from the active templates list without deleting it \u2014 a reversible alternative to delete_email_template. Requires Firebase auth. Get the templateId from list_email_templates.",
|
|
4013
|
+
{
|
|
4014
|
+
templateId: import_zod25.z.string().describe("The template id to archive/unarchive (from list_email_templates, the `id` field)."),
|
|
4015
|
+
archived: import_zod25.z.boolean().optional().describe("true to archive (default), false to restore from archive."),
|
|
4016
|
+
locationId: import_zod25.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
4017
|
+
},
|
|
4018
|
+
async ({ templateId, archived, locationId: locationId2 }) => {
|
|
4019
|
+
try {
|
|
4020
|
+
const loc = locationId2 ?? client.locationId;
|
|
4021
|
+
const result = await builderRequest("PATCH", `/${templateId}`, { locationId: loc, archived: archived ?? true });
|
|
4022
|
+
return jsonResponse(result);
|
|
4023
|
+
} catch (error) {
|
|
4024
|
+
return errorResponse(error);
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
);
|
|
4028
|
+
server2.tool(
|
|
4029
|
+
"create_sms_template",
|
|
4030
|
+
"Create an SMS/text template (a 'snippet') for quick-insert into the conversations composer and SMS workflow actions. The body can include merge fields like {{contact.first_name}}. Set type to 'email' to create an HTML email snippet instead. Returns the created snippet including its id. Requires Firebase auth \u2014 the public bearer key returns 401 on this endpoint.",
|
|
4031
|
+
{
|
|
4032
|
+
name: import_zod25.z.string().describe("Template/snippet name (shown in the snippets list, not sent to contacts)."),
|
|
4033
|
+
body: import_zod25.z.string().describe("The message body. SMS: plain text with optional merge fields. Email snippet: HTML."),
|
|
4034
|
+
type: import_zod25.z.enum(["sms", "email"]).optional().describe("Snippet type. Defaults to 'sms' (text). Use 'email' for an HTML email snippet."),
|
|
4035
|
+
locationId: import_zod25.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
4036
|
+
},
|
|
4037
|
+
async ({ name, body, type, locationId: locationId2 }) => {
|
|
4038
|
+
try {
|
|
4039
|
+
const loc = locationId2 ?? client.locationId;
|
|
4040
|
+
const headers = await client.buildHeaders();
|
|
4041
|
+
const response = await fetch(`${SNIPPETS_BASE}/${loc}`, {
|
|
4042
|
+
method: "POST",
|
|
4043
|
+
headers,
|
|
4044
|
+
body: JSON.stringify({
|
|
4045
|
+
name,
|
|
4046
|
+
template: { body, attachments: [] },
|
|
4047
|
+
useForLiveChat: false,
|
|
4048
|
+
urlAttachments: [],
|
|
4049
|
+
type: type ?? "sms",
|
|
4050
|
+
isFolder: false,
|
|
4051
|
+
parentId: ""
|
|
4052
|
+
})
|
|
4053
|
+
});
|
|
4054
|
+
if (!response.ok) {
|
|
4055
|
+
const text2 = await response.text();
|
|
4056
|
+
throw new Error(`Snippets API Error ${response.status}: POST /snippets/${loc}
|
|
4057
|
+
${text2}`);
|
|
4058
|
+
}
|
|
4059
|
+
const text = await response.text();
|
|
4060
|
+
return jsonResponse(text ? JSON.parse(text) : { ok: true });
|
|
4061
|
+
} catch (error) {
|
|
4062
|
+
return errorResponse(error);
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
);
|
|
4066
|
+
}
|
|
3955
4067
|
|
|
3956
4068
|
// src/tools/trigger-links.ts
|
|
3957
4069
|
var import_zod26 = require("zod");
|
|
@@ -5658,7 +5770,7 @@ async function validateFirebase(firebaseKey, refreshToken) {
|
|
|
5658
5770
|
function registerSetupTool(server2) {
|
|
5659
5771
|
server2.tool(
|
|
5660
5772
|
"setup_ghl_mcp",
|
|
5661
|
-
"First-run setup for GHL Command MCP. Validates your license and GHL credentials, then writes them to a per-user credentials file. Restart Claude after this completes to load all
|
|
5773
|
+
"First-run setup for GHL Command MCP. Validates your license and GHL credentials, then writes them to a per-user credentials file. Restart Claude after this completes to load all 212 tools (163 if you skip the optional Firebase fields; add Firebase later with enable_workflow_builder).",
|
|
5662
5774
|
{
|
|
5663
5775
|
email: import_zod37.z.string().email().describe("Email used at purchase."),
|
|
5664
5776
|
license_key: import_zod37.z.string().min(20).describe("License key from your purchase email."),
|
|
@@ -5713,7 +5825,7 @@ Note: Firebase credentials rejected (${fb.error}). Saved without Workflow Builde
|
|
|
5713
5825
|
ghl_firebase_api_key: workflowBuilderEnabled ? args.ghl_firebase_api_key?.trim() : void 0,
|
|
5714
5826
|
ghl_firebase_refresh_token: workflowBuilderEnabled ? args.ghl_firebase_refresh_token?.trim() : void 0
|
|
5715
5827
|
});
|
|
5716
|
-
const toolCount = workflowBuilderEnabled ? "
|
|
5828
|
+
const toolCount = workflowBuilderEnabled ? "212" : "163";
|
|
5717
5829
|
const wfLine = workflowBuilderEnabled ? "Workflow Builder: enabled." : "Workflow Builder: not configured (optional).";
|
|
5718
5830
|
const wfTip = workflowBuilderEnabled ? "" : "\nTo enable Workflow Builder later (8 extra tools): run enable_workflow_builder with your three Firebase values. No need to re-enter license/API key/location ID.";
|
|
5719
5831
|
return {
|
|
@@ -6838,6 +6950,43 @@ ${text2}`);
|
|
|
6838
6950
|
}
|
|
6839
6951
|
}
|
|
6840
6952
|
);
|
|
6953
|
+
server2.tool(
|
|
6954
|
+
"list_reviews",
|
|
6955
|
+
"List the reviews a location has received (Google, Facebook, etc.) with rating, author, text, reply status, and source. Supports paging and an optional rating filter. NOTE: location is resolved through nested filter params internally \u2014 a flat locationId is what caused the long-standing 'No Location Found' error, now fixed. Responding to a review is not yet available via API. Requires Firebase auth.",
|
|
6956
|
+
{
|
|
6957
|
+
locationId: import_zod43.z.string().optional().describe("Location ID. Falls back to the active builder client's location."),
|
|
6958
|
+
pageNumber: import_zod43.z.number().optional().describe("1-based page number. Defaults to 1."),
|
|
6959
|
+
pageSize: import_zod43.z.number().optional().describe("Results per page. Defaults to 10."),
|
|
6960
|
+
rating: import_zod43.z.number().optional().describe("Optional: only return reviews with this star rating (1-5)."),
|
|
6961
|
+
includeDeleted: import_zod43.z.boolean().optional().describe("Include deleted reviews. Defaults to false.")
|
|
6962
|
+
},
|
|
6963
|
+
async ({ locationId: locationId2, pageNumber, pageSize, rating, includeDeleted }) => {
|
|
6964
|
+
try {
|
|
6965
|
+
const loc = locationId2 ?? client.locationId;
|
|
6966
|
+
const q = [
|
|
6967
|
+
`filterParams[locationId][0][value]=${encodeURIComponent(loc)}`,
|
|
6968
|
+
`filterParams[locationId][0][condition]=eq`,
|
|
6969
|
+
`filterParams[deleted][0][value]=${includeDeleted ? "true" : "false"}`,
|
|
6970
|
+
`filterParams[deleted][0][condition]=eq`
|
|
6971
|
+
];
|
|
6972
|
+
if (rating !== void 0) {
|
|
6973
|
+
q.push(`filterParams[rating][0][value]=${rating}`);
|
|
6974
|
+
q.push(`filterParams[rating][0][condition]=eq`);
|
|
6975
|
+
}
|
|
6976
|
+
q.push(`sortParams[dateAdded]=-1`);
|
|
6977
|
+
q.push(`pageNumber=${pageNumber ?? 1}`);
|
|
6978
|
+
q.push(`pageSize=${pageSize ?? 10}`);
|
|
6979
|
+
const query = q.map((p) => {
|
|
6980
|
+
const i = p.indexOf("=");
|
|
6981
|
+
return `${encodeURIComponent(p.slice(0, i))}=${p.slice(i + 1)}`;
|
|
6982
|
+
}).join("&");
|
|
6983
|
+
const result = await reputationRequest("GET", `/reviews?${query}`);
|
|
6984
|
+
return jsonResponse(result);
|
|
6985
|
+
} catch (error) {
|
|
6986
|
+
return errorResponse(error);
|
|
6987
|
+
}
|
|
6988
|
+
}
|
|
6989
|
+
);
|
|
6841
6990
|
}
|
|
6842
6991
|
|
|
6843
6992
|
// src/tools/email-campaigns.ts
|
|
@@ -6901,12 +7050,16 @@ var MEMBERSHIP_BASE = "https://backend.leadconnectorhq.com/membership";
|
|
|
6901
7050
|
function registerMembershipTools(server2, builderClient) {
|
|
6902
7051
|
const client = builderClient;
|
|
6903
7052
|
if (!client) return;
|
|
6904
|
-
async function membershipRequest(path6) {
|
|
7053
|
+
async function membershipRequest(path6, method = "GET", body) {
|
|
6905
7054
|
const headers = await client.buildHeaders();
|
|
6906
|
-
const response = await fetch(`${MEMBERSHIP_BASE}${path6}`, {
|
|
7055
|
+
const response = await fetch(`${MEMBERSHIP_BASE}${path6}`, {
|
|
7056
|
+
method,
|
|
7057
|
+
headers,
|
|
7058
|
+
body: body ? JSON.stringify(body) : void 0
|
|
7059
|
+
});
|
|
6907
7060
|
if (!response.ok) {
|
|
6908
7061
|
const text2 = await response.text();
|
|
6909
|
-
throw new Error(`Membership API Error ${response.status}:
|
|
7062
|
+
throw new Error(`Membership API Error ${response.status}: ${method} ${path6}
|
|
6910
7063
|
${text2}`);
|
|
6911
7064
|
}
|
|
6912
7065
|
const text = await response.text();
|
|
@@ -6963,6 +7116,130 @@ ${text2}`);
|
|
|
6963
7116
|
}
|
|
6964
7117
|
}
|
|
6965
7118
|
);
|
|
7119
|
+
server2.tool(
|
|
7120
|
+
"create_course",
|
|
7121
|
+
"Create a membership course (a 'product') in a location. Creates the course shell with a title and description; add categories (create_membership_category) and lessons (create_membership_lesson) into it, and an offer (create_membership_offer) to grant access. Returns the new product, including its id. Requires Firebase auth.",
|
|
7122
|
+
{
|
|
7123
|
+
title: import_zod45.z.string().describe("Course title."),
|
|
7124
|
+
description: import_zod45.z.string().optional().describe("Course description. Defaults to empty."),
|
|
7125
|
+
locationId: import_zod45.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
7126
|
+
},
|
|
7127
|
+
async ({ title, description, locationId: locationId2 }) => {
|
|
7128
|
+
try {
|
|
7129
|
+
const loc = locationId2 ?? client.locationId;
|
|
7130
|
+
const result = await membershipRequest(`/locations/${loc}/products`, "POST", {
|
|
7131
|
+
title,
|
|
7132
|
+
description: description ?? ""
|
|
7133
|
+
});
|
|
7134
|
+
return jsonResponse(result);
|
|
7135
|
+
} catch (error) {
|
|
7136
|
+
return errorResponse(error);
|
|
7137
|
+
}
|
|
7138
|
+
}
|
|
7139
|
+
);
|
|
7140
|
+
server2.tool(
|
|
7141
|
+
"create_membership_category",
|
|
7142
|
+
"Create a category (module/section) inside a membership course. Categories group lessons. Needs the productId of the course (from create_course or list_membership_offers). Returns the new category, including its id (use it as categoryId when creating lessons). Requires Firebase auth.",
|
|
7143
|
+
{
|
|
7144
|
+
title: import_zod45.z.string().describe("Category title (e.g. 'Module 1')."),
|
|
7145
|
+
productId: import_zod45.z.string().describe("The course/product id this category belongs to."),
|
|
7146
|
+
description: import_zod45.z.string().optional().describe("Category description. Defaults to empty."),
|
|
7147
|
+
visibility: import_zod45.z.enum(["published", "draft"]).optional().describe("'published' (default) or 'draft'."),
|
|
7148
|
+
sequenceNo: import_zod45.z.number().optional().describe("Display order within the course. Defaults to 0."),
|
|
7149
|
+
dripDays: import_zod45.z.number().optional().describe("Days after enrollment before this category unlocks. Defaults to 0 (no drip)."),
|
|
7150
|
+
locationId: import_zod45.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
7151
|
+
},
|
|
7152
|
+
async ({ title, productId, description, visibility, sequenceNo, dripDays, locationId: locationId2 }) => {
|
|
7153
|
+
try {
|
|
7154
|
+
const loc = locationId2 ?? client.locationId;
|
|
7155
|
+
const result = await membershipRequest(`/locations/${loc}/categories`, "POST", {
|
|
7156
|
+
title,
|
|
7157
|
+
productId,
|
|
7158
|
+
visibility: visibility ?? "published",
|
|
7159
|
+
sequenceNo: sequenceNo ?? 0,
|
|
7160
|
+
dripDays: dripDays ?? 0,
|
|
7161
|
+
description: description ?? "",
|
|
7162
|
+
parentCategory: null,
|
|
7163
|
+
posterImage: "",
|
|
7164
|
+
lockedBy: null,
|
|
7165
|
+
lockedByCategory: null,
|
|
7166
|
+
commentPermission: null,
|
|
7167
|
+
metadata: null
|
|
7168
|
+
});
|
|
7169
|
+
return jsonResponse(result);
|
|
7170
|
+
} catch (error) {
|
|
7171
|
+
return errorResponse(error);
|
|
7172
|
+
}
|
|
7173
|
+
}
|
|
7174
|
+
);
|
|
7175
|
+
server2.tool(
|
|
7176
|
+
"create_membership_lesson",
|
|
7177
|
+
"Create a lesson (a 'post') inside a membership course category. Needs both the categoryId (from create_membership_category) and the productId of the course. Description is the lesson body as HTML. Returns the new lesson, including its id. Requires Firebase auth.",
|
|
7178
|
+
{
|
|
7179
|
+
title: import_zod45.z.string().describe("Lesson title."),
|
|
7180
|
+
categoryId: import_zod45.z.string().describe("The category id this lesson belongs to (from create_membership_category)."),
|
|
7181
|
+
productId: import_zod45.z.string().describe("The course/product id this lesson belongs to."),
|
|
7182
|
+
description: import_zod45.z.string().optional().describe("Lesson body as HTML. Defaults to empty."),
|
|
7183
|
+
contentType: import_zod45.z.enum(["video", "audio", "text", "pdf", "assignment"]).optional().describe("Lesson content type. Defaults to 'video'."),
|
|
7184
|
+
visibility: import_zod45.z.enum(["published", "draft"]).optional().describe("'published' (default) or 'draft'."),
|
|
7185
|
+
sequenceNo: import_zod45.z.number().optional().describe("Display order within the category. Defaults to 0."),
|
|
7186
|
+
locationId: import_zod45.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
7187
|
+
},
|
|
7188
|
+
async ({ title, categoryId, productId, description, contentType, visibility, sequenceNo, locationId: locationId2 }) => {
|
|
7189
|
+
try {
|
|
7190
|
+
const loc = locationId2 ?? client.locationId;
|
|
7191
|
+
const result = await membershipRequest(`/locations/${loc}/posts`, "POST", {
|
|
7192
|
+
title,
|
|
7193
|
+
description: description ?? "",
|
|
7194
|
+
categoryId,
|
|
7195
|
+
productId,
|
|
7196
|
+
visibility: visibility ?? "published",
|
|
7197
|
+
sequenceNo: sequenceNo ?? 0,
|
|
7198
|
+
posterImage: null,
|
|
7199
|
+
commentStatus: "visible",
|
|
7200
|
+
contentType: contentType ?? "video",
|
|
7201
|
+
commentPermission: "enabled",
|
|
7202
|
+
lockedByPost: null,
|
|
7203
|
+
lockedByCategory: null,
|
|
7204
|
+
certificateTemplateId: null,
|
|
7205
|
+
metaData: null,
|
|
7206
|
+
contentId: null
|
|
7207
|
+
});
|
|
7208
|
+
return jsonResponse(result);
|
|
7209
|
+
} catch (error) {
|
|
7210
|
+
return errorResponse(error);
|
|
7211
|
+
}
|
|
7212
|
+
}
|
|
7213
|
+
);
|
|
7214
|
+
server2.tool(
|
|
7215
|
+
"create_membership_offer",
|
|
7216
|
+
"Create a membership offer \u2014 the access grant that enrolls contacts into one or more courses/products. Link it to course product ids. Defaults to a free offer; for paid, set type to 'recurring' or 'one_time' with an amount. Returns the new offer, including its id (referenced by the offer_access_granted trigger). Requires Firebase auth.",
|
|
7217
|
+
{
|
|
7218
|
+
title: import_zod45.z.string().describe("Offer title (shown at checkout / in the offer list)."),
|
|
7219
|
+
productIds: import_zod45.z.array(import_zod45.z.string()).describe("Course/product ids this offer grants access to (from create_course or list_membership_offers)."),
|
|
7220
|
+
type: import_zod45.z.enum(["free", "recurring", "one_time"]).optional().describe("Offer type. Defaults to 'free'."),
|
|
7221
|
+
amount: import_zod45.z.number().optional().describe("Price for paid offers. Defaults to 0 (free)."),
|
|
7222
|
+
currency: import_zod45.z.string().optional().describe("Currency code for paid offers. Defaults to 'USD'."),
|
|
7223
|
+
locationId: import_zod45.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
7224
|
+
},
|
|
7225
|
+
async ({ title, productIds, type, amount, currency, locationId: locationId2 }) => {
|
|
7226
|
+
try {
|
|
7227
|
+
const loc = locationId2 ?? client.locationId;
|
|
7228
|
+
const result = await membershipRequest(`/locations/${loc}/offers`, "POST", {
|
|
7229
|
+
title,
|
|
7230
|
+
type: type ?? "free",
|
|
7231
|
+
isLivePaymentMode: true,
|
|
7232
|
+
locationId: loc,
|
|
7233
|
+
productIds,
|
|
7234
|
+
amount: amount ?? 0,
|
|
7235
|
+
currency: currency ?? "USD"
|
|
7236
|
+
});
|
|
7237
|
+
return jsonResponse(result);
|
|
7238
|
+
} catch (error) {
|
|
7239
|
+
return errorResponse(error);
|
|
7240
|
+
}
|
|
7241
|
+
}
|
|
7242
|
+
);
|
|
6966
7243
|
}
|
|
6967
7244
|
|
|
6968
7245
|
// src/tools/template-deployer.ts
|
|
@@ -7863,6 +8140,7 @@ function registerAllTools(server2, client, registry2, mcpVersion) {
|
|
|
7863
8140
|
registerSmartListTools(server2, builderClient);
|
|
7864
8141
|
registerReputationTools(server2, builderClient);
|
|
7865
8142
|
registerEmailCampaignTools(server2, builderClient);
|
|
8143
|
+
registerEmailBuilderInternalTools(server2, builderClient);
|
|
7866
8144
|
registerMembershipTools(server2, builderClient);
|
|
7867
8145
|
registerValidatorTools(server2, client, builderClient);
|
|
7868
8146
|
registerDiagnosticTools(server2, mcpVersion ?? "unknown", client, builderClient, registry2 ?? null);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elitedcs/ghl-mcp",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "GoHighLevel MCP Server for Claude.
|
|
3
|
+
"version": "3.16.0",
|
|
4
|
+
"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.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ghl-mcp": "dist/index.js"
|