@helm-protocol/ttt-mcp 0.4.0 → 0.4.2

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/README.md CHANGED
@@ -55,12 +55,12 @@ Claude workflow → [context compressed] → agents call pot_query(eventId)
55
55
  ### Claude Code
56
56
 
57
57
  ```bash
58
- claude mcp add ttt -- npx -y @helm-protocol/ttt-mcp@0.3.3
58
+ claude mcp add ttt -- npx -y @helm-protocol/ttt-mcp@0.4.0
59
59
  ```
60
60
 
61
61
  With an API key (raises the free limit to your plan's monthly quota):
62
62
  ```bash
63
- claude mcp add ttt -e TTT_API_KEY=your-key -- npx -y @helm-protocol/ttt-mcp@0.3.3
63
+ claude mcp add ttt -e TTT_API_KEY=your-key -- npx -y @helm-protocol/ttt-mcp@0.4.0
64
64
  ```
65
65
 
66
66
  ### Claude Desktop
@@ -72,7 +72,7 @@ Add to `claude_desktop_config.json`:
72
72
  "mcpServers": {
73
73
  "ttt": {
74
74
  "command": "npx",
75
- "args": ["-y", "@helm-protocol/ttt-mcp@0.3.3"],
75
+ "args": ["-y", "@helm-protocol/ttt-mcp@0.4.0"],
76
76
  "env": { "TTT_API_KEY": "your-key" }
77
77
  }
78
78
  }
@@ -81,7 +81,7 @@ Add to `claude_desktop_config.json`:
81
81
 
82
82
  ### Cursor
83
83
 
84
- [![Add to Cursor](https://img.shields.io/badge/Add%20to%20Cursor-1a1a1a?style=flat&logo=cursor&logoColor=white)](https://cursor.com/install-mcp?name=ttt&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBoZWxtLXByb3RvY29sL3R0dC1tY3BAMC4zLjMiXX0=)
84
+ [![Add to Cursor](https://img.shields.io/badge/Add%20to%20Cursor-1a1a1a?style=flat&logo=cursor&logoColor=white)](https://cursor.com/install-mcp?name=ttt&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBoZWxtLXByb3RvY29sL3R0dC1tY3BAMC40LjAiXX0=)
85
85
 
86
86
  One-click install, or add the same `mcpServers` block above to `.cursor/mcp.json`.
87
87
 
@@ -127,12 +127,14 @@ pot_graph(eventId: "step-2", depth: 5)
127
127
 
128
128
  ---
129
129
 
130
- ## 8 Tools
130
+ ## 10 Tools
131
131
 
132
132
  | Tool | Purpose |
133
133
  |------|---------|
134
134
  | `pot_generate` | Stamp a workflow step with a cryptographic timestamp |
135
135
  | `pot_verify` | Verify a PoT signature |
136
+ | `pot_generate_v2` | Generate the draft-11 180-octet PoT Record v2 core (canonical wire profile) |
137
+ | `pot_verify_v2` | Verify a v2 record; optional TLS 1.3 exporter binding proof (fails closed without TLS) |
136
138
  | `pot_verify_v08` | Verify a draft-08 §3 Payload Digest record |
137
139
  | `pot_query` | O(1) exact lookup by eventId — core amnesia recovery |
138
140
  | `pot_graph` | Traverse causal DAG (backward + forward chain) |
package/dist/index.js CHANGED
@@ -105,7 +105,7 @@ function toolSuccess(result) {
105
105
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
106
106
  }
107
107
  function buildMcpServer() {
108
- const s = new import_mcp.McpServer({ name: "ttt-mcp", version: "0.3.2" });
108
+ const s = new import_mcp.McpServer({ name: "ttt-mcp", version: "0.4.2" });
109
109
  const registerTool = s.tool.bind(s);
110
110
  registerTool(
111
111
  "pot_generate",
@@ -213,7 +213,7 @@ function buildMcpServer() {
213
213
  );
214
214
  registerTool(
215
215
  "pot_query",
216
- "Query Proof of Time records. Use eventId for exact O(1) lookup of a specific workflow step (collision probability 2^-256). Use startTime/endTime for time-range queries.",
216
+ "Query Proof of Time records. Use eventId for exact lookup of a specific workflow step. Use startTime/endTime for time-range queries.",
217
217
  {
218
218
  eventId: import_zod.z.string().optional().describe("Exact eventId lookup \u2014 call this at workflow start to restore action history after context compression"),
219
219
  startTime: import_zod.z.number().optional().describe("Start time (unix ms). Default: 24h ago"),
@@ -304,7 +304,7 @@ async function main() {
304
304
  const requestHandler = async (req, res) => {
305
305
  if (req.method === "GET" && (req.url === "/health" || req.url === "/ping")) {
306
306
  res.writeHead(200, { "Content-Type": "application/json" });
307
- res.end(JSON.stringify({ status: "ok", server: "ttt-mcp", version: "0.3.2" }));
307
+ res.end(JSON.stringify({ status: "ok", server: "ttt-mcp", version: "0.4.2" }));
308
308
  return;
309
309
  }
310
310
  if (req.method === "POST") {
@@ -364,8 +364,8 @@ async function main() {
364
364
  }
365
365
  const httpServer = certFile && keyFile ? (0, import_https.createServer)({ minVersion: "TLSv1.3", cert: (0, import_fs.readFileSync)(certFile), key: (0, import_fs.readFileSync)(keyFile) }, (req, res) => {
366
366
  const exporter = (0, import_tls_exporter.extractTls13Exporter)(req);
367
- return (0, import_transport_context.withTransportBinding)({ exporter, clientId: String(req.headers["x-ttt-client-id"] ?? ""), sessionId: String(req.headers["x-ttt-session-id"] ?? "") }, () => requestHandler(req, res));
368
- }) : (0, import_http.createServer)((req, res) => (0, import_transport_context.withTransportBinding)({ clientId: String(req.headers["x-ttt-client-id"] ?? ""), sessionId: String(req.headers["x-ttt-session-id"] ?? "") }, () => requestHandler(req, res)));
367
+ return (0, import_transport_context.withTransportBinding)({ exporter, remoteAddress: req.socket.remoteAddress ?? "", clientId: String(req.headers["x-ttt-client-id"] ?? ""), sessionId: String(req.headers["x-ttt-session-id"] ?? "") }, () => requestHandler(req, res));
368
+ }) : (0, import_http.createServer)((req, res) => (0, import_transport_context.withTransportBinding)({ remoteAddress: req.socket.remoteAddress ?? "", clientId: String(req.headers["x-ttt-client-id"] ?? ""), sessionId: String(req.headers["x-ttt-session-id"] ?? "") }, () => requestHandler(req, res)));
369
369
  httpServer.listen(port, () => {
370
370
  console.error(`[ttt-mcp] OpenTTT MCP Server (${certFile ? "HTTPS/TLS1.3" : "HTTP"}) on port ${port}`);
371
371
  });
package/dist/tools.js CHANGED
@@ -441,17 +441,63 @@ async function potGenerateV2(args) {
441
441
  bindingProofRequired: process.env.TTTPS_V2_REQUIRE_BINDING === "1"
442
442
  });
443
443
  }
444
- async function potVerifyV2(args) {
444
+ function trustedKeyList(envName) {
445
+ return (process.env[envName] ?? "").split(",").map((s) => s.trim()).filter((s) => /^[0-9a-fA-F]{64}$/.test(s)).map((h) => Buffer.from(h, "hex"));
446
+ }
447
+ let roughtimeAdmitCache = null;
448
+ async function roughtimeChainStatus() {
449
+ const url = process.env.TTTPS_ADMISSION_STATUS_URL ?? "https://api.kenosian.com/pot/status";
450
+ const ttlMs = Number.parseInt(process.env.TTTPS_ADMISSION_TTL_MS ?? "5000", 10);
451
+ if (roughtimeAdmitCache && Date.now() - roughtimeAdmitCache.at < ttlMs) {
452
+ return { ok: roughtimeAdmitCache.ok, digest: roughtimeAdmitCache.digest };
453
+ }
454
+ try {
455
+ const timeoutMs = Number.parseInt(process.env.TTTPS_ADMISSION_TIMEOUT_MS ?? "1500", 10);
456
+ const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
457
+ const j = await res.json();
458
+ const ok = j?.chain_valid === true || j?.chain_valid === void 0 && j?.roughtime_ok === true;
459
+ const digest = typeof j?.d_chain_digest === "string" ? j.d_chain_digest : "";
460
+ roughtimeAdmitCache = { ok, digest, at: Date.now() };
461
+ return { ok, digest };
462
+ } catch {
463
+ roughtimeAdmitCache = { ok: false, digest: "", at: Date.now() };
464
+ return { ok: false, digest: "" };
465
+ }
466
+ }
467
+ async function potVerifyV2Core(args) {
445
468
  (0, import_telemetry.telemetryIncrement)("pot_verify_v2");
446
469
  const bindingRequired = process.env.TTTPS_V2_REQUIRE_BINDING === "1";
447
470
  if (bindingRequired && !args.bindingProof) {
448
471
  return serialize({ verdict: "rejected", reason: "TLS exporter binding proof is required" });
449
472
  }
450
473
  const record = Buffer.from(args.potRecordV2, "hex");
451
- const issuerPubKey = args.issuerPubKey ? Buffer.from(args.issuerPubKey, "hex") : potSignerPublicKeyRawV08;
452
- const freshness = args.nowTaiUs !== void 0 && args.maxSkewUs !== void 0 ? { nowTaiUs: BigInt(args.nowTaiUs), maxSkewUs: BigInt(args.maxSkewUs) } : void 0;
453
- const result = (0, import_pot_record_v2.verifyPotRecordV2)(record, issuerPubKey, freshness);
454
- if (result.verdict === "rejected") return serialize(result);
474
+ if (process.env.TTTPS_REQUIRE_ROUGHTIME_QUORUM === "1") {
475
+ const chain = await roughtimeChainStatus();
476
+ if (!chain.ok) {
477
+ return serialize({ verdict: "rejected", reason: "roughtime chain quorum unavailable" });
478
+ }
479
+ }
480
+ const freshness = process.env.TTTPS_ENFORCE_FRESHNESS === "1" ? { nowTaiUs: BigInt(Date.now()) * 1000n, maxSkewUs: BigInt(process.env.TTTPS_MAX_SKEW_US ?? "60000000") } : args.nowTaiUs !== void 0 && args.maxSkewUs !== void 0 ? { nowTaiUs: BigInt(args.nowTaiUs), maxSkewUs: BigInt(args.maxSkewUs) } : void 0;
481
+ const trustedIssuers = trustedKeyList("TTTPS_TRUSTED_ISSUERS");
482
+ const pinSelfIssuer = process.env.TTTPS_PIN_SELF_ISSUER === "1";
483
+ const candidates = trustedIssuers.length > 0 ? trustedIssuers : pinSelfIssuer ? [potSignerPublicKeyRawV08] : [args.issuerPubKey ? Buffer.from(args.issuerPubKey, "hex") : potSignerPublicKeyRawV08];
484
+ let result = (0, import_pot_record_v2.verifyPotRecordV2)(record, candidates[0], freshness);
485
+ for (let i = 1; i < candidates.length && result.verdict === "rejected" && result.reason === "issuer signature invalid"; i++) {
486
+ result = (0, import_pot_record_v2.verifyPotRecordV2)(record, candidates[i], freshness);
487
+ }
488
+ if (result.verdict === "rejected") {
489
+ if (trustedIssuers.length > 0 && result.reason === "issuer signature invalid") {
490
+ return serialize({ verdict: "rejected", reason: "issuer not trusted" });
491
+ }
492
+ return serialize(result);
493
+ }
494
+ const trustedHolders = trustedKeyList("TTTPS_TRUSTED_HOLDERS");
495
+ if (trustedHolders.length > 0) {
496
+ const holder = record.subarray(48, 80);
497
+ if (!trustedHolders.some((h) => h.equals(holder))) {
498
+ return serialize({ verdict: "rejected", reason: "holder not authorized" });
499
+ }
500
+ }
455
501
  if (args.bindingProof) {
456
502
  const exporter = (0, import_transport_context.currentTransportBinding)()?.exporter;
457
503
  if (!exporter) return serialize({ verdict: "rejected", reason: "live TLS exporter unavailable" });
@@ -471,6 +517,70 @@ async function potVerifyV2(args) {
471
517
  }
472
518
  return serialize({ verdict: "intact", wireProfile: "draft-helmprotocol-tttps-11-v2", nonce: result.nonce.toString("hex") });
473
519
  }
520
+ const V2_AUDIT_STREAM = process.env.TTTPS_AUDIT_STREAM ?? "tttps:audit:v2";
521
+ const V2_AUDIT_MAXLEN = Number.parseInt(process.env.TTTPS_AUDIT_MAXLEN ?? "100000", 10);
522
+ const TTTPS_FORMAL_RECEIPT = {
523
+ spec: "draft-helmprotocol-tttps-11",
524
+ // Honest status. There is a TLA+ model (TLC model-checked) and a Lean 4 module whose
525
+ // theorems the kernel accepts (sorry 0, axiom propext), and this runtime. They are
526
+ // RELATED artifacts, not a machine-checked end-to-end 1:1 equivalence. Treat this as
527
+ // provenance the auditor can chase, not proof of the byte parser or the cryptography.
528
+ binding_status: "provenance-only; TLA+<->Lean<->runtime 1:1 equivalence is NOT machine-checked end-to-end (HOLD)",
529
+ lean: {
530
+ module: "KLean.TTTPS.Core",
531
+ theorems: [
532
+ "invalid_ingress_no_mutation",
533
+ "parse_is_deterministic",
534
+ "parsed_record_has_fixed_size",
535
+ "valid_ingress_commit_delta",
536
+ "zero_state_preservation"
537
+ ],
538
+ axioms: ["propext"],
539
+ content_hash: "sha256:6116b82318d540a1e4e5b694e31978739851f658356330349b9d4289aeb5bae8",
540
+ abstraction: "models admission at frame-size (=180) granularity; does NOT model field offsets, Ed25519, SHA-256, or the TLS exporter"
541
+ },
542
+ tla: { spec: "TTTPS_Ingress_Gate.tla", method: "TLC bounded model check (not an unbounded theorem)" },
543
+ anchor: {
544
+ receipt_id: "33108706ed730e5296ae1b06",
545
+ time_source: "roughtime_chain",
546
+ verify_url: "https://kpp.kenosian.com/v1/verify?receipt_id=33108706ed730e5296ae1b06"
547
+ },
548
+ revision: { package_commit: "677a3b9", note: "live gate runs this revision with admission flags ON; published npm 0.4.0 bytes carry the v2 core only" },
549
+ scope: "Kernel-checks the 180-octet parse + admission state boundary (invalid ingress => zero state mutation). Does NOT prove Ed25519/SHA-256/TLS-exporter cryptographic security, nor a byte-exact parser equivalence."
550
+ };
551
+ async function potVerifyV2(args) {
552
+ const startedNs = process.hrtime.bigint();
553
+ let result;
554
+ try {
555
+ result = await potVerifyV2Core(args);
556
+ } catch (e) {
557
+ result = { verdict: "rejected", reason: `verifier exception: ${e instanceof Error ? e.message : String(e)}` };
558
+ }
559
+ const serverLatencyUs = Number((process.hrtime.bigint() - startedNs) / 1000n);
560
+ const binding = (0, import_transport_context.currentTransportBinding)();
561
+ const recordHex = typeof args.potRecordV2 === "string" ? args.potRecordV2 : "";
562
+ const record = Buffer.from(recordHex, "hex");
563
+ const fields = {
564
+ ts_ms: String(Date.now()),
565
+ verdict: String(result.verdict ?? "unknown"),
566
+ reason: String(result.reason ?? ""),
567
+ case: String(args.clientId ?? binding?.clientId ?? ""),
568
+ session: String(args.sessionId ?? binding?.sessionId ?? ""),
569
+ remote: String(binding?.remoteAddress ?? ""),
570
+ transport: binding?.exporter ? "tls13" : "none",
571
+ binding_proof: args.bindingProof ? "present" : "absent",
572
+ record_len: String(record.length),
573
+ record_sha16: record.length ? (0, import_crypto.createHash)("sha256").update(record).digest("hex").slice(0, 16) : "",
574
+ ctx_id: record.length >= 48 ? record.subarray(16, 32).toString("hex") : "",
575
+ nonce: record.length >= 48 ? record.subarray(32, 48).toString("hex") : "",
576
+ latency_us: String(serverLatencyUs)
577
+ };
578
+ const flat = [];
579
+ for (const [k, v] of Object.entries(fields)) flat.push(k, v);
580
+ redis.call("XADD", V2_AUDIT_STREAM, "MAXLEN", "~", String(V2_AUDIT_MAXLEN), "*", ...flat).catch(() => void 0);
581
+ const emitFormal = process.env.TTTPS_EMIT_FORMAL_RECEIPT !== "0";
582
+ return serialize({ ...result, serverLatencyUs, ...emitFormal ? { formal: TTTPS_FORMAL_RECEIPT } : {} });
583
+ }
474
584
  async function potVerifyV08(args) {
475
585
  (0, import_telemetry.telemetryIncrement)("pot_verify_v08");
476
586
  const record = Buffer.from(args.potRecordV08, "hex");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helm-protocol/ttt-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Proof-of-Time attestation — Ed25519-signed timestamps with multi-source corroboration and explicit error bounds. IETF draft-helmprotocol-tttps",
5
5
  "main": "dist/index.js",
6
6
  "bin": {