0nmcp 2.1.0 → 2.3.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/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "0nmcp",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "mcpName": "io.github.0nork/0nMCP",
5
- "description": "Universal AI API Orchestrator — 550 tools, 26 services, portable AI Brain bundles + machine-bound vault encryption + Application Engine. The most comprehensive MCP server available. Free and open source from 0nORK.",
5
+ "description": "Universal AI API Orchestrator — 819 tools, 48 services, portable AI Brain bundles + machine-bound vault encryption + Application Engine. The most comprehensive MCP server available. Free and open source from 0nORK.",
6
6
  "type": "module",
7
7
  "main": "index.js",
8
8
  "types": "types/index.d.ts",
@@ -170,6 +170,32 @@
170
170
  "application-engine",
171
171
  "container",
172
172
  "mcp-registry",
173
+ "quickbooks",
174
+ "asana",
175
+ "intercom",
176
+ "dropbox",
177
+ "whatsapp",
178
+ "instagram",
179
+ "twitter",
180
+ "x-ads",
181
+ "tiktok",
182
+ "google-ads",
183
+ "facebook-ads",
184
+ "plaid",
185
+ "square",
186
+ "tiktok-ads",
187
+ "linkedin-ads",
188
+ "instagram-ads",
189
+ "smartlead",
190
+ "zapier",
191
+ "mulesoft",
192
+ "azure",
193
+ "pipedrive",
194
+ "linkedin",
195
+ "advertising",
196
+ "cold-email",
197
+ "finance",
198
+ "accounting",
173
199
  "0n",
174
200
  "0nork",
175
201
  "0nmcp"
@@ -232,17 +258,19 @@
232
258
  "LICENSE"
233
259
  ],
234
260
  "0nmcp-stats": {
235
- "tools": 290,
261
+ "tools": 545,
236
262
  "crmTools": 245,
237
263
  "vaultTools": 4,
264
+ "vaultContainerTools": 8,
265
+ "deedTools": 6,
238
266
  "engineTools": 6,
239
267
  "appTools": 5,
240
- "totalTools": 550,
241
- "services": 26,
242
- "actions": 65,
243
- "triggers": 93,
244
- "totalCapabilities": 708,
245
- "categories": 13,
246
- "lastUpdated": "2026-02-28T08:51:26.876Z"
268
+ "totalTools": 819,
269
+ "services": 48,
270
+ "actions": 104,
271
+ "triggers": 155,
272
+ "totalCapabilities": 1078,
273
+ "categories": 21,
274
+ "lastUpdated": "2026-03-03T09:49:34.405Z"
247
275
  }
248
276
  }
package/vault/sync.js ADDED
@@ -0,0 +1,250 @@
1
+ // ============================================================
2
+ // 0nMCP — Vault: Cross-Platform E2E Encrypted Sync
3
+ // ============================================================
4
+ // Syncs vault credentials across CLI, web console, and other
5
+ // platforms using Argon2id key derivation + AES-256-GCM.
6
+ //
7
+ // The server NEVER sees plaintext credentials — only encrypted
8
+ // blobs are stored/transmitted. The sync passphrase never
9
+ // leaves the user's device.
10
+ // ============================================================
11
+
12
+ import { readFileSync, writeFileSync, readdirSync, existsSync, mkdirSync } from "fs";
13
+ import { join } from "path";
14
+ import { homedir } from "os";
15
+ import { randomBytes, createCipheriv, createDecipheriv } from "crypto";
16
+ import { createRequire } from "module";
17
+
18
+ const require = createRequire(import.meta.url);
19
+
20
+ const DOT_ON = join(homedir(), ".0n");
21
+ const CONNECTIONS_DIR = join(DOT_ON, "connections");
22
+ const SYNC_SALT_FILE = join(DOT_ON, "sync-salt");
23
+ const API_BASE = "https://0nmcp.com";
24
+
25
+ // AES-256-GCM constants
26
+ const ALGORITHM = "aes-256-gcm";
27
+ const KEY_LENGTH = 32;
28
+ const IV_LENGTH = 12; // 96 bits (GCM recommended)
29
+ const TAG_LENGTH = 16;
30
+
31
+ // Argon2id parameters (same as crypto-container.js)
32
+ const ARGON2_MEMORY = 65536; // 64MB
33
+ const ARGON2_ITERATIONS = 4;
34
+ const ARGON2_PARALLELISM = 2;
35
+ const ARGON2_HASH_LENGTH = 32; // 256 bits
36
+
37
+ // ── Key Derivation ──────────────────────────────────────────
38
+
39
+ /**
40
+ * Derive a 256-bit encryption key from passphrase using Argon2id.
41
+ * Memory-hard and GPU-resistant.
42
+ *
43
+ * @param {string} passphrase - User's sync passphrase
44
+ * @param {Buffer} salt - 32-byte salt
45
+ * @returns {Promise<Buffer>} 32-byte AES-256 key
46
+ */
47
+ async function deriveKey(passphrase, salt) {
48
+ let argon2;
49
+ try {
50
+ argon2 = await import("argon2");
51
+ } catch {
52
+ throw new Error("argon2 package required for vault sync. Install: npm i argon2");
53
+ }
54
+
55
+ const mod = argon2.default || argon2;
56
+ const hash = await mod.hash(passphrase, {
57
+ type: 2, // argon2id
58
+ memoryCost: ARGON2_MEMORY,
59
+ timeCost: ARGON2_ITERATIONS,
60
+ parallelism: ARGON2_PARALLELISM,
61
+ hashLength: ARGON2_HASH_LENGTH,
62
+ salt,
63
+ raw: true,
64
+ });
65
+ return hash;
66
+ }
67
+
68
+ // ── Encryption / Decryption ─────────────────────────────────
69
+
70
+ /**
71
+ * Encrypt data for cloud sync.
72
+ * Uses Argon2id-derived key + AES-256-GCM.
73
+ *
74
+ * @param {string} plaintext - Data to encrypt
75
+ * @param {string} passphrase - Sync passphrase
76
+ * @returns {Promise<{ encrypted_data: string, iv: string, salt: string }>}
77
+ */
78
+ export async function encryptForSync(plaintext, passphrase) {
79
+ const salt = randomBytes(32);
80
+ const iv = randomBytes(IV_LENGTH);
81
+ const key = await deriveKey(passphrase, salt);
82
+
83
+ const cipher = createCipheriv(ALGORITHM, key, iv);
84
+ const encrypted = Buffer.concat([
85
+ cipher.update(plaintext, "utf8"),
86
+ cipher.final(),
87
+ ]);
88
+ const tag = cipher.getAuthTag();
89
+
90
+ // Combine tag + ciphertext for storage
91
+ const combined = Buffer.concat([tag, encrypted]);
92
+
93
+ return {
94
+ encrypted_data: combined.toString("base64"),
95
+ iv: iv.toString("base64"),
96
+ salt: salt.toString("base64"),
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Decrypt data from cloud sync.
102
+ *
103
+ * @param {string} encryptedB64 - Base64 encoded [tag + ciphertext]
104
+ * @param {string} ivB64 - Base64 encoded IV
105
+ * @param {string} saltB64 - Base64 encoded salt
106
+ * @param {string} passphrase - Sync passphrase
107
+ * @returns {Promise<string>} Decrypted plaintext
108
+ */
109
+ export async function decryptFromSync(encryptedB64, ivB64, saltB64, passphrase) {
110
+ const combined = Buffer.from(encryptedB64, "base64");
111
+ const iv = Buffer.from(ivB64, "base64");
112
+ const salt = Buffer.from(saltB64, "base64");
113
+
114
+ if (combined.length < TAG_LENGTH + 1) {
115
+ throw new Error("Invalid encrypted data: too short");
116
+ }
117
+
118
+ const tag = combined.subarray(0, TAG_LENGTH);
119
+ const ciphertext = combined.subarray(TAG_LENGTH);
120
+
121
+ const key = await deriveKey(passphrase, salt);
122
+
123
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
124
+ decipher.setAuthTag(tag);
125
+
126
+ try {
127
+ const decrypted = Buffer.concat([
128
+ decipher.update(ciphertext),
129
+ decipher.final(),
130
+ ]);
131
+ return decrypted.toString("utf8");
132
+ } catch {
133
+ throw new Error("Decryption failed — wrong sync passphrase.");
134
+ }
135
+ }
136
+
137
+ // ── Sync Operations ─────────────────────────────────────────
138
+
139
+ async function apiCall(method, path, token, body = null) {
140
+ const headers = {
141
+ "Authorization": `Bearer ${token}`,
142
+ "Content-Type": "application/json",
143
+ };
144
+
145
+ const opts = { method, headers };
146
+ if (body) opts.body = JSON.stringify(body);
147
+
148
+ const res = await fetch(`${API_BASE}${path}`, opts);
149
+ const data = await res.json();
150
+ return { ok: res.ok, status: res.status, data };
151
+ }
152
+
153
+ /**
154
+ * Push all local connections to cloud (E2E encrypted).
155
+ *
156
+ * @param {string} token - Access token
157
+ * @param {string} passphrase - Sync passphrase
158
+ * @returns {Promise<{ pushed: number, errors: string[] }>}
159
+ */
160
+ export async function pushToCloud(token, passphrase) {
161
+ if (!existsSync(CONNECTIONS_DIR)) {
162
+ return { pushed: 0, errors: ["No connections directory found. Run: 0nmcp init"] };
163
+ }
164
+
165
+ const files = readdirSync(CONNECTIONS_DIR).filter(f => f.endsWith(".0n"));
166
+ const errors = [];
167
+ let pushed = 0;
168
+
169
+ for (const file of files) {
170
+ const serviceKey = file.replace(".0n", "");
171
+ try {
172
+ const content = readFileSync(join(CONNECTIONS_DIR, file), "utf8");
173
+ const { encrypted_data, iv, salt } = await encryptForSync(content, passphrase);
174
+
175
+ const { ok, data } = await apiCall("PUT", "/api/vault/sync", token, {
176
+ service_key: serviceKey,
177
+ encrypted_data,
178
+ iv,
179
+ salt,
180
+ });
181
+
182
+ if (ok) {
183
+ pushed++;
184
+ } else {
185
+ errors.push(`${serviceKey}: ${data.error || "Unknown error"}`);
186
+ }
187
+ } catch (err) {
188
+ errors.push(`${serviceKey}: ${err.message}`);
189
+ }
190
+ }
191
+
192
+ return { pushed, errors };
193
+ }
194
+
195
+ /**
196
+ * Pull all connections from cloud and write to local.
197
+ *
198
+ * @param {string} token - Access token
199
+ * @param {string} passphrase - Sync passphrase
200
+ * @returns {Promise<{ pulled: number, errors: string[] }>}
201
+ */
202
+ export async function pullFromCloud(token, passphrase) {
203
+ if (!existsSync(CONNECTIONS_DIR)) {
204
+ mkdirSync(CONNECTIONS_DIR, { recursive: true });
205
+ }
206
+
207
+ const { ok, data } = await apiCall("GET", "/api/vault/sync", token);
208
+ if (!ok) {
209
+ return { pulled: 0, errors: [data.error || "Failed to fetch from cloud"] };
210
+ }
211
+
212
+ const entries = data.entries || [];
213
+ const errors = [];
214
+ let pulled = 0;
215
+
216
+ for (const entry of entries) {
217
+ try {
218
+ const plaintext = await decryptFromSync(
219
+ entry.encrypted_data,
220
+ entry.iv,
221
+ entry.salt,
222
+ passphrase
223
+ );
224
+
225
+ const filePath = join(CONNECTIONS_DIR, `${entry.service_key}.0n`);
226
+ writeFileSync(filePath, plaintext);
227
+ pulled++;
228
+ } catch (err) {
229
+ errors.push(`${entry.service_key}: ${err.message}`);
230
+ }
231
+ }
232
+
233
+ return { pulled, errors };
234
+ }
235
+
236
+ /**
237
+ * Store sync salt locally (so the passphrase prompt
238
+ * can remind the user they've set one up).
239
+ */
240
+ export function hasSyncSetup() {
241
+ return existsSync(SYNC_SALT_FILE);
242
+ }
243
+
244
+ /**
245
+ * Mark that sync has been configured.
246
+ */
247
+ export function markSyncSetup() {
248
+ if (!existsSync(DOT_ON)) mkdirSync(DOT_ON, { recursive: true });
249
+ writeFileSync(SYNC_SALT_FILE, new Date().toISOString());
250
+ }