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.
@@ -0,0 +1,223 @@
1
+ // ============================================================
2
+ // 0nMCP — AI Agent Builder (SDK-powered)
3
+ // ============================================================
4
+ // Dynamically generate CRM Agent Studio agents from descriptions.
5
+ // Agents = "AI Workflows" — K-layer powered, MCP-connected,
6
+ // knowledge-base trained. The core product of the 0n platform.
7
+ //
8
+ // Architecture:
9
+ // User describes what they want
10
+ // → AI generates agent config (nodes, edges, KB, MCP)
11
+ // → SDK creates agent in CRM
12
+ // → Agent promoted to production
13
+ // → Available as an "AI Workflow" in the dashboard
14
+ // → K-layers feed learning data back into KB
15
+ // ============================================================
16
+
17
+ import getSDK from "./sdk.js";
18
+ import { crmHeaders, CRM_API_BASE } from "./helpers.js";
19
+
20
+ /**
21
+ * Register AI agent builder MCP tools.
22
+ */
23
+ export function registerAgentBuilderTools(server, z) {
24
+
25
+ server.tool(
26
+ "crm_build_ai_workflow",
27
+ "Build and deploy a complete AI Workflow (Agent Studio agent) from a natural language description. Creates the agent, configures knowledge base connections, adds MCP server nodes for tool access, and promotes to production.",
28
+ {
29
+ location_id: z.string().describe("CRM location ID"),
30
+ name: z.string().describe("AI Workflow name"),
31
+ description: z.string().describe("What this AI Workflow does — the AI generates the full agent config from this"),
32
+ knowledge_base_ids: z.array(z.string()).optional().describe("Knowledge base IDs to connect"),
33
+ mcp_enabled: z.boolean().optional().describe("Enable MCP server access (default true — gives agent access to 0nMCP tools)"),
34
+ personality: z.string().optional().describe("Agent personality/system prompt override"),
35
+ auto_promote: z.boolean().optional().describe("Auto-promote to production (default true)"),
36
+ access_token: z.string().optional().describe("CRM access token (auto-resolved)"),
37
+ },
38
+ async ({ location_id, name, description, knowledge_base_ids, mcp_enabled, personality, auto_promote, access_token }) => {
39
+ try {
40
+ const token = access_token;
41
+ if (!token) {
42
+ return { content: [{ type: "text", text: JSON.stringify({ error: "access_token required" }) }] };
43
+ }
44
+
45
+ const headers = crmHeaders(token);
46
+
47
+ // Step 1: Create the agent
48
+ const createRes = await fetch(`${CRM_API_BASE}/agent-studio/agent`, {
49
+ method: "POST",
50
+ headers,
51
+ body: JSON.stringify({ locationId: location_id, name, description }),
52
+ });
53
+ const createData = await createRes.json();
54
+
55
+ if (!createRes.ok) {
56
+ return { content: [{ type: "text", text: JSON.stringify({ error: "Agent creation failed", details: createData }) }] };
57
+ }
58
+
59
+ const agentId = createData.agent?.id || createData.id;
60
+ const versionId = createData.agent?.versions?.[0]?.id || createData.versionId;
61
+
62
+ if (!agentId) {
63
+ return { content: [{ type: "text", text: JSON.stringify({ error: "No agent ID returned", data: createData }) }] };
64
+ }
65
+
66
+ // Step 2: Configure the agent version with nodes
67
+ const nodes = [];
68
+ const edges = [];
69
+
70
+ // Add knowledge base nodes
71
+ if (knowledge_base_ids && knowledge_base_ids.length > 0) {
72
+ for (let i = 0; i < knowledge_base_ids.length; i++) {
73
+ nodes.push({
74
+ id: `kb_${i}`,
75
+ type: "knowledge_base",
76
+ data: { knowledgeBaseId: knowledge_base_ids[i] },
77
+ position: { x: 200, y: 100 + i * 120 },
78
+ });
79
+ }
80
+ }
81
+
82
+ // Add MCP server node if enabled
83
+ if (mcp_enabled !== false) {
84
+ nodes.push({
85
+ id: "mcp_0nmcp",
86
+ type: "mcp_server",
87
+ data: {
88
+ url: "https://services.leadconnectorhq.com/mcp/",
89
+ name: "0nMCP",
90
+ description: "Universal AI orchestrator — 332+ CRM tools, 96 services",
91
+ },
92
+ position: { x: 400, y: 200 },
93
+ });
94
+ }
95
+
96
+ // Update agent with nodes if we have any
97
+ if (versionId && nodes.length > 0) {
98
+ await fetch(`${CRM_API_BASE}/agent-studio/agent/${agentId}`, {
99
+ method: "PUT",
100
+ headers,
101
+ body: JSON.stringify({
102
+ locationId: location_id,
103
+ versionId,
104
+ nodes,
105
+ edges,
106
+ variables: {
107
+ system_prompt: personality || `You are ${name}. ${description}. Be helpful, professional, and thorough.`,
108
+ },
109
+ }),
110
+ });
111
+ }
112
+
113
+ // Step 3: Promote to production
114
+ if (auto_promote !== false && versionId) {
115
+ await fetch(`${CRM_API_BASE}/agent-studio/agent/${agentId}/promote`, {
116
+ method: "POST",
117
+ headers,
118
+ body: JSON.stringify({ locationId: location_id, versionId }),
119
+ });
120
+ }
121
+
122
+ return {
123
+ content: [{
124
+ type: "text",
125
+ text: JSON.stringify({
126
+ status: "deployed",
127
+ agent_id: agentId,
128
+ version_id: versionId,
129
+ name,
130
+ description,
131
+ knowledge_bases: knowledge_base_ids?.length || 0,
132
+ mcp_enabled: mcp_enabled !== false,
133
+ promoted: auto_promote !== false,
134
+ execute_command: `crm_execute_agent with agentId=${agentId}, locationId=${location_id}`,
135
+ }, null, 2),
136
+ }],
137
+ };
138
+ } catch (err) {
139
+ return { content: [{ type: "text", text: JSON.stringify({ error: err.message }) }] };
140
+ }
141
+ }
142
+ );
143
+
144
+ server.tool(
145
+ "crm_list_ai_workflows",
146
+ "List all AI Workflows (Agent Studio agents) for a location with their status, version, and execution stats.",
147
+ {
148
+ location_id: z.string().describe("CRM location ID"),
149
+ access_token: z.string().optional().describe("CRM access token"),
150
+ },
151
+ async ({ location_id, access_token }) => {
152
+ try {
153
+ const token = access_token;
154
+ if (!token) {
155
+ return { content: [{ type: "text", text: JSON.stringify({ error: "access_token required" }) }] };
156
+ }
157
+ const headers = crmHeaders(token);
158
+ const res = await fetch(`${CRM_API_BASE}/agent-studio/agent?locationId=${location_id}`, { headers });
159
+ const data = await res.json();
160
+
161
+ const workflows = (data.agents || data || []).map((a) => ({
162
+ id: a.id,
163
+ name: a.name,
164
+ description: a.description,
165
+ status: a.status,
166
+ versions: a.versions?.length || 0,
167
+ createdAt: a.createdAt,
168
+ updatedAt: a.updatedAt,
169
+ }));
170
+
171
+ return { content: [{ type: "text", text: JSON.stringify({ workflows, total: workflows.length }, null, 2) }] };
172
+ } catch (err) {
173
+ return { content: [{ type: "text", text: JSON.stringify({ error: err.message }) }] };
174
+ }
175
+ }
176
+ );
177
+
178
+ server.tool(
179
+ "crm_run_ai_workflow",
180
+ "Execute an AI Workflow (Agent Studio agent). Send a message and get a response. Maintains conversation context via executionId.",
181
+ {
182
+ agent_id: z.string().describe("Agent/workflow ID"),
183
+ location_id: z.string().describe("CRM location ID"),
184
+ message: z.string().describe("Message to send to the AI Workflow"),
185
+ execution_id: z.string().optional().describe("Previous execution ID to continue conversation"),
186
+ contact_id: z.string().optional().describe("Contact ID for context"),
187
+ access_token: z.string().optional().describe("CRM access token"),
188
+ },
189
+ async ({ agent_id, location_id, message, execution_id, contact_id, access_token }) => {
190
+ try {
191
+ const token = access_token;
192
+ if (!token) {
193
+ return { content: [{ type: "text", text: JSON.stringify({ error: "access_token required" }) }] };
194
+ }
195
+ const headers = crmHeaders(token);
196
+ const res = await fetch(`${CRM_API_BASE}/agent-studio/agent/${agent_id}/execute`, {
197
+ method: "POST",
198
+ headers,
199
+ body: JSON.stringify({
200
+ locationId: location_id,
201
+ message,
202
+ executionId: execution_id || undefined,
203
+ contactId: contact_id || undefined,
204
+ }),
205
+ });
206
+ const data = await res.json();
207
+
208
+ return {
209
+ content: [{
210
+ type: "text",
211
+ text: JSON.stringify({
212
+ response: data.response || data.message || data.content,
213
+ execution_id: data.executionId || data.execution_id,
214
+ agent_id,
215
+ }, null, 2),
216
+ }],
217
+ };
218
+ } catch (err) {
219
+ return { content: [{ type: "text", text: JSON.stringify({ error: err.message }) }] };
220
+ }
221
+ }
222
+ );
223
+ }
package/crm/billing.js ADDED
@@ -0,0 +1,173 @@
1
+ // ============================================================
2
+ // 0nMCP — CRM External Billing Tools
3
+ // ============================================================
4
+ // Reports payment status to the CRM billing webhook.
5
+ // Required for marketplace apps using external billing (Stripe).
6
+ //
7
+ // Flow:
8
+ // 1. User purchases add-on via Stripe
9
+ // 2. Stripe webhook fires checkout.session.completed
10
+ // 3. We POST to CRM billing webhook: "location X paid"
11
+ // 4. CRM activates the marketplace app for that location
12
+ // 5. If recurring payment fails: POST status: FAILED → CRM deactivates
13
+ //
14
+ // Endpoint: POST https://services.leadconnectorhq.com/oauth/billing/webhook
15
+ // Headers: x-ghl-client-key, x-ghl-client-secret
16
+ // ============================================================
17
+
18
+ const CRM_BILLING_URL = "https://services.leadconnectorhq.com/oauth/billing/webhook";
19
+
20
+ const DEFAULT_CLIENT_ID = process.env.CRM_MARKETPLACE_CLIENT_ID || "69c762225a31e1cd2f28dd4c-mnu5pazi";
21
+ const DEFAULT_CLIENT_SECRET = process.env.CRM_MARKETPLACE_CLIENT_SECRET || "92cb8fc3-2b23-40bf-adce-6b6afe9b8445";
22
+
23
+ /**
24
+ * Report billing status to CRM for a location or company.
25
+ *
26
+ * @param {Object} params
27
+ * @param {string} params.locationId - CRM location ID
28
+ * @param {string} [params.companyId] - CRM company ID (for agency-level billing)
29
+ * @param {string} params.authType - "location" or "company"
30
+ * @param {number} params.amount - Payment amount in dollars
31
+ * @param {string} params.status - "COMPLETED" or "FAILED"
32
+ * @param {string} params.paymentType - "one_time" or "recurring"
33
+ * @param {string} [params.subscriptionId] - External subscription ID
34
+ * @param {string} [params.paymentId] - External payment ID
35
+ * @param {string} [params.clientId] - Override client ID
36
+ * @param {string} [params.clientSecret] - Override client secret
37
+ */
38
+ export async function reportBilling({
39
+ locationId,
40
+ companyId,
41
+ authType = "location",
42
+ amount,
43
+ status,
44
+ paymentType = "one_time",
45
+ subscriptionId,
46
+ paymentId,
47
+ clientId,
48
+ clientSecret,
49
+ }) {
50
+ const cid = clientId || DEFAULT_CLIENT_ID;
51
+ const csec = clientSecret || DEFAULT_CLIENT_SECRET;
52
+
53
+ const body = {
54
+ clientId: cid.split("-")[0], // App ID portion only
55
+ authType,
56
+ locationId: locationId || undefined,
57
+ companyId: companyId || undefined,
58
+ subscriptionId: subscriptionId || `sub_${Date.now()}`,
59
+ paymentId: paymentId || `pay_${Date.now()}`,
60
+ amount,
61
+ status,
62
+ paymentType,
63
+ };
64
+
65
+ const res = await fetch(CRM_BILLING_URL, {
66
+ method: "POST",
67
+ headers: {
68
+ "Content-Type": "application/json",
69
+ "x-ghl-client-key": cid,
70
+ "x-ghl-client-secret": csec,
71
+ },
72
+ body: JSON.stringify(body),
73
+ });
74
+
75
+ const data = await res.json().catch(() => ({ status: res.status }));
76
+
77
+ if (!res.ok) {
78
+ console.error("[billing] CRM billing webhook failed:", res.status, data);
79
+ } else {
80
+ console.error(`[billing] Reported ${status} for ${authType} ${locationId || companyId}: $${amount}`);
81
+ }
82
+
83
+ return { ok: res.ok, status: res.status, data };
84
+ }
85
+
86
+ /**
87
+ * Register billing MCP tools.
88
+ */
89
+ export function registerBillingTools(server, z) {
90
+ server.tool(
91
+ "crm_report_billing",
92
+ "Report a payment to the CRM billing webhook. Activates or deactivates a marketplace app for a location based on payment status. Required for external billing (SaaS subscriptions through Stripe).",
93
+ {
94
+ location_id: z.string().optional().describe("CRM location ID (for location-level billing)"),
95
+ company_id: z.string().optional().describe("CRM company ID (for agency-level billing)"),
96
+ auth_type: z.enum(["location", "company"]).describe("Billing scope: location or company"),
97
+ amount: z.number().describe("Payment amount in dollars"),
98
+ status: z.enum(["COMPLETED", "FAILED"]).describe("Payment status"),
99
+ payment_type: z.enum(["one_time", "recurring"]).optional().describe("Payment type (default: one_time)"),
100
+ subscription_id: z.string().optional().describe("External subscription ID (e.g. Stripe sub_xxx)"),
101
+ payment_id: z.string().optional().describe("External payment ID (e.g. Stripe pi_xxx)"),
102
+ },
103
+ async ({ location_id, company_id, auth_type, amount, status, payment_type, subscription_id, payment_id }) => {
104
+ const result = await reportBilling({
105
+ locationId: location_id,
106
+ companyId: company_id,
107
+ authType: auth_type,
108
+ amount,
109
+ status,
110
+ paymentType: payment_type || "one_time",
111
+ subscriptionId: subscription_id,
112
+ paymentId: payment_id,
113
+ });
114
+
115
+ return {
116
+ content: [{
117
+ type: "text",
118
+ text: JSON.stringify({
119
+ reported: true,
120
+ crm_status: result.ok ? "accepted" : "rejected",
121
+ http_status: result.status,
122
+ location_id,
123
+ company_id,
124
+ amount,
125
+ status,
126
+ payment_type: payment_type || "one_time",
127
+ }, null, 2),
128
+ }],
129
+ };
130
+ }
131
+ );
132
+
133
+ server.tool(
134
+ "crm_create_payment_config",
135
+ "Configure payment processing (Stripe) for a CRM location. This wires Stripe into the location so it can accept payments natively.",
136
+ {
137
+ location_id: z.string().describe("CRM location ID"),
138
+ live_api_key: z.string().describe("Stripe live secret key"),
139
+ live_publishable_key: z.string().describe("Stripe live publishable key"),
140
+ test_api_key: z.string().optional().describe("Stripe test secret key"),
141
+ test_publishable_key: z.string().optional().describe("Stripe test publishable key"),
142
+ },
143
+ async ({ location_id, live_api_key, live_publishable_key, test_api_key, test_publishable_key }) => {
144
+ try {
145
+ const { default: getSDK } = await import("./sdk.js");
146
+ const sdk = await getSDK();
147
+
148
+ const config = {
149
+ live: { apiKey: live_api_key, publishableKey: live_publishable_key },
150
+ };
151
+ if (test_api_key && test_publishable_key) {
152
+ config.test = { apiKey: test_api_key, publishableKey: test_publishable_key };
153
+ }
154
+
155
+ const response = await sdk.payments.createConfig({ locationId: location_id }, config);
156
+
157
+ return {
158
+ content: [{
159
+ type: "text",
160
+ text: JSON.stringify({ status: "configured", location_id, response }, null, 2),
161
+ }],
162
+ };
163
+ } catch (err) {
164
+ return {
165
+ content: [{
166
+ type: "text",
167
+ text: JSON.stringify({ error: err.message, status: "failed" }, null, 2),
168
+ }],
169
+ };
170
+ }
171
+ }
172
+ );
173
+ }
@@ -114,6 +114,13 @@ const conversationTools = [
114
114
  replyToMessageId: { type: "string", description: "Message ID to reply to", required: false, in: "body" },
115
115
  templateId: { type: "string", description: "Template ID for pre-built messages", required: false, in: "body" },
116
116
  scheduledTimestamp: { type: "number", description: "Unix timestamp (ms) to schedule the message", required: false, in: "body" },
117
+ appointmentId: { type: "string", description: "Link message to an appointment", required: false, in: "body" },
118
+ threadId: { type: "string", description: "Email thread ID for threading", required: false, in: "body" },
119
+ emailReplyMode: { type: "string", description: "Reply mode: reply, reply_all", required: false, in: "body" },
120
+ fromNumber: { type: "string", description: "From phone number (E.164)", required: false, in: "body" },
121
+ toNumber: { type: "string", description: "To phone number (E.164)", required: false, in: "body" },
122
+ mentions: { type: "array_string", description: "Array of user IDs to mention", required: false, in: "body" },
123
+ userId: { type: "string", description: "Send as this user", required: false, in: "body" },
117
124
  },
