@elitedcs/ghl-mcp 3.83.6 → 3.83.7
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 +29 -0
- package/README.md +1 -1
- package/dist/index.js +72 -42
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 3.83.7 — the cockpit answers only to its own address, and keeps its key to itself
|
|
6
|
+
|
|
7
|
+
**Security fix. Update to this version.**
|
|
8
|
+
|
|
9
|
+
The cockpit runs on your own computer and only accepts connections from it. Two
|
|
10
|
+
things it should have refused, it did not:
|
|
11
|
+
|
|
12
|
+
- **A design run's details were readable without the cockpit's key.** Anything
|
|
13
|
+
able to reach the cockpit on your computer could list the Design Studio runs it
|
|
14
|
+
had started: the brand, headline, points and audience you typed, the run's log,
|
|
15
|
+
and the folder it saved to, which includes your computer's user name.
|
|
16
|
+
- **The cockpit handed its key to a request that used a different address for
|
|
17
|
+
it.** A web page that tricks your browser into reaching the cockpit under
|
|
18
|
+
another name could have picked up that key, and with it read what the cockpit
|
|
19
|
+
reads for your clients: intake answers, uploaded intake documents, what each
|
|
20
|
+
account already has set up, and your snapshot list. Nothing could be changed
|
|
21
|
+
that way; every change the cockpit makes kept refusing such requests. No
|
|
22
|
+
GoHighLevel key was exposed by either path.
|
|
23
|
+
|
|
24
|
+
Now the cockpit refuses any request that is not addressed to `localhost` or
|
|
25
|
+
`127.0.0.1` on its own port, before it does anything else. It gives its key only
|
|
26
|
+
to its own pages opened on your computer, never to anything else. Design Studio's
|
|
27
|
+
run list and progress need the key, like the cockpit's other private reads. And a
|
|
28
|
+
request that spells a page's address in an unusual way is refused rather than
|
|
29
|
+
guessed at.
|
|
30
|
+
|
|
31
|
+
Nothing changes in how you open it: the address the cockpit opens for you is one of
|
|
32
|
+
the two it accepts.
|
|
33
|
+
|
|
5
34
|
## 3.83.6 — what a design run actually costs you, measured
|
|
6
35
|
|
|
7
36
|
3.83.5 gave a running job a clock. This gives it an honest number on it.
|
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -28605,7 +28605,7 @@ var require_package = __commonJS({
|
|
|
28605
28605
|
"package.json"(exports2, module2) {
|
|
28606
28606
|
module2.exports = {
|
|
28607
28607
|
name: "@elitedcs/ghl-mcp",
|
|
28608
|
-
version: "3.83.
|
|
28608
|
+
version: "3.83.7",
|
|
28609
28609
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
28610
28610
|
description: "GoHighLevel MCP Server for Claude. 250 tools \u2014 full CRM, automation, marketing control, account-wide workflow audit, live funnel-capture verification, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
|
|
28611
28611
|
main: "dist/index.js",
|
|
@@ -32442,7 +32442,7 @@ document.getElementById("make").addEventListener("submit",async function(e){
|
|
|
32442
32442
|
document.getElementById("mk-cancel").hidden=false;
|
|
32443
32443
|
started=Date.now();passes=0;tick();ticker=setInterval(tick,1000);
|
|
32444
32444
|
poll=setInterval(async function(){
|
|
32445
|
-
var s=await (await fetch("/api/design/run?id="+encodeURIComponent(runId))).json();
|
|
32445
|
+
var s=await (await fetch("/api/design/run?id="+encodeURIComponent(runId),{headers:{"x-command-os-seat":KEY}})).json();
|
|
32446
32446
|
var lines=s.tail||[];
|
|
32447
32447
|
show(lines.join("\\n"));
|
|
32448
32448
|
passes=lines.filter(function(l){return l.trim().slice(0,5)==="pass "}).length;
|
|
@@ -33985,9 +33985,11 @@ __export(dashboard_exports, {
|
|
|
33985
33985
|
insideDir: () => insideDir,
|
|
33986
33986
|
intakeInput: () => intakeInput,
|
|
33987
33987
|
intakeRequestError: () => intakeRequestError,
|
|
33988
|
+
isAllowedCockpitHost: () => isAllowedCockpitHost,
|
|
33988
33989
|
isLoopback: () => isLoopback,
|
|
33989
33990
|
launcherAlreadyOffered: () => launcherAlreadyOffered,
|
|
33990
33991
|
listClientDocs: () => listClientDocs,
|
|
33992
|
+
mayServeSeat: () => mayServeSeat,
|
|
33991
33993
|
memberFacingError: () => memberFacingError,
|
|
33992
33994
|
newClientFailure: () => newClientFailure,
|
|
33993
33995
|
normalizedIntake: () => normalizedIntake,
|
|
@@ -35752,22 +35754,45 @@ function checkSeatToken(req, expected) {
|
|
|
35752
35754
|
}
|
|
35753
35755
|
return null;
|
|
35754
35756
|
}
|
|
35755
|
-
function
|
|
35756
|
-
const
|
|
35757
|
-
|
|
35758
|
-
|
|
35759
|
-
|
|
35760
|
-
|
|
35761
|
-
}
|
|
35762
|
-
|
|
35763
|
-
|
|
35764
|
-
|
|
35757
|
+
function cockpitHeader(req, name) {
|
|
35758
|
+
const value = req.headers[name];
|
|
35759
|
+
return Array.isArray(value) ? value.join(",") : value ?? "";
|
|
35760
|
+
}
|
|
35761
|
+
function isAllowedCockpitHost(req, port) {
|
|
35762
|
+
const host = cockpitHeader(req, "host").toLowerCase();
|
|
35763
|
+
return host === `127.0.0.1:${port}` || host === `localhost:${port}`;
|
|
35764
|
+
}
|
|
35765
|
+
function mayServeSeat(req, port) {
|
|
35766
|
+
return isLoopback(req) && isAllowedCockpitHost(req, port);
|
|
35767
|
+
}
|
|
35768
|
+
function cockpitPath(req) {
|
|
35769
|
+
return (req.url || "/").split("?")[0];
|
|
35770
|
+
}
|
|
35771
|
+
function checkCockpitPath(req) {
|
|
35772
|
+
const pathname = cockpitPath(req);
|
|
35773
|
+
if (!pathname.startsWith("/") || /\/\/|\\|#|[\x00-\x20\x7f]|%(?:2e|2f|5c)|%(?![0-9a-f]{2})/i.test(pathname) || pathname.split("/").some((segment) => segment === "." || segment === "..")) {
|
|
35774
|
+
return { status: 400, error: "Use a canonical cockpit path." };
|
|
35765
35775
|
}
|
|
35766
|
-
|
|
35767
|
-
|
|
35776
|
+
return null;
|
|
35777
|
+
}
|
|
35778
|
+
function checkCockpitOrigin(req, port) {
|
|
35779
|
+
const origin = cockpitHeader(req, "origin").toLowerCase();
|
|
35780
|
+
if (req.headers.origin !== void 0 && origin !== `http://127.0.0.1:${port}` && origin !== `http://localhost:${port}`) {
|
|
35768
35781
|
return { status: 403, error: "cross-origin request rejected" };
|
|
35769
35782
|
}
|
|
35770
|
-
|
|
35783
|
+
return null;
|
|
35784
|
+
}
|
|
35785
|
+
function checkMutationGuard(req, port) {
|
|
35786
|
+
if (!isAllowedCockpitHost(req, port)) {
|
|
35787
|
+
return { status: 403, error: "This Host is not allowed to access the cockpit." };
|
|
35788
|
+
}
|
|
35789
|
+
const pathDenied = checkCockpitPath(req);
|
|
35790
|
+
if (pathDenied) return pathDenied;
|
|
35791
|
+
const method = (req.method || "GET").toUpperCase();
|
|
35792
|
+
if (method === "GET" || method === "HEAD" || method === "OPTIONS") return null;
|
|
35793
|
+
const originDenied = checkCockpitOrigin(req, port);
|
|
35794
|
+
if (originDenied) return originDenied;
|
|
35795
|
+
if (cockpitHeader(req, "content-type").split(";")[0].trim().toLowerCase() !== "application/json") {
|
|
35771
35796
|
return { status: 415, error: "expected application/json" };
|
|
35772
35797
|
}
|
|
35773
35798
|
return null;
|
|
@@ -35775,8 +35800,13 @@ function checkMutationGuard(req, port) {
|
|
|
35775
35800
|
function cockpitGate(req, seatToken, port) {
|
|
35776
35801
|
const denied = checkMutationGuard(req, port) ?? checkSeatToken(req, seatToken);
|
|
35777
35802
|
if (denied) return denied;
|
|
35778
|
-
const pathname = (req
|
|
35779
|
-
if (SEAT_GUARDED_READS.includes(pathname)
|
|
35803
|
+
const pathname = cockpitPath(req);
|
|
35804
|
+
if (SEAT_GUARDED_READS.includes(pathname) || pathname === "/api/design" || pathname.startsWith("/api/design/")) {
|
|
35805
|
+
if (cockpitHeader(req, "sec-fetch-site").toLowerCase() === "cross-site") {
|
|
35806
|
+
return { status: 403, error: "cross-site request rejected" };
|
|
35807
|
+
}
|
|
35808
|
+
return checkCockpitOrigin(req, port) ?? checkSeatToken({ method: "POST", headers: req.headers }, seatToken);
|
|
35809
|
+
}
|
|
35780
35810
|
return null;
|
|
35781
35811
|
}
|
|
35782
35812
|
function guarded(seatToken, port, routes) {
|
|
@@ -35901,7 +35931,7 @@ async function runDashboard(argv) {
|
|
|
35901
35931
|
questions: (id) => composeQuestionSet({ industry: readCockpitState().accounts?.[id]?.industry, agency: readOverlay() })
|
|
35902
35932
|
};
|
|
35903
35933
|
const server2 = http.createServer(guarded(seatToken, port, (req, res) => {
|
|
35904
|
-
const url = req
|
|
35934
|
+
const url = cockpitPath(req);
|
|
35905
35935
|
if (req.method === "POST" && url === "/api/client-assessment") {
|
|
35906
35936
|
readBody(req, res, 2e4, async (body) => {
|
|
35907
35937
|
try {
|
|
@@ -35923,10 +35953,10 @@ async function runDashboard(argv) {
|
|
|
35923
35953
|
return;
|
|
35924
35954
|
}
|
|
35925
35955
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
35926
|
-
res.end(html(currentRoster(),
|
|
35956
|
+
res.end(html(currentRoster(), mayServeSeat(req, port) ? seatToken : "", "for", cap));
|
|
35927
35957
|
return;
|
|
35928
35958
|
}
|
|
35929
|
-
if (req.method === "GET" && url
|
|
35959
|
+
if (req.method === "GET" && url === "/api/capability") {
|
|
35930
35960
|
const u = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
35931
35961
|
const cap = capabilityByKey(u.searchParams.get("key") ?? "");
|
|
35932
35962
|
const locationId2 = u.searchParams.get("locationId") ?? "";
|
|
@@ -36061,7 +36091,7 @@ async function runDashboard(argv) {
|
|
|
36061
36091
|
});
|
|
36062
36092
|
return;
|
|
36063
36093
|
}
|
|
36064
|
-
if (req.method === "GET" && url
|
|
36094
|
+
if (req.method === "GET" && url === "/api/intake-documents") {
|
|
36065
36095
|
const u = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
36066
36096
|
const seat = checkSeatToken({ method: "POST", headers: req.headers }, seatToken);
|
|
36067
36097
|
if (seat) {
|
|
@@ -36109,7 +36139,7 @@ async function runDashboard(argv) {
|
|
|
36109
36139
|
const boardScreen = BOARD_ROUTES[url];
|
|
36110
36140
|
if (req.method === "GET" && boardScreen) {
|
|
36111
36141
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
36112
|
-
res.end(html(currentRoster(),
|
|
36142
|
+
res.end(html(currentRoster(), mayServeSeat(req, port) ? seatToken : "", boardScreen));
|
|
36113
36143
|
return;
|
|
36114
36144
|
}
|
|
36115
36145
|
if ((req.method === "GET" || req.method === "HEAD") && (url === "/assessment" || url === "/assessment.html")) {
|
|
@@ -36121,7 +36151,7 @@ async function runDashboard(argv) {
|
|
|
36121
36151
|
"X-Robots-Tag": "noindex, nofollow"
|
|
36122
36152
|
});
|
|
36123
36153
|
const roster2 = currentRoster().map((r) => ({ locationId: r.locationId, name: r.name }));
|
|
36124
|
-
const clientsTag = `<script>window.COMMAND_OS_CLIENTS=${scriptJson(roster2)};window.COMMAND_OS_SEAT=${scriptJson(
|
|
36154
|
+
const clientsTag = `<script>window.COMMAND_OS_CLIENTS=${scriptJson(roster2)};window.COMMAND_OS_SEAT=${scriptJson(mayServeSeat(req, port) ? seatToken : "")};</script>`;
|
|
36125
36155
|
res.end(req.method === "HEAD" ? void 0 : withCockpitShell(ASSESSMENT_HTML, "assessment", assessmentAgencyTag() + clientsTag));
|
|
36126
36156
|
return;
|
|
36127
36157
|
}
|
|
@@ -36164,14 +36194,14 @@ async function runDashboard(argv) {
|
|
|
36164
36194
|
});
|
|
36165
36195
|
return;
|
|
36166
36196
|
}
|
|
36167
|
-
if (req.method === "GET" && url
|
|
36197
|
+
if (req.method === "GET" && url === "/api/client-preflight") {
|
|
36168
36198
|
const seat = checkSeatToken({ method: "POST", headers: req.headers }, seatToken);
|
|
36169
36199
|
if (seat) {
|
|
36170
36200
|
res.writeHead(seat.status);
|
|
36171
36201
|
res.end(JSON.stringify({ ok: false, error: seat.error }));
|
|
36172
36202
|
return;
|
|
36173
36203
|
}
|
|
36174
|
-
const id = new URL(url, "http://x").searchParams.get("locationId") || "";
|
|
36204
|
+
const id = new URL(req.url || "/", "http://x").searchParams.get("locationId") || "";
|
|
36175
36205
|
if (!readableAccount(id)) {
|
|
36176
36206
|
res.writeHead(400);
|
|
36177
36207
|
res.end(JSON.stringify({ ok: false, error: "Choose a registered client." }));
|
|
@@ -36327,8 +36357,8 @@ async function runDashboard(argv) {
|
|
|
36327
36357
|
});
|
|
36328
36358
|
return;
|
|
36329
36359
|
}
|
|
36330
|
-
if (req.method === "GET" && url
|
|
36331
|
-
const industry = new URL(url, "http://x").searchParams.get("industry") || void 0;
|
|
36360
|
+
if (req.method === "GET" && url === "/api/intake") {
|
|
36361
|
+
const industry = new URL(req.url || "/", "http://x").searchParams.get("industry") || void 0;
|
|
36332
36362
|
const agency = readOverlay();
|
|
36333
36363
|
const composed = composeQuestionSet({ industry, agency });
|
|
36334
36364
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -36357,10 +36387,10 @@ async function runDashboard(argv) {
|
|
|
36357
36387
|
});
|
|
36358
36388
|
return;
|
|
36359
36389
|
}
|
|
36360
|
-
if (req.method === "GET" && url
|
|
36390
|
+
if (req.method === "GET" && url === "/api/client-intake") {
|
|
36361
36391
|
(async () => {
|
|
36362
36392
|
try {
|
|
36363
|
-
const locationId2 = new URL(url, "http://x").searchParams.get("locationId") || "";
|
|
36393
|
+
const locationId2 = new URL(req.url || "/", "http://x").searchParams.get("locationId") || "";
|
|
36364
36394
|
const seat = checkSeatToken({ method: "POST", headers: req.headers }, seatToken);
|
|
36365
36395
|
if (seat) {
|
|
36366
36396
|
res.writeHead(seat.status, { "Content-Type": "application/json" });
|
|
@@ -36384,7 +36414,7 @@ async function runDashboard(argv) {
|
|
|
36384
36414
|
res.end(JSON.stringify({ ok: true, ...snap, questions, industry, inputHash: hash, unansweredRequired: questions.filter((q2) => q2.required && !String(answers[q2.label] ?? answers[q2.key] ?? "").trim()).map((q2) => q2.label), costs: readRunCosts().filter((r) => r.locationId === locationId2 && (r.kind === "intake extraction" || r.kind === "engine-check")).slice(-10), submittedAnswers: snap.answers ?? {}, answers, savedAt: st.intakeDrafts?.[locationId2]?.savedAt, connectionPending: !c }));
|
|
36385
36415
|
} catch (e) {
|
|
36386
36416
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
36387
|
-
logQuietly(`client-intake ${new URL(url, "http://x").searchParams.get("locationId") || ""}: ${e instanceof Error ? e.message : String(e)}`);
|
|
36417
|
+
logQuietly(`client-intake ${new URL(req.url || "/", "http://x").searchParams.get("locationId") || ""}: ${e instanceof Error ? e.message : String(e)}`);
|
|
36388
36418
|
res.end(JSON.stringify({ ok: false, error: intakeRequestError(e) }));
|
|
36389
36419
|
}
|
|
36390
36420
|
})();
|
|
@@ -36508,12 +36538,12 @@ async function runDashboard(argv) {
|
|
|
36508
36538
|
if (!rows.some((r) => r.locationId === locationId2)) rows.push({ locationId: locationId2, name: e.clientName || "(no account connected)", engagement: e });
|
|
36509
36539
|
}
|
|
36510
36540
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
36511
|
-
res.end(agencyProfilePageHtml(readAgencyProfile(), rows,
|
|
36541
|
+
res.end(agencyProfilePageHtml(readAgencyProfile(), rows, mayServeSeat(req, port) ? seatToken : ""));
|
|
36512
36542
|
return;
|
|
36513
36543
|
}
|
|
36514
36544
|
if (req.method === "GET" && url === "/brands") {
|
|
36515
36545
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
36516
|
-
res.end(brandsPageHtml(listBrands(), houseLooks(), listReferenceLooks(),
|
|
36546
|
+
res.end(brandsPageHtml(listBrands(), houseLooks(), listReferenceLooks(), mayServeSeat(req, port) ? seatToken : ""));
|
|
36517
36547
|
return;
|
|
36518
36548
|
}
|
|
36519
36549
|
if (req.method === "GET" && /^\/brand\/[a-z0-9-]+\/screenshot\.png$/.test(url)) {
|
|
@@ -36534,7 +36564,7 @@ async function runDashboard(argv) {
|
|
|
36534
36564
|
const dna = houseLooks().find((d) => d.slug === slug3) ?? readBrand(slug3);
|
|
36535
36565
|
const dir = brandExists(slug3) ? brandDir(slug3) : void 0;
|
|
36536
36566
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
36537
|
-
res.end(brandCardHtml(dna, { screenshot: dir ? fs27.existsSync(path26.join(dir, "screenshot.png")) : false, dir, seatToken:
|
|
36567
|
+
res.end(brandCardHtml(dna, { screenshot: dir ? fs27.existsSync(path26.join(dir, "screenshot.png")) : false, dir, seatToken: mayServeSeat(req, port) ? seatToken : "" }));
|
|
36538
36568
|
} catch (e) {
|
|
36539
36569
|
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
36540
36570
|
res.end(e.message);
|
|
@@ -36560,8 +36590,8 @@ async function runDashboard(argv) {
|
|
|
36560
36590
|
});
|
|
36561
36591
|
return;
|
|
36562
36592
|
}
|
|
36563
|
-
if (req.method === "GET" && url
|
|
36564
|
-
const id = new URL(url, "http://x").searchParams.get("id");
|
|
36593
|
+
if (req.method === "GET" && url === "/api/design/run") {
|
|
36594
|
+
const id = new URL(req.url || "/", "http://x").searchParams.get("id");
|
|
36565
36595
|
const payload = id ? designRunStatus(id) : { runs: listDesignRuns() };
|
|
36566
36596
|
res.writeHead(payload ? 200 : 404, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
36567
36597
|
res.end(JSON.stringify(payload ?? { ok: false, error: "no such run" }));
|
|
@@ -36687,8 +36717,8 @@ async function runDashboard(argv) {
|
|
|
36687
36717
|
});
|
|
36688
36718
|
return;
|
|
36689
36719
|
}
|
|
36690
|
-
if (req.method === "GET" && url
|
|
36691
|
-
const locationId2 = new URL(url, "http://x").searchParams.get("locationId") || "";
|
|
36720
|
+
if (req.method === "GET" && url === "/handoff") {
|
|
36721
|
+
const locationId2 = new URL(req.url || "/", "http://x").searchParams.get("locationId") || "";
|
|
36692
36722
|
if (!readableAccount(locationId2)) {
|
|
36693
36723
|
res.writeHead(400);
|
|
36694
36724
|
res.end("Choose a registered client.");
|
|
@@ -36791,8 +36821,8 @@ async function runDashboard(argv) {
|
|
|
36791
36821
|
});
|
|
36792
36822
|
return;
|
|
36793
36823
|
}
|
|
36794
|
-
if (req.method === "GET" && url
|
|
36795
|
-
const q2 = new URL(url, "http://x").searchParams;
|
|
36824
|
+
if (req.method === "GET" && url === "/blueprint") {
|
|
36825
|
+
const q2 = new URL(req.url || "/", "http://x").searchParams;
|
|
36796
36826
|
const locationId2 = q2.get("locationId") || "";
|
|
36797
36827
|
const st = readCockpitState();
|
|
36798
36828
|
const review = st.reviews?.[locationId2];
|
|
@@ -36806,8 +36836,8 @@ async function runDashboard(argv) {
|
|
|
36806
36836
|
res.end(reviewDocumentHtml(review, { assessment: st.assessmentAttachments?.[locationId2], purchasedFeatureIds: st.assessmentPurchases?.[locationId2], planLines: readSavedPlan(locationId2) ? executablePlanLines(readSavedPlan(locationId2)) : [], clientName: name, agencyName: q2.get("agency") || st.agencyName, accent: q2.get("accent") || st.agencyAccent }));
|
|
36807
36837
|
return;
|
|
36808
36838
|
}
|
|
36809
|
-
if (req.method === "GET" && url
|
|
36810
|
-
const locationId2 = new URL(url, "http://x").searchParams.get("locationId") || "";
|
|
36839
|
+
if (req.method === "GET" && url === "/api/review") {
|
|
36840
|
+
const locationId2 = new URL(req.url || "/", "http://x").searchParams.get("locationId") || "";
|
|
36811
36841
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
36812
36842
|
res.end(JSON.stringify(readCockpitState().reviews?.[locationId2] ?? { ok: false, error: "not generated yet" }));
|
|
36813
36843
|
return;
|
|
@@ -37399,7 +37429,7 @@ var init_dashboard = __esm({
|
|
|
37399
37429
|
anthropicKeyWrites = Promise.resolve();
|
|
37400
37430
|
SEAT_TOKEN_HEADER = "x-command-os-seat";
|
|
37401
37431
|
NOT_SIGNED_IN_MSG = "This browser isn't signed in to Command OS on this computer. Open Command OS on the machine it runs on, and use the page it opens for you.";
|
|
37402
|
-
SEAT_GUARDED_READS = ["/api/capability", "/api/client-intake", "/api/intake-documents", "/api/snapshots"];
|
|
37432
|
+
SEAT_GUARDED_READS = ["/api/capability", "/api/client-intake", "/api/intake-documents", "/api/snapshots", "/api/agency-connection", "/api/client-preflight"];
|
|
37403
37433
|
}
|
|
37404
37434
|
});
|
|
37405
37435
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elitedcs/ghl-mcp",
|
|
3
|
-
"version": "3.83.
|
|
3
|
+
"version": "3.83.7",
|
|
4
4
|
"mcpName": "io.github.drjerryrelth/ghl-command",
|
|
5
5
|
"description": "GoHighLevel MCP Server for Claude. 250 tools — full CRM, automation, marketing control, account-wide workflow audit, live funnel-capture verification, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
|
|
6
6
|
"main": "dist/index.js",
|