0nmcp 3.2.1 → 4.0.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.
@@ -0,0 +1,88 @@
1
+ // ============================================================
2
+ // 0nMCP — CRM Phone System API Tool Definitions
3
+ // ============================================================
4
+ // Phone number purchasing, management, and number pools.
5
+ // Scopes: phonenumbers.read, phonenumbers.write, numberpools.read
6
+ // ============================================================
7
+
8
+ export default [
9
+ {
10
+ name: "crm_purchase_phone_number",
11
+ description: "Purchase a phone number for a CRM location. Specify country, capabilities (voice/sms), and optionally an area code or contains pattern.",
12
+ method: "POST",
13
+ path: "/phone-number/purchase",
14
+ params: {
15
+ locationId: { type: "string", description: "Location ID", required: true, in: "body" },
16
+ number: { type: "string", description: "Specific phone number to purchase (E.164 format, e.g. +14155551234)", required: false, in: "body" },
17
+ country: { type: "string", description: "Country code (US, CA, GB, etc.)", required: false, in: "body" },
18
+ areaCode: { type: "string", description: "Desired area code (e.g. 412, 724)", required: false, in: "body" },
19
+ contains: { type: "string", description: "Pattern the number should contain", required: false, in: "body" },
20
+ capabilities: { type: "array", description: "Capabilities: ['voice', 'sms', 'mms']", required: false, in: "body" },
21
+ },
22
+ body: ["locationId", "number", "country", "areaCode", "contains", "capabilities"],
23
+ },
24
+
25
+ {
26
+ name: "crm_search_phone_numbers",
27
+ description: "Search available phone numbers to purchase by area code, country, or pattern.",
28
+ method: "GET",
29
+ path: "/phone-number/search",
30
+ params: {
31
+ locationId: { type: "string", description: "Location ID", required: true, in: "query" },
32
+ country: { type: "string", description: "Country code (US, CA, GB, etc.)", required: false, in: "query" },
33
+ areaCode: { type: "string", description: "Area code to search (e.g. 412)", required: false, in: "query" },
34
+ contains: { type: "string", description: "Pattern to search for", required: false, in: "query" },
35
+ type: { type: "string", description: "Number type: local, tollfree, mobile", required: false, in: "query" },
36
+ limit: { type: "number", description: "Max results", required: false, in: "query" },
37
+ },
38
+ },
39
+
40
+ {
41
+ name: "crm_list_active_numbers",
42
+ description: "List all active phone numbers for a location with pagination.",
43
+ method: "GET",
44
+ path: "/phone-number/active",
45
+ params: {
46
+ locationId: { type: "string", description: "Location ID", required: true, in: "query" },
47
+ page: { type: "number", description: "Page number", required: false, in: "query" },
48
+ pageSize: { type: "number", description: "Results per page", required: false, in: "query" },
49
+ searchFilter: { type: "string", description: "Filter by number or label", required: false, in: "query" },
50
+ },
51
+ },
52
+
53
+ {
54
+ name: "crm_release_phone_number",
55
+ description: "Release (cancel) a phone number from a location.",
56
+ method: "DELETE",
57
+ path: "/phone-number/:phoneNumberId",
58
+ params: {
59
+ phoneNumberId: { type: "string", description: "Phone number ID to release", required: true, in: "path" },
60
+ locationId: { type: "string", description: "Location ID", required: true, in: "query" },
61
+ },
62
+ },
63
+
64
+ {
65
+ name: "crm_update_phone_number",
66
+ description: "Update phone number settings — label, forwarding, voicemail, call recording.",
67
+ method: "PUT",
68
+ path: "/phone-number/:phoneNumberId",
69
+ params: {
70
+ phoneNumberId: { type: "string", description: "Phone number ID", required: true, in: "path" },
71
+ locationId: { type: "string", description: "Location ID", required: true, in: "body" },
72
+ name: { type: "string", description: "Label/name for the number", required: false, in: "body" },
73
+ forwardingNumber: { type: "string", description: "Forward calls to this number", required: false, in: "body" },
74
+ callRecording: { type: "boolean", description: "Enable call recording", required: false, in: "body" },
75
+ },
76
+ body: ["locationId", "name", "forwardingNumber", "callRecording"],
77
+ },
78
+
79
+ {
80
+ name: "crm_list_number_pools",
81
+ description: "List number pools for a location (used for call tracking).",
82
+ method: "GET",
83
+ path: "/phone-number/number-pool",
84
+ params: {
85
+ locationId: { type: "string", description: "Location ID", required: true, in: "query" },
86
+ },
87
+ },
88
+ ]
@@ -0,0 +1,72 @@
1
+ // ============================================================
2
+ // 0nMCP — CRM SaaS Management Tools (SDK-powered)
3
+ // ============================================================
4
+ // SDK-only tools that complement the data-driven saas.js module.
5
+ // These use the SDK directly for methods not in the REST catalog.
6
+ // ============================================================
7
+
8
+ import getSDK from "./sdk.js";
9
+
10
+ export function registerSaasManagementTools(server, z) {
11
+
12
+ server.tool(
13
+ "crm_saas_generate_payment_link",
14
+ "Generate a payment link for a sub-account to pay for their SaaS subscription.",
15
+ {
16
+ company_id: z.string().describe("Agency company ID"),
17
+ location_id: z.string().describe("Location ID"),
18
+ },
19
+ async ({ company_id, location_id }) => {
20
+ try {
21
+ const sdk = await getSDK();
22
+ const result = await sdk.saasApi.generatePaymentLink({
23
+ companyId: company_id,
24
+ locationId: location_id,
25
+ });
26
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
27
+ } catch (err) {
28
+ return { content: [{ type: "text", text: JSON.stringify({ error: err.message }) }] };
29
+ }
30
+ }
31
+ );
32
+
33
+ server.tool(
34
+ "crm_saas_get_company_info",
35
+ "Get agency company details — name, settings, billing configuration.",
36
+ {
37
+ company_id: z.string().describe("Agency company ID"),
38
+ },
39
+ async ({ company_id }) => {
40
+ try {
41
+ const sdk = await getSDK();
42
+ const result = await sdk.companies.getCompany({ companyId: company_id });
43
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
44
+ } catch (err) {
45
+ return { content: [{ type: "text", text: JSON.stringify({ error: err.message }) }] };
46
+ }
47
+ }
48
+ );
49
+
50
+ server.tool(
51
+ "crm_saas_update_location_rebilling",
52
+ "Update rebilling/pricing configuration for a specific sub-account location via SDK.",
53
+ {
54
+ company_id: z.string().describe("Agency company ID"),
55
+ location_id: z.string().describe("Location ID"),
56
+ config: z.record(z.any()).describe("Rebilling configuration object"),
57
+ },
58
+ async ({ company_id, location_id, config }) => {
59
+ try {
60
+ const sdk = await getSDK();
61
+ const result = await sdk.saasApi.updateRebilling({
62
+ companyId: company_id,
63
+ locationId: location_id,
64
+ ...config,
65
+ });
66
+ return { content: [{ type: "text", text: JSON.stringify({ updated: true, data: result }, null, 2) }] };
67
+ } catch (err) {
68
+ return { content: [{ type: "text", text: JSON.stringify({ error: err.message }) }] };
69
+ }
70
+ }
71
+ );
72
+ }
package/crm/sdk.js ADDED
@@ -0,0 +1,60 @@
1
+ // ============================================================
2
+ // 0nMCP — HighLevel SDK Singleton
3
+ // ============================================================
4
+ // Initialized once with 0nCORE marketplace app credentials.
5
+ // Supabase-backed session storage for persistent OAuth tokens.
6
+ // Every module available: courses, voiceAi, marketplace, etc.
7
+ // ============================================================
8
+
9
+ import { SupabaseSessionStorage } from "./supabase-session-storage.js";
10
+
11
+ let _sdk = null;
12
+ let _initPromise = null;
13
+
14
+ const CLIENT_ID = process.env.CRM_MARKETPLACE_CLIENT_ID || "69c762225a31e1cd2f28dd4c-mnu5pazi";
15
+ const CLIENT_SECRET = process.env.CRM_MARKETPLACE_CLIENT_SECRET || "92cb8fc3-2b23-40bf-adce-6b6afe9b8445";
16
+
17
+ /**
18
+ * Get the HighLevel SDK singleton.
19
+ * Lazy-loads and initializes with Supabase session storage.
20
+ */
21
+ export async function getSDK() {
22
+ if (_sdk) return _sdk;
23
+ if (_initPromise) return _initPromise;
24
+
25
+ _initPromise = (async () => {
26
+ const { HighLevel } = await import("@gohighlevel/api-client");
27
+
28
+ _sdk = new HighLevel({
29
+ clientId: CLIENT_ID,
30
+ clientSecret: CLIENT_SECRET,
31
+ sessionStorage: new SupabaseSessionStorage(),
32
+ });
33
+
34
+ console.error("[0nMCP] HighLevel SDK initialized with Supabase session storage");
35
+ return _sdk;
36
+ })();
37
+
38
+ return _initPromise;
39
+ }
40
+
41
+ /**
42
+ * Store an OAuth token in the SDK's session storage.
43
+ * Call this after OAuth callback to persist tokens.
44
+ */
45
+ export async function storeOAuthToken(locationId, tokenData) {
46
+ const sdk = await getSDK();
47
+ const key = `location:${locationId}`;
48
+ await sdk.config.sessionStorage.set(key, {
49
+ access_token: tokenData.access_token,
50
+ refresh_token: tokenData.refresh_token,
51
+ expires_at: Date.now() + (tokenData.expires_in || 86400) * 1000,
52
+ location_id: locationId,
53
+ company_id: tokenData.companyId || null,
54
+ token_type: tokenData.token_type || "Bearer",
55
+ scope: tokenData.scope || "",
56
+ });
57
+ console.error(`[0nMCP] OAuth token stored for location ${locationId}`);
58
+ }
59
+
60
+ export default getSDK;
@@ -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
@@ -1,5 +1,5 @@
1
1
  {
2
- "generated": "2026-04-05T22:06:13.010Z",
2
+ "generated": "2026-04-20T04:31:57.943Z",
3
3
  "catalogVersion": "3.0.0",
4
4
  "services": 96,
5
5
  "tools": 1280,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0nmcp",
3
- "version": "3.2.1",
3
+ "version": "4.0.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",
@@ -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-05T22:06:13.010Z"
223
+ "lastUpdated": "2026-04-20T04:31:57.943Z"
223
224
  }
224
225
  }