@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 +7 -5
- package/dist/index.js +5 -5
- package/dist/tools.js +115 -5
- package/package.json +1 -1
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.
|
|
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.
|
|
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.
|
|
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
|
-
[](https://cursor.com/install-mcp?name=ttt&config=
|
|
84
|
+
[](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
|
-
##
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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.
|
|
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": {
|