0nmcp 3.2.2 → 4.5.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/LICENSE +94 -21
- package/README.md +9 -8
- package/crm/addons.js +319 -0
- package/crm/agent-builder.js +223 -0
- package/crm/billing.js +173 -0
- package/crm/conversations.js +14 -0
- package/crm/course-generator.js +161 -0
- package/crm/email-campaigns.js +250 -0
- package/crm/helpers.js +9 -0
- package/crm/index.js +48 -2
- package/crm/marketplace-billing.js +162 -0
- package/crm/media.js +167 -0
- package/crm/oauth-store.js +262 -0
- package/crm/phone-system.js +88 -0
- package/crm/saas-management.js +72 -0
- package/crm/sdk.js +60 -0
- package/crm/supabase-session-storage.js +54 -0
- package/crm/surveys-forms.js +96 -0
- package/crm/user-context.js +103 -0
- package/lib/stats.json +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// 0nMCP — Supabase Session Storage for HighLevel SDK
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Replaces in-memory token storage with persistent Supabase.
|
|
5
|
+
// Auto-refresh handled by SDK, we just store/retrieve.
|
|
6
|
+
//
|
|
7
|
+
// Table: crm_oauth_sessions (on pwujhhmlrtxjmjzyttwn)
|
|
8
|
+
// ============================================================
|
|
9
|
+
|
|
10
|
+
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL || "https://pwujhhmlrtxjmjzyttwn.supabase.co";
|
|
11
|
+
const SUPABASE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY || "";
|
|
12
|
+
|
|
13
|
+
async function sb(path, method = "GET", body = null) {
|
|
14
|
+
const opts = {
|
|
15
|
+
method,
|
|
16
|
+
headers: {
|
|
17
|
+
apikey: SUPABASE_KEY,
|
|
18
|
+
Authorization: `Bearer ${SUPABASE_KEY}`,
|
|
19
|
+
"Content-Type": "application/json",
|
|
20
|
+
Prefer: method === "POST" ? "return=representation,resolution=merge-duplicates" : "return=representation",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
if (body) opts.body = JSON.stringify(body);
|
|
24
|
+
const res = await fetch(`${SUPABASE_URL}/rest/v1${path}`, opts);
|
|
25
|
+
if (!res.ok) return null;
|
|
26
|
+
const data = await res.json();
|
|
27
|
+
return Array.isArray(data) ? data[0] || null : data;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Supabase session storage adapter for @gohighlevel/api-client
|
|
32
|
+
* Implements the SessionStorage interface: get, set, delete
|
|
33
|
+
*/
|
|
34
|
+
export class SupabaseSessionStorage {
|
|
35
|
+
async get(key) {
|
|
36
|
+
const row = await sb(`/crm_oauth_sessions?session_key=eq.${encodeURIComponent(key)}&select=*`);
|
|
37
|
+
if (!row) return null;
|
|
38
|
+
return row.session_data;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async set(key, value) {
|
|
42
|
+
await sb("/crm_oauth_sessions", "POST", {
|
|
43
|
+
session_key: key,
|
|
44
|
+
session_data: value,
|
|
45
|
+
updated_at: new Date().toISOString(),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async delete(key) {
|
|
50
|
+
await sb(`/crm_oauth_sessions?session_key=eq.${encodeURIComponent(key)}`, "DELETE");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default SupabaseSessionStorage;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// 0nMCP — CRM Surveys & Forms SDK Tools
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Submission fetching, search, analytics, file upload.
|
|
5
|
+
// Complements data-driven list tools in funnels.js.
|
|
6
|
+
// ============================================================
|
|
7
|
+
|
|
8
|
+
import getSDK from "./sdk.js";
|
|
9
|
+
|
|
10
|
+
export function registerSurveyFormTools(server, z) {
|
|
11
|
+
|
|
12
|
+
server.tool(
|
|
13
|
+
"crm_sdk_survey_submissions",
|
|
14
|
+
"Get survey submissions with search, pagination, and date filtering. Returns respondent data, answers, and metadata.",
|
|
15
|
+
{
|
|
16
|
+
location_id: z.string().describe("CRM location ID"),
|
|
17
|
+
survey_id: z.string().optional().describe("Filter to specific survey ID"),
|
|
18
|
+
query: z.string().optional().describe("Search by name or email"),
|
|
19
|
+
page: z.number().optional().describe("Page number (default 1)"),
|
|
20
|
+
limit: z.number().optional().describe("Results per page (default 20)"),
|
|
21
|
+
start_at: z.string().optional().describe("Start date (ISO)"),
|
|
22
|
+
end_at: z.string().optional().describe("End date (ISO)"),
|
|
23
|
+
},
|
|
24
|
+
async ({ location_id, survey_id, query, page, limit, start_at, end_at }) => {
|
|
25
|
+
try {
|
|
26
|
+
const sdk = await getSDK();
|
|
27
|
+
const result = await sdk.surveys.getSurveysSubmissions({
|
|
28
|
+
locationId: location_id,
|
|
29
|
+
surveyId: survey_id || undefined,
|
|
30
|
+
q: query || undefined,
|
|
31
|
+
page: page || 1,
|
|
32
|
+
limit: limit || 20,
|
|
33
|
+
startAt: start_at || undefined,
|
|
34
|
+
endAt: end_at || undefined,
|
|
35
|
+
});
|
|
36
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
37
|
+
} catch (err) {
|
|
38
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: err.message }) }] };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
server.tool(
|
|
44
|
+
"crm_sdk_form_submissions",
|
|
45
|
+
"Get form submissions with search, pagination, and date filtering. Returns field values, contact info, and event data.",
|
|
46
|
+
{
|
|
47
|
+
location_id: z.string().describe("CRM location ID"),
|
|
48
|
+
form_id: z.string().optional().describe("Filter to specific form ID"),
|
|
49
|
+
query: z.string().optional().describe("Search query"),
|
|
50
|
+
page: z.number().optional().describe("Page number (default 1)"),
|
|
51
|
+
limit: z.number().optional().describe("Results per page (default 20)"),
|
|
52
|
+
start_at: z.string().optional().describe("Start date (ISO)"),
|
|
53
|
+
end_at: z.string().optional().describe("End date (ISO)"),
|
|
54
|
+
},
|
|
55
|
+
async ({ location_id, form_id, query, page, limit, start_at, end_at }) => {
|
|
56
|
+
try {
|
|
57
|
+
const sdk = await getSDK();
|
|
58
|
+
const result = await sdk.forms.getFormsSubmissions({
|
|
59
|
+
locationId: location_id,
|
|
60
|
+
formId: form_id || undefined,
|
|
61
|
+
q: query || undefined,
|
|
62
|
+
page: page || 1,
|
|
63
|
+
limit: limit || 20,
|
|
64
|
+
startAt: start_at || undefined,
|
|
65
|
+
endAt: end_at || undefined,
|
|
66
|
+
});
|
|
67
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
68
|
+
} catch (err) {
|
|
69
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: err.message }) }] };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
server.tool(
|
|
75
|
+
"crm_form_upload_file",
|
|
76
|
+
"Upload a file to a contact's custom field via the forms API.",
|
|
77
|
+
{
|
|
78
|
+
location_id: z.string().describe("CRM location ID"),
|
|
79
|
+
contact_id: z.string().describe("Contact ID"),
|
|
80
|
+
file_url: z.string().describe("URL of file to upload"),
|
|
81
|
+
},
|
|
82
|
+
async ({ location_id, contact_id, file_url }) => {
|
|
83
|
+
try {
|
|
84
|
+
const sdk = await getSDK();
|
|
85
|
+
const result = await sdk.forms.uploadToCustomFields({
|
|
86
|
+
locationId: location_id,
|
|
87
|
+
contactId: contact_id,
|
|
88
|
+
fileUrl: file_url,
|
|
89
|
+
});
|
|
90
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
91
|
+
} catch (err) {
|
|
92
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: err.message }) }] };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// 0nMCP — CRM Marketplace App User Context
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Decrypt user session data from marketplace app embeds.
|
|
5
|
+
// Uses shared secret from marketplace app settings.
|
|
6
|
+
//
|
|
7
|
+
// Frontend: window.exposeSessionDetails(APP_ID) → encrypted
|
|
8
|
+
// Backend: decrypt with shared secret → user/location data
|
|
9
|
+
// ============================================================
|
|
10
|
+
|
|
11
|
+
import { createDecipheriv, createHash } from "crypto";
|
|
12
|
+
|
|
13
|
+
const SHARED_SECRET = process.env.CRM_MARKETPLACE_SHARED_SECRET || "a420cba6-4e6e-47ba-80e6-75cb57ebf71a";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Decrypt user context from marketplace app embed.
|
|
17
|
+
* CRM encrypts with AES using the shared secret.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} encryptedData - Base64 encrypted payload from frontend
|
|
20
|
+
* @returns {Object} Decrypted user context
|
|
21
|
+
*/
|
|
22
|
+
export function decryptUserContext(encryptedData) {
|
|
23
|
+
try {
|
|
24
|
+
// CryptoJS AES decrypt compatible
|
|
25
|
+
const rawData = Buffer.from(encryptedData, "base64");
|
|
26
|
+
|
|
27
|
+
// CryptoJS format: "Salted__" + 8-byte salt + ciphertext
|
|
28
|
+
const isSalted = rawData.slice(0, 8).toString("utf8") === "Salted__";
|
|
29
|
+
|
|
30
|
+
if (isSalted) {
|
|
31
|
+
const salt = rawData.slice(8, 16);
|
|
32
|
+
const ciphertext = rawData.slice(16);
|
|
33
|
+
|
|
34
|
+
// Derive key + IV from password + salt (OpenSSL EVP_BytesToKey)
|
|
35
|
+
const keyIv = evpBytesToKey(Buffer.from(SHARED_SECRET, "utf8"), salt, 32, 16);
|
|
36
|
+
const key = keyIv.slice(0, 32);
|
|
37
|
+
const iv = keyIv.slice(32, 48);
|
|
38
|
+
|
|
39
|
+
const decipher = createDecipheriv("aes-256-cbc", key, iv);
|
|
40
|
+
let decrypted = decipher.update(ciphertext);
|
|
41
|
+
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
42
|
+
|
|
43
|
+
return JSON.parse(decrypted.toString("utf8"));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Fallback: try raw AES-256-CBC with key derived from secret
|
|
47
|
+
const key = createHash("sha256").update(SHARED_SECRET).digest();
|
|
48
|
+
const iv = rawData.slice(0, 16);
|
|
49
|
+
const ciphertext = rawData.slice(16);
|
|
50
|
+
|
|
51
|
+
const decipher = createDecipheriv("aes-256-cbc", key, iv);
|
|
52
|
+
let decrypted = decipher.update(ciphertext);
|
|
53
|
+
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
54
|
+
|
|
55
|
+
return JSON.parse(decrypted.toString("utf8"));
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error("[user-context] Decryption failed:", err.message);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* OpenSSL EVP_BytesToKey implementation for CryptoJS compatibility.
|
|
64
|
+
*/
|
|
65
|
+
function evpBytesToKey(password, salt, keyLen, ivLen) {
|
|
66
|
+
const totalLen = keyLen + ivLen;
|
|
67
|
+
const result = Buffer.alloc(totalLen);
|
|
68
|
+
let prev = Buffer.alloc(0);
|
|
69
|
+
let offset = 0;
|
|
70
|
+
|
|
71
|
+
while (offset < totalLen) {
|
|
72
|
+
const hash = createHash("md5");
|
|
73
|
+
hash.update(prev);
|
|
74
|
+
hash.update(password);
|
|
75
|
+
if (salt) hash.update(salt);
|
|
76
|
+
prev = hash.digest();
|
|
77
|
+
const copyLen = Math.min(prev.length, totalLen - offset);
|
|
78
|
+
prev.copy(result, offset, 0, copyLen);
|
|
79
|
+
offset += copyLen;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Register user context MCP tool.
|
|
87
|
+
*/
|
|
88
|
+
export function registerUserContextTools(server, z) {
|
|
89
|
+
server.tool(
|
|
90
|
+
"crm_decrypt_user_context",
|
|
91
|
+
"Decrypt encrypted user session data from a CRM marketplace app embed. Returns userId, companyId, locationId, email, role.",
|
|
92
|
+
{
|
|
93
|
+
encrypted_data: z.string().describe("Base64 encrypted payload from window.exposeSessionDetails()"),
|
|
94
|
+
},
|
|
95
|
+
async ({ encrypted_data }) => {
|
|
96
|
+
const userData = decryptUserContext(encrypted_data);
|
|
97
|
+
if (!userData) {
|
|
98
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Decryption failed" }) }] };
|
|
99
|
+
}
|
|
100
|
+
return { content: [{ type: "text", text: JSON.stringify(userData, null, 2) }] };
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
package/lib/stats.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "0nmcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"mcpName": "io.github.0nork/0nMCP",
|
|
5
5
|
"description": "Universal AI API Orchestrator — 1554 tools, 96 services, portable AI Brain bundles + machine-bound vault encryption + Application Engine. The most comprehensive MCP server available. Free and open source from 0nORK.",
|
|
6
6
|
"type": "module",
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
"email": "hello@0nork.com",
|
|
152
152
|
"url": "https://0nork.com"
|
|
153
153
|
},
|
|
154
|
-
"license": "
|
|
154
|
+
"license": "BSL-1.1",
|
|
155
155
|
"repository": {
|
|
156
156
|
"type": "git",
|
|
157
157
|
"url": "git+https://github.com/0nork/0nMCP.git"
|
|
@@ -164,6 +164,7 @@
|
|
|
164
164
|
"node": ">=18.0.0"
|
|
165
165
|
},
|
|
166
166
|
"dependencies": {
|
|
167
|
+
"@gohighlevel/api-client": "^2.2.2",
|
|
167
168
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
168
169
|
"@supabase/supabase-js": "^2.99.3",
|
|
169
170
|
"argon2": "^0.44.0",
|
|
@@ -219,6 +220,6 @@
|
|
|
219
220
|
"triggers": 310,
|
|
220
221
|
"totalCapabilities": 2073,
|
|
221
222
|
"categories": 21,
|
|
222
|
-
"lastUpdated": "2026-04-
|
|
223
|
+
"lastUpdated": "2026-04-20T15:07:46.869Z"
|
|
223
224
|
}
|
|
224
225
|
}
|