0nmcp 2.5.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/agent-studio.js +114 -0
- package/crm/funnels.js +90 -0
- package/crm/index.js +15 -0
- package/crm/knowledge-base.js +69 -0
- package/crm/objects.js +5 -69
- package/crm/saas.js +147 -0
- package/crm/users.js +5 -80
- package/crm/voice-ai.js +150 -0
- 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/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/crm/voice-ai.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// 0nMCP — CRM Voice AI API Tool Definitions
|
|
3
|
+
// ============================================================
|
|
4
|
+
// AI-powered phone agents. Create agents, define actions,
|
|
5
|
+
// manage call logs. Each agent can use Knowledge Bases.
|
|
6
|
+
// ============================================================
|
|
7
|
+
|
|
8
|
+
export default [
|
|
9
|
+
// ── Agents ─────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
{
|
|
12
|
+
name: "crm_list_voice_agents",
|
|
13
|
+
description: "List all Voice AI agents for a location.",
|
|
14
|
+
method: "GET",
|
|
15
|
+
path: "/voice-ai/agents",
|
|
16
|
+
params: {
|
|
17
|
+
locationId: { type: "string", description: "Location ID", required: true, in: "query" },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
name: "crm_create_voice_agent",
|
|
23
|
+
description: "Create a new Voice AI agent. Can be configured with knowledge bases, actions, and personality settings.",
|
|
24
|
+
method: "POST",
|
|
25
|
+
path: "/voice-ai/agents",
|
|
26
|
+
params: {
|
|
27
|
+
locationId: { type: "string", description: "Location ID", required: true, in: "body" },
|
|
28
|
+
name: { type: "string", description: "Agent name", required: true, in: "body" },
|
|
29
|
+
personality: { type: "string", description: "Agent personality/system prompt", required: false, in: "body" },
|
|
30
|
+
knowledgeBases: { type: "array", description: "Array of knowledge base IDs to attach", required: false, in: "body" },
|
|
31
|
+
actions: { type: "array", description: "Array of action IDs the agent can perform", required: false, in: "body" },
|
|
32
|
+
greeting: { type: "string", description: "Initial greeting message", required: false, in: "body" },
|
|
33
|
+
voiceId: { type: "string", description: "Voice ID for text-to-speech", required: false, in: "body" },
|
|
34
|
+
},
|
|
35
|
+
body: ["locationId", "name", "personality", "knowledgeBases", "actions", "greeting", "voiceId"],
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
name: "crm_get_voice_agent",
|
|
40
|
+
description: "Get a Voice AI agent by ID with full configuration.",
|
|
41
|
+
method: "GET",
|
|
42
|
+
path: "/voice-ai/agents/:agentId",
|
|
43
|
+
params: {
|
|
44
|
+
agentId: { type: "string", description: "Agent ID", required: true, in: "path" },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
{
|
|
49
|
+
name: "crm_update_voice_agent",
|
|
50
|
+
description: "Update a Voice AI agent's configuration.",
|
|
51
|
+
method: "PATCH",
|
|
52
|
+
path: "/voice-ai/agents/:agentId",
|
|
53
|
+
params: {
|
|
54
|
+
agentId: { type: "string", description: "Agent ID", required: true, in: "path" },
|
|
55
|
+
name: { type: "string", description: "Agent name", required: false, in: "body" },
|
|
56
|
+
personality: { type: "string", description: "Agent personality/system prompt", required: false, in: "body" },
|
|
57
|
+
knowledgeBases: { type: "array", description: "Array of knowledge base IDs", required: false, in: "body" },
|
|
58
|
+
actions: { type: "array", description: "Array of action IDs", required: false, in: "body" },
|
|
59
|
+
greeting: { type: "string", description: "Initial greeting", required: false, in: "body" },
|
|
60
|
+
voiceId: { type: "string", description: "Voice ID", required: false, in: "body" },
|
|
61
|
+
active: { type: "boolean", description: "Whether agent is active", required: false, in: "body" },
|
|
62
|
+
},
|
|
63
|
+
body: ["name", "personality", "knowledgeBases", "actions", "greeting", "voiceId", "active"],
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
{
|
|
67
|
+
name: "crm_delete_voice_agent",
|
|
68
|
+
description: "Delete a Voice AI agent.",
|
|
69
|
+
method: "DELETE",
|
|
70
|
+
path: "/voice-ai/agents/:agentId",
|
|
71
|
+
params: {
|
|
72
|
+
agentId: { type: "string", description: "Agent ID", required: true, in: "path" },
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// ── Actions ────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
{
|
|
79
|
+
name: "crm_create_voice_action",
|
|
80
|
+
description: "Create an action that a Voice AI agent can perform (e.g., book appointment, transfer call, query knowledge base).",
|
|
81
|
+
method: "POST",
|
|
82
|
+
path: "/voice-ai/actions",
|
|
83
|
+
params: {
|
|
84
|
+
locationId: { type: "string", description: "Location ID", required: true, in: "body" },
|
|
85
|
+
name: { type: "string", description: "Action name", required: true, in: "body" },
|
|
86
|
+
type: { type: "string", description: "Action type (e.g., 'knowledge_base', 'book_appointment', 'transfer_call', 'webhook')", required: true, in: "body" },
|
|
87
|
+
config: { type: "object", description: "Action-specific configuration (e.g., knowledgeBaseId, calendarId, phoneNumber, webhookUrl)", required: false, in: "body" },
|
|
88
|
+
},
|
|
89
|
+
body: ["locationId", "name", "type", "config"],
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
{
|
|
93
|
+
name: "crm_get_voice_action",
|
|
94
|
+
description: "Get a Voice AI action by ID.",
|
|
95
|
+
method: "GET",
|
|
96
|
+
path: "/voice-ai/actions/:actionId",
|
|
97
|
+
params: {
|
|
98
|
+
actionId: { type: "string", description: "Action ID", required: true, in: "path" },
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
{
|
|
103
|
+
name: "crm_update_voice_action",
|
|
104
|
+
description: "Update a Voice AI action.",
|
|
105
|
+
method: "PUT",
|
|
106
|
+
path: "/voice-ai/actions/:actionId",
|
|
107
|
+
params: {
|
|
108
|
+
actionId: { type: "string", description: "Action ID", required: true, in: "path" },
|
|
109
|
+
name: { type: "string", description: "Action name", required: false, in: "body" },
|
|
110
|
+
type: { type: "string", description: "Action type", required: false, in: "body" },
|
|
111
|
+
config: { type: "object", description: "Action configuration", required: false, in: "body" },
|
|
112
|
+
},
|
|
113
|
+
body: ["name", "type", "config"],
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
{
|
|
117
|
+
name: "crm_delete_voice_action",
|
|
118
|
+
description: "Delete a Voice AI action.",
|
|
119
|
+
method: "DELETE",
|
|
120
|
+
path: "/voice-ai/actions/:actionId",
|
|
121
|
+
params: {
|
|
122
|
+
actionId: { type: "string", description: "Action ID", required: true, in: "path" },
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// ── Call Logs ──────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
{
|
|
129
|
+
name: "crm_list_voice_call_logs",
|
|
130
|
+
description: "List Voice AI call logs with filters.",
|
|
131
|
+
method: "GET",
|
|
132
|
+
path: "/voice-ai/dashboard/call-logs",
|
|
133
|
+
params: {
|
|
134
|
+
locationId: { type: "string", description: "Location ID", required: true, in: "query" },
|
|
135
|
+
agentId: { type: "string", description: "Filter by agent ID", required: false, in: "query" },
|
|
136
|
+
limit: { type: "number", description: "Results per page", required: false, in: "query" },
|
|
137
|
+
offset: { type: "number", description: "Pagination offset", required: false, in: "query" },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
{
|
|
142
|
+
name: "crm_get_voice_call_log",
|
|
143
|
+
description: "Get a specific Voice AI call log with transcript and details.",
|
|
144
|
+
method: "GET",
|
|
145
|
+
path: "/voice-ai/dashboard/call-logs/:callId",
|
|
146
|
+
params: {
|
|
147
|
+
callId: { type: "string", description: "Call ID", required: true, in: "path" },
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
]
|
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
|
/**
|