@elitedcs/ghl-mcp 3.53.0 → 3.53.2
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 +33 -0
- package/dist/capture-helper.js +5 -1
- package/dist/index.js +201 -116
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.53.2 — Fixes a serious 3.53.1 regression: upgrade immediately
|
|
4
|
+
|
|
5
|
+
3.53.1 resolved the machine identity randomly when nothing was passed in, and
|
|
6
|
+
that is exactly how attestation verification asks for it. Cached attestations
|
|
7
|
+
failed to verify, the tool dropped to bootstrap, and each restart would have
|
|
8
|
+
claimed another install slot until the member hit their cap.
|
|
9
|
+
|
|
10
|
+
- No-argument identity resolution is deterministic again: stored `device_id`
|
|
11
|
+
first, then the fingerprint inside an existing attestation (so an upgrade
|
|
12
|
+
keeps the slot it already holds), then the legacy scheme as a last resort.
|
|
13
|
+
- Two tests pin the determinism so this cannot ship again.
|
|
14
|
+
|
|
15
|
+
If you ran 3.53.1, upgrade. If your install count looks inflated, write to
|
|
16
|
+
support@ghlcommand.com and we will reset it.
|
|
17
|
+
|
|
18
|
+
## 3.53.1 — Laptops stop eating install slots; "cancelled" stops lying
|
|
19
|
+
|
|
20
|
+
Reported by Carlos Boyd, who hit his install cap with two physical machines.
|
|
21
|
+
|
|
22
|
+
- **Device identity is stable now.** The fingerprint was derived from the
|
|
23
|
+
machine hostname, and a Mac's `.local` hostname changes with the network
|
|
24
|
+
(new Wi-Fi, DHCP, a name collision) — so the same laptop could present a new
|
|
25
|
+
identity and claim another install slot. Identity is now a random id
|
|
26
|
+
persisted in the config dir, so it survives reinstalls and renames. On
|
|
27
|
+
upgrade it ADOPTS the fingerprint from the existing cached attestation, so no
|
|
28
|
+
current install loses the slot it already holds. `legacyDeviceFingerprint()`
|
|
29
|
+
is kept for reference.
|
|
30
|
+
- **Install-limit is no longer reported as "cancelled".** Both conditions come
|
|
31
|
+
back from the gate as 403, and the client labelled both `cancelled`, so a
|
|
32
|
+
full machine list read as a billing failure — two contradictory lines in the
|
|
33
|
+
same block. `install_limit` is its own reason, and the gate prints the
|
|
34
|
+
server's own sentence instead of a reason code beside it.
|
|
35
|
+
|
|
3
36
|
## 3.52.1 — Docs: positioning vs. HighLevel's official Anthropic MCP
|
|
4
37
|
|
|
5
38
|
No behavior changes; README only.
|
package/dist/capture-helper.js
CHANGED
|
@@ -48,7 +48,11 @@ var CredentialsSchema = import_zod.z.object({
|
|
|
48
48
|
// hand-crafted-credentials.json bypass that existed through v3.23.0).
|
|
49
49
|
// Optional in the schema only so files written by earlier versions still
|
|
50
50
|
// parse — index.ts's gate forces a re-validate when the field is missing.
|
|
51
|
-
signed_attestation: import_zod.z.string().optional()
|
|
51
|
+
signed_attestation: import_zod.z.string().optional(),
|
|
52
|
+
/** Stable machine id (2026-07-28). Replaces the hostname-derived
|
|
53
|
+
* fingerprint, which changed with the network on laptops and silently ate
|
|
54
|
+
* install slots. Written once and reused forever. */
|
|
55
|
+
device_id: import_zod.z.string().optional()
|
|
52
56
|
});
|
|
53
57
|
function appDataDir() {
|
|
54
58
|
const override = process.env.GHL_MCP_CONFIG_DIR?.trim();
|
package/dist/index.js
CHANGED
|
@@ -41,6 +41,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
41
41
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
42
42
|
mod
|
|
43
43
|
));
|
|
44
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
44
45
|
|
|
45
46
|
// src/retry.ts
|
|
46
47
|
function computeRetryDelay(retryAfterHeader, attempt, baseMs) {
|
|
@@ -214,6 +215,16 @@ ${errorBody}`
|
|
|
214
215
|
});
|
|
215
216
|
|
|
216
217
|
// src/credentials-store.ts
|
|
218
|
+
var credentials_store_exports = {};
|
|
219
|
+
__export(credentials_store_exports, {
|
|
220
|
+
appDataDir: () => appDataDir,
|
|
221
|
+
credentialsPath: () => credentialsPath,
|
|
222
|
+
ensureAppDataDir: () => ensureAppDataDir,
|
|
223
|
+
foldCredentialsIntoEnv: () => foldCredentialsIntoEnv,
|
|
224
|
+
readCredentials: () => readCredentials,
|
|
225
|
+
tokenRegistryPath: () => tokenRegistryPath,
|
|
226
|
+
writeCredentials: () => writeCredentials
|
|
227
|
+
});
|
|
217
228
|
function appDataDir() {
|
|
218
229
|
const override = process.env.GHL_MCP_CONFIG_DIR?.trim();
|
|
219
230
|
if (override) {
|
|
@@ -331,7 +342,11 @@ var init_credentials_store = __esm({
|
|
|
331
342
|
// hand-crafted-credentials.json bypass that existed through v3.23.0).
|
|
332
343
|
// Optional in the schema only so files written by earlier versions still
|
|
333
344
|
// parse — index.ts's gate forces a re-validate when the field is missing.
|
|
334
|
-
signed_attestation: import_zod.z.string().optional()
|
|
345
|
+
signed_attestation: import_zod.z.string().optional(),
|
|
346
|
+
/** Stable machine id (2026-07-28). Replaces the hostname-derived
|
|
347
|
+
* fingerprint, which changed with the network on laptops and silently ate
|
|
348
|
+
* install slots. Written once and reused forever. */
|
|
349
|
+
device_id: import_zod.z.string().optional()
|
|
335
350
|
});
|
|
336
351
|
}
|
|
337
352
|
});
|
|
@@ -962,10 +977,184 @@ var init_interactive_capture = __esm({
|
|
|
962
977
|
}
|
|
963
978
|
});
|
|
964
979
|
|
|
980
|
+
// src/attestation.ts
|
|
981
|
+
var attestation_exports = {};
|
|
982
|
+
__export(attestation_exports, {
|
|
983
|
+
ATTESTATION_GRACE_DAYS: () => ATTESTATION_GRACE_DAYS,
|
|
984
|
+
ATTESTATION_TTL_DAYS: () => ATTESTATION_TTL_DAYS,
|
|
985
|
+
ATTESTATION_VERSION: () => ATTESTATION_VERSION,
|
|
986
|
+
deviceFingerprint: () => deviceFingerprint,
|
|
987
|
+
legacyDeviceFingerprint: () => legacyDeviceFingerprint,
|
|
988
|
+
parseAttestationPayload: () => parseAttestationPayload,
|
|
989
|
+
planFromPayload: () => planFromPayload,
|
|
990
|
+
shouldRenew: () => shouldRenew,
|
|
991
|
+
tierFromPayload: () => tierFromPayload,
|
|
992
|
+
verifyAttestation: () => verifyAttestation
|
|
993
|
+
});
|
|
994
|
+
function tierFromPayload(payload) {
|
|
995
|
+
return payload?.tier === "free" ? "free" : "full";
|
|
996
|
+
}
|
|
997
|
+
function planFromPayload(payload) {
|
|
998
|
+
return payload?.plan === "command-os" ? "command-os" : "mcp";
|
|
999
|
+
}
|
|
1000
|
+
function legacyDeviceFingerprint() {
|
|
1001
|
+
const os5 = require("node:os");
|
|
1002
|
+
const crypto4 = require("node:crypto");
|
|
1003
|
+
const raw = `${os5.hostname()}:${os5.userInfo().username}:${os5.platform()}:${os5.arch()}`;
|
|
1004
|
+
return crypto4.createHash("sha256").update(raw).digest("hex").slice(0, 16);
|
|
1005
|
+
}
|
|
1006
|
+
function deviceFingerprint(opts) {
|
|
1007
|
+
if (opts?.stored) return opts.stored;
|
|
1008
|
+
if (opts?.cached) {
|
|
1009
|
+
opts.persist?.(opts.cached);
|
|
1010
|
+
return opts.cached;
|
|
1011
|
+
}
|
|
1012
|
+
const crypto4 = require("node:crypto");
|
|
1013
|
+
if (!opts) {
|
|
1014
|
+
try {
|
|
1015
|
+
const store = (init_credentials_store(), __toCommonJS(credentials_store_exports));
|
|
1016
|
+
const creds = store.readCredentials();
|
|
1017
|
+
if (creds?.device_id) return creds.device_id;
|
|
1018
|
+
const adopted = creds?.signed_attestation ? parseAttestationPayload(creds.signed_attestation)?.device_fingerprint : void 0;
|
|
1019
|
+
const id = adopted || crypto4.randomBytes(8).toString("hex");
|
|
1020
|
+
if (creds) {
|
|
1021
|
+
try {
|
|
1022
|
+
store.writeCredentials({ ...creds, device_id: id });
|
|
1023
|
+
} catch {
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
return id;
|
|
1027
|
+
} catch {
|
|
1028
|
+
return legacyDeviceFingerprint();
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
const fresh = crypto4.randomBytes(8).toString("hex");
|
|
1032
|
+
opts.persist?.(fresh);
|
|
1033
|
+
return fresh;
|
|
1034
|
+
}
|
|
1035
|
+
function base64urlDecode(s) {
|
|
1036
|
+
const b64 = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
1037
|
+
const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
|
|
1038
|
+
return new Uint8Array(Buffer.from(padded, "base64"));
|
|
1039
|
+
}
|
|
1040
|
+
async function getPublicKey() {
|
|
1041
|
+
if (cachedPublicKey) return cachedPublicKey;
|
|
1042
|
+
const raw = Buffer.from(ATTESTATION_PUBLIC_KEY_B64, "base64");
|
|
1043
|
+
cachedPublicKey = await import_node_crypto.webcrypto.subtle.importKey(
|
|
1044
|
+
"raw",
|
|
1045
|
+
raw,
|
|
1046
|
+
{ name: "Ed25519" },
|
|
1047
|
+
false,
|
|
1048
|
+
["verify"]
|
|
1049
|
+
);
|
|
1050
|
+
return cachedPublicKey;
|
|
1051
|
+
}
|
|
1052
|
+
function parseAttestationPayload(token) {
|
|
1053
|
+
try {
|
|
1054
|
+
const dot = token.indexOf(".");
|
|
1055
|
+
if (dot <= 0 || dot === token.length - 1) return null;
|
|
1056
|
+
const payloadJson = new TextDecoder().decode(base64urlDecode(token.slice(0, dot)));
|
|
1057
|
+
const payload = JSON.parse(payloadJson);
|
|
1058
|
+
if (typeof payload.v !== "number" || typeof payload.email !== "string" || typeof payload.license_key !== "string" || typeof payload.device_fingerprint !== "string" || typeof payload.installs_max !== "number" || typeof payload.issued_at !== "string" || typeof payload.expires_at !== "string") {
|
|
1059
|
+
return null;
|
|
1060
|
+
}
|
|
1061
|
+
return payload;
|
|
1062
|
+
} catch {
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
async function verifyAttestation(token, bindings, now = /* @__PURE__ */ new Date()) {
|
|
1067
|
+
if (typeof token !== "string" || !token) return { ok: false, reason: "malformed" };
|
|
1068
|
+
const dot = token.indexOf(".");
|
|
1069
|
+
if (dot <= 0 || dot === token.length - 1) return { ok: false, reason: "malformed" };
|
|
1070
|
+
let payloadBytes;
|
|
1071
|
+
let signatureBytes;
|
|
1072
|
+
try {
|
|
1073
|
+
payloadBytes = base64urlDecode(token.slice(0, dot));
|
|
1074
|
+
signatureBytes = base64urlDecode(token.slice(dot + 1));
|
|
1075
|
+
} catch {
|
|
1076
|
+
return { ok: false, reason: "malformed" };
|
|
1077
|
+
}
|
|
1078
|
+
let payload;
|
|
1079
|
+
try {
|
|
1080
|
+
const parsed = JSON.parse(new TextDecoder().decode(payloadBytes));
|
|
1081
|
+
if (typeof parsed.v !== "number") return { ok: false, reason: "malformed" };
|
|
1082
|
+
if (parsed.v !== ATTESTATION_VERSION) return { ok: false, reason: "unsupported-version" };
|
|
1083
|
+
payload = parsed;
|
|
1084
|
+
} catch {
|
|
1085
|
+
return { ok: false, reason: "malformed" };
|
|
1086
|
+
}
|
|
1087
|
+
let signatureValid;
|
|
1088
|
+
try {
|
|
1089
|
+
const key = await getPublicKey();
|
|
1090
|
+
signatureValid = await import_node_crypto.webcrypto.subtle.verify({ name: "Ed25519" }, key, signatureBytes, payloadBytes);
|
|
1091
|
+
} catch {
|
|
1092
|
+
return { ok: false, reason: "bad-signature" };
|
|
1093
|
+
}
|
|
1094
|
+
if (!signatureValid) return { ok: false, reason: "bad-signature" };
|
|
1095
|
+
if (payload.email.toLowerCase() !== bindings.email.toLowerCase()) {
|
|
1096
|
+
return { ok: false, reason: "wrong-email" };
|
|
1097
|
+
}
|
|
1098
|
+
if (payload.license_key !== bindings.license_key) {
|
|
1099
|
+
return { ok: false, reason: "wrong-license" };
|
|
1100
|
+
}
|
|
1101
|
+
const fingerprint = bindings.expectedFingerprint ?? deviceFingerprint();
|
|
1102
|
+
if (payload.device_fingerprint !== fingerprint) {
|
|
1103
|
+
return { ok: false, reason: "wrong-device" };
|
|
1104
|
+
}
|
|
1105
|
+
const expiry = Date.parse(payload.expires_at);
|
|
1106
|
+
if (!Number.isFinite(expiry)) return { ok: false, reason: "malformed" };
|
|
1107
|
+
const graceDeadline = expiry + ATTESTATION_GRACE_DAYS * 24 * 60 * 60 * 1e3;
|
|
1108
|
+
if (now.getTime() <= expiry) {
|
|
1109
|
+
return { ok: true, payload, reason: "valid" };
|
|
1110
|
+
}
|
|
1111
|
+
if (now.getTime() <= graceDeadline) {
|
|
1112
|
+
return { ok: true, payload, reason: "in-grace" };
|
|
1113
|
+
}
|
|
1114
|
+
return { ok: false, reason: "expired" };
|
|
1115
|
+
}
|
|
1116
|
+
function shouldRenew(payload, now = /* @__PURE__ */ new Date()) {
|
|
1117
|
+
const expiry = Date.parse(payload.expires_at);
|
|
1118
|
+
if (!Number.isFinite(expiry)) return true;
|
|
1119
|
+
const remainingMs = expiry - now.getTime();
|
|
1120
|
+
return remainingMs < 7 * 24 * 60 * 60 * 1e3;
|
|
1121
|
+
}
|
|
1122
|
+
var import_node_crypto, ATTESTATION_PUBLIC_KEY_B64, ATTESTATION_VERSION, ATTESTATION_TTL_DAYS, ATTESTATION_GRACE_DAYS, cachedPublicKey;
|
|
1123
|
+
var init_attestation = __esm({
|
|
1124
|
+
"src/attestation.ts"() {
|
|
1125
|
+
"use strict";
|
|
1126
|
+
import_node_crypto = require("node:crypto");
|
|
1127
|
+
ATTESTATION_PUBLIC_KEY_B64 = "8KJo8V3Z7ngeToIQfOXpKdlr/EA34hXJVEIpW0A7wqs=";
|
|
1128
|
+
ATTESTATION_VERSION = 1;
|
|
1129
|
+
ATTESTATION_TTL_DAYS = 30;
|
|
1130
|
+
ATTESTATION_GRACE_DAYS = 14;
|
|
1131
|
+
cachedPublicKey = null;
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
|
|
965
1135
|
// src/setup-tool.ts
|
|
966
|
-
function
|
|
967
|
-
|
|
968
|
-
|
|
1136
|
+
function deviceFingerprint2() {
|
|
1137
|
+
try {
|
|
1138
|
+
const store = (init_credentials_store(), __toCommonJS(credentials_store_exports));
|
|
1139
|
+
const creds = store.readCredentials();
|
|
1140
|
+
if (creds?.device_id) return creds.device_id;
|
|
1141
|
+
let adopted;
|
|
1142
|
+
if (creds?.signed_attestation) {
|
|
1143
|
+
const att = (init_attestation(), __toCommonJS(attestation_exports));
|
|
1144
|
+
adopted = att.parseAttestationPayload(creds.signed_attestation)?.device_fingerprint;
|
|
1145
|
+
}
|
|
1146
|
+
const id = adopted || crypto2.randomBytes(8).toString("hex");
|
|
1147
|
+
if (creds) {
|
|
1148
|
+
try {
|
|
1149
|
+
store.writeCredentials({ ...creds, device_id: id });
|
|
1150
|
+
} catch {
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
return id;
|
|
1154
|
+
} catch {
|
|
1155
|
+
const raw = `${os2.userInfo().username}:${os2.platform()}:${os2.arch()}`;
|
|
1156
|
+
return crypto2.createHash("sha256").update(raw).digest("hex").slice(0, 16);
|
|
1157
|
+
}
|
|
969
1158
|
}
|
|
970
1159
|
async function validateLicense(email, licenseKey) {
|
|
971
1160
|
let res;
|
|
@@ -976,7 +1165,7 @@ async function validateLicense(email, licenseKey) {
|
|
|
976
1165
|
body: JSON.stringify({
|
|
977
1166
|
email: email.trim(),
|
|
978
1167
|
license_key: licenseKey.trim(),
|
|
979
|
-
device_fingerprint:
|
|
1168
|
+
device_fingerprint: deviceFingerprint2()
|
|
980
1169
|
}),
|
|
981
1170
|
signal: AbortSignal.timeout(1e4)
|
|
982
1171
|
});
|
|
@@ -997,11 +1186,12 @@ async function validateLicense(email, licenseKey) {
|
|
|
997
1186
|
};
|
|
998
1187
|
}
|
|
999
1188
|
const errorText = String(data.error || data.message || "").toLowerCase();
|
|
1000
|
-
const
|
|
1189
|
+
const isInstallLimit = errorText.includes("install limit") || errorText.includes("installs");
|
|
1190
|
+
const looksCancelled = !isInstallLimit && (res.status === 403 || errorText.includes("cancel"));
|
|
1001
1191
|
return {
|
|
1002
1192
|
ok: false,
|
|
1003
1193
|
error: data.error || data.message || `License validation failed (HTTP ${res.status})`,
|
|
1004
|
-
reason: looksCancelled ? "cancelled" : "unauthorized"
|
|
1194
|
+
reason: isInstallLimit ? "install_limit" : looksCancelled ? "cancelled" : "unauthorized"
|
|
1005
1195
|
};
|
|
1006
1196
|
}
|
|
1007
1197
|
async function validateGhl(apiKey2, locationId2) {
|
|
@@ -1907,118 +2097,12 @@ var init_question_set = __esm({
|
|
|
1907
2097
|
}
|
|
1908
2098
|
});
|
|
1909
2099
|
|
|
1910
|
-
// src/attestation.ts
|
|
1911
|
-
function tierFromPayload(payload) {
|
|
1912
|
-
return payload?.tier === "free" ? "free" : "full";
|
|
1913
|
-
}
|
|
1914
|
-
function planFromPayload(payload) {
|
|
1915
|
-
return payload?.plan === "command-os" ? "command-os" : "mcp";
|
|
1916
|
-
}
|
|
1917
|
-
function deviceFingerprint2() {
|
|
1918
|
-
const os5 = require("node:os");
|
|
1919
|
-
const crypto4 = require("node:crypto");
|
|
1920
|
-
const raw = `${os5.hostname()}:${os5.userInfo().username}:${os5.platform()}:${os5.arch()}`;
|
|
1921
|
-
return crypto4.createHash("sha256").update(raw).digest("hex").slice(0, 16);
|
|
1922
|
-
}
|
|
1923
|
-
function base64urlDecode(s) {
|
|
1924
|
-
const b64 = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
1925
|
-
const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
|
|
1926
|
-
return new Uint8Array(Buffer.from(padded, "base64"));
|
|
1927
|
-
}
|
|
1928
|
-
async function getPublicKey() {
|
|
1929
|
-
if (cachedPublicKey) return cachedPublicKey;
|
|
1930
|
-
const raw = Buffer.from(ATTESTATION_PUBLIC_KEY_B64, "base64");
|
|
1931
|
-
cachedPublicKey = await import_node_crypto.webcrypto.subtle.importKey(
|
|
1932
|
-
"raw",
|
|
1933
|
-
raw,
|
|
1934
|
-
{ name: "Ed25519" },
|
|
1935
|
-
false,
|
|
1936
|
-
["verify"]
|
|
1937
|
-
);
|
|
1938
|
-
return cachedPublicKey;
|
|
1939
|
-
}
|
|
1940
|
-
function parseAttestationPayload(token) {
|
|
1941
|
-
try {
|
|
1942
|
-
const dot = token.indexOf(".");
|
|
1943
|
-
if (dot <= 0 || dot === token.length - 1) return null;
|
|
1944
|
-
const payloadJson = new TextDecoder().decode(base64urlDecode(token.slice(0, dot)));
|
|
1945
|
-
const payload = JSON.parse(payloadJson);
|
|
1946
|
-
if (typeof payload.v !== "number" || typeof payload.email !== "string" || typeof payload.license_key !== "string" || typeof payload.device_fingerprint !== "string" || typeof payload.installs_max !== "number" || typeof payload.issued_at !== "string" || typeof payload.expires_at !== "string") {
|
|
1947
|
-
return null;
|
|
1948
|
-
}
|
|
1949
|
-
return payload;
|
|
1950
|
-
} catch {
|
|
1951
|
-
return null;
|
|
1952
|
-
}
|
|
1953
|
-
}
|
|
1954
|
-
async function verifyAttestation(token, bindings, now = /* @__PURE__ */ new Date()) {
|
|
1955
|
-
if (typeof token !== "string" || !token) return { ok: false, reason: "malformed" };
|
|
1956
|
-
const dot = token.indexOf(".");
|
|
1957
|
-
if (dot <= 0 || dot === token.length - 1) return { ok: false, reason: "malformed" };
|
|
1958
|
-
let payloadBytes;
|
|
1959
|
-
let signatureBytes;
|
|
1960
|
-
try {
|
|
1961
|
-
payloadBytes = base64urlDecode(token.slice(0, dot));
|
|
1962
|
-
signatureBytes = base64urlDecode(token.slice(dot + 1));
|
|
1963
|
-
} catch {
|
|
1964
|
-
return { ok: false, reason: "malformed" };
|
|
1965
|
-
}
|
|
1966
|
-
let payload;
|
|
1967
|
-
try {
|
|
1968
|
-
const parsed = JSON.parse(new TextDecoder().decode(payloadBytes));
|
|
1969
|
-
if (typeof parsed.v !== "number") return { ok: false, reason: "malformed" };
|
|
1970
|
-
if (parsed.v !== ATTESTATION_VERSION) return { ok: false, reason: "unsupported-version" };
|
|
1971
|
-
payload = parsed;
|
|
1972
|
-
} catch {
|
|
1973
|
-
return { ok: false, reason: "malformed" };
|
|
1974
|
-
}
|
|
1975
|
-
let signatureValid;
|
|
1976
|
-
try {
|
|
1977
|
-
const key = await getPublicKey();
|
|
1978
|
-
signatureValid = await import_node_crypto.webcrypto.subtle.verify({ name: "Ed25519" }, key, signatureBytes, payloadBytes);
|
|
1979
|
-
} catch {
|
|
1980
|
-
return { ok: false, reason: "bad-signature" };
|
|
1981
|
-
}
|
|
1982
|
-
if (!signatureValid) return { ok: false, reason: "bad-signature" };
|
|
1983
|
-
if (payload.email.toLowerCase() !== bindings.email.toLowerCase()) {
|
|
1984
|
-
return { ok: false, reason: "wrong-email" };
|
|
1985
|
-
}
|
|
1986
|
-
if (payload.license_key !== bindings.license_key) {
|
|
1987
|
-
return { ok: false, reason: "wrong-license" };
|
|
1988
|
-
}
|
|
1989
|
-
const fingerprint = bindings.expectedFingerprint ?? deviceFingerprint2();
|
|
1990
|
-
if (payload.device_fingerprint !== fingerprint) {
|
|
1991
|
-
return { ok: false, reason: "wrong-device" };
|
|
1992
|
-
}
|
|
1993
|
-
const expiry = Date.parse(payload.expires_at);
|
|
1994
|
-
if (!Number.isFinite(expiry)) return { ok: false, reason: "malformed" };
|
|
1995
|
-
const graceDeadline = expiry + ATTESTATION_GRACE_DAYS * 24 * 60 * 60 * 1e3;
|
|
1996
|
-
if (now.getTime() <= expiry) {
|
|
1997
|
-
return { ok: true, payload, reason: "valid" };
|
|
1998
|
-
}
|
|
1999
|
-
if (now.getTime() <= graceDeadline) {
|
|
2000
|
-
return { ok: true, payload, reason: "in-grace" };
|
|
2001
|
-
}
|
|
2002
|
-
return { ok: false, reason: "expired" };
|
|
2003
|
-
}
|
|
2004
|
-
var import_node_crypto, ATTESTATION_PUBLIC_KEY_B64, ATTESTATION_VERSION, ATTESTATION_GRACE_DAYS, cachedPublicKey;
|
|
2005
|
-
var init_attestation = __esm({
|
|
2006
|
-
"src/attestation.ts"() {
|
|
2007
|
-
"use strict";
|
|
2008
|
-
import_node_crypto = require("node:crypto");
|
|
2009
|
-
ATTESTATION_PUBLIC_KEY_B64 = "8KJo8V3Z7ngeToIQfOXpKdlr/EA34hXJVEIpW0A7wqs=";
|
|
2010
|
-
ATTESTATION_VERSION = 1;
|
|
2011
|
-
ATTESTATION_GRACE_DAYS = 14;
|
|
2012
|
-
cachedPublicKey = null;
|
|
2013
|
-
}
|
|
2014
|
-
});
|
|
2015
|
-
|
|
2016
2100
|
// package.json
|
|
2017
2101
|
var require_package = __commonJS({
|
|
2018
2102
|
"package.json"(exports2, module2) {
|
|
2019
2103
|
module2.exports = {
|
|
2020
2104
|
name: "@elitedcs/ghl-mcp",
|
|
2021
|
-
version: "3.53.
|
|
2105
|
+
version: "3.53.2",
|
|
2022
2106
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
2023
2107
|
description: "GoHighLevel MCP Server for Claude. 232 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.",
|
|
2024
2108
|
main: "dist/index.js",
|
|
@@ -16986,10 +17070,11 @@ async function renewAttestation(creds) {
|
|
|
16986
17070
|
}
|
|
16987
17071
|
return "renewed";
|
|
16988
17072
|
}
|
|
16989
|
-
if (lic.reason === "cancelled" || lic.reason === "unauthorized") {
|
|
17073
|
+
if (lic.reason === "cancelled" || lic.reason === "unauthorized" || lic.reason === "install_limit") {
|
|
16990
17074
|
try {
|
|
16991
17075
|
writeCredentials({ ...creds, signed_attestation: void 0 });
|
|
16992
|
-
process.stderr.write(`[ghl-mcp]
|
|
17076
|
+
process.stderr.write(lic.reason === "install_limit" ? `[ghl-mcp] ${lic.error}
|
|
17077
|
+
` : `[ghl-mcp] ${lic.error} Dropping to bootstrap mode.
|
|
16993
17078
|
`);
|
|
16994
17079
|
} catch {
|
|
16995
17080
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elitedcs/ghl-mcp",
|
|
3
|
-
"version": "3.53.
|
|
3
|
+
"version": "3.53.2",
|
|
4
4
|
"mcpName": "io.github.drjerryrelth/ghl-command",
|
|
5
|
-
"description": "GoHighLevel MCP Server for Claude. 232 tools
|
|
5
|
+
"description": "GoHighLevel MCP Server for Claude. 232 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.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"ghl-mcp": "dist/index.js"
|