forge-trust-chain 0.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.
@@ -0,0 +1,534 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * FORGE MCP Server — The AI Agent Trust Layer
5
+ *
6
+ * This is the "SSL of AI agent operations."
7
+ *
8
+ * When an AI agent connects to this MCP server, every tool call
9
+ * automatically generates a TrustAtom. The agent doesn't need to
10
+ * explicitly "log" — trust recording is embedded in the protocol.
11
+ *
12
+ * Tools:
13
+ * forge_scan — Enumerate trust assumptions on this system
14
+ * forge_log — Explicitly record a state transition
15
+ * forge_verify — Verify chain integrity
16
+ * forge_seal — Seal atoms into a Merkle block
17
+ * forge_status — Show chain status and recent atoms
18
+ * forge_prove — Generate Merkle proof for a specific atom
19
+ * forge_export — Export full chain as JSON
20
+ *
21
+ * Transport: stdio (works directly with Claude Code / claude desktop)
22
+ */
23
+
24
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
25
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
26
+ import { z } from "zod";
27
+ import { execSync } from "node:child_process";
28
+ import { hostname, userInfo } from "node:os";
29
+
30
+ import { hash } from "../core/trust-pixel.js";
31
+ import { createAtom, verifyAtom, formatAtom } from "../core/trust-atom.js";
32
+ import { TrustChain, findDivergence } from "../core/chain.js";
33
+ import { Store } from "../store/store.js";
34
+ import { scan, formatScanResults } from "../scanner/index.js";
35
+ import { submitToOTS, checkOTSUpgrade, witnessSummary, createBilateralWitness } from "../core/witness.js";
36
+
37
+ /* ================================================================
38
+ SHARED STATE
39
+ ================================================================ */
40
+
41
+ const store = new Store();
42
+
43
+ function getIdentity() {
44
+ try {
45
+ return `ai-agent@${hostname()}`;
46
+ } catch {
47
+ return "ai-agent@unknown";
48
+ }
49
+ }
50
+
51
+ function stateSnapshot() {
52
+ const parts = {};
53
+ try {
54
+ parts.processes = execSync("ps aux 2>/dev/null | wc -l", { encoding: "utf8" }).trim();
55
+ } catch { parts.processes = "unknown"; }
56
+ try {
57
+ parts.connections = execSync("ss -s 2>/dev/null | head -1", { encoding: "utf8" }).trim();
58
+ } catch { parts.connections = "unknown"; }
59
+ parts.timestamp = Date.now();
60
+ return parts;
61
+ }
62
+
63
+ /**
64
+ * Core function: record a TrustAtom.
65
+ * Called by every tool to create automatic audit trail.
66
+ */
67
+ function recordAtom(action, fromState, toState) {
68
+ const prev = store.lastProof();
69
+ const atom = createAtom({
70
+ who: getIdentity(),
71
+ from: fromState || stateSnapshot(),
72
+ action,
73
+ to: toState || stateSnapshot(),
74
+ prev,
75
+ });
76
+ const index = store.appendAtom(atom);
77
+ return { atom, index };
78
+ }
79
+
80
+ /* ================================================================
81
+ MCP SERVER
82
+ ================================================================ */
83
+
84
+ const server = new McpServer({
85
+ name: "forge-trust-chain",
86
+ version: "0.1.0",
87
+ });
88
+
89
+ /* ---- forge_scan ---- */
90
+
91
+ server.tool(
92
+ "forge_scan",
93
+ "Scan the current system for trust assumptions — exposed ports, weak auth, firewall gaps, management panels, etc. Returns a categorized risk assessment.",
94
+ {},
95
+ async () => {
96
+ const results = scan();
97
+
98
+ // Auto-record this scan as a TrustAtom
99
+ recordAtom("forge_scan", { type: "pre-scan" }, { type: "scan-complete", summary: results.summary });
100
+
101
+ const text = formatScanResults(results)
102
+ .replace(/\x1b\[[0-9;]*m/g, ""); // strip ANSI colors for MCP
103
+
104
+ return {
105
+ content: [{ type: "text", text }],
106
+ };
107
+ }
108
+ );
109
+
110
+ /* ---- forge_log ---- */
111
+
112
+ server.tool(
113
+ "forge_log",
114
+ "Record a TrustAtom — one verifiable state transition. Use this to log any operation: deployment, config change, package install, etc. Each log entry is chained to the previous one via cryptographic hash, making the timeline tamper-evident.",
115
+ {
116
+ action: z.string().describe("Description of the operation performed"),
117
+ from_state: z.string().optional().describe("Description of state before (auto-captured if omitted)"),
118
+ to_state: z.string().optional().describe("Description of state after (auto-captured if omitted)"),
119
+ },
120
+ async ({ action, from_state, to_state }) => {
121
+ const fromState = from_state ? { description: from_state, ...stateSnapshot() } : stateSnapshot();
122
+ const toState = to_state ? { description: to_state, ...stateSnapshot() } : stateSnapshot();
123
+
124
+ const { atom, index } = recordAtom(action, fromState, toState);
125
+
126
+ return {
127
+ content: [{
128
+ type: "text",
129
+ text: [
130
+ "✓ TrustAtom recorded",
131
+ ` Index: #${index}`,
132
+ ` Action: ${action}`,
133
+ ` Proof: ${atom.proof}`,
134
+ ` Chain: ${store.atomCount} atoms total`,
135
+ ` Prev: ${atom.prev[0] === "genesis" ? "genesis" : atom.prev[0].slice(0, 32) + "…"}`,
136
+ "",
137
+ " Witness: self (local). Use forge_seal to create Merkle block for anchoring.",
138
+ ].join("\n"),
139
+ }],
140
+ };
141
+ }
142
+ );
143
+
144
+ /* ---- forge_verify ---- */
145
+
146
+ server.tool(
147
+ "forge_verify",
148
+ "Verify the integrity of the entire TrustAtom chain. Checks that every atom's proof hash is correct and every chain link is unbroken. Returns VALID or identifies the exact point of tampering.",
149
+ {},
150
+ async () => {
151
+ const atoms = store.getAtoms();
152
+
153
+ if (atoms.length === 0) {
154
+ return {
155
+ content: [{ type: "text", text: "No atoms to verify. Use forge_log to record operations first." }],
156
+ };
157
+ }
158
+
159
+ let broken = -1;
160
+ let reason = "";
161
+ for (let i = 0; i < atoms.length; i++) {
162
+ if (!verifyAtom(atoms[i])) {
163
+ broken = i;
164
+ reason = "proof_mismatch";
165
+ break;
166
+ }
167
+ if (i > 0 && !atoms[i].prev.includes(atoms[i - 1].proof)) {
168
+ broken = i;
169
+ reason = "chain_break";
170
+ break;
171
+ }
172
+ }
173
+
174
+ // Record the verification itself
175
+ recordAtom("forge_verify", { atoms: atoms.length }, { valid: broken < 0, broken_at: broken });
176
+
177
+ if (broken >= 0) {
178
+ return {
179
+ content: [{
180
+ type: "text",
181
+ text: `✗ CHAIN BROKEN at atom #${broken}\n Reason: ${reason}\n This indicates records have been tampered with.`,
182
+ }],
183
+ };
184
+ }
185
+
186
+ return {
187
+ content: [{
188
+ type: "text",
189
+ text: `✓ CHAIN VALID\n ${atoms.length} atoms verified, all proofs correct, all links intact.`,
190
+ }],
191
+ };
192
+ }
193
+ );
194
+
195
+ /* ---- forge_seal ---- */
196
+
197
+ server.tool(
198
+ "forge_seal",
199
+ "Seal current TrustAtoms into a Merkle block. The block's root hash can be anchored to a public timestamp service (like OpenTimestamps) for independent existence proof, upgrading trust from Level 1 (self) to Level 3 (public).",
200
+ {},
201
+ async () => {
202
+ const atoms = store.getAtoms();
203
+ if (atoms.length === 0) {
204
+ return {
205
+ content: [{ type: "text", text: "No atoms to seal." }],
206
+ };
207
+ }
208
+
209
+ const chain = new TrustChain(getIdentity());
210
+ chain.atoms = atoms;
211
+ const block = chain.seal();
212
+
213
+ if (!block) {
214
+ return {
215
+ content: [{ type: "text", text: "No new atoms to seal since last block." }],
216
+ };
217
+ }
218
+
219
+ store.appendBlock(block);
220
+
221
+ // Record the seal operation
222
+ recordAtom("forge_seal", { unsealed_atoms: block.atom_count }, { merkle_root: block.root, block_hash: block.block_hash });
223
+
224
+ return {
225
+ content: [{
226
+ type: "text",
227
+ text: [
228
+ "✓ Merkle block sealed",
229
+ ` Root: ${block.root}`,
230
+ ` Atoms: ${block.atom_count}`,
231
+ ` Block hash: ${block.block_hash}`,
232
+ "",
233
+ " This Merkle root can now be anchored to a public timestamp service.",
234
+ " Anyone with the root can verify any atom in this block in O(log n).",
235
+ ].join("\n"),
236
+ }],
237
+ };
238
+ }
239
+ );
240
+
241
+ /* ---- forge_status ---- */
242
+
243
+ server.tool(
244
+ "forge_status",
245
+ "Show current trust chain status: identity, atom count, block count, and recent operations.",
246
+ {},
247
+ async () => {
248
+ const atoms = store.getAtoms();
249
+ const blocks = store.getBlocks();
250
+
251
+ const lines = [
252
+ "── FORGE Chain Status ──",
253
+ ` Identity: ${getIdentity()}`,
254
+ ` Atoms: ${atoms.length}`,
255
+ ` Blocks: ${blocks.length}`,
256
+ ` Store: ~/.forge/chain.json`,
257
+ ];
258
+
259
+ if (atoms.length > 0) {
260
+ lines.push("");
261
+ lines.push(" Recent atoms:");
262
+ const recent = atoms.slice(-5);
263
+ const offset = atoms.length - recent.length;
264
+ for (let i = 0; i < recent.length; i++) {
265
+ const a = recent[i];
266
+ const time = new Date(a.when).toISOString().slice(0, 19);
267
+ lines.push(` #${offset + i} [${time}] proof:${a.proof.slice(0, 32)}…`);
268
+ }
269
+ }
270
+
271
+ if (blocks.length > 0) {
272
+ lines.push("");
273
+ lines.push(" Latest block:");
274
+ const b = blocks[blocks.length - 1];
275
+ lines.push(` Root: ${b.root.slice(0, 32)}…`);
276
+ lines.push(` Hash: ${b.block_hash.slice(0, 32)}…`);
277
+ }
278
+
279
+ return {
280
+ content: [{ type: "text", text: lines.join("\n") }],
281
+ };
282
+ }
283
+ );
284
+
285
+ /* ---- forge_prove ---- */
286
+
287
+ server.tool(
288
+ "forge_prove",
289
+ "Generate a Merkle proof for a specific atom by index. This proves the atom exists in a sealed block WITHOUT revealing other atoms (zero-knowledge principle). Requires atoms to be sealed first.",
290
+ {
291
+ atom_index: z.number().int().min(0).describe("The index of the atom to prove"),
292
+ },
293
+ async ({ atom_index }) => {
294
+ const atoms = store.getAtoms();
295
+ const blocks = store.getBlocks();
296
+
297
+ if (atom_index >= atoms.length) {
298
+ return {
299
+ content: [{ type: "text", text: `Atom #${atom_index} does not exist. Chain has ${atoms.length} atoms.` }],
300
+ };
301
+ }
302
+
303
+ if (blocks.length === 0) {
304
+ return {
305
+ content: [{ type: "text", text: "No sealed blocks. Run forge_seal first to create Merkle blocks." }],
306
+ };
307
+ }
308
+
309
+ // Rebuild chain for proof generation
310
+ const chain = new TrustChain(getIdentity());
311
+ chain.atoms = atoms;
312
+ // Re-seal to rebuild layers (store doesn't persist full layers)
313
+ chain.blocks = []; // reset
314
+ const block = chain.seal();
315
+ if (!block) {
316
+ return {
317
+ content: [{ type: "text", text: "Could not rebuild Merkle tree." }],
318
+ };
319
+ }
320
+
321
+ const proof = chain.proveAtom(atom_index);
322
+ if (!proof) {
323
+ return {
324
+ content: [{ type: "text", text: `Atom #${atom_index} is not in any sealed block.` }],
325
+ };
326
+ }
327
+
328
+ const verified = chain.verifyProof(proof.atom.proof, proof.merkle_proof, proof.merkle_root);
329
+
330
+ // Record the prove operation
331
+ recordAtom("forge_prove", { atom_index }, { verified, merkle_root: proof.merkle_root });
332
+
333
+ return {
334
+ content: [{
335
+ type: "text",
336
+ text: [
337
+ `Merkle proof for atom #${atom_index}:`,
338
+ ` Atom proof: ${proof.atom.proof}`,
339
+ ` Merkle root: ${proof.merkle_root}`,
340
+ ` Path length: ${proof.merkle_proof.length} steps`,
341
+ ` Verified: ${verified ? "✓ YES" : "✗ NO"}`,
342
+ "",
343
+ " This proof demonstrates atom #" + atom_index + " exists in the sealed block",
344
+ " without revealing any other atom's content.",
345
+ "",
346
+ JSON.stringify({
347
+ atom_proof: proof.atom.proof,
348
+ merkle_root: proof.merkle_root,
349
+ merkle_path: proof.merkle_proof,
350
+ verified,
351
+ }, null, 2),
352
+ ].join("\n"),
353
+ }],
354
+ };
355
+ }
356
+ );
357
+
358
+ /* ---- forge_export ---- */
359
+
360
+ server.tool(
361
+ "forge_export",
362
+ "Export the entire trust chain as JSON. This can be shared with a counterparty for cross-chain verification, or archived for dispute resolution.",
363
+ {},
364
+ async () => {
365
+ const data = store.exportAll();
366
+
367
+ // Record the export
368
+ recordAtom("forge_export", { atoms: data.atoms.length }, { exported: true });
369
+
370
+ return {
371
+ content: [{
372
+ type: "text",
373
+ text: JSON.stringify(data, null, 2),
374
+ }],
375
+ };
376
+ }
377
+ );
378
+
379
+ /* ---- forge_anchor ---- */
380
+
381
+ server.tool(
382
+ "forge_anchor",
383
+ "Anchor a sealed Merkle block to the Bitcoin blockchain via OpenTimestamps. This upgrades trust from Level 1 (self-witness) to Level 3 (public attestation), and eventually Level 4 (Bitcoin anchored). Free, no API key needed. Requires a sealed block first.",
384
+ {
385
+ upgrade: z.boolean().optional().describe("If true, check if pending attestations have been confirmed in Bitcoin"),
386
+ },
387
+ async ({ upgrade }) => {
388
+ const blocks = store.getBlocks();
389
+ if (blocks.length === 0) {
390
+ return {
391
+ content: [{ type: "text", text: "No sealed blocks. Run forge_seal first." }],
392
+ };
393
+ }
394
+
395
+ const latestBlock = blocks[blocks.length - 1];
396
+ const merkleRoot = latestBlock.root;
397
+
398
+ if (upgrade) {
399
+ // Check if pending attestation has been upgraded
400
+ const result = await checkOTSUpgrade(merkleRoot);
401
+ recordAtom("forge_anchor_upgrade_check", { merkle_root: merkleRoot }, { result: result.message });
402
+
403
+ return {
404
+ content: [{
405
+ type: "text",
406
+ text: [
407
+ `Checking OTS upgrade for Merkle root: ${merkleRoot.slice(0, 32)}…`,
408
+ ` Upgraded: ${result.upgraded}`,
409
+ ` Pending: ${result.pending}`,
410
+ ` ${result.message}`,
411
+ "",
412
+ JSON.stringify(result.details, null, 2),
413
+ ].join("\n"),
414
+ }],
415
+ };
416
+ }
417
+
418
+ // Submit to OTS calendars
419
+ const result = await submitToOTS(merkleRoot);
420
+ recordAtom("forge_anchor", { merkle_root: merkleRoot }, { level: result.level, calendars: result.successful_submissions });
421
+
422
+ const lines = [
423
+ result.successful_submissions > 0
424
+ ? `✓ Submitted to ${result.successful_submissions}/${result.total_calendars} OTS calendars`
425
+ : "✗ Failed to submit to any calendar",
426
+ ` Merkle root: ${merkleRoot.slice(0, 32)}…`,
427
+ ` Digest: ${result.digest.slice(0, 32)}… (privacy-protected)`,
428
+ ` Trust level: ${result.level === 3 ? "Level 3 (public attestation — pending)" : "Level 1 (self)"}`,
429
+ "",
430
+ ];
431
+
432
+ for (const cal of result.calendars) {
433
+ const icon = cal.status === "submitted" ? "✓" : "✗";
434
+ lines.push(` ${icon} ${cal.calendar}: ${cal.status}${cal.error ? " — " + cal.error : ""}`);
435
+ }
436
+
437
+ if (result.successful_submissions > 0) {
438
+ lines.push("");
439
+ lines.push(" Next: Wait ~2 hours, then run forge_anchor with upgrade=true");
440
+ lines.push(" to check for Bitcoin confirmation (Level 4).");
441
+ }
442
+
443
+ return {
444
+ content: [{ type: "text", text: lines.join("\n") }],
445
+ };
446
+ }
447
+ );
448
+
449
+ /* ---- forge_witness ---- */
450
+
451
+ server.tool(
452
+ "forge_witness",
453
+ "Show witness status for the latest sealed block. Displays current trust level, all witnesses, and the upgrade path to higher trust levels.",
454
+ {
455
+ bilateral_party: z.string().optional().describe("If provided, create a bilateral witness receipt for this counterparty"),
456
+ },
457
+ async ({ bilateral_party }) => {
458
+ const blocks = store.getBlocks();
459
+ if (blocks.length === 0) {
460
+ return {
461
+ content: [{ type: "text", text: "No sealed blocks. Run forge_seal first." }],
462
+ };
463
+ }
464
+
465
+ const latestBlock = blocks[blocks.length - 1];
466
+ const merkleRoot = latestBlock.root;
467
+
468
+ if (bilateral_party) {
469
+ const receipt = createBilateralWitness(merkleRoot, bilateral_party);
470
+ recordAtom("forge_witness_bilateral", { merkle_root: merkleRoot }, { counterparty: bilateral_party });
471
+
472
+ return {
473
+ content: [{
474
+ type: "text",
475
+ text: [
476
+ `✓ Bilateral witness created`,
477
+ ` Counterparty: ${bilateral_party}`,
478
+ ` Receipt hash: ${receipt.receipt_hash}`,
479
+ ` Trust level: Level 2 (bilateral — neither party can deny)`,
480
+ "",
481
+ " Share this receipt with the counterparty:",
482
+ JSON.stringify(receipt, null, 2),
483
+ ].join("\n"),
484
+ }],
485
+ };
486
+ }
487
+
488
+ const summary = witnessSummary(merkleRoot);
489
+
490
+ const lines = [
491
+ `── Witness Status ──`,
492
+ ` Merkle root: ${merkleRoot.slice(0, 32)}…`,
493
+ ` Trust level: Level ${summary.current_level.level} (${summary.current_level.label})`,
494
+ ` ${summary.current_level.description}`,
495
+ ` Witnesses: ${summary.witness_count}`,
496
+ ];
497
+
498
+ if (summary.witnesses.length > 0) {
499
+ lines.push("");
500
+ for (const w of summary.witnesses) {
501
+ lines.push(` [L${w.level}] ${w.type} — ${w.created_at}`);
502
+ }
503
+ }
504
+
505
+ if (summary.upgrade_path.length > 0) {
506
+ lines.push("");
507
+ lines.push(" Upgrade path:");
508
+ for (const p of summary.upgrade_path) {
509
+ lines.push(` → ${p}`);
510
+ }
511
+ }
512
+
513
+ return {
514
+ content: [{ type: "text", text: lines.join("\n") }],
515
+ };
516
+ }
517
+ );
518
+
519
+ /* ================================================================
520
+ START
521
+ ================================================================ */
522
+
523
+ async function main() {
524
+ const transport = new StdioServerTransport();
525
+ await server.connect(transport);
526
+
527
+ // Record that MCP server started
528
+ recordAtom("mcp_server_started", { type: "startup" }, { type: "ready", identity: getIdentity() });
529
+ }
530
+
531
+ main().catch((err) => {
532
+ console.error("FORGE MCP Server error:", err);
533
+ process.exit(1);
534
+ });