@helm-protocol/ttt-mcp 0.2.1 → 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.
package/README.md CHANGED
@@ -4,79 +4,115 @@
4
4
 
5
5
  **MCP Server for OpenTTT — Proof of Time tools for AI agents**
6
6
 
7
- > AI Agent A and Agent B both trigger a payment at the same time.
8
- > Who was first?
9
- >
10
- > OpenTTT answers this with cryptographic Proof of Time — synthesized from
11
- > multiple independent time sources, verified through GRG integrity shards,
12
- > and signed with Ed25519 for non-repudiation.
7
+ ---
13
8
 
14
- ## Quick Start
9
+ ## The Problem: Workflow Amnesia
10
+
11
+ Large Claude Code workflows — 20-agent Dynamic Workflows, multi-day multi-session projects, 100K+ token contexts — all face the same failure mode: **context compression erases action history.**
12
+
13
+ Agent B has no memory of what Agent A decided. Agent A resumes after compression with no record of its own prior steps. Duplicate work. Lost decisions. State corruption.
14
+
15
+ **ttt-mcp is the external nervous system that survives context compression.**
16
+
17
+ Every workflow step is anchored to a cryptographic timestamp on an **external server** — physically separate from Claude's context window. When compression happens, agents query their exact action history through the MCP tools and resume with full causal context.
15
18
 
16
- ```bash
17
- npm install @helm-protocol/ttt-mcp
18
19
  ```
20
+ Claude workflow → [context compressed] → agents call pot_query(eventId)
21
+ → external server returns full timeline
22
+ → workflow resumes, zero lost state
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Mathematical Guarantee
28
+
29
+ | Layer | Mechanism | Guarantee |
30
+ |-------|-----------|-----------|
31
+ | **Identity** | SHA-3 eventId (256-bit) | Collision probability 2⁻²⁵⁶ ≈ 0 — practically 100% exact step recall |
32
+ | **Ordering** | TTTPS causal timestamps | Total order on events — tamper-proof sequence proof |
33
+ | **Causal chain** | prevEventId DAG | O(depth) traversal — depth ~100 for 1B-token workflows |
34
+ | **Fingerprint** | Multi-layer cryptographic pipeline | Formally bounded tamper-evident step identity |
35
+ | **Non-repudiation** | Ed25519 signature | Cryptographic proof of who acted when |
36
+
37
+ ---
38
+
39
+ ## Quick Start
19
40
 
41
+ ```bash
42
+ # Claude Desktop
20
43
  ```json
21
- // claude_desktop_config.json
22
44
  {
23
45
  "mcpServers": {
24
46
  "ttt": {
25
47
  "command": "npx",
26
- "args": ["@helm-protocol/ttt-mcp"]
48
+ "args": ["-y", "@helm-protocol/ttt-mcp"]
27
49
  }
28
50
  }
29
51
  }
30
52
  ```
31
53
 
32
- That's it. Your AI agent now has access to 5 Proof of Time tools.
54
+ Add `TTT_API_KEY` for unlimited calls (free tier: 100 calls/day per IP).
55
+
56
+ ---
33
57
 
34
58
  ## Tools
35
59
 
36
60
  | Tool | Description |
37
61
  |------|-------------|
38
- | `pot_generate` | Generate a Proof of Time for a transaction |
39
- | `pot_verify` | Verify a Proof of Time using its hash and GRG shards |
40
- | `pot_query` | Query PoT history from local log and on-chain subgraph |
62
+ | `pot_generate` | Stamp a workflow step with eventId + prevEventId (builds causal chain) |
63
+ | `pot_verify` | Verify a Proof of Time using its hash and integrity shards |
64
+ | `pot_query` | O(1) exact lookup by eventId — call this after context compression |
65
+ | `pot_graph` | Traverse full causal DAG — backward + forward chain from any step |
41
66
  | `pot_stats` | Get turbo/full mode statistics for a time period |
42
- | `pot_health` | Check system health: time sources, subgraph sync, uptime |
67
+ | `pot_health` | Check system health: time sources, uptime, current mode |
68
+ | `pot_checkpoint` | Create a compressed rollup checkpoint of workflow history |
69
+
70
+ ---
43
71
 
44
72
  ## Tool Parameters
45
73
 
46
74
  ### pot_generate
47
75
 
48
- Generate a Proof of Time for a transaction. Returns potHash, timestamp, stratum, and GRG integrity shards.
76
+ Stamp a workflow step with a cryptographic timestamp. For Claude Code: use `eventId` + `prevEventId`. For DeFi: use `txHash` + `chainId` + `poolAddress`. One of `eventId` or `txHash` is required.
49
77
 
50
78
  | Parameter | Type | Required | Description |
51
79
  |-----------|------|----------|-------------|
52
- | txHash | string | Yes | Transaction hash (hex with 0x prefix) |
53
- | chainId | number | Yes | Chain ID (e.g. 8453 for Base, 84532 for Base Sepolia) |
54
- | poolAddress | string | Yes | DEX pool contract address |
80
+ | eventId | string | Either/or | Workflow step identifier. E.g. `"refactor_auth_step1"` |
81
+ | prevEventId | string | No | Previous step's eventId — links steps into a causal chain |
82
+ | txHash | string | Either/or | Transaction hash (DeFi, hex with 0x prefix) |
83
+ | chainId | number | No | EVM chain ID (DeFi) |
84
+ | poolAddress | string | No | DEX pool contract address (DeFi) |
55
85
 
56
- ### pot_verify
86
+ ### pot_query
57
87
 
58
- Verify a Proof of Time using its hash and GRG shards. Returns validity, mode (turbo/full), and timestamp.
88
+ Query Proof of Time records. Use `eventId` for O(1) exact lookup after context compression.
59
89
 
60
90
  | Parameter | Type | Required | Description |
61
91
  |-----------|------|----------|-------------|
62
- | potHash | string | Yes | PoT hash to verify (hex with 0x prefix) |
63
- | grgShards | string[] | Yes | Array of hex-encoded GRG integrity shards |
64
- | chainId | number | Yes | EVM chain ID (e.g. 84532 for Base Sepolia) |
65
- | poolAddress | string | Yes | Uniswap V4 pool address (0x-prefixed) |
92
+ | eventId | string | No | Exact step lookup — collision probability 2⁻²⁵⁶ |
93
+ | startTime | number | No | Start time (unix ms). Default: 24h ago |
94
+ | endTime | number | No | End time (unix ms). Default: now |
95
+ | limit | number | No | Max entries to return. Default: 100, max: 1000 |
66
96
 
67
- ### pot_query
97
+ ### pot_graph
68
98
 
69
- Query Proof of Time history from local log and on-chain subgraph.
99
+ Traverse the causal chain from any step. Returns backward chain (ancestors) and forward chain (descendants).
70
100
 
71
101
  | Parameter | Type | Required | Description |
72
102
  |-----------|------|----------|-------------|
73
- | startTime | number | No | Start time (unix ms). Default: 24h ago |
74
- | endTime | number | No | End time (unix ms). Default: now |
75
- | limit | number | No | Max entries to return. Default: 100, max: 1000 |
103
+ | eventId | string | Yes | Step to traverse from |
104
+ | depth | number | No | Max backward depth. Default: 10, max: 100 |
76
105
 
77
- ### pot_stats
106
+ ### pot_verify
107
+
108
+ | Parameter | Type | Required | Description |
109
+ |-----------|------|----------|-------------|
110
+ | potHash | string | Yes | PoT hash to verify (hex with 0x prefix) |
111
+ | grgShards | string[] | Yes | Array of hex-encoded cryptographic integrity shards |
112
+ | chainId | number | Yes | EVM chain ID |
113
+ | poolAddress | string | Yes | Uniswap V4 pool address |
78
114
 
79
- Get PoT statistics: total swaps, turbo/full counts, and turbo ratio for a given period.
115
+ ### pot_stats
80
116
 
81
117
  | Parameter | Type | Required | Description |
82
118
  |-----------|------|----------|-------------|
@@ -84,107 +120,182 @@ Get PoT statistics: total swaps, turbo/full counts, and turbo ratio for a given
84
120
 
85
121
  ### pot_health
86
122
 
87
- Check PoT system health: time source status, subgraph sync, server uptime, and current mode.
123
+ No parameters.
124
+
125
+ ### pot_checkpoint
126
+
127
+ Creates a compressed rollup checkpoint of workflow history.
128
+
129
+ **Use when:** Approaching context limit, before long tasks, or every ~100 events.
88
130
 
89
131
  | Parameter | Type | Required | Description |
90
132
  |-----------|------|----------|-------------|
91
- | *(none)* | — | — | This tool takes no parameters |
133
+ | fromEventId | string | No | Start of range — first eventId in the causal chain to include |
134
+ | toEventId | string | No | End of range — last eventId in the causal chain to include |
135
+ | startTime | number | No | Unix ms. Default: 1 hour ago |
136
+ | endTime | number | No | Unix ms. Default: now |
137
+ | maxTokens | number | No | Approximate max tokens for rollup output. Default: 2000 |
92
138
 
93
- ## Example: Generate and Verify a PoT
139
+ **Returns:**
140
+ - `checkpointId` — unique checkpoint identifier
141
+ - `rollupSummary` — compressed event history (depth-adaptive: full/compact/minimal/rollup)
142
+ - `chainIntact` — whether the causal chain is unbroken
143
+ - `nextCheckpointHint` — recommended events before next checkpoint
144
+
145
+ **Depth-adaptive compression:**
146
+
147
+ | Depth | Format | ~Tokens |
148
+ |-------|--------|---------|
149
+ | 1–5 | Full entry | ~200/event |
150
+ | 6–20 | Compact (id+hash+ts) | ~80/event |
151
+ | 21–50 | Minimal (id+ts) | ~30/event |
152
+ | 51+ | Rollup string | ~10/event |
153
+
154
+ ---
155
+
156
+ ## Use Cases
157
+
158
+ ### 1. Claude Code Workflow — Amnesia Prevention
159
+
160
+ **Problem**: A 20-agent Dynamic Workflow refactors a 500K-line codebase over hours. After each context compression, agents have no memory of what they already processed. Duplicate work. State corruption.
161
+
162
+ **Solution**: Each agent stamps its steps with `pot_generate(eventId, prevEventId)`. After compression, it calls `pot_query(eventId)` to recover its exact action history — what ran, when, in what order — from the external server. The server is outside Claude's context window; compression never touches it.
94
163
 
95
164
  ```typescript
