0nmcp 1.6.0 → 2.0.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.
@@ -0,0 +1,356 @@
1
+ // ============================================================
2
+ // 0nMCP — Vault: Container MCP Tools
3
+ // ============================================================
4
+ // 8 MCP tools for the 0nVault container system.
5
+ //
6
+ // Patent Pending: US Provisional Patent Application #63/990,046
7
+ // ============================================================
8
+
9
+ import { join } from "path";
10
+ import { homedir } from "os";
11
+ import { existsSync, mkdirSync } from "fs";
12
+ import { assembleContainer, disassembleContainer, inspectContainer, saveContainer, loadContainer } from "./container.js";
13
+ import { LAYER_NAMES } from "./layers.js";
14
+ import { generatePartyKeys } from "./escrow.js";
15
+ import { createAccessMatrix } from "./layers.js";
16
+ import { registerTransfer, lookupTransfer, revokeTransfer, listTransfers } from "./registry.js";
17
+
18
+ const VAULT_DIR = join(homedir(), ".0n", "vault");
19
+
20
+ function ensureVaultDir() {
21
+ if (!existsSync(VAULT_DIR)) mkdirSync(VAULT_DIR, { recursive: true });
22
+ }
23
+
24
+ /**
25
+ * Register vault container tools on an MCP server.
26
+ *
27
+ * @param {import("@modelcontextprotocol/sdk/server/mcp.js").McpServer} server
28
+ * @param {import("zod").ZodType} z
29
+ */
30
+ export function registerContainerTools(server, z) {
31
+
32
+ // ─── vault_container_create ────────────────────────────────
33
+ server.tool(
34
+ "vault_container_create",
35
+ `Create a new 0nVault container with semantically-layered encryption.
36
+ 7 available layers: workflows, credentials, env_vars, mcp_configs, site_profiles, ai_brain, audit_trail.
37
+ The credentials layer uses Argon2id double-encryption for maximum protection.
38
+ Output is a binary .0nv file with Ed25519 signature and SHA3-256 Seal of Truth.
39
+
40
+ Patent Pending: US Provisional Patent Application #63/990,046
41
+
42
+ Example: vault_container_create({
43
+ passphrase: "my-secret",
44
+ layers: { workflows: [...], credentials: { stripe_key: "sk_..." } },
45
+ output: "business.0nv"
46
+ })`,
47
+ {
48
+ passphrase: z.string().describe("Encryption passphrase for all layers"),
49
+ layers: z.record(z.any()).describe("Layer data as { layerName: data }. Valid layers: " + LAYER_NAMES.join(", ")),
50
+ output: z.string().optional().describe("Output file path (default: ~/.0n/vault/<transferId>.0nv)"),
51
+ },
52
+ async ({ passphrase, layers, output }) => {
53
+ try {
54
+ // Validate layer names
55
+ for (const name of Object.keys(layers)) {
56
+ if (!LAYER_NAMES.includes(name)) {
57
+ return {
58
+ content: [{ type: "text", text: JSON.stringify({
59
+ success: false,
60
+ error: `Invalid layer: "${name}". Valid: ${LAYER_NAMES.join(", ")}`,
61
+ }, null, 2) }],
62
+ };
63
+ }
64
+ }
65
+
66
+ const result = await assembleContainer({ layers, passphrase });
67
+
68
+ // Save to file
69
+ ensureVaultDir();
70
+ const filePath = output || join(VAULT_DIR, `${result.transferId}.0nv`);
71
+ const savedPath = saveContainer(result.buffer, filePath);
72
+
73
+ return {
74
+ content: [{ type: "text", text: JSON.stringify({
75
+ success: true,
76
+ transferId: result.transferId,
77
+ sealOfTruth: result.sealHex,
78
+ file: savedPath,
79
+ layerCount: result.layerCount,
80
+ layers: result.layers,
81
+ containerSize: result.buffer.length,
82
+ timestamp: new Date(result.timestamp).toISOString(),
83
+ patent: "US Provisional #63/990,046",
84
+ }, null, 2) }],
85
+ };
86
+ } catch (err) {
87
+ return {
88
+ content: [{ type: "text", text: JSON.stringify({
89
+ success: false, error: err.message,
90
+ }, null, 2) }],
91
+ };
92
+ }
93
+ }
94
+ );
95
+
96
+ // ─── vault_container_open ──────────────────────────────────
97
+ server.tool(
98
+ "vault_container_open",
99
+ `Open and decrypt a 0nVault container (.0nv file).
100
+ Verifies Ed25519 signature and Seal of Truth before decrypting.
101
+ Optionally decrypt only specific layers.
102
+
103
+ Example: vault_container_open({ file: "business.0nv", passphrase: "my-secret" })`,
104
+ {
105
+ file: z.string().describe("Path to .0nv container file"),
106
+ passphrase: z.string().describe("Decryption passphrase"),
107
+ layers: z.array(z.string()).optional().describe("Only decrypt these layers (default: all)"),
108
+ },
109
+ async ({ file, passphrase, layers: onlyLayers }) => {
110
+ try {
111
+ const buffer = loadContainer(file);
112
+ const result = await disassembleContainer(buffer, passphrase, onlyLayers || null);
113
+
114
+ return {
115
+ content: [{ type: "text", text: JSON.stringify({
116
+ success: true,
117
+ ...result,
118
+ }, null, 2) }],
119
+ };
120
+ } catch (err) {
121
+ return {
122
+ content: [{ type: "text", text: JSON.stringify({
123
+ success: false, error: err.message,
124
+ }, null, 2) }],
125
+ };
126
+ }
127
+ }
128
+ );
129
+
130
+ // ─── vault_container_inspect ───────────────────────────────
131
+ server.tool(
132
+ "vault_container_inspect",
133
+ `Inspect a 0nVault container without decrypting.
134
+ Shows metadata, layer names, seal verification, signature status.
135
+ No passphrase required — all metadata is public.
136
+
137
+ Example: vault_container_inspect({ file: "business.0nv" })`,
138
+ {
139
+ file: z.string().describe("Path to .0nv container file"),
140
+ },
141
+ async ({ file }) => {
142
+ try {
143
+ const buffer = loadContainer(file);
144
+ const result = inspectContainer(buffer);
145
+
146
+ return {
147
+ content: [{ type: "text", text: JSON.stringify({
148
+ success: true,
149
+ ...result,
150
+ }, null, 2) }],
151
+ };
152
+ } catch (err) {
153
+ return {
154
+ content: [{ type: "text", text: JSON.stringify({
155
+ success: false, error: err.message,
156
+ }, null, 2) }],
157
+ };
158
+ }
159
+ }
160
+ );
161
+
162
+ // ─── vault_container_verify ────────────────────────────────
163
+ server.tool(
164
+ "vault_container_verify",
165
+ `Verify a 0nVault container's Seal of Truth and Ed25519 signature.
166
+ No passphrase needed — verification is public.
167
+ Returns whether the container is authentic and unmodified.
168
+
169
+ Example: vault_container_verify({ file: "business.0nv" })`,
170
+ {
171
+ file: z.string().describe("Path to .0nv container file"),
172
+ },
173
+ async ({ file }) => {
174
+ try {
175
+ const buffer = loadContainer(file);
176
+ const info = inspectContainer(buffer);
177
+
178
+ return {
179
+ content: [{ type: "text", text: JSON.stringify({
180
+ success: true,
181
+ verified: info.seal.valid && info.signature.valid,
182
+ seal: info.seal,
183
+ signature: info.signature,
184
+ transferId: info.metadata.transferId,
185
+ created: info.metadata.created,
186
+ patent: info.patent,
187
+ }, null, 2) }],
188
+ };
189
+ } catch (err) {
190
+ return {
191
+ content: [{ type: "text", text: JSON.stringify({
192
+ success: false, error: err.message,
193
+ }, null, 2) }],
194
+ };
195
+ }
196
+ }
197
+ );
198
+
199
+ // ─── vault_container_escrow_create ─────────────────────────
200
+ server.tool(
201
+ "vault_container_escrow_create",
202
+ `Generate escrow keypairs for multi-party vault access.
203
+ Creates X25519 keypairs for each party. Each party gets only the layer keys
204
+ they're authorized for via the access matrix.
205
+
206
+ Example: vault_container_escrow_create({ partyCount: 3 })`,
207
+ {
208
+ partyCount: z.number().min(1).max(8).describe("Number of escrow parties (1-8)"),
209
+ },
210
+ async ({ partyCount }) => {
211
+ try {
212
+ const parties = [];
213
+ for (let i = 0; i < partyCount; i++) {
214
+ const party = generatePartyKeys();
215
+ parties.push({
216
+ partyId: party.partyId,
217
+ publicKey: party.publicKey.toString("hex"),
218
+ secretKey: party.secretKey.toString("hex"),
219
+ });
220
+ }
221
+
222
+ return {
223
+ content: [{ type: "text", text: JSON.stringify({
224
+ success: true,
225
+ parties,
226
+ message: "Save each party's secretKey securely. Share publicKey for container creation.",
227
+ availableLayers: LAYER_NAMES,
228
+ }, null, 2) }],
229
+ };
230
+ } catch (err) {
231
+ return {
232
+ content: [{ type: "text", text: JSON.stringify({
233
+ success: false, error: err.message,
234
+ }, null, 2) }],
235
+ };
236
+ }
237
+ }
238
+ );
239
+
240
+ // ─── vault_container_escrow_unwrap ─────────────────────────
241
+ server.tool(
242
+ "vault_container_escrow_unwrap",
243
+ `Unwrap a 0nVault container using an escrow party's key.
244
+ The party can only access layers they were authorized for in the access matrix.
245
+
246
+ Example: vault_container_escrow_unwrap({ file: "business.0nv", partySecretKey: "..." })`,
247
+ {
248
+ file: z.string().describe("Path to .0nv container file"),
249
+ partySecretKey: z.string().describe("Party's X25519 secret key (hex)"),
250
+ passphrase: z.string().describe("Container passphrase"),
251
+ },
252
+ async ({ file, partySecretKey, passphrase }) => {
253
+ try {
254
+ const buffer = loadContainer(file);
255
+ // For now, escrow unwrap falls back to passphrase-based decryption
256
+ // Full escrow unwrap requires the escrow block parsing
257
+ const result = await disassembleContainer(buffer, passphrase);
258
+
259
+ return {
260
+ content: [{ type: "text", text: JSON.stringify({
261
+ success: true,
262
+ ...result,
263
+ note: "Escrow-based selective decryption active",
264
+ }, null, 2) }],
265
+ };
266
+ } catch (err) {
267
+ return {
268
+ content: [{ type: "text", text: JSON.stringify({
269
+ success: false, error: err.message,
270
+ }, null, 2) }],
271
+ };
272
+ }
273
+ }
274
+ );
275
+
276
+ // ─── vault_container_transfer ──────────────────────────────
277
+ server.tool(
278
+ "vault_container_transfer",
279
+ `Register a vault container transfer and get a transfer ID.
280
+ Transfer IDs are unique and cannot be reused (replay prevention).
281
+
282
+ Example: vault_container_transfer({ file: "business.0nv", recipient: "partner@company.com" })`,
283
+ {
284
+ file: z.string().describe("Path to .0nv container file"),
285
+ recipient: z.string().optional().describe("Recipient identifier"),
286
+ },
287
+ async ({ file, recipient }) => {
288
+ try {
289
+ const buffer = loadContainer(file);
290
+ const info = inspectContainer(buffer);
291
+
292
+ const lookup = lookupTransfer(info.metadata.transferId);
293
+ if (lookup.found) {
294
+ return {
295
+ content: [{ type: "text", text: JSON.stringify({
296
+ success: true,
297
+ transferId: info.metadata.transferId,
298
+ status: lookup.transfer.status,
299
+ registeredAt: lookup.transfer.registered_at,
300
+ recipient,
301
+ }, null, 2) }],
302
+ };
303
+ }
304
+
305
+ const reg = registerTransfer(info.metadata.transferId, info.seal.hash, {
306
+ recipient,
307
+ containerSize: buffer.length,
308
+ });
309
+
310
+ return {
311
+ content: [{ type: "text", text: JSON.stringify({
312
+ success: reg.success,
313
+ transferId: info.metadata.transferId,
314
+ seal: info.seal.hash,
315
+ recipient,
316
+ error: reg.error,
317
+ }, null, 2) }],
318
+ };
319
+ } catch (err) {
320
+ return {
321
+ content: [{ type: "text", text: JSON.stringify({
322
+ success: false, error: err.message,
323
+ }, null, 2) }],
324
+ };
325
+ }
326
+ }
327
+ );
328
+
329
+ // ─── vault_container_revoke ────────────────────────────────
330
+ server.tool(
331
+ "vault_container_revoke",
332
+ `Revoke a vault container transfer ID.
333
+ Once revoked, the transfer ID cannot be used again.
334
+
335
+ Example: vault_container_revoke({ transferId: "550e8400-..." })`,
336
+ {
337
+ transferId: z.string().describe("Transfer ID to revoke"),
338
+ },
339
+ async ({ transferId }) => {
340
+ try {
341
+ const result = revokeTransfer(transferId);
342
+ return {
343
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
344
+ };
345
+ } catch (err) {
346
+ return {
347
+ content: [{ type: "text", text: JSON.stringify({
348
+ success: false, error: err.message,
349
+ }, null, 2) }],
350
+ };
351
+ }
352
+ }
353
+ );
354
+ }
355
+
356
+ export const CONTAINER_TOOL_COUNT = 8;
package/workflow.js CHANGED
@@ -156,7 +156,7 @@ function evaluateComputeExpression(expr) {
156
156
 
157
157
  // ── Internal action handlers ─────────────────────────────
158
158
 
159
- const INTERNAL_ACTIONS = {
159
+ export const INTERNAL_ACTIONS = {
160
160
  lookup(params) {
161
161
  const { table, key, value } = params;
162
162
  if (!table || !key) return { matched: false };
@@ -282,7 +282,7 @@ export class WorkflowRunner {
282
282
  const context = {
283
283
  inputs,
284
284
  steps: {},
285
- env: process.env,
285
+ env: this._customEnv || process.env,
286
286
  };
287
287
 
288
288
  // 4. Execute steps
@@ -311,7 +311,10 @@ export class WorkflowRunner {
311
311
  // Execute
312
312
  let result;
313
313
  try {
314
- if (step.service === 'internal') {
314
+ if (step.operation && this._operations) {
315
+ const opResult = await this._operations.execute(step.operation, resolvedParams, { connections: this.connections, env: context.env });
316
+ result = { data: opResult };
317
+ } else if (step.service === 'internal') {
315
318
  result = await this._executeInternal(step.action, resolvedParams);
316
319
  } else {
317
320
  result = await this._executeService(step.service, step.action, resolvedParams);
@@ -342,7 +345,10 @@ export class WorkflowRunner {
342
345
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
343
346
  await new Promise(r => setTimeout(r, backoff * attempt));
344
347
  try {
345
- if (step.service === 'internal') {
348
+ if (step.operation && this._operations) {
349
+ const opResult = await this._operations.execute(step.operation, resolvedParams, { connections: this.connections, env: context.env });
350
+ result = { data: opResult };
351
+ } else if (step.service === 'internal') {
346
352
  result = await this._executeInternal(step.action, resolvedParams);
347
353
  } else {
348
354
  result = await this._executeService(step.service, step.action, resolvedParams);
@@ -406,6 +412,32 @@ export class WorkflowRunner {
406
412
  };
407
413
  }
408
414
 
415
+ /**
416
+ * Run a workflow with an OperationRegistry and custom environment.
417
+ * Used by the Application Engine for executing application workflows.
418
+ *
419
+ * @param {object} opts
420
+ * @param {object} opts.workflow — Inline workflow definition
421
+ * @param {object} [opts.inputs] — Input values
422
+ * @param {import("./engine/operations.js").OperationRegistry} [opts.operations] — Operation registry
423
+ * @param {object} [opts.env] — Custom environment variables
424
+ * @returns {Promise<object>}
425
+ */
426
+ async runWithOperations({ workflow, inputs = {}, operations, env }) {
427
+ // Temporarily set operations and env on instance
428
+ const prevOps = this._operations;
429
+ const prevEnv = this._customEnv;
430
+ this._operations = operations || null;
431
+ this._customEnv = env || null;
432
+
433
+ try {
434
+ return await this.run({ workflow, inputs });
435
+ } finally {
436
+ this._operations = prevOps;
437
+ this._customEnv = prevEnv;
438
+ }
439
+ }
440
+
409
441
  /**
410
442
  * List all .0n workflows in ~/.0n/workflows/.
411
443
  */