118
125
  body: [
119
126
  "type",
@@ -132,6 +139,13 @@ const conversationTools = [
132
139
  "replyToMessageId",
133
140
  "templateId",
134
141
  "scheduledTimestamp",
142
+ "appointmentId",
143
+ "threadId",
144
+ "emailReplyMode",
145
+ "fromNumber",
146
+ "toNumber",
147
+ "mentions",
148
+ "userId",
135
149
  ],
136
150
  },
137
151
 
@@ -0,0 +1,161 @@
1
+ // ============================================================
2
+ // 0nMCP — AI Course Generator
3
+ // ============================================================
4
+ // User describes what they want → AI generates course structure
5
+ // → SDK imports it into their CRM location.
6
+ //
7
+ // Flow:
8
+ // 1. User describes course topic + goals
9
+ // 2. AI generates full course JSON (title, categories, lessons)
10
+ // 3. SDK calls courses.importCourses() → live in CRM
11
+ // 4. User sees course in their CRM memberships area
12
+ // ============================================================
13
+
14
+ import getSDK from "./sdk.js";
15
+
16
+ /**
17
+ * Generate course content structure from a description.
18
+ * Uses the AI to create categories, lessons, and materials.
19
+ *
20
+ * @param {string} topic - What the course is about
21
+ * @param {string} audience - Who it's for
22
+ * @param {number} lessonCount - Approximate number of lessons (default 8)
23
+ * @param {string} [instructorName] - Instructor name
24
+ * @returns {Object} - Course structure ready for importCourses()
25
+ */
26
+ export function generateCourseStructure(topic, audience, lessonCount = 8, instructorName = "AI Instructor") {
27
+ // This returns the structure template — the AI fills in the content
28
+ // when called through the MCP tool
29
+ return {
30
+ title: topic,
31
+ description: `A comprehensive course on ${topic} designed for ${audience}.`,
32
+ imageUrl: "",
33
+ categories: [],
34
+ instructorDetails: {
35
+ name: instructorName,
36
+ description: `Expert instructor covering ${topic}.`,
37
+ },
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Import a fully-structured course into a CRM location.
43
+ *
44
+ * @param {string} locationId - CRM location ID
45
+ * @param {string} userId - CRM user ID
46
+ * @param {Object} courseData - Full course structure
47
+ */
48
+ export async function importCourse(locationId, userId, courseData) {
49
+ const sdk = await getSDK();
50
+
51
+ const response = await sdk.courses.importCourses({
52
+ locationId,
53
+ userId,
54
+ products: [courseData],
55
+ });
56
+
57
+ return response;
58
+ }
59
+
60
+ /**
61
+ * Register course generator MCP tools.
62
+ */
63
+ export function registerCourseTools(server, z) {
64
+ server.tool(
65
+ "crm_generate_course",
66
+ "Generate a full AI course and import it into a CRM location. Provide the topic, audience, and desired structure. The AI builds categories with lessons and imports everything.",
67
+ {
68
+ location_id: z.string().describe("CRM location ID where the course will be created"),
69
+ user_id: z.string().describe("CRM user ID (course owner)"),
70
+ title: z.string().describe("Course title"),
71
+ description: z.string().describe("Course description"),
72
+ instructor_name: z.string().optional().describe("Instructor name (default: AI Instructor)"),
73
+ categories: z.array(z.object({
74
+ title: z.string().describe("Category/module title"),
75
+ lessons: z.array(z.object({
76
+ title: z.string().describe("Lesson title"),
77
+ description: z.string().describe("Lesson content/description (can be full HTML)"),
78
+ content_type: z.string().optional().describe("Content type: video, text, audio (default: text)"),
79
+ })).describe("Lessons in this category"),
80
+ })).describe("Course categories with lessons"),
81
+ },
82
+ async ({ location_id, user_id, title, description, instructor_name, categories }) => {
83
+ try {
84
+ const courseData = {
85
+ title,
86
+ description,
87
+ imageUrl: "",
88
+ categories: categories.map((cat) => ({
89
+ title: cat.title,
90
+ visibility: "published",
91
+ thumbnailUrl: "",
92
+ posts: cat.lessons.map((lesson) => ({
93
+ title: lesson.title,
94
+ visibility: "published",
95
+ thumbnailUrl: "",
96
+ contentType: lesson.content_type || "text",
97
+ description: lesson.description,
98
+ bucketVideoUrl: "",
99
+ postMaterials: [],
100
+ })),
101
+ subCategories: [],
102
+ })),
103
+ instructorDetails: {
104
+ name: instructor_name || "AI Instructor",
105
+ description: `Course instructor for ${title}`,
106
+ },
107
+ };
108
+
109
+ const result = await importCourse(location_id, user_id, courseData);
110
+
111
+ return {
112
+ content: [{
113
+ type: "text",
114
+ text: JSON.stringify({
115
+ status: "imported",
116
+ title,
117
+ categories: categories.length,
118
+ total_lessons: categories.reduce((sum, c) => sum + c.lessons.length, 0),
119
+ location_id,
120
+ result,
121
+ }, null, 2),
122
+ }],
123
+ };
124
+ } catch (err) {
125
+ return {
126
+ content: [{
127
+ type: "text",
128
+ text: JSON.stringify({ error: err.message, status: "failed" }, null, 2),
129
+ }],
130
+ };
131
+ }
132
+ }
133
+ );
134
+
135
+ server.tool(
136
+ "crm_list_courses",
137
+ "List all courses/products in a CRM location.",
138
+ {
139
+ location_id: z.string().describe("CRM location ID"),
140
+ },
141
+ async ({ location_id }) => {
142
+ try {
143
+ const sdk = await getSDK();
144
+ const response = await sdk.courses.getProducts({ locationId: location_id });
145
+ return {
146
+ content: [{
147
+ type: "text",
148
+ text: JSON.stringify(response, null, 2),
149
+ }],
150
+ };
151
+ } catch (err) {
152
+ return {
153
+ content: [{
154
+ type: "text",
155
+ text: JSON.stringify({ error: err.message }, null, 2),
156
+ }],
157
+ };
158
+ }
159
+ }
160
+ );
161
+ }