96
- // In your AI agent's tool call:
97
- const pot = await pot_generate({
98
- txHash: "0xabc123...",
99
- chainId: 84532,
100
- poolAddress: "0xdef456..."
165
+ // Agent starts a workflow step
166
+ const pot = await client.callTool({
167
+ name: "pot_generate",
168
+ arguments: {
169
+ eventId: "refactor_auth_module_step3",
170
+ prevEventId: "refactor_auth_module_step2"
171
+ }
172
+ });
173
+ // pot.potHash — cryptographic proof this step happened at this time
174
+
175
+ // After context compression, agent recovers its history:
176
+ const history = await client.callTool({
177
+ name: "pot_query",
178
+ arguments: { eventId: "refactor_auth_module_step3" }
179
+ });
180
+ // history.local[0] — exact record: timestamp, prevEventId, potHash
181
+ // history.found: true — O(1) lookup, collision probability 2⁻²⁵⁶
182
+
183
+ // Traverse full causal chain:
184
+ const chain = await client.callTool({
185
+ name: "pot_graph",
186
+ arguments: { eventId: "refactor_auth_module_step3", depth: 20 }
101
187
  });
188
+ // chain.backwardChain — all ancestor steps in chronological order
189
+ // chain.forwardChain — steps that follow this one
190
+ ```
191
+
192
+ **Before a long task or every ~100 events — create a checkpoint:**
102
193
 
103
- // pot.potHash — unique Proof of Time hash
104
- // pot.grgShards — GRG integrity shards for verification
105
- // pot.timestamp — synthesized nanosecond timestamp
106
- // pot.mode — "turbo" (honest) or "full" (requires full verification)
194
+ ```typescript
195
+ // Compress workflow history before context fills up — by causal range:
196
+ const checkpoint = await client.callTool({
197
+ name: "pot_checkpoint",
198
+ arguments: {
199
+ fromEventId: "refactor_auth_module_step1",
200
+ toEventId: "refactor_auth_module_step3"
201
+ }
202
+ });
203
+ // checkpoint.checkpointId — store this; resume from it after compression
204
+ // checkpoint.rollupSummary — depth-adaptive compressed history (10–200 tokens/event)
205
+ // checkpoint.chainIntact: true — causal chain verified unbroken
206
+ // checkpoint.nextCheckpointHint: 87 — suggested events before next checkpoint
207
+
208
+ // Or compress by time window with a token budget:
209
+ const checkpoint = await client.callTool({
210
+ name: "pot_checkpoint",
211
+ arguments: {
212
+ startTime: Date.now() - 3_600_000, // last 1 hour
213
+ maxTokens: 1500
214
+ }
215
+ });
107
216
 
108
- const verification = await pot_verify({
109
- potHash: pot.potHash,
110
- grgShards: pot.grgShards
217
+ // After context compression, restore from checkpoint instead of re-querying all events:
218
+ const history = await client.callTool({
219
+ name: "pot_query",
220
+ arguments: { eventId: checkpoint.checkpointId }
111
221
  });
112
- // verification.valid — true if integrity shards reconstruct correctly
222
+ // Full causal context restored in a single call
113
223
  ```
114
224
 
115
- ## How It Works
225
+ **Outcome**: Zero duplicate work. Full workflow timeline recoverable even after complete context resets.
116
226
 
117
- 1. **Time Synthesis** — Queries multiple independent time sources (NIST, Google, Cloudflare) via HTTPS/NTP and synthesizes a median timestamp with uncertainty bounds
118
- 2. **GRG Pipeline** — Encodes transaction data through a multi-layer integrity pipeline, producing verifiable shards
119
- 3. **Ed25519 Signing** — Signs the PoT hash for non-repudiation
120
- 4. **Adaptive Mode** — Honest builders get `turbo` mode (fast, profitable); tampered sequences get `full` mode (slow, costly) — natural economic selection
227
+ ---
121
228
 
122
- ## Claude Desktop Configuration
229
+ ### 2. MEV Bot — Transaction Ordering Proof
123
230
 
124
- Add to your `claude_desktop_config.json`:
231
+ **Problem**: You got front-run. You can't prove it — mempool timestamps are per-node, unsigned, non-authoritative.
125
232
 
126
- ```json
127
- {
128
- "mcpServers": {
129
- "ttt": {
130
- "command": "npx",
131
- "args": ["@helm-protocol/ttt-mcp"]
132
- }
133
- }
134
- }
233
+ **Solution**: Call `pot_generate` before every submission. The PoT receipt is cryptographically signed by three independent time sources (NIST, Google, Cloudflare), anchored on Base Sepolia TTT ERC-1155. If front-running occurs, you have a timestamped, on-chain record predating the attacker's block inclusion.
234
+
235
+ ```typescript
236
+ const pot = await client.callTool({
237
+ name: "pot_generate",
238
+ arguments: { txHash: pendingTxHash, chainId: 8453, poolAddress: "0x..." }
239
+ });
240
+ // pot.potHash — your evidence, timestamped by NIST+Google+Cloudflare
135
241
  ```
136
242
 
137
- Config file locations:
138
- - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
139
- - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
140
- - **Linux**: `~/.config/Claude/claude_desktop_config.json`
243
+ ---
141
244
 
142
- ## Requirements
245
+ ### 3. DEX Protocol — Sandwich Deterrence
143
246
 
144
- - Node.js >= 18
145
- - Network access for time synthesis (HTTPS to time.nist.gov, time.google.com, time.cloudflare.com)
247
+ **Solution**: Integrate `TTTHookSimple` (Uniswap V4 hook, Base Sepolia: `0x8C633b05b833a476925F7d9818da6E215760F2c7`). Honest builders get `turbo` mode. Tampered sequences get `full` mode (exponential backoff). Economics, not governance.
146
248
 
147
- ## TypeScript: MEV Bot Integration
249
+ ---
148
250
 
149
- ```typescript
150
- import { McpClient } from "@modelcontextprotocol/sdk/client/mcp.js";
251
+ ### 4. Hedge Fund / Prop Desk — MiFIR Art.22c Compliance
252
+
253
+ **Problem**: MiFIR Article 22c / RTS 25 requires microsecond-precision UTC-synchronized timestamps. Hardware PTP appliances cost $50K–$500K.
254
+
255
+ **Solution**: `pot_generate` produces an Ed25519-signed timestamp with uncertainty bound and multi-source attestation. Structurally compatible with RTS 25 audit record requirements. One API call per trade.
151
256
 
152
- // Generate a Proof of Time for a transaction
153
- const result = await client.callTool({
257
+ ```typescript
258
+ const audit = await client.callTool({
154
259
  name: "pot_generate",
155
- arguments: {
156
- txHash: "0xabc123...",
157
- chainId: 8453,
158
- poolAddress: "0xdef456..."
159
- }
260
+ arguments: { txHash: tradeHash, chainId: 8453 }
160
261
  });
161
- // Returns: { potHash, timestamp, stratum, grg_shards }
262
+ // audit.timestamp: nanosecond precision
263
+ // audit.uncertainty: ±ms bound (RTS 25 required field)
264
+ // audit.confidence: fraction of sources that agreed
162
265
  ```
163
266
 
164
- ## Python: Hedge Fund Audit
267
+ **Outcome**: MiFIR-grade audit trail. IETF standardized via `draft-helmprotocol-tttps-00`.
165
268
 
166
- ```python
167
- import subprocess, json
269
+ ---
168
270
 
169
- result = subprocess.run(
170
- ["npx", "-y", "@helm-protocol/ttt-mcp"],
171
- input=json.dumps({
172
- "tool": "pot_verify",
173
- "potHash": "0x...",
174
- "expectedChainId": 8453
175
- }),
176
- capture_output=True, text=True
177
- )
178
- ```
271
+ ### 5. Multi-Agent Coordination — Causal Order Proof
272
+
273
+ **Problem**: When multiple AI agents interact in a pipeline, the causal order matters for debugging and audit. Agent logs are unverifiable.
274
+
275
+ **Solution**: Each agent stamps its action with `pot_generate`. The potHash chain is independently verifiable. `pot_graph` reconstructs who did what and in what order.
276
+
277
+ ---
179
278
 
180
279
  ## Rate Limits & Pricing
181
280
 
182
281
  ```
183
282
  Free Tier: 100 calls/day per IP — no API key needed
184
- Paid Tier: Set TTT_API_KEY env var — unlimited
185
- Commercial: peter@kenosian.com (hedge funds, DEX protocols, OTC desks)
283
+ BOT Tier: $199/mo — unlimited, SLA
284
+ DEX Tier: $499/mo — unlimited, priority support
285
+ FUND Tier: $2K+/mo — enterprise, dedicated infra
186
286
  ```
187
287
 
288
+ Contact: heime.jorgen@proton.me
289
+
290
+ ---
291
+
292
+ ## Requirements
293
+
294
+ - Node.js >= 18
295
+ - Network access for time synthesis (HTTPS to time.nist.gov, time.google.com, time.cloudflare.com)
296
+
297
+ ---
298
+
188
299
  ## Learn More
189
300
 
190
301
  - [OpenTTT SDK](https://www.npmjs.com/package/openttt) — The underlying SDK
@@ -193,4 +304,8 @@ Commercial: peter@kenosian.com (hedge funds, DEX protocols, OTC desks)
193
304
 
194
305
  ## License
195
306
 
196
- BSL-1.1
307
+ BSL-1.1 — free for non-commercial use.
308
+
309
+ **Commercial use** (production bots, hedge funds, prop desks) requires a license.
310
+
311
+ Change Date: 2029-05-28 → Apache 2.0
package/dist/auth.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,14 +17,28 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
  var auth_exports = {};
20
30
  __export(auth_exports, {
31
+ FREE_TIER_LIMIT: () => FREE_TIER_LIMIT,
21
32
  checkRateLimit: () => checkRateLimit,
22
33
  resolveApiKey: () => resolveApiKey
23
34
  });
24
35
  module.exports = __toCommonJS(auth_exports);
36
+ var fs = __toESM(require("fs"));
37
+ var path = __toESM(require("path"));
38
+ var os = __toESM(require("os"));
25
39
  const FREE_TIER_LIMIT = parseInt(process.env.FREE_TIER_LIMIT ?? "100", 10);
40
+ const USAGE_DIR = path.join(os.homedir(), ".ttt-mcp");
41
+ const USAGE_FILE = path.join(USAGE_DIR, "usage.json");
26
42
  const buckets = /* @__PURE__ */ new Map();
27
43
  function nextMidnightUtc() {
28
44
  const now = /* @__PURE__ */ new Date();
@@ -31,36 +47,58 @@ function nextMidnightUtc() {
31
47
  );
32
48
  return midnight.getTime();
33
49
  }
50
+ function readUsageFile() {
51
+ try {
52
+ if (!fs.existsSync(USAGE_DIR)) fs.mkdirSync(USAGE_DIR, { recursive: true });
53
+ if (fs.existsSync(USAGE_FILE)) {
54
+ return JSON.parse(fs.readFileSync(USAGE_FILE, "utf8"));
55
+ }
56
+ } catch {
57
+ }
58
+ return { count: 0, resetAt: nextMidnightUtc() };
59
+ }
60
+ function writeUsageFile(entry) {
61
+ try {
62
+ if (!fs.existsSync(USAGE_DIR)) fs.mkdirSync(USAGE_DIR, { recursive: true });
63
+ fs.writeFileSync(USAGE_FILE, JSON.stringify(entry), "utf8");
64
+ } catch {
65
+ }
66
+ }
34
67
  function checkRateLimit(apiKey, clientIp) {
35
68
  if (apiKey && apiKey.trim().length > 0) {
36
69
  return { allowed: true, remaining: -1, tier: "paid" };
37
70
  }
38
- const bucketKey = `ip:${clientIp}`;
39
71
  const now = Date.now();
72
+ if (clientIp === "stdio") {
73
+ let entry2 = readUsageFile();
74
+ if (now >= entry2.resetAt) {
75
+ entry2 = { count: 0, resetAt: nextMidnightUtc() };
76
+ }
77
+ if (entry2.count >= FREE_TIER_LIMIT) {
78
+ return { allowed: false, remaining: 0, tier: "free" };
79
+ }
80
+ entry2.count += 1;
81
+ writeUsageFile(entry2);
82
+ return { allowed: true, remaining: FREE_TIER_LIMIT - entry2.count, tier: "free" };
83
+ }
84
+ const bucketKey = `ip:${clientIp}`;
40
85
  let entry = buckets.get(bucketKey);
41
86
  if (!entry || now >= entry.resetAt) {
42
87
  entry = { count: 0, resetAt: nextMidnightUtc() };
43
88
  buckets.set(bucketKey, entry);
44
89
  }
45
90
  if (entry.count >= FREE_TIER_LIMIT) {
46
- return {
47
- allowed: false,
48
- remaining: 0,
49
- tier: "free"
50
- };
91
+ return { allowed: false, remaining: 0, tier: "free" };
51
92
  }
52
93
  entry.count += 1;
53
- return {
54
- allowed: true,
55
- remaining: FREE_TIER_LIMIT - entry.count,
56
- tier: "free"
57
- };
94
+ return { allowed: true, remaining: FREE_TIER_LIMIT - entry.count, tier: "free" };
58
95
  }
59
96
  function resolveApiKey(headerValue) {
60
97
  return headerValue?.trim() || process.env.TTT_API_KEY?.trim() || void 0;
61
98
  }
62
99
  // Annotate the CommonJS export names for ESM import in node:
63
100
  0 && (module.exports = {
101
+ FREE_TIER_LIMIT,
64
102
  checkRateLimit,
65
103
  resolveApiKey
66
104
  });
package/dist/index.js CHANGED
@@ -7,127 +7,183 @@ var import_http = require("http");
7
7
  var import_zod = require("zod");
8
8
  var import_tools = require("./tools");
9
9
  var import_auth = require("./auth");
10
- const server = new import_mcp.McpServer({
11
- name: "ttt-mcp",
12
- version: "0.1.0"
13
- });
14
- server.tool(
15
- "pot_generate",
16
- "Generate a Proof of Time for a transaction. Returns potHash, timestamp, stratum, and GRG integrity shards.",
17
- {
18
- txHash: import_zod.z.string().describe("Transaction hash (hex with 0x prefix)"),
19
- chainId: import_zod.z.number().describe("Chain ID (e.g. 8453 for Base, 84532 for Base Sepolia)"),
20
- poolAddress: import_zod.z.string().describe("DEX pool contract address")
21
- },
22
- async (args) => {
23
- try {
24
- const result = await (0, import_tools.potGenerate)(args);
25
- return {
26
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
27
- };
28
- } catch (err) {
29
- const message = err instanceof Error ? err.message : String(err);
30
- return {
31
- content: [{ type: "text", text: `Error: ${message}` }],
32
- isError: true
33
- };
34
- }
10
+ async function restoreDAGFromRedis() {
11
+ try {
12
+ await Promise.race([
13
+ import_tools.redis.connect(),
14
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 3e3))
15
+ ]);
16
+ } catch {
17
+ return 0;
35
18
  }
36
- );
37
- server.tool(
38
- "pot_verify",
39
- "Verify a Proof of Time using its hash and GRG shards. Returns validity, mode (turbo/full), and timestamp.",
40
- {
41
- potHash: import_zod.z.string().describe("PoT hash to verify (hex with 0x prefix)"),
42
- grgShards: import_zod.z.array(import_zod.z.string()).describe("Array of hex-encoded GRG integrity shards"),
43
- chainId: import_zod.z.number().describe("EVM chain ID (e.g. 84532 for Base Sepolia)"),
44
- poolAddress: import_zod.z.string().describe("Uniswap V4 pool address (0x-prefixed)")
45
- },
46
- async (args) => {
47
- try {
48
- const result = await (0, import_tools.potVerify)(args);
49
- return {
50
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
51
- };
52
- } catch (err) {
53
- const message = err instanceof Error ? err.message : String(err);
54
- return {
55
- content: [{ type: "text", text: `Error: ${message}` }],
56
- isError: true
57
- };
58
- }
19
+ let cursor = "0";
20
+ let restored = 0;
21
+ const entries = [];
22
+ try {
23
+ do {
24
+ const [next, keys] = await import_tools.redis.scan(cursor, "MATCH", "dag:*", "COUNT", "200");
25
+ cursor = next;
26
+ if (keys.length === 0) continue;
27
+ const values = await import_tools.redis.mget(...keys);
28
+ for (const v of values) {
29
+ if (!v) continue;
30
+ try {
31
+ const entry = JSON.parse(v);
32
+ if (entry.eventId) entries.push(entry);
33
+ } catch {
34
+ }
35
+ }
36
+ } while (cursor !== "0");
37
+ } catch {
38
+ return 0;
59
39
  }
60
- );
61
- server.tool(
62
- "pot_query",
63
- "Query Proof of Time history from local log and on-chain subgraph.",
64
- {
65
- startTime: import_zod.z.number().optional().describe("Start time (unix ms). Default: 24h ago"),
66
- endTime: import_zod.z.number().optional().describe("End time (unix ms). Default: now"),
67
- limit: import_zod.z.number().optional().describe("Max entries to return. Default: 100, max: 1000")
68
- },
69
- async (args) => {
40
+ entries.sort((a, b) => a.createdAt - b.createdAt);
41
+ for (const e of entries) {
70
42
  try {
71
- const result = await (0, import_tools.potQuery)(args);
72
- return {
73
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
74
- };
75
- } catch (err) {
76
- const message = err instanceof Error ? err.message : String(err);
77
- return {
78
- content: [{ type: "text", text: `Error: ${message}` }],
79
- isError: true
80
- };
43
+ (0, import_tools.restoreDagEntry)({
44
+ eventId: e.eventId,
45
+ prevEventId: e.prevEventId,
46
+ potHash: e.potHash,
47
+ timestamp: e.timestamp,
48
+ stratum: e.stratum,
49
+ mode: e.mode,
50
+ createdAt: e.createdAt,
51
+ chainId: e.chainId,
52
+ poolAddress: e.poolAddress
53
+ });
54
+ restored++;
55
+ } catch {
81
56
  }
82
57
  }
83
- );
84
- server.tool(
85
- "pot_stats",
86
- "Get PoT statistics: total swaps, turbo/full counts, and turbo ratio for a given period.",
87
- {
88
- period: import_zod.z.enum(["day", "week", "month"]).describe("Time period for statistics")
89
- },
90
- async (args) => {
91
- try {
92
- const result = await (0, import_tools.potStats)(args);
93
- return {
94
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
95
- };
96
- } catch (err) {
97
- const message = err instanceof Error ? err.message : String(err);
98
- return {
99
- content: [{ type: "text", text: `Error: ${message}` }],
100
- isError: true
101
- };
102
- }
58
+ if (restored > 0) {
59
+ console.error(`[ttt-mcp] DAG restored from Redis: ${restored} entries`);
103
60
  }
104
- );
105
- server.tool(
106
- "pot_health",
107
- "Check PoT system health: time source status, subgraph sync, server uptime, and current mode.",
108
- {},
109
- async () => {
110
- try {
111
- const result = await (0, import_tools.potHealth)();
112
- return {
113
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
114
- };
115
- } catch (err) {
116
- const message = err instanceof Error ? err.message : String(err);
117
- return {
118
- content: [{ type: "text", text: `Error: ${message}` }],
119
- isError: true
120
- };
61
+ return restored;
62
+ }
63
+ function buildMcpServer() {
64
+ const s = new import_mcp.McpServer({ name: "ttt-mcp", version: "0.1.0" });
65
+ s.tool(
66
+ "pot_generate",
67
+ "Generate a cryptographic Proof of Time timestamp. For Claude Code workflows: use eventId + prevEventId to build a causal chain. For DeFi: use txHash + chainId + poolAddress. Either eventId or txHash is required.",
68
+ {
69
+ eventId: import_zod.z.string().optional().describe("Workflow step identifier (Claude Code). E.g. 'refactor_auth_step1'"),
70
+ prevEventId: import_zod.z.string().optional().describe("Previous step's eventId \u2014 links steps into a causal chain"),
71
+ txHash: import_zod.z.string().optional().describe("Transaction hash (DeFi, hex with 0x prefix)"),
72
+ chainId: import_zod.z.number().optional().describe("EVM chain ID (DeFi, e.g. 8453 for Base)"),
73
+ poolAddress: import_zod.z.string().optional().describe("DEX pool contract address (DeFi)")
74
+ },
75
+ async (args) => {
76
+ try {
77
+ const result = await (0, import_tools.potGenerate)(args);
78
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
79
+ } catch (err) {
80
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
81
+ }
121
82
  }
122
- }
123
- );
83
+ );
84
+ s.tool(
85
+ "pot_verify",
86
+ "Verify a Proof of Time using its hash and integrity shards. Returns validity, mode (turbo/full), and timestamp.",
87
+ {
88
+ potHash: import_zod.z.string().describe("PoT hash to verify (hex with 0x prefix)"),
89
+ grgShards: import_zod.z.array(import_zod.z.string()).describe("Array of hex-encoded cryptographic integrity shards"),
90
+ chainId: import_zod.z.number().describe("EVM chain ID (e.g. 84532 for Base Sepolia)"),
91
+ poolAddress: import_zod.z.string().describe("Uniswap V4 pool address (0x-prefixed)")
92
+ },
93
+ async (args) => {
94
+ try {
95
+ const result = await (0, import_tools.potVerify)(args);
96
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
97
+ } catch (err) {
98
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
99
+ }
100
+ }
101
+ );
102
+ s.tool(
103
+ "pot_query",
104
+ "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.",
105
+ {
106
+ eventId: import_zod.z.string().optional().describe("Exact eventId lookup \u2014 call this at workflow start to restore action history after context compression"),
107
+ startTime: import_zod.z.number().optional().describe("Start time (unix ms). Default: 24h ago"),
108
+ endTime: import_zod.z.number().optional().describe("End time (unix ms). Default: now"),
109
+ limit: import_zod.z.number().optional().describe("Max entries to return. Default: 100, max: 1000")
110
+ },
111
+ async (args) => {
112
+ try {
113
+ const result = await (0, import_tools.potQuery)(args);
114
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
115
+ } catch (err) {
116
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
117
+ }
118
+ }
119
+ );
120
+ s.tool(
121
+ "pot_graph",
122
+ "Traverse the causal chain of workflow steps. Given an eventId, returns the full backward chain (ancestors via prevEventId) and forward chain (steps that follow). Use after context compression to reconstruct the complete workflow timeline.",
123
+ {
124
+ eventId: import_zod.z.string().describe("The workflow step to start traversal from"),
125
+ depth: import_zod.z.number().optional().describe("Max backward traversal depth. Default: 10, max: 100")
126
+ },
127
+ async (args) => {
128
+ try {
129
+ const result = await (0, import_tools.potGraph)(args);
130
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
131
+ } catch (err) {
132
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
133
+ }
134
+ }
135
+ );
136
+ s.tool(
137
+ "pot_stats",
138
+ "Get PoT statistics: total swaps, turbo/full counts, and turbo ratio for a given period.",
139
+ { period: import_zod.z.enum(["day", "week", "month"]).describe("Time period for statistics") },
140
+ async (args) => {
141
+ try {
142
+ const result = await (0, import_tools.potStats)(args);
143
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
144
+ } catch (err) {
145
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
146
+ }
147
+ }
148
+ );
149
+ s.tool(
150
+ "pot_health",
151
+ "Check PoT system health: time source status, subgraph sync, server uptime, and current mode.",
152
+ {},
153
+ async () => {
154
+ try {
155
+ const result = await (0, import_tools.potHealth)();
156
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
157
+ } catch (err) {
158
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
159
+ }
160
+ }
161
+ );
162
+ s.tool(
163
+ "pot_checkpoint",
164
+ "Create a compressed rollup checkpoint of workflow history. Call this periodically to prevent token explosion when recovering from context compression. Returns checkpointId, compressed event history, chainIntact status, and nextCheckpointHint (recommended events before next checkpoint).",
165
+ {
166
+ fromEventId: import_zod.z.string().optional().describe("Start of range by eventId (optional, use with toEventId)"),
167
+ toEventId: import_zod.z.string().optional().describe("End of range by eventId (optional, use with fromEventId)"),
168
+ startTime: import_zod.z.number().optional().describe("Unix ms start time (optional, default: 1h ago)"),
169
+ endTime: import_zod.z.number().optional().describe("Unix ms end time (optional, default: now)"),
170
+ maxTokens: import_zod.z.number().optional().describe("Approximate max tokens for rollup (default: 2000)")
171
+ },
172
+ async (args) => {
173
+ try {
174
+ const result = await (0, import_tools.potCheckpoint)(args);
175
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
176
+ } catch (err) {
177
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
178
+ }
179
+ }
180
+ );
181
+ return s;
182
+ }
124
183
  async function main() {
184
+ await restoreDAGFromRedis();
125
185
  const port = process.env.PORT ? parseInt(process.env.PORT, 10) : null;
126
186
  if (port) {
127
- const transport = new import_streamableHttp.StreamableHTTPServerTransport({
128
- sessionIdGenerator: void 0
129
- });
130
- await server.connect(transport);
131
187
  const httpServer = (0, import_http.createServer)(async (req, res) => {
132
188
  if (req.method === "GET" && (req.url === "/health" || req.url === "/ping")) {
133
189
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -148,7 +204,7 @@ async function main() {
148
204
  res.end(
149
205
  JSON.stringify({
150
206
  error: "rate_limit_exceeded",
151
- message: "Free tier limit reached (100 calls/day). Add X-API-Key header for unlimited access.",
207
+ message: "Free tier: 100 calls/day reached. Contact heime.jorgen@proton.me for commercial access.",
152
208
  tier: "free"
153
209
  })
154
210
  );
@@ -172,6 +228,9 @@ async function main() {
172
228
  }
173
229
  }
174
230
  try {
231
+ const reqServer = buildMcpServer();
232
+ const transport = new import_streamableHttp.StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
233
+ await reqServer.connect(transport);
175
234
  await transport.handleRequest(req, res);
176
235
  } catch (err) {
177
236
  if (!res.headersSent) {
@@ -184,8 +243,9 @@ async function main() {
184
243
  console.error(`[ttt-mcp] OpenTTT MCP Server (HTTP) on port ${port}`);
185
244
  });
186
245
  } else {
246
+ const stdioServer = buildMcpServer();
187
247
  const transport = new import_stdio.StdioServerTransport();
188
- await server.connect(transport);
248
+ await stdioServer.connect(transport);
189
249
  console.error("[ttt-mcp] OpenTTT MCP Server running on stdio");
190
250
  }
191
251
  }
package/dist/tools.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,24 +17,52 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
  var tools_exports = {};
20
30
  __export(tools_exports, {
31
+ potCheckpoint: () => potCheckpoint,
21
32
  potGenerate: () => potGenerate,
33
+ potGraph: () => potGraph,
22
34
  potHealth: () => potHealth,
23
35
  potQuery: () => potQuery,
24
36
  potStats: () => potStats,
25
- potVerify: () => potVerify
37
+ potVerify: () => potVerify,
38
+ redis: () => redis,
39
+ restoreDagEntry: () => restoreDagEntry
26
40
  });
27
41
  module.exports = __toCommonJS(tools_exports);
28
42
  var import_openttt = require("openttt");
29
43
  var import_telemetry = require("./telemetry");
44
+ var import_ioredis = __toESM(require("ioredis"));
45
+ const GrgPipeline = (
46
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
47
+ require("openttt").GrgPipeline ?? null
48
+ );
49
+ const redis = new import_ioredis.default(process.env.REDIS_URL ?? "redis://127.0.0.1:6379", {
50
+ lazyConnect: true,
51
+ enableOfflineQueue: false,
52
+ maxRetriesPerRequest: 1
53
+ });
54
+ redis.on("error", () => {
55
+ });
30
56
  const timeSynth = new import_openttt.TimeSynthesis();
31
57
  const adaptiveSwitch = new import_openttt.AdaptiveSwitch();
32
58
  const potSigner = new import_openttt.PotSigner();
33
59
  const startedAt = Date.now();
34
60
  const POT_LOG_MAX = 1e4;
35
61
  const potLog = [];
62
+ const potByEventId = /* @__PURE__ */ new Map();
63
+ const potByPrevEventId = /* @__PURE__ */ new Map();
64
+ const evictedEventIds = /* @__PURE__ */ new Set();
65
+ const EVICTED_MAX = 1e3;
36
66
  function bigintReplacer(_key, value) {
37
67
  return typeof value === "bigint" ? value.toString() : value;
38
68
  }
@@ -40,26 +70,181 @@ function serialize(obj) {
40
70
  return JSON.parse(JSON.stringify(obj, bigintReplacer));
41
71
  }
42
72
  const SUBGRAPH_URL = "https://api.studio.thegraph.com/query/1744392/openttt-base-sepolia/v0.1.0";
73
+ function compressEntry(entry, depth, depthThreshold) {
74
+ const t = depthThreshold ?? { compact: 5, minimal: 20, rollup: 50 };
75
+ if (depth <= t.compact) return entry;
76
+ if (depth <= t.minimal) return {
77
+ // compact
78
+ eventId: entry.eventId,
79
+ potHash: entry.potHash,
80
+ timestamp: entry.timestamp,
81
+ prevEventId: entry.prevEventId
82
+ };
83
+ if (depth <= t.rollup) return {
84
+ // minimal
85
+ eventId: entry.eventId,
86
+ timestamp: entry.timestamp
87
+ };
88
+ return `${entry.eventId ?? "?"}@${entry.timestamp}`;
89
+ }
90
+ function depthThresholdFromTokens(maxTokens, entryCount) {
91
+ const charBudget = maxTokens * 4;
92
+ const fullBudget = charBudget * 0.6;
93
+ const compactBudget = charBudget * 0.25;
94
+ const minimalBudget = charBudget * 0.1;
95
+ const fullDepth = Math.max(1, Math.floor(fullBudget / 300));
96
+ const compactDepth = fullDepth + Math.max(0, Math.floor(compactBudget / 120));
97
+ const minimalDepth = compactDepth + Math.max(0, Math.floor(minimalBudget / 60));
98
+ return {
99
+ compact: Math.min(fullDepth, entryCount),
100
+ minimal: Math.min(compactDepth, entryCount),
101
+ rollup: Math.min(minimalDepth, entryCount)
102
+ };
103
+ }
104
+ function restoreDagEntry(entry) {
105
+ if (potByEventId.has(entry.eventId)) return;
106
+ const e = {
107
+ potHash: entry.potHash,
108
+ timestamp: entry.timestamp,
109
+ stratum: entry.stratum,
110
+ mode: entry.mode,
111
+ chainId: entry.chainId ?? void 0,
112
+ poolAddress: entry.poolAddress ?? void 0,
113
+ eventId: entry.eventId,
114
+ prevEventId: entry.prevEventId ?? void 0,
115
+ createdAt: entry.createdAt
116
+ };
117
+ if (potLog.length >= POT_LOG_MAX) {
118
+ const evicted = potLog.shift();
119
+ if (evicted.eventId) {
120
+ potByEventId.delete(evicted.eventId);
121
+ evictedEventIds.add(evicted.eventId);
122
+ if (evictedEventIds.size > EVICTED_MAX) {
123
+ const first = evictedEventIds.values().next().value;
124
+ if (first !== void 0) evictedEventIds.delete(first);
125
+ }
126
+ }
127
+ if (evicted.prevEventId) {
128
+ const siblings = potByPrevEventId.get(evicted.prevEventId);
129
+ if (siblings) {
130
+ const filtered = siblings.filter((s) => s !== evicted);
131
+ if (filtered.length === 0) potByPrevEventId.delete(evicted.prevEventId);
132
+ else potByPrevEventId.set(evicted.prevEventId, filtered);
133
+ }
134
+ }
135
+ }
136
+ potLog.push(e);
137
+ potByEventId.set(entry.eventId, e);
138
+ if (entry.prevEventId) {
139
+ const bucket = potByPrevEventId.get(entry.prevEventId) ?? [];
140
+ bucket.push(e);
141
+ potByPrevEventId.set(entry.prevEventId, bucket);
142
+ }
143
+ }
43
144
  async function potGenerate(args) {
145
+ if (!args.eventId && !args.txHash) {
146
+ throw new Error("Either eventId (Claude Code) or txHash (DeFi) is required");
147
+ }
44
148
  (0, import_telemetry.telemetryIncrement)("pot_generate");
45
- const pot = await timeSynth.generateProofOfTime();
149
+ let pot;
150
+ let isOfflineFallback = false;
151
+ try {
152
+ pot = await timeSynth.generateProofOfTime();
153
+ } catch {
154
+ isOfflineFallback = true;
155
+ const nowMs = BigInt(Date.now());
156
+ pot = {
157
+ timestamp: nowMs * 1000000n,
158
+ stratum: 16,
159
+ uncertainty: 999999999,
160
+ confidence: 0,
161
+ sources: 0,
162
+ nonce: Buffer.from(crypto.getRandomValues(new Uint8Array(16))).toString("hex"),
163
+ expiresAt: (nowMs + 300000n) * 1000000n,
164
+ sourceReadings: []
165
+ };
166
+ }
46
167
  const potHash = import_openttt.TimeSynthesis.getOnChainHash(pot);
47
- const txData = new TextEncoder().encode(args.txHash);
48
- const grgShards = import_openttt.GrgPipeline.processForward(txData, args.chainId, args.poolAddress);
168
+ let grgShards = [];
169
+ let currentMode;
170
+ if (args.txHash && args.chainId != null && args.poolAddress) {
171
+ const txData = new TextEncoder().encode(args.txHash);
172
+ grgShards = GrgPipeline.processForward(txData, args.chainId, args.poolAddress).map((s) => Buffer.from(s).toString("hex"));
173
+ const syntheticBlock = {
174
+ timestamp: Number(pot.timestamp / 1000000n),
175
+ // ns → ms
176
+ txs: [args.txHash],
177
+ data: new TextEncoder().encode(args.txHash)
178
+ };
179
+ const syntheticTTTRecord = {
180
+ time: Number(pot.timestamp / 1000000n),
181
+ txOrder: [args.txHash],
182
+ grgPayload: []
183
+ };
184
+ currentMode = adaptiveSwitch.verifyBlock(syntheticBlock, syntheticTTTRecord, args.chainId, args.poolAddress);
185
+ } else {
186
+ currentMode = adaptiveSwitch.getCurrentMode();
187
+ }
49
188
  const signature = potSigner.signPot(potHash);
50
189
  const entry = {
51
190
  potHash,
52
191
  timestamp: pot.timestamp.toString(),
53
192
  stratum: pot.stratum,
54
- mode: adaptiveSwitch.getCurrentMode(),
193
+ mode: currentMode,
55
194
  chainId: args.chainId,
56
195
  poolAddress: args.poolAddress,
196
+ eventId: args.eventId,
197
+ prevEventId: args.prevEventId,
57
198
  createdAt: Date.now()
58
199
  };
200
+ if (potLog.length >= POT_LOG_MAX) {
201
+ const evicted = potLog.shift();
202
+ if (evicted.eventId) {
203
+ potByEventId.delete(evicted.eventId);
204
+ evictedEventIds.add(evicted.eventId);
205
+ if (evictedEventIds.size > EVICTED_MAX) {
206
+ const first = evictedEventIds.values().next().value;
207
+ if (first !== void 0) evictedEventIds.delete(first);
208
+ }
209
+ }
210
+ if (evicted.prevEventId) {
211
+ const siblings = potByPrevEventId.get(evicted.prevEventId);
212
+ if (siblings) {
213
+ const filtered = siblings.filter((e) => e !== evicted);
214
+ if (filtered.length === 0) potByPrevEventId.delete(evicted.prevEventId);
215
+ else potByPrevEventId.set(evicted.prevEventId, filtered);
216
+ }
217
+ }
218
+ }
59
219
  potLog.push(entry);
60
- if (potLog.length > POT_LOG_MAX) potLog.shift();
220
+ if (args.eventId) potByEventId.set(args.eventId, entry);
221
+ if (args.prevEventId) {
222
+ const bucket = potByPrevEventId.get(args.prevEventId) ?? [];
223
+ bucket.push(entry);
224
+ potByPrevEventId.set(args.prevEventId, bucket);
225
+ }
226
+ if (args.eventId) {
227
+ redis.setex(
228
+ `dag:${args.eventId}`,
229
+ 90 * 86400,
230
+ JSON.stringify({
231
+ eventId: args.eventId,
232
+ prevEventId: args.prevEventId ?? null,
233
+ potHash,
234
+ timestamp: pot.timestamp.toString(),
235
+ stratum: pot.stratum,
236
+ mode: currentMode,
237
+ createdAt: entry.createdAt,
238
+ chainId: args.chainId ?? null,
239
+ poolAddress: args.poolAddress ?? null
240
+ })
241
+ ).catch(() => {
242
+ });
243
+ }
61
244
  return serialize({
62
245
  potHash,
246
+ eventId: args.eventId ?? null,
247
+ prevEventId: args.prevEventId ?? null,
63
248
  timestamp: pot.timestamp.toString(),
64
249
  stratum: pot.stratum,
65
250
  uncertainty: pot.uncertainty,
@@ -67,7 +252,8 @@ async function potGenerate(args) {
67
252
  sources: pot.sources,
68
253
  nonce: pot.nonce,
69
254
  expiresAt: pot.expiresAt.toString(),
70
- grgShards: grgShards.map((s) => Buffer.from(s).toString("hex")),
255
+ ...isOfflineFallback && { mode: "local" },
256
+ ...grgShards.length > 0 && { grgShards },
71
257
  signature: {
72
258
  issuerPubKey: signature.issuerPubKey,
73
259
  signature: signature.signature,
@@ -81,7 +267,7 @@ async function potVerify(args) {
81
267
  let valid = false;
82
268
  let reconstructedSize = 0;
83
269
  try {
84
- const recovered = import_openttt.GrgPipeline.processInverse(shards, 0, args.chainId, args.poolAddress);
270
+ const recovered = GrgPipeline.processInverse(shards, 0, args.chainId, args.poolAddress);
85
271
  valid = recovered.length > 0;
86
272
  reconstructedSize = recovered.length;
87
273
  } catch {
@@ -98,6 +284,16 @@ async function potVerify(args) {
98
284
  }
99
285
  async function potQuery(args) {
100
286
  (0, import_telemetry.telemetryIncrement)("pot_query");
287
+ if (args.eventId) {
288
+ const entry = potByEventId.get(args.eventId);
289
+ return serialize({
290
+ local: entry ? [entry] : [],
291
+ subgraph: [],
292
+ found: !!entry,
293
+ totalLocal: potLog.length,
294
+ query: { eventId: args.eventId }
295
+ });
296
+ }
101
297
  const limit = Math.min(args.limit ?? 100, 1e3);
102
298
  const now = Date.now();
103
299
  const startTime = args.startTime ?? now - 864e5;
@@ -140,6 +336,35 @@ async function potQuery(args) {
140
336
  query: { startTime, endTime, limit }
141
337
  });
142
338
  }
339
+ async function potGraph(args) {
340
+ (0, import_telemetry.telemetryIncrement)("pot_graph");
341
+ const maxDepth = Math.min(args.depth ?? 10, 100);
342
+ const backwardChain = [];
343
+ let cursor = potByEventId.get(args.eventId);
344
+ let d = 0;
345
+ while (cursor && d < maxDepth) {
346
+ backwardChain.unshift(cursor);
347
+ cursor = cursor.prevEventId ? potByEventId.get(cursor.prevEventId) : void 0;
348
+ d++;
349
+ }
350
+ const forwardChain = potByPrevEventId.get(args.eventId) ?? [];
351
+ const found = potByEventId.has(args.eventId);
352
+ const chainBroken = backwardChain.some((e) => e.eventId && evictedEventIds.has(e.eventId)) || cursor?.prevEventId != null && potByEventId.get(cursor.prevEventId) == null;
353
+ const chainRoot = backwardChain.length > 0 ? backwardChain[0] : null;
354
+ const isServerRestart = chainBroken && chainRoot !== null && chainRoot.prevEventId != null && !potByEventId.has(chainRoot.prevEventId) && !evictedEventIds.has(chainRoot.prevEventId);
355
+ const brokenAt = chainBroken ? isServerRestart ? "server_restart" : backwardChain[0]?.eventId ?? null : null;
356
+ const compressedBackward = backwardChain.map((e, i) => compressEntry(e, i + 1));
357
+ return serialize({
358
+ eventId: args.eventId,
359
+ found,
360
+ backwardChain: compressedBackward,
361
+ forwardChain,
362
+ chainLength: backwardChain.length + forwardChain.length,
363
+ reachableDepth: backwardChain.length,
364
+ chainBroken,
365
+ brokenAt
366
+ });
367
+ }
143
368
  async function potStats(args) {
144
369
  (0, import_telemetry.telemetryIncrement)("pot_stats");
145
370
  const now = Date.now();
@@ -220,11 +445,54 @@ async function potHealth() {
220
445
  }
221
446
  });
222
447
  }
448
+ async function potCheckpoint(args) {
449
+ (0, import_telemetry.telemetryIncrement)("pot_checkpoint");
450
+ const now = Date.now();
451
+ const startTime = args.startTime ?? now - 36e5;
452
+ const endTime = args.endTime ?? now;
453
+ let entries;
454
+ if (args.fromEventId && args.toEventId) {
455
+ entries = [];
456
+ let cursor = potByEventId.get(args.fromEventId);
457
+ const maxDepth = 1e3;
458
+ let d = 0;
459
+ while (cursor && d < maxDepth) {
460
+ entries.push(cursor);
461
+ if (cursor.eventId === args.toEventId) break;
462
+ const nexts = potByPrevEventId.get(cursor.eventId ?? "") ?? [];
463
+ cursor = nexts[0];
464
+ d++;
465
+ }
466
+ } else {
467
+ entries = potLog.filter((e) => e.createdAt >= startTime && e.createdAt <= endTime);
468
+ }
469
+ const eventCount = entries.length;
470
+ const depthThreshold = args.maxTokens ? depthThresholdFromTokens(args.maxTokens, entries.length) : void 0;
471
+ const compressed = entries.map((e, i) => compressEntry(e, i + 1, depthThreshold));
472
+ const chainIntact = !entries.some((e) => e.eventId && evictedEventIds.has(e.eventId));
473
+ const nextCheckpointHint = Math.max(10, 100 - eventCount % 100);
474
+ const checkpointId = `ckpt_${now}_${eventCount}`;
475
+ const firstTs = entries[0]?.timestamp ?? null;
476
+ const lastTs = entries[entries.length - 1]?.timestamp ?? null;
477
+ return serialize({
478
+ checkpointId,
479
+ eventCount,
480
+ chainIntact,
481
+ nextCheckpointHint,
482
+ rollup: compressed,
483
+ summary: `${eventCount} events from ${firstTs ?? "?"} to ${lastTs ?? "?"}`,
484
+ generatedAt: now
485
+ });
486
+ }
223
487
  // Annotate the CommonJS export names for ESM import in node:
224
488
  0 && (module.exports = {
489
+ potCheckpoint,
225
490
  potGenerate,
491
+ potGraph,
226
492
  potHealth,
227
493
  potQuery,
228
494
  potStats,
229
- potVerify
495
+ potVerify,
496
+ redis,
497
+ restoreDagEntry
230
498
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helm-protocol/ttt-mcp",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "MCP Server for OpenTTT — Proof of Time tools for AI agents",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -13,7 +13,23 @@
13
13
  "scripts": {
14
14
  "build": "esbuild index.ts tools.ts telemetry.ts auth.ts --platform=node --target=node18 --format=cjs --outdir=dist",
15
15
  "start": "node dist/index.js",
16
- "dev": "npx ts-node index.ts"
16
+ "dev": "npx ts-node index.ts",
17
+ "test": "jest --forceExit"
18
+ },
19
+ "jest": {
20
+ "preset": "ts-jest",
21
+ "testEnvironment": "node",
22
+ "testMatch": ["**/__tests__/**/*.test.ts"],
23
+ "moduleNameMapper": {
24
+ "^openttt$": "<rootDir>/node_modules/openttt/dist/index.js"
25
+ },
26
+ "transform": {
27
+ "^.+\\.tsx?$": ["ts-jest", {
28
+ "tsconfig": {
29
+ "module": "CommonJS"
30
+ }
31
+ }]
32
+ }
17
33
  },
18
34
  "keywords": [
19
35
  "mcp",
@@ -36,12 +52,16 @@
36
52
  },
37
53
  "dependencies": {
38
54
  "@modelcontextprotocol/sdk": "^1.12.1",
39
- "openttt": "^0.2.6",
55
+ "ioredis": "^5.11.0",
56
+ "openttt": "^0.2.13",
40
57
  "zod": "^3.25.0"
41
58
  },
42
59
  "devDependencies": {
60
+ "@types/jest": "^30.0.0",
43
61
  "@types/node": "^20.11.19",
44
62
  "esbuild": "^0.27.4",
63
+ "jest": "^30.4.2",
64
+ "ts-jest": "^29.4.11",
45
65
  "typescript": "^5.3.3"
46
66
  },
47
67
  "mcpName": "io.github.Helm-Protocol/openttt-pot"