0nmcp 2.6.0 → 2.7.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/README.md +233 -695
- package/cli.js +9 -1
- package/crm/objects.js +5 -69
- package/crm/users.js +5 -80
- package/engine/index.js +338 -2
- package/engine/multi-ai.js +525 -0
- package/engine/plugin-builder.js +578 -0
- package/engine/plugin-registry.js +419 -0
- package/engine/plugin.js +448 -0
- package/engine/training-feed.js +520 -0
- package/engine/training.js +875 -0
- package/index.js +9 -1
- package/lib/stats.json +1 -1
- package/package.json +12 -2
package/cli.js
CHANGED
|
@@ -76,6 +76,14 @@ async function main() {
|
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
// ── Install (full onboarding — no login required) ─────────
|
|
80
|
+
|
|
81
|
+
if (command === 'install') {
|
|
82
|
+
const { install } = await import('./install.js');
|
|
83
|
+
await install();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
79
87
|
// ── Auth commands (no login required) ──────────────────────
|
|
80
88
|
|
|
81
89
|
if (command === 'login') {
|
|
@@ -194,7 +202,7 @@ ${c.bright}Links:${c.reset}
|
|
|
194
202
|
|
|
195
203
|
// ── Auth Gate ──────────────────────────────────────────────
|
|
196
204
|
// All commands below this point require authentication.
|
|
197
|
-
const AUTH_FREE = ['help', '--help', '-h', 'login', 'logout', 'whoami', 'version', '--version', '-v'];
|
|
205
|
+
const AUTH_FREE = ['help', '--help', '-h', 'login', 'logout', 'whoami', 'version', '--version', '-v', 'install'];
|
|
198
206
|
if (!AUTH_FREE.includes(command)) {
|
|
199
207
|
try {
|
|
200
208
|
const { isAuthenticated } = await import('./auth.js');
|
package/crm/objects.js
CHANGED
|
@@ -155,36 +155,8 @@ export default [
|
|
|
155
155
|
|
|
156
156
|
// ── Associations ────────────────────────────────────────────
|
|
157
157
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
description: "List all association definitions for a custom object schema in a CRM location.",
|
|
161
|
-
method: "GET",
|
|
162
|
-
path: "/associations/",
|
|
163
|
-
params: {
|
|
164
|
-
locationId: { type: "string", description: "Location / sub-account ID", required: true, in: "query" },
|
|
165
|
-
schemaKey: { type: "string", description: "Custom object schema key to list associations for", required: true, in: "query" },
|
|
166
|
-
},
|
|
167
|
-
query: ["locationId", "schemaKey"],
|
|
168
|
-
body: [],
|
|
169
|
-
},
|
|
170
|
-
|
|
171
|
-
{
|
|
172
|
-
name: "crm_create_association",
|
|
173
|
-
description: "Create a new association definition between two custom object schemas.",
|
|
174
|
-
method: "POST",
|
|
175
|
-
path: "/associations/",
|
|
176
|
-
params: {
|
|
177
|
-
locationId: { type: "string", description: "Location / sub-account ID", required: true, in: "body" },
|
|
178
|
-
name: { type: "string", description: "Display name for the association", required: true, in: "body" },
|
|
179
|
-
key: { type: "string", description: "Unique key for the association (lowercase, no spaces)", required: true, in: "body" },
|
|
180
|
-
fromSchemaKey: { type: "string", description: "Schema key of the source object", required: true, in: "body" },
|
|
181
|
-
toSchemaKey: { type: "string", description: "Schema key of the target object", required: true, in: "body" },
|
|
182
|
-
fromDisplayField: { type: "string", description: "Field key shown when viewing from the source side", required: false, in: "body" },
|
|
183
|
-
toDisplayField: { type: "string", description: "Field key shown when viewing from the target side", required: false, in: "body" },
|
|
184
|
-
},
|
|
185
|
-
query: [],
|
|
186
|
-
body: ["locationId", "name", "key", "fromSchemaKey", "toSchemaKey", "fromDisplayField", "toDisplayField"],
|
|
187
|
-
},
|
|
158
|
+
// crm_list_associations — defined in funnels.js
|
|
159
|
+
// crm_create_association — defined in funnels.js
|
|
188
160
|
|
|
189
161
|
// ── Email Builder ───────────────────────────────────────────
|
|
190
162
|
|
|
@@ -310,45 +282,9 @@ export default [
|
|
|
310
282
|
|
|
311
283
|
// ── Snapshots ───────────────────────────────────────────────
|
|
312
284
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
method: "GET",
|
|
317
|
-
path: "/snapshots/",
|
|
318
|
-
params: {
|
|
319
|
-
companyId: { type: "string", description: "Company / agency ID", required: true, in: "query" },
|
|
320
|
-
},
|
|
321
|
-
query: ["companyId"],
|
|
322
|
-
body: [],
|
|
323
|
-
},
|
|
324
|
-
|
|
325
|
-
{
|
|
326
|
-
name: "crm_create_snapshot_share_link",
|
|
327
|
-
description: "Create a shareable link for a snapshot so it can be pushed to other locations.",
|
|
328
|
-
method: "POST",
|
|
329
|
-
path: "/snapshots/share/link",
|
|
330
|
-
params: {
|
|
331
|
-
companyId: { type: "string", description: "Company / agency ID", required: true, in: "body" },
|
|
332
|
-
snapshotId: { type: "string", description: "Snapshot ID to share", required: true, in: "body" },
|
|
333
|
-
shareType: { type: "string", description: "Type of share (e.g. link, permanent)", required: true, in: "body" },
|
|
334
|
-
relationshipNumber: { type: "string", description: "Relationship number for the share link", required: false, in: "body" },
|
|
335
|
-
},
|
|
336
|
-
query: [],
|
|
337
|
-
body: ["companyId", "snapshotId", "shareType", "relationshipNumber"],
|
|
338
|
-
},
|
|
339
|
-
|
|
340
|
-
{
|
|
341
|
-
name: "crm_get_snapshot_push_status",
|
|
342
|
-
description: "Get the push status between a snapshot and a specific location.",
|
|
343
|
-
method: "GET",
|
|
344
|
-
path: "/snapshots/snapshot-status/:snapshotId/:locationId",
|
|
345
|
-
params: {
|
|
346
|
-
snapshotId: { type: "string", description: "Snapshot ID to check", required: true, in: "path" },
|
|
347
|
-
locationId: { type: "string", description: "Location / sub-account ID to check status against", required: true, in: "path" },
|
|
348
|
-
},
|
|
349
|
-
query: [],
|
|
350
|
-
body: [],
|
|
351
|
-
},
|
|
285
|
+
// crm_list_snapshots — defined in funnels.js
|
|
286
|
+
// crm_create_snapshot_share_link — defined in funnels.js
|
|
287
|
+
// crm_get_snapshot_push_status — defined in funnels.js
|
|
352
288
|
|
|
353
289
|
// ── Trigger Links ───────────────────────────────────────────
|
|
354
290
|
|
package/crm/users.js
CHANGED
|
@@ -105,20 +105,7 @@ const users = [
|
|
|
105
105
|
|
|
106
106
|
// ── FORMS ─────────────────────────────────────────────────
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
name: "crm_list_forms",
|
|
110
|
-
description: "List all forms for a CRM location with optional pagination and type filter.",
|
|
111
|
-
method: "GET",
|
|
112
|
-
path: "/forms/",
|
|
113
|
-
params: {
|
|
114
|
-
locationId: { type: "string", description: "Location / sub-account ID", required: true, in: "query" },
|
|
115
|
-
skip: { type: "number", description: "Number of records to skip for pagination", required: false, in: "query" },
|
|
116
|
-
limit: { type: "number", description: "Maximum number of forms to return", required: false, in: "query" },
|
|
117
|
-
type: { type: "string", description: "Filter by form type", required: false, in: "query" },
|
|
118
|
-
},
|
|
119
|
-
query: ["locationId", "skip", "limit", "type"],
|
|
120
|
-
body: [],
|
|
121
|
-
},
|
|
108
|
+
// crm_list_forms — defined in funnels.js
|
|
122
109
|
|
|
123
110
|
{
|
|
124
111
|
name: "crm_get_form_submissions",
|
|
@@ -154,20 +141,7 @@ const users = [
|
|
|
154
141
|
|
|
155
142
|
// ── SURVEYS ───────────────────────────────────────────────
|
|
156
143
|
|
|
157
|
-
|
|
158
|
-
name: "crm_list_surveys",
|
|
159
|
-
description: "List all surveys for a CRM location with optional pagination and type filter.",
|
|
160
|
-
method: "GET",
|
|
161
|
-
path: "/surveys/",
|
|
162
|
-
params: {
|
|
163
|
-
locationId: { type: "string", description: "Location / sub-account ID", required: true, in: "query" },
|
|
164
|
-
skip: { type: "number", description: "Number of records to skip for pagination", required: false, in: "query" },
|
|
165
|
-
limit: { type: "number", description: "Maximum number of surveys to return", required: false, in: "query" },
|
|
166
|
-
type: { type: "string", description: "Filter by survey type", required: false, in: "query" },
|
|
167
|
-
},
|
|
168
|
-
query: ["locationId", "skip", "limit", "type"],
|
|
169
|
-
body: [],
|
|
170
|
-
},
|
|
144
|
+
// crm_list_surveys — defined in funnels.js
|
|
171
145
|
|
|
172
146
|
{
|
|
173
147
|
name: "crm_get_survey_submissions",
|
|
@@ -187,60 +161,11 @@ const users = [
|
|
|
187
161
|
body: [],
|
|
188
162
|
},
|
|
189
163
|
|
|
190
|
-
// ── FUNNELS / WEBSITES
|
|
191
|
-
|
|
192
|
-
{
|
|
193
|
-
name: "crm_list_funnels",
|
|
194
|
-
description: "List funnels and websites for a CRM location with optional filters for type, name, category, and parent.",
|
|
195
|
-
method: "GET",
|
|
196
|
-
path: "/funnels/funnel/list",
|
|
197
|
-
params: {
|
|
198
|
-
locationId: { type: "string", description: "Location / sub-account ID", required: true, in: "query" },
|
|
199
|
-
limit: { type: "number", description: "Maximum number of funnels to return", required: false, in: "query" },
|
|
200
|
-
offset: { type: "number", description: "Number of records to skip for pagination", required: false, in: "query" },
|
|
201
|
-
type: { type: "string", description: "Filter by funnel type", required: false, in: "query" },
|
|
202
|
-
name: { type: "string", description: "Filter by exact funnel name", required: false, in: "query" },
|
|
203
|
-
search: { type: "string", description: "Free-text search across funnel names", required: false, in: "query" },
|
|
204
|
-
category: { type: "string", description: "Filter by funnel category", required: false, in: "query" },
|
|
205
|
-
parentId: { type: "string", description: "Filter by parent funnel ID", required: false, in: "query" },
|
|
206
|
-
},
|
|
207
|
-
query: ["locationId", "limit", "offset", "type", "name", "search", "category", "parentId"],
|
|
208
|
-
body: [],
|
|
209
|
-
},
|
|
210
|
-
|
|
211
|
-
{
|
|
212
|
-
name: "crm_get_funnel_pages",
|
|
213
|
-
description: "List pages within a funnel with optional filters for name and search.",
|
|
214
|
-
method: "GET",
|
|
215
|
-
path: "/funnels/page",
|
|
216
|
-
params: {
|
|
217
|
-
locationId: { type: "string", description: "Location / sub-account ID", required: true, in: "query" },
|
|
218
|
-
funnelId: { type: "string", description: "Funnel ID to list pages for", required: true, in: "query" },
|
|
219
|
-
limit: { type: "number", description: "Maximum number of pages to return", required: false, in: "query" },
|
|
220
|
-
offset: { type: "number", description: "Number of records to skip for pagination", required: false, in: "query" },
|
|
221
|
-
name: { type: "string", description: "Filter by exact page name", required: false, in: "query" },
|
|
222
|
-
search: { type: "string", description: "Free-text search across page names", required: false, in: "query" },
|
|
223
|
-
},
|
|
224
|
-
query: ["locationId", "funnelId", "limit", "offset", "name", "search"],
|
|
225
|
-
body: [],
|
|
226
|
-
},
|
|
227
|
-
|
|
228
|
-
{
|
|
229
|
-
name: "crm_count_funnel_pages",
|
|
230
|
-
description: "Get the total count of pages within a funnel, optionally filtered by name.",
|
|
231
|
-
method: "GET",
|
|
232
|
-
path: "/funnels/page/count",
|
|
233
|
-
params: {
|
|
234
|
-
locationId: { type: "string", description: "Location / sub-account ID", required: true, in: "query" },
|
|
235
|
-
funnelId: { type: "string", description: "Funnel ID to count pages for", required: true, in: "query" },
|
|
236
|
-
name: { type: "string", description: "Filter count by page name", required: false, in: "query" },
|
|
237
|
-
},
|
|
238
|
-
query: ["locationId", "funnelId", "name"],
|
|
239
|
-
body: [],
|
|
240
|
-
},
|
|
164
|
+
// ── FUNNELS / WEBSITES (defined in funnels.js) ────────────
|
|
165
|
+
// crm_list_funnels, crm_get_funnel_pages, crm_count_funnel_pages → see funnels.js
|
|
241
166
|
|
|
242
167
|
{
|
|
243
|
-
name: "
|
|
168
|
+
name: "crm_funnel_redirect_lookup_by_id",
|
|
244
169
|
description: "Look up a funnel redirect by its redirect ID.",
|
|
245
170
|
method: "GET",
|
|
246
171
|
path: "/funnels/lookup/redirect",
|
package/engine/index.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// ============================================================
|
|
4
4
|
// The .0n Conversion Engine — import credentials, verify keys,
|
|
5
5
|
// generate platform configs, create portable AI Brain bundles,
|
|
6
|
-
//
|
|
6
|
+
// build/run application bundles, and the 0nEngine Plugin System.
|
|
7
7
|
//
|
|
8
|
-
//
|
|
8
|
+
// 16 MCP Tools:
|
|
9
9
|
// engine_import — Import credentials from .env/CSV/JSON
|
|
10
10
|
// engine_verify — Verify API keys with test calls
|
|
11
11
|
// engine_platforms — Generate platform configs
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
// app_inspect — Show application metadata (no passphrase)
|
|
18
18
|
// app_validate — Validate application cross-references
|
|
19
19
|
// app_list — List installed applications
|
|
20
|
+
// plugin_list — List all plugins (catalog + custom)
|
|
21
|
+
// plugin_build — Build a plugin from service key or spec
|
|
22
|
+
// plugin_execute — Execute a plugin endpoint with .0n fields
|
|
23
|
+
// plugin_inspect — Inspect a plugin's capabilities & endpoints
|
|
24
|
+
// plugin_create — Generate a new custom plugin spec
|
|
20
25
|
//
|
|
21
26
|
// Patent Pending: US Provisional Patent Application #63/968,814
|
|
22
27
|
// ============================================================
|
|
@@ -34,6 +39,18 @@ export { Application } from "./application.js";
|
|
|
34
39
|
export { createApplication, openApplication, inspectApplication, validateApplication } from "./app-builder.js";
|
|
35
40
|
export { ApplicationServer } from "./app-server.js";
|
|
36
41
|
|
|
42
|
+
// ── 0nEngine Plugin System ──────────────────────────────────
|
|
43
|
+
export { Plugin } from "./plugin.js";
|
|
44
|
+
export { PluginBuilder, getPluginBuilder, buildPlugin, buildAllPlugins, buildFromSpec, generatePluginSpec } from "./plugin-builder.js";
|
|
45
|
+
export { PluginRegistry, getPluginRegistry } from "./plugin-registry.js";
|
|
46
|
+
|
|
47
|
+
// ── 0nAI Training Center ────────────────────────────────────
|
|
48
|
+
export { registerTrainingTools } from "./training.js";
|
|
49
|
+
export { TrainingFeedEngine, registerFeedTools, FEED_SOURCES } from "./training-feed.js";
|
|
50
|
+
|
|
51
|
+
// ── Multi-AI Council ────────────────────────────────────────
|
|
52
|
+
export { registerCouncilTools, getAvailableProviders, askAll, PROVIDERS } from "./multi-ai.js";
|
|
53
|
+
|
|
37
54
|
// ── Imports for tool handlers ──────────────────────────────
|
|
38
55
|
import { parseFile } from "./parser.js";
|
|
39
56
|
import { mapEnvVars, groupByService, validateMapping } from "./mapper.js";
|
|
@@ -41,6 +58,8 @@ import { verifyCredentials, verifyAll } from "./validator.js";
|
|
|
41
58
|
import { generatePlatformConfig, generateAllPlatformConfigs, getPlatformInfo, listPlatforms } from "./platforms.js";
|
|
42
59
|
import { createBundle, openBundle, inspectBundle, verifyBundle } from "./bundler.js";
|
|
43
60
|
import { createApplication, openApplication, inspectApplication, validateApplication } from "./app-builder.js";
|
|
61
|
+
import { PluginBuilder, getPluginBuilder } from "./plugin-builder.js";
|
|
62
|
+
import { PluginRegistry, getPluginRegistry } from "./plugin-registry.js";
|
|
44
63
|
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
45
64
|
import { join } from "path";
|
|
46
65
|
import { homedir } from "os";
|
|
@@ -642,6 +661,323 @@ Example: app_list({})`,
|
|
|
642
661
|
}
|
|
643
662
|
}
|
|
644
663
|
);
|
|
664
|
+
|
|
665
|
+
// ═══════════════════════════════════════════════════════════
|
|
666
|
+
// 0nEngine Plugin Tools
|
|
667
|
+
// ═══════════════════════════════════════════════════════════
|
|
668
|
+
|
|
669
|
+
// ─── plugin_list ───────────────────────────────────────────
|
|
670
|
+
server.tool(
|
|
671
|
+
"plugin_list",
|
|
672
|
+
`List all available plugins — catalog services + custom plugins from ~/.0n/plugins/.
|
|
673
|
+
Shows connection status, endpoint counts, capability counts, and field coverage.
|
|
674
|
+
|
|
675
|
+
Example: plugin_list({})
|
|
676
|
+
Example: plugin_list({ type: "email" })
|
|
677
|
+
Example: plugin_list({ connected: true })`,
|
|
678
|
+
{
|
|
679
|
+
type: z.string().optional().describe("Filter by service type (email, payments, crm, etc.)"),
|
|
680
|
+
connected: z.boolean().optional().describe("Filter by connection status"),
|
|
681
|
+
search: z.string().optional().describe("Search by keyword in name/description"),
|
|
682
|
+
},
|
|
683
|
+
async ({ type, connected, search }) => {
|
|
684
|
+
try {
|
|
685
|
+
const registry = getPluginRegistry();
|
|
686
|
+
|
|
687
|
+
let plugins = registry.all();
|
|
688
|
+
|
|
689
|
+
if (type) plugins = plugins.filter(p => p.type === type);
|
|
690
|
+
if (connected !== undefined) plugins = plugins.filter(p => p.isConnected === connected);
|
|
691
|
+
if (search) {
|
|
692
|
+
const q = search.toLowerCase();
|
|
693
|
+
plugins = plugins.filter(p =>
|
|
694
|
+
p.name.toLowerCase().includes(q) ||
|
|
695
|
+
p.description.toLowerCase().includes(q) ||
|
|
696
|
+
p.key.toLowerCase().includes(q)
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const list = plugins.map(p => ({
|
|
701
|
+
key: p.key,
|
|
702
|
+
name: p.name,
|
|
703
|
+
type: p.type,
|
|
704
|
+
connected: p.isConnected,
|
|
705
|
+
endpoints: Object.keys(p.endpoints).length,
|
|
706
|
+
capabilities: p.capabilities.length,
|
|
707
|
+
}));
|
|
708
|
+
|
|
709
|
+
return {
|
|
710
|
+
content: [{
|
|
711
|
+
type: "text",
|
|
712
|
+
text: JSON.stringify({
|
|
713
|
+
status: "ok",
|
|
714
|
+
total: list.length,
|
|
715
|
+
plugins: list,
|
|
716
|
+
types: [...new Set(plugins.map(p => p.type))],
|
|
717
|
+
}, null, 2),
|
|
718
|
+
}],
|
|
719
|
+
};
|
|
720
|
+
} catch (err) {
|
|
721
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "failed", error: err.message }, null, 2) }] };
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
);
|
|
725
|
+
|
|
726
|
+
// ─── plugin_inspect ────────────────────────────────────────
|
|
727
|
+
server.tool(
|
|
728
|
+
"plugin_inspect",
|
|
729
|
+
`Inspect a plugin's full details — capabilities, endpoints, field mappings, and stats.
|
|
730
|
+
|
|
731
|
+
Example: plugin_inspect({ service: "stripe" })`,
|
|
732
|
+
{
|
|
733
|
+
service: z.string().describe("Service key to inspect (e.g., stripe, crm, sendgrid)"),
|
|
734
|
+
},
|
|
735
|
+
async ({ service }) => {
|
|
736
|
+
try {
|
|
737
|
+
const builder = getPluginBuilder();
|
|
738
|
+
const plugin = builder.build(service);
|
|
739
|
+
|
|
740
|
+
const info = plugin.inspect();
|
|
741
|
+
const fields = builder.getServiceFields(service);
|
|
742
|
+
|
|
743
|
+
return {
|
|
744
|
+
content: [{
|
|
745
|
+
type: "text",
|
|
746
|
+
text: JSON.stringify({
|
|
747
|
+
status: "ok",
|
|
748
|
+
...info,
|
|
749
|
+
fieldMappings: fields,
|
|
750
|
+
fieldCount: Object.keys(fields).length,
|
|
751
|
+
}, null, 2),
|
|
752
|
+
}],
|
|
753
|
+
};
|
|
754
|
+
} catch (err) {
|
|
755
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "failed", error: err.message }, null, 2) }] };
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
// ─── plugin_execute ────────────────────────────────────────
|
|
761
|
+
server.tool(
|
|
762
|
+
"plugin_execute",
|
|
763
|
+
`Execute a plugin endpoint with automatic .0n field resolution.
|
|
764
|
+
Accepts canonical .0n fields (email.0n, fullname.0n, etc.) and auto-translates
|
|
765
|
+
to the service's native format. Handles auth, rate limiting, and response normalization.
|
|
766
|
+
|
|
767
|
+
Example: plugin_execute({
|
|
768
|
+
service: "stripe",
|
|
769
|
+
endpoint: "create_customer",
|
|
770
|
+
params: { "email.0n": "mike@rocketopp.com", "fullname.0n": "Mike Mento" }
|
|
771
|
+
})`,
|
|
772
|
+
{
|
|
773
|
+
service: z.string().describe("Service key (e.g., stripe, crm, sendgrid)"),
|
|
774
|
+
endpoint: z.string().describe("Endpoint name from the service catalog (e.g., create_customer, send_email)"),
|
|
775
|
+
params: z.record(z.any()).optional().describe("Parameters — supports .0n canonical fields (email.0n) and raw service fields"),
|
|
776
|
+
credentials: z.record(z.string()).optional().describe("One-time credentials override (otherwise uses ~/.0n/connections/)"),
|
|
777
|
+
},
|
|
778
|
+
async ({ service, endpoint, params, credentials }) => {
|
|
779
|
+
try {
|
|
780
|
+
const builder = getPluginBuilder();
|
|
781
|
+
const plugin = builder.build(service);
|
|
782
|
+
|
|
783
|
+
// Connect with provided or disk credentials
|
|
784
|
+
if (credentials) {
|
|
785
|
+
plugin.connect(credentials);
|
|
786
|
+
} else {
|
|
787
|
+
// Try to load from disk
|
|
788
|
+
const connFile = join(CONNECTIONS_DIR, `${service}.0n`);
|
|
789
|
+
const connFileJson = join(CONNECTIONS_DIR, `${service}.0n.json`);
|
|
790
|
+
const connPath = existsSync(connFile) ? connFile : existsSync(connFileJson) ? connFileJson : null;
|
|
791
|
+
|
|
792
|
+
if (connPath) {
|
|
793
|
+
const data = JSON.parse(readFileSync(connPath, "utf-8"));
|
|
794
|
+
if (data.auth?.credentials) {
|
|
795
|
+
plugin.connect(data.auth.credentials);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (!plugin.isConnected) {
|
|
801
|
+
return {
|
|
802
|
+
content: [{
|
|
803
|
+
type: "text",
|
|
804
|
+
text: JSON.stringify({
|
|
805
|
+
status: "not_connected",
|
|
806
|
+
service,
|
|
807
|
+
message: `Plugin "${service}" has no credentials. Provide credentials or connect via connect_service.`,
|
|
808
|
+
requiredKeys: plugin.credentialKeys,
|
|
809
|
+
}, null, 2),
|
|
810
|
+
}],
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const result = await plugin.execute(endpoint, params || {});
|
|
815
|
+
|
|
816
|
+
return {
|
|
817
|
+
content: [{
|
|
818
|
+
type: "text",
|
|
819
|
+
text: JSON.stringify(result, null, 2),
|
|
820
|
+
}],
|
|
821
|
+
};
|
|
822
|
+
} catch (err) {
|
|
823
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "failed", error: err.message }, null, 2) }] };
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
// ─── plugin_build ──────────────────────────────────────────
|
|
829
|
+
server.tool(
|
|
830
|
+
"plugin_build",
|
|
831
|
+
`Build a plugin from a service key or custom spec.
|
|
832
|
+
If building from catalog, returns the plugin's full tool manifest.
|
|
833
|
+
If building from spec, creates a custom plugin and saves to ~/.0n/plugins/.
|
|
834
|
+
|
|
835
|
+
Example (catalog): plugin_build({ service: "stripe" })
|
|
836
|
+
Example (custom): plugin_build({
|
|
837
|
+
spec: {
|
|
838
|
+
service: "acme",
|
|
839
|
+
name: "Acme CRM",
|
|
840
|
+
baseUrl: "https://api.acme.com/v1",
|
|
841
|
+
authType: "api_key",
|
|
842
|
+
credentialKeys: ["apiKey"],
|
|
843
|
+
endpoints: {
|
|
844
|
+
list_contacts: { method: "GET", path: "/contacts" },
|
|
845
|
+
create_contact: { method: "POST", path: "/contacts", body: { email: "", name: "" } }
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
})`,
|
|
849
|
+
{
|
|
850
|
+
service: z.string().optional().describe("Catalog service key (e.g., stripe) — builds from catalog"),
|
|
851
|
+
spec: z.record(z.any()).optional().describe("Custom plugin spec — builds from definition"),
|
|
852
|
+
save: z.boolean().optional().describe("Save custom plugin to ~/.0n/plugins/ (default: true)"),
|
|
853
|
+
},
|
|
854
|
+
async ({ service, spec, save }) => {
|
|
855
|
+
try {
|
|
856
|
+
const builder = getPluginBuilder();
|
|
857
|
+
|
|
858
|
+
if (spec) {
|
|
859
|
+
// Build from custom spec
|
|
860
|
+
const generated = builder.generate(spec);
|
|
861
|
+
const plugin = builder.buildFromSpec(generated);
|
|
862
|
+
|
|
863
|
+
if (save !== false) {
|
|
864
|
+
builder.generateAndSave(spec);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return {
|
|
868
|
+
content: [{
|
|
869
|
+
type: "text",
|
|
870
|
+
text: JSON.stringify({
|
|
871
|
+
status: "built",
|
|
872
|
+
source: "custom_spec",
|
|
873
|
+
plugin: plugin.inspect(),
|
|
874
|
+
tools: plugin.toMcpTools().map(t => t.name),
|
|
875
|
+
spec: generated,
|
|
876
|
+
saved: save !== false,
|
|
877
|
+
message: `Custom plugin "${spec.service || spec.key}" built with ${Object.keys(spec.endpoints || {}).length} endpoints.`,
|
|
878
|
+
}, null, 2),
|
|
879
|
+
}],
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (service) {
|
|
884
|
+
// Build from catalog
|
|
885
|
+
const plugin = builder.build(service);
|
|
886
|
+
const tools = plugin.toMcpTools();
|
|
887
|
+
|
|
888
|
+
return {
|
|
889
|
+
content: [{
|
|
890
|
+
type: "text",
|
|
891
|
+
text: JSON.stringify({
|
|
892
|
+
status: "built",
|
|
893
|
+
source: "catalog",
|
|
894
|
+
plugin: plugin.inspect(),
|
|
895
|
+
tools: tools.map(t => t.name),
|
|
896
|
+
toolCount: tools.length,
|
|
897
|
+
message: `Plugin "${service}" built from catalog with ${tools.length} tools.`,
|
|
898
|
+
}, null, 2),
|
|
899
|
+
}],
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
return {
|
|
904
|
+
content: [{
|
|
905
|
+
type: "text",
|
|
906
|
+
text: JSON.stringify({
|
|
907
|
+
status: "failed",
|
|
908
|
+
error: "Provide either 'service' (catalog key) or 'spec' (custom definition).",
|
|
909
|
+
}, null, 2),
|
|
910
|
+
}],
|
|
911
|
+
};
|
|
912
|
+
} catch (err) {
|
|
913
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "failed", error: err.message }, null, 2) }] };
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
);
|
|
917
|
+
|
|
918
|
+
// ─── plugin_create ─────────────────────────────────────────
|
|
919
|
+
server.tool(
|
|
920
|
+
"plugin_create",
|
|
921
|
+
`Generate a new custom plugin spec for a service not in the catalog.
|
|
922
|
+
Auto-infers capabilities from endpoints and maps .0n canonical fields.
|
|
923
|
+
Saves to ~/.0n/plugins/ for automatic loading.
|
|
924
|
+
|
|
925
|
+
Example: plugin_create({
|
|
926
|
+
key: "acme",
|
|
927
|
+
name: "Acme API",
|
|
928
|
+
baseUrl: "https://api.acme.com/v1",
|
|
929
|
+
authType: "api_key",
|
|
930
|
+
credentialKeys: ["apiKey"],
|
|
931
|
+
endpoints: {
|
|
932
|
+
list_users: { method: "GET", path: "/users" },
|
|
933
|
+
create_user: { method: "POST", path: "/users", body: { email: "", name: "" } },
|
|
934
|
+
get_user: { method: "GET", path: "/users/{userId}" },
|
|
935
|
+
update_user: { method: "PUT", path: "/users/{userId}", body: {} },
|
|
936
|
+
delete_user: { method: "DELETE", path: "/users/{userId}" }
|
|
937
|
+
}
|
|
938
|
+
})`,
|
|
939
|
+
{
|
|
940
|
+
key: z.string().describe("Service key (lowercase, no spaces)"),
|
|
941
|
+
name: z.string().optional().describe("Display name"),
|
|
942
|
+
baseUrl: z.string().describe("API base URL"),
|
|
943
|
+
authType: z.enum(["api_key", "oauth", "basic", "none"]).optional().describe("Auth type (default: api_key)"),
|
|
944
|
+
credentialKeys: z.array(z.string()).optional().describe("Required credential keys (default: ['apiKey'])"),
|
|
945
|
+
type: z.string().optional().describe("Category type (e.g., crm, email, payments)"),
|
|
946
|
+
description: z.string().optional().describe("Service description"),
|
|
947
|
+
endpoints: z.record(z.any()).describe("Endpoint definitions { name: { method, path, body?, query? } }"),
|
|
948
|
+
fieldMappings: z.record(z.any()).optional().describe("Custom .0n field mappings { 'email.0n': 'user_email' }"),
|
|
949
|
+
},
|
|
950
|
+
async ({ key, name, baseUrl, authType, credentialKeys, type, description, endpoints, fieldMappings }) => {
|
|
951
|
+
try {
|
|
952
|
+
const builder = getPluginBuilder();
|
|
953
|
+
|
|
954
|
+
const def = { key, name, baseUrl, authType, credentialKeys, type, description, endpoints, fieldMappings };
|
|
955
|
+
const { spec, path } = builder.generateAndSave(def);
|
|
956
|
+
|
|
957
|
+
// Also register in the active registry
|
|
958
|
+
const registry = getPluginRegistry();
|
|
959
|
+
const plugin = registry.registerFromSpec(spec);
|
|
960
|
+
|
|
961
|
+
return {
|
|
962
|
+
content: [{
|
|
963
|
+
type: "text",
|
|
964
|
+
text: JSON.stringify({
|
|
965
|
+
status: "created",
|
|
966
|
+
service: key,
|
|
967
|
+
path,
|
|
968
|
+
endpoints: Object.keys(endpoints).length,
|
|
969
|
+
capabilities: spec.capabilities?.length || 0,
|
|
970
|
+
fieldMappings: spec.fieldMappings ? Object.keys(spec.fieldMappings).length : 0,
|
|
971
|
+
tools: plugin.toMcpTools().map(t => t.name),
|
|
972
|
+
message: `Plugin "${key}" created and saved to ${path}. It's now available in the registry.`,
|
|
973
|
+
}, null, 2),
|
|
974
|
+
}],
|
|
975
|
+
};
|
|
976
|
+
} catch (err) {
|
|
977
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "failed", error: err.message }, null, 2) }] };
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
);
|
|
645
981
|
}
|
|
646
982
|
|
|
647
983
|
/**
|