0nmcp 1.3.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js CHANGED
@@ -6,6 +6,8 @@
6
6
  *
7
7
  * Usage:
8
8
  * npx 0nmcp Start MCP server (stdio)
9
+ * npx 0nmcp serve Start HTTP server (REST + MCP + webhooks)
10
+ * npx 0nmcp run <wf> Run a .0n workflow from CLI
9
11
  * npx 0nmcp init Initialize ~/.0n directory
10
12
  * npx 0nmcp connect Interactive connection setup
11
13
  * npx 0nmcp list List connected services
@@ -68,12 +70,22 @@ async function main() {
68
70
  console.log(`
69
71
  ${c.bright}Usage:${c.reset}
70
72
 
71
- ${c.cyan}npx 0nmcp${c.reset} Start MCP server (for Claude Desktop)
73
+ ${c.cyan}npx 0nmcp${c.reset} Start MCP server (stdio, for Claude Desktop)
74
+ ${c.cyan}npx 0nmcp serve${c.reset} Start HTTP server (REST + MCP + webhooks)
75
+ ${c.cyan}npx 0nmcp run <wf>${c.reset} Run a .0n workflow from CLI
72
76
  ${c.cyan}npx 0nmcp init${c.reset} Initialize ~/.0n directory
73
77
  ${c.cyan}npx 0nmcp connect${c.reset} Interactive connection setup
74
78
  ${c.cyan}npx 0nmcp list${c.reset} List connected services
75
79
  ${c.cyan}npx 0nmcp migrate${c.reset} Migrate from ~/.0nmcp to ~/.0n
76
80
 
81
+ ${c.bright}Serve options:${c.reset}
82
+
83
+ ${c.cyan}npx 0nmcp serve --port 3000 --host 0.0.0.0${c.reset}
84
+
85
+ ${c.bright}Run options:${c.reset}
86
+
87
+ ${c.cyan}npx 0nmcp run invoice-notify --input customer_email=test@x.com --input amount=100${c.reset}
88
+
77
89
  ${c.bright}Configure Claude Desktop:${c.reset}
78
90
 
79
91
  Add to your claude_desktop_config.json:
@@ -117,6 +129,85 @@ ${c.bright}Links:${c.reset}
117
129
  return;
118
130
  }
119
131
 
132
+ // Serve (HTTP server)
133
+ if (command === 'serve') {
134
+ console.log(BANNER);
135
+ const port = getFlag(args, '--port', 3000);
136
+ const host = getFlag(args, '--host', '0.0.0.0');
137
+
138
+ console.log(`${c.bright}Starting HTTP server...${c.reset}\n`);
139
+
140
+ const { startServer } = await import('./server.js');
141
+ await startServer({ port: Number(port), host: String(host) });
142
+ return;
143
+ }
144
+
145
+ // Run (execute a workflow from CLI)
146
+ if (command === 'run') {
147
+ const workflowName = args[1];
148
+ if (!workflowName) {
149
+ console.log(`${c.red}Usage: npx 0nmcp run <workflow-name> [--input key=value]${c.reset}`);
150
+ process.exit(1);
151
+ }
152
+
153
+ // Parse --input flags
154
+ const inputs = {};
155
+ for (let i = 2; i < args.length; i++) {
156
+ if (args[i] === '--input' && args[i + 1]) {
157
+ const [key, ...valueParts] = args[i + 1].split('=');
158
+ const value = valueParts.join('=');
159
+ // Auto-type: numbers and booleans
160
+ if (value === 'true') inputs[key] = true;
161
+ else if (value === 'false') inputs[key] = false;
162
+ else if (!isNaN(value) && value !== '') inputs[key] = Number(value);
163
+ else inputs[key] = value;
164
+ i++;
165
+ }
166
+ }
167
+
168
+ console.log(`${c.bright}Running workflow: ${c.cyan}${workflowName}${c.reset}`);
169
+ if (Object.keys(inputs).length > 0) {
170
+ console.log(`${c.bright}Inputs:${c.reset}`, JSON.stringify(inputs, null, 2));
171
+ }
172
+ console.log('');
173
+
174
+ try {
175
+ const { ConnectionManager } = await import('./connections.js');
176
+ const { WorkflowRunner } = await import('./workflow.js');
177
+
178
+ const connections = new ConnectionManager();
179
+ const runner = new WorkflowRunner(connections);
180
+ const result = await runner.run({ workflowPath: workflowName, inputs });
181
+
182
+ if (result.success) {
183
+ console.log(`${c.green}${c.bright}Workflow completed successfully${c.reset}`);
184
+ } else {
185
+ console.log(`${c.red}${c.bright}Workflow failed${c.reset}`);
186
+ }
187
+
188
+ console.log(`\n${c.bright}Execution ID:${c.reset} ${result.executionId}`);
189
+ console.log(`${c.bright}Steps:${c.reset} ${result.stepsSuccessful}/${result.stepsExecuted} successful`);
190
+ console.log(`${c.bright}Duration:${c.reset} ${result.duration}ms`);
191
+
192
+ if (result.outputs && Object.keys(result.outputs).length > 0) {
193
+ console.log(`\n${c.bright}Outputs:${c.reset}`);
194
+ console.log(JSON.stringify(result.outputs, null, 2));
195
+ }
196
+
197
+ if (result.errors.length > 0) {
198
+ console.log(`\n${c.red}${c.bright}Errors:${c.reset}`);
199
+ for (const err of result.errors) {
200
+ console.log(` ${c.red}●${c.reset} ${err.service}.${err.action}: ${err.error}`);
201
+ }
202
+ }
203
+
204
+ process.exit(result.success ? 0 : 1);
205
+ } catch (err) {
206
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
207
+ process.exit(1);
208
+ }
209
+ }
210
+
120
211
  // Migrate
121
212
  if (command === 'migrate') {
122
213
  console.log(BANNER);
@@ -132,6 +223,15 @@ ${c.bright}Links:${c.reset}
132
223
  process.exit(1);
133
224
  }
134
225
 
226
+ /**
227
+ * Get a CLI flag value: --flag value
228
+ */
229
+ function getFlag(args, flag, defaultValue) {
230
+ const idx = args.indexOf(flag);
231
+ if (idx !== -1 && args[idx + 1]) return args[idx + 1];
232
+ return defaultValue;
233
+ }
234
+
135
235
  function initDotOn() {
136
236
  const dirs = [
137
237
  DOT_ON_DIR,
package/connections.js CHANGED
@@ -244,8 +244,12 @@ export class ConnectionManager {
244
244
 
245
245
  /**
246
246
  * Get credentials for a service.
247
+ * Checks vault unsealed cache first for sealed connections.
247
248
  */
248
249
  getCredentials(serviceKey) {
250
+ // Check if vault has unsealed credentials in memory
251
+ const unsealed = this._vaultCache?.get(serviceKey);
252
+ if (unsealed) return unsealed;
249
253
  return this.connections[serviceKey]?.credentials || null;
250
254
  }
251
255
 
package/index.js CHANGED
@@ -21,362 +21,53 @@
21
21
 
22
22
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
23
23
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
24
- import { z } from "zod";
25
24
 
26
- import { SERVICE_CATALOG, listServices, getService } from "./catalog.js";
27
25
  import { ConnectionManager } from "./connections.js";
28
26
  import { Orchestrator } from "./orchestrator.js";
27
+ import { WorkflowRunner } from "./workflow.js";
28
+ import { registerAllTools } from "./tools.js";
29
29
  import { registerCrmTools } from "./crm/index.js";
30
+ import { registerVaultTools, autoUnseal } from "./vault/index.js";
31
+ import { unsealedCache } from "./vault/cache.js";
30
32
 
31
33
  // ── Initialize ─────────────────────────────────────────────
32
34
  const connections = new ConnectionManager();
35
+ connections._vaultCache = unsealedCache;
33
36
  const orchestrator = new Orchestrator(connections);
37
+ const workflowRunner = new WorkflowRunner(connections);
34
38
 
35
39
  const server = new McpServer({
36
40
  name: "0nMCP",
37
- version: "1.3.1",
41
+ version: "1.5.0",
38
42
  });
39
43
 
40
44
  // ============================================================
41
- // UNIVERSAL TOOLS
45
+ // REGISTER ALL TOOLS
42
46
  // ============================================================
43
47
 
44
- // ─── execute ───────────────────────────────────────────────
45
- server.tool(
46
- "execute",
47
- `Execute any task using connected services. The AI orchestrator automatically:
48
- 1. Parses your intent from natural language
49
- 2. Finds the best services to use
50
- 3. Creates an execution plan
51
- 4. Executes all necessary API calls
52
- 5. Returns results
53
-
54
- Examples:
55
- - "Send an email to john@example.com about the meeting tomorrow"
56
- - "Create a Stripe customer for sarah@test.com"
57
- - "Post to #sales on Slack: We just closed a deal!"
58
- - "Get my Stripe balance"
59
- - "Add a record to Airtable: Name=John, Status=Active"
60
- - "Send an SMS to +1234567890: Your order shipped"
61
- - "Create a GitHub issue: Bug in login page"`,
62
- {
63
- task: z.string().describe("Natural language description of what you want to accomplish"),
64
- },
65
- async ({ task }) => {
66
- const result = await orchestrator.execute(task);
67
-
68
- if (result.success) {
69
- return {
70
- content: [{
71
- type: "text",
72
- text: JSON.stringify({
73
- status: "completed",
74
- message: result.message,
75
- steps_executed: result.details?.stepsExecuted || 0,
76
- steps_successful: result.details?.stepsSuccessful || 0,
77
- duration_ms: result.details?.duration || 0,
78
- services_used: result.details?.servicesUsed || [],
79
- plan: result.details?.plan || [],
80
- }, null, 2),
81
- }],
82
- };
83
- } else {
84
- return {
85
- content: [{
86
- type: "text",
87
- text: JSON.stringify({
88
- status: "failed",
89
- error: result.error,
90
- suggestion: result.suggestion,
91
- connected_services: result.connected_services,
92
- }, null, 2),
93
- }],
94
- };
95
- }
96
- }
97
- );
98
-
99
- // ─── connect_service ───────────────────────────────────────
100
- server.tool(
101
- "connect_service",
102
- `Connect a service so the orchestrator can use it. Each service requires specific credentials.
103
-
104
- Examples:
105
- - Stripe: { "apiKey": "sk_live_..." }
106
- - SendGrid: { "apiKey": "SG..." }
107
- - Twilio: { "accountSid": "AC...", "authToken": "..." }
108
- - Slack: { "botToken": "xoxb-..." }
109
- - OpenAI: { "apiKey": "sk-..." }
110
- - GitHub: { "token": "ghp_..." }
111
- - Notion: { "apiKey": "ntn_..." }
112
- - Airtable: { "apiKey": "pat..." }
113
- - CRM: { "access_token": "..." }
114
- - HubSpot: { "accessToken": "..." }
115
- - Shopify: { "accessToken": "...", "store": "mystore" }
116
- - Supabase: { "apiKey": "...", "projectRef": "..." }
117
- - Gmail: { "access_token": "..." }
118
- - Google Sheets: { "access_token": "..." }
119
- - Google Drive: { "access_token": "..." }
120
- - Jira: { "email": "...", "apiToken": "...", "domain": "mycompany" }
121
- - Zendesk: { "email": "...", "apiToken": "...", "subdomain": "mycompany" }
122
- - Mailchimp: { "apiKey": "...-us21" }
123
- - Zoom: { "access_token": "..." }
124
- - Microsoft 365: { "access_token": "..." }
125
- - MongoDB: { "apiKey": "...", "appId": "..." }`,
126
- {
127
- service: z.string().describe("Service key (e.g., stripe, sendgrid, twilio, slack, crm, github, notion, airtable, openai, shopify, hubspot, supabase, discord, linear, resend, calendly, google_calendar, gmail, google_sheets, google_drive, jira, zendesk, mailchimp, zoom, microsoft, mongodb)"),
128
- credentials: z.record(z.string()).describe("Service credentials as key-value pairs"),
129
- },
130
- async ({ service, credentials }) => {
131
- const result = connections.connect(service, credentials);
132
-
133
- if (result.success) {
134
- return {
135
- content: [{
136
- type: "text",
137
- text: JSON.stringify({
138
- status: "connected",
139
- service: result.service.name,
140
- capabilities: result.service.capabilities,
141
- message: `Connected to ${result.service.name}. You now have ${result.service.capabilities} capabilities available.`,
142
- }, null, 2),
143
- }],
144
- };
145
- } else {
146
- return {
147
- content: [{
148
- type: "text",
149
- text: JSON.stringify({ status: "failed", error: result.error }, null, 2),
150
- }],
151
- };
152
- }
153
- }
154
- );
155
-
156
- // ─── disconnect_service ────────────────────────────────────
157
- server.tool(
158
- "disconnect_service",
159
- "Disconnect a connected service. Removes stored credentials.",
160
- {
161
- service: z.string().describe("Service key to disconnect (e.g., stripe, sendgrid)"),
162
- },
163
- async ({ service }) => {
164
- const result = connections.disconnect(service);
165
- return {
166
- content: [{
167
- type: "text",
168
- text: JSON.stringify({
169
- status: result.success ? "disconnected" : "failed",
170
- error: result.error,
171
- }, null, 2),
172
- }],
173
- };
174
- }
175
- );
176
-
177
- // ─── list_connections ──────────────────────────────────────
178
- server.tool(
179
- "list_connections",
180
- "List all connected services, their types, and capability counts.",
181
- {},
182
- async () => {
183
- const connected = connections.list();
184
-
185
- if (connected.length === 0) {
186
- return {
187
- content: [{
188
- type: "text",
189
- text: JSON.stringify({
190
- count: 0,
191
- services: [],
192
- message: "No services connected. Use connect_service to add integrations.",
193
- }, null, 2),
194
- }],
195
- };
196
- }
197
-
198
- return {
199
- content: [{
200
- type: "text",
201
- text: JSON.stringify({
202
- count: connected.length,
203
- services: connected,
204
- }, null, 2),
205
- }],
206
- };
207
- }
208
- );
209
-
210
- // ─── list_available_services ───────────────────────────────
211
- server.tool(
212
- "list_available_services",
213
- "List all services that can be connected, grouped by category.",
214
- {},
215
- async () => {
216
- const services = listServices();
217
- const connected = new Set(connections.keys());
218
-
219
- // Group by type
220
- const grouped = {};
221
- for (const svc of services) {
222
- if (!grouped[svc.type]) grouped[svc.type] = [];
223
- grouped[svc.type].push({
224
- key: svc.key,
225
- name: svc.name,
226
- description: svc.description,
227
- capabilities: svc.capabilityCount,
228
- authType: svc.authType,
229
- credentialKeys: svc.credentialKeys,
230
- connected: connected.has(svc.key),
231
- });
232
- }
233
-
234
- return {
235
- content: [{
236
- type: "text",
237
- text: JSON.stringify({
238
- total: services.length,
239
- connected: connected.size,
240
- services: grouped,
241
- }, null, 2),
242
- }],
243
- };
244
- }
245
- );
246
-
247
- // ─── get_service_info ──────────────────────────────────────
248
- server.tool(
249
- "get_service_info",
250
- "Get detailed information about a specific service — capabilities, endpoints, and required credentials.",
251
- {
252
- service: z.string().describe("Service key (e.g., stripe, crm)"),
253
- },
254
- async ({ service }) => {
255
- const catalog = getService(service);
256
- if (!catalog) {
257
- return {
258
- content: [{
259
- type: "text",
260
- text: JSON.stringify({ error: `Unknown service: ${service}`, available: listServices().map(s => s.key) }, null, 2),
261
- }],
262
- };
263
- }
264
-
265
- return {
266
- content: [{
267
- type: "text",
268
- text: JSON.stringify({
269
- key: service,
270
- name: catalog.name,
271
- type: catalog.type,
272
- description: catalog.description,
273
- authType: catalog.authType,
274
- credentialKeys: catalog.credentialKeys,
275
- connected: connections.isConnected(service),
276
- capabilities: catalog.capabilities,
277
- endpoints: Object.entries(catalog.endpoints).map(([key, ep]) => ({
278
- name: key,
279
- method: ep.method,
280
- path: ep.path,
281
- })),
282
- }, null, 2),
283
- }],
284
- };
285
- }
286
- );
287
-
288
- // ─── api_call ──────────────────────────────────────────────
289
- server.tool(
290
- "api_call",
291
- "Make a direct API call to any connected service. For advanced use when you need fine-grained control beyond the execute tool.",
292
- {
293
- service: z.string().describe("Service key (e.g., stripe, sendgrid)"),
294
- endpoint: z.string().describe("Endpoint name from the service catalog"),
295
- params: z.record(z.any()).optional().describe("Parameters for the API call"),
296
- },
297
- async ({ service, endpoint, params }) => {
298
- const catalog = getService(service);
299
- if (!catalog) {
300
- return { content: [{ type: "text", text: JSON.stringify({ error: `Unknown service: ${service}` }, null, 2) }] };
301
- }
302
-
303
- const ep = catalog.endpoints[endpoint];
304
- if (!ep) {
305
- return {
306
- content: [{
307
- type: "text",
308
- text: JSON.stringify({ error: `Unknown endpoint: ${endpoint}`, available: Object.keys(catalog.endpoints) }, null, 2),
309
- }],
310
- };
311
- }
312
-
313
- const creds = connections.getCredentials(service);
314
- if (!creds) {
315
- return { content: [{ type: "text", text: JSON.stringify({ error: `Service ${service} not connected` }, null, 2) }] };
316
- }
317
-
318
- try {
319
- let url = catalog.baseUrl + ep.path;
320
- const allParams = { ...creds, ...(params || {}) };
321
-
322
- // Substitute path params
323
- url = url.replace(/\{(\w+)\}/g, (_, key) => allParams[key] || `{${key}}`);
324
-
325
- const headers = catalog.authHeader(creds);
326
- const options = { method: ep.method, headers };
327
-
328
- if (ep.method !== "GET" && params) {
329
- const contentType = ep.contentType || "application/json";
330
- if (contentType === "application/x-www-form-urlencoded") {
331
- headers["Content-Type"] = "application/x-www-form-urlencoded";
332
- const flat = {};
333
- for (const [k, v] of Object.entries(params)) {
334
- if (typeof v !== "object") flat[k] = String(v);
335
- }
336
- options.body = new URLSearchParams(flat).toString();
337
- } else {
338
- headers["Content-Type"] = "application/json";
339
- options.body = JSON.stringify(params);
340
- }
341
- }
342
-
343
- if (ep.method === "GET" && params) {
344
- const flat = {};
345
- for (const [k, v] of Object.entries(params)) {
346
- if (typeof v !== "object") flat[k] = String(v);
347
- }
348
- const qs = new URLSearchParams(flat).toString();
349
- if (qs) url += (url.includes("?") ? "&" : "?") + qs;
350
- }
351
-
352
- const response = await fetch(url, options);
353
- const data = await response.json().catch(() => ({ status: response.status, statusText: response.statusText }));
354
-
355
- return {
356
- content: [{
357
- type: "text",
358
- text: JSON.stringify({ success: response.ok, status: response.status, data }, null, 2),
359
- }],
360
- };
361
- } catch (err) {
362
- return { content: [{ type: "text", text: JSON.stringify({ error: err.message }, null, 2) }] };
363
- }
364
- }
365
- );
48
+ registerAllTools(server, connections, orchestrator, workflowRunner);
366
49
 
367
50
  // ============================================================
368
51
  // SERVICE-SPECIFIC TOOLS
369
52
  // ============================================================
370
53
 
371
- // Register CRM tools (the first full integration)
54
+ import { z } from "zod";
372
55
  registerCrmTools(server, z);
373
56
 
374
- // Future: registerStripeTools(server, z);
375
- // Future: registerSlackTools(server, z);
376
- // Future: registerShopifyTools(server, z);
57
+ // ============================================================
58
+ // VAULT TOOLS (machine-bound credential encryption)
59
+ // ============================================================
60
+
61
+ registerVaultTools(server, z);
62
+
63
+ // Auto-unseal sealed connections if ON_VAULT_PASSPHRASE is set
64
+ const vaultResult = autoUnseal();
65
+ if (vaultResult.unsealed.length > 0) {
66
+ console.error(`Vault: auto-unsealed ${vaultResult.unsealed.length} connection(s)`);
67
+ }
377
68
 
378
69
  // ============================================================
379
- // START SERVER
70
+ // START SERVER (stdio transport)
380
71
  // ============================================================
381
72
 
382
73
  const transport = new StdioServerTransport();
package/lib/badges.json CHANGED
@@ -26,7 +26,7 @@
26
26
  "total": {
27
27
  "schemaVersion": 1,
28
28
  "label": "capabilities",
29
- "message": "693",
29
+ "message": "697",
30
30
  "color": "00ff88"
31
31
  }
32
32
  }
package/lib/stats.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "generated": "2026-02-12T04:15:16.550Z",
2
+ "generated": "2026-02-15T00:30:00.808Z",
3
3
  "catalogVersion": "1.2.2",
4
4
  "services": 26,
5
5
  "tools": 290,
@@ -792,6 +792,7 @@
792
792
  "mongodb"
793
793
  ],
794
794
  "crmTools": 245,
795
- "totalTools": 535,
796
- "totalCapabilities": 693
795
+ "vaultTools": 4,
796
+ "totalTools": 539,
797
+ "totalCapabilities": 697
797
798
  }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "0nmcp",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "mcpName": "io.github.0nork/0nMCP",
5
- "description": "Universal AI API Orchestrator — 535 tools, 26 services, natural language interface. The most comprehensive MCP server available. Free and open source from 0nORK.",
5
+ "description": "Universal AI API Orchestrator — 539 tools, 26 services, machine-bound vault encryption. The most comprehensive MCP server available. Free and open source from 0nORK.",
6
6
  "type": "module",
7
7
  "main": "index.js",
8
8
  "types": "types/index.d.ts",
9
9
  "bin": {
10
- "0nmcp": "./cli.js"
10
+ "0nmcp": "cli.js"
11
11
  },
12
12
  "exports": {
13
13
  ".": {
@@ -34,10 +34,23 @@
34
34
  },
35
35
  "./orchestrator": {
36
36
  "import": "./orchestrator.js"
37
+ },
38
+ "./workflow": {
39
+ "import": "./workflow.js"
40
+ },
41
+ "./tools": {
42
+ "import": "./tools.js"
43
+ },
44
+ "./server": {
45
+ "import": "./server.js"
46
+ },
47
+ "./vault": {
48
+ "import": "./vault/index.js"
37
49
  }
38
50
  },
39
51
  "scripts": {
40
52
  "start": "node index.js",
53
+ "serve": "node cli.js serve",
41
54
  "test": "node --test",
42
55
  "stats": "node scripts/generate-stats.js --all",
43
56
  "stats:badge": "node scripts/generate-stats.js --badge",
@@ -97,6 +110,10 @@
97
110
  "teams",
98
111
  "onedrive",
99
112
  "mongodb",
113
+ "vault",
114
+ "encryption",
115
+ "machine-bound",
116
+ "credential-storage",
100
117
  "0n",
101
118
  "0nork",
102
119
  "0nmcp"
@@ -119,7 +136,11 @@
119
136
  "node": ">=18.0.0"
120
137
  },
121
138
  "dependencies": {
122
- "@modelcontextprotocol/sdk": "^1.26.0"
139
+ "@modelcontextprotocol/sdk": "^1.26.0",
140
+ "express": "^4.21.0"
141
+ },
142
+ "optionalDependencies": {
143
+ "0n-spec": "^1.1.0"
123
144
  },
124
145
  "peerDependencies": {
125
146
  "@anthropic-ai/sdk": ">=0.30.0"
@@ -132,6 +153,9 @@
132
153
  "files": [
133
154
  "index.js",
134
155
  "cli.js",
156
+ "tools.js",
157
+ "workflow.js",
158
+ "server.js",
135
159
  "catalog.js",
136
160
  "connections.js",
137
161
  "orchestrator.js",
@@ -139,6 +163,7 @@
139
163
  "crm/",
140
164
  "webhooks.js",
141
165
  "ratelimit.js",
166
+ "vault/",
142
167
  "lib/",
143
168
  "types/",
144
169
  "README.md",
@@ -147,12 +172,13 @@
147
172
  "0nmcp-stats": {
148
173
  "tools": 290,
149
174
  "crmTools": 245,
150
- "totalTools": 535,
175
+ "vaultTools": 4,
176
+ "totalTools": 539,
151
177
  "services": 26,
152
178
  "actions": 65,
153
179
  "triggers": 93,
154
- "totalCapabilities": 693,
180
+ "totalCapabilities": 697,
155
181
  "categories": 13,
156
- "lastUpdated": "2026-02-12T04:15:16.550Z"
182
+ "lastUpdated": "2026-02-15T00:30:00.808Z"
157
183
  }
158
184
  }