0nmcp 2.2.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/cli.js +143 -0
- package/lib/badges.json +5 -5
- package/lib/catalog.json +653 -3
- package/lib/stats.json +647 -35
- package/package.json +12 -10
- package/vault/sync.js +250 -0
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "0nmcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"mcpName": "io.github.0nork/0nMCP",
|
|
5
|
-
"description": "Universal AI API Orchestrator —
|
|
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",
|
|
@@ -258,17 +258,19 @@
|
|
|
258
258
|
"LICENSE"
|
|
259
259
|
],
|
|
260
260
|
"0nmcp-stats": {
|
|
261
|
-
"tools":
|
|
261
|
+
"tools": 545,
|
|
262
262
|
"crmTools": 245,
|
|
263
263
|
"vaultTools": 4,
|
|
264
|
+
"vaultContainerTools": 8,
|
|
265
|
+
"deedTools": 6,
|
|
264
266
|
"engineTools": 6,
|
|
265
267
|
"appTools": 5,
|
|
266
|
-
"totalTools":
|
|
267
|
-
"services":
|
|
268
|
-
"actions":
|
|
269
|
-
"triggers":
|
|
270
|
-
"totalCapabilities":
|
|
271
|
-
"categories":
|
|
272
|
-
"lastUpdated": "2026-
|
|
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"
|
|
273
275
|
}
|
|
274
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
|
+
}
|