@helm-protocol/ttt-mcp 0.2.2 → 0.3.1

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/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,67 @@ 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,
40
+ tttsFreshnessSeal: () => tttsFreshnessSeal,
41
+ updateLastPot: () => updateLastPot
26
42
  });
27
43
  module.exports = __toCommonJS(tools_exports);
28
44
  var import_openttt = require("openttt");
29
45
  var import_telemetry = require("./telemetry");
46
+ var import_server = require("./server");
47
+ var import_ioredis = __toESM(require("ioredis"));
48
+ const GrgPipeline = (
49
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
50
+ require("openttt").GrgPipeline ?? null
51
+ );
52
+ function applyAdvisory(result, advisory) {
53
+ if (!advisory) return result;
54
+ if (result === null || typeof result !== "object") return result;
55
+ const notices = [];
56
+ if (advisory.warning) notices.push(advisory.warning);
57
+ if (advisory.overageActive) notices.push("Overage billing is active \u2014 usage above your plan limit will be charged.");
58
+ if (notices.length === 0) return result;
59
+ return { ...result, _quotaNotice: notices.join(" | ") };
60
+ }
61
+ function resolvePaidApiKey() {
62
+ return process.env.TTT_API_KEY?.trim() || void 0;
63
+ }
64
+ const redis = new import_ioredis.default(process.env.REDIS_URL ?? "redis://127.0.0.1:6379", {
65
+ lazyConnect: true,
66
+ enableOfflineQueue: false,
67
+ maxRetriesPerRequest: 1
68
+ });
69
+ redis.on("error", () => {
70
+ });
30
71
  const timeSynth = new import_openttt.TimeSynthesis();
31
72
  const adaptiveSwitch = new import_openttt.AdaptiveSwitch();
32
73
  const potSigner = new import_openttt.PotSigner();
33
74
  const startedAt = Date.now();
34
75
  const POT_LOG_MAX = 1e4;
35
76
  const potLog = [];
77
+ const potByEventId = /* @__PURE__ */ new Map();
78
+ const potByPrevEventId = /* @__PURE__ */ new Map();
79
+ const evictedEventIds = /* @__PURE__ */ new Set();
80
+ const EVICTED_MAX = 1e3;
36
81
  function bigintReplacer(_key, value) {
37
82
  return typeof value === "bigint" ? value.toString() : value;
38
83
  }
@@ -40,26 +85,213 @@ function serialize(obj) {
40
85
  return JSON.parse(JSON.stringify(obj, bigintReplacer));
41
86
  }
42
87
  const SUBGRAPH_URL = "https://api.studio.thegraph.com/query/1744392/openttt-base-sepolia/v0.1.0";
88
+ function compressEntry(entry, depth, depthThreshold) {
89
+ const t = depthThreshold ?? { compact: 5, minimal: 20, rollup: 50 };
90
+ if (depth <= t.compact) return entry;
91
+ if (depth <= t.minimal) return {
92
+ // compact
93
+ eventId: entry.eventId,
94
+ potHash: entry.potHash,
95
+ timestamp: entry.timestamp,
96
+ prevEventId: entry.prevEventId
97
+ };
98
+ if (depth <= t.rollup) return {
99
+ // minimal
100
+ eventId: entry.eventId,
101
+ timestamp: entry.timestamp
102
+ };
103
+ return `${entry.eventId ?? "?"}@${entry.timestamp}`;
104
+ }
105
+ function depthThresholdFromTokens(maxTokens, entryCount) {
106
+ const charBudget = maxTokens * 4;
107
+ const fullBudget = charBudget * 0.6;
108
+ const compactBudget = charBudget * 0.25;
109
+ const minimalBudget = charBudget * 0.1;
110
+ const fullDepth = Math.max(1, Math.floor(fullBudget / 300));
111
+ const compactDepth = fullDepth + Math.max(0, Math.floor(compactBudget / 120));
112
+ const minimalDepth = compactDepth + Math.max(0, Math.floor(minimalBudget / 60));
113
+ return {
114
+ compact: Math.min(fullDepth, entryCount),
115
+ minimal: Math.min(compactDepth, entryCount),
116
+ rollup: Math.min(minimalDepth, entryCount)
117
+ };
118
+ }
119
+ let _lastPotTimestampNs = null;
120
+ let _lastPotStratum = 16;
121
+ let _lastPotSources = 0;
122
+ function updateLastPot(timestampNs, stratum, sources) {
123
+ _lastPotTimestampNs = timestampNs;
124
+ _lastPotStratum = stratum;
125
+ _lastPotSources = sources;
126
+ }
127
+ function tttsFreshnessSeal() {
128
+ if (_lastPotTimestampNs === null) return null;
129
+ const nowNs = BigInt(Date.now()) * 1000000n;
130
+ const age_ms = Number((nowNs - _lastPotTimestampNs) / 1000000n);
131
+ const ttlMs = _lastPotStratum <= 3 ? 100 : _lastPotStratum <= 8 ? 1e3 : _lastPotStratum <= 15 ? 5e3 : 0;
132
+ return { age_ms, stratum: _lastPotStratum, sources: _lastPotSources, ttlMs };
133
+ }
134
+ function restoreDagEntry(entry) {
135
+ if (potByEventId.has(entry.eventId)) return;
136
+ const e = {
137
+ potHash: entry.potHash,
138
+ timestamp: entry.timestamp,
139
+ stratum: entry.stratum,
140
+ mode: entry.mode,
141
+ chainId: entry.chainId ?? void 0,
142
+ poolAddress: entry.poolAddress ?? void 0,
143
+ eventId: entry.eventId,
144
+ prevEventId: entry.prevEventId ?? void 0,
145
+ createdAt: entry.createdAt
146
+ };
147
+ if (potLog.length >= POT_LOG_MAX) {
148
+ const evicted = potLog.shift();
149
+ if (evicted.eventId) {
150
+ potByEventId.delete(evicted.eventId);
151
+ evictedEventIds.add(evicted.eventId);
152
+ if (evictedEventIds.size > EVICTED_MAX) {
153
+ const first = evictedEventIds.values().next().value;
154
+ if (first !== void 0) evictedEventIds.delete(first);
155
+ }
156
+ }
157
+ if (evicted.prevEventId) {
158
+ const siblings = potByPrevEventId.get(evicted.prevEventId);
159
+ if (siblings) {
160
+ const filtered = siblings.filter((s) => s !== evicted);
161
+ if (filtered.length === 0) potByPrevEventId.delete(evicted.prevEventId);
162
+ else potByPrevEventId.set(evicted.prevEventId, filtered);
163
+ }
164
+ }
165
+ }
166
+ potLog.push(e);
167
+ potByEventId.set(entry.eventId, e);
168
+ if (entry.prevEventId) {
169
+ const bucket = potByPrevEventId.get(entry.prevEventId) ?? [];
170
+ bucket.push(e);
171
+ potByPrevEventId.set(entry.prevEventId, bucket);
172
+ }
173
+ }
43
174
  async function potGenerate(args) {
175
+ if (!args.eventId && !args.txHash) {
176
+ throw new Error("Either eventId (Claude Code) or txHash (DeFi) is required");
177
+ }
44
178
  (0, import_telemetry.telemetryIncrement)("pot_generate");
45
- const pot = await timeSynth.generateProofOfTime();
179
+ const apiKey = resolvePaidApiKey();
180
+ if (apiKey && args.eventId) {
181
+ const { data, advisory } = await (0, import_server.delegateToServer)({
182
+ apiKey,
183
+ method: "POST",
184
+ path: "/pot/generate",
185
+ body: { eventId: args.eventId, prevEventId: args.prevEventId }
186
+ });
187
+ if (data && typeof data === "object") {
188
+ const d = data;
189
+ if (typeof d.stratum === "number" && typeof d.sources === "number" && typeof d.timestamp === "string") {
190
+ updateLastPot(BigInt(d.timestamp), d.stratum, d.sources);
191
+ }
192
+ }
193
+ return applyAdvisory(data, advisory);
194
+ }
195
+ let pot;
196
+ let isOfflineFallback = false;
197
+ try {
198
+ pot = await timeSynth.generateProofOfTime();
199
+ } catch {
200
+ isOfflineFallback = true;
201
+ const nowMs = BigInt(Date.now());
202
+ pot = {
203
+ timestamp: nowMs * 1000000n,
204
+ stratum: 16,
205
+ uncertainty: 999999999,
206
+ confidence: 0,
207
+ sources: 0,
208
+ nonce: Buffer.from(crypto.getRandomValues(new Uint8Array(16))).toString("hex"),
209
+ expiresAt: (nowMs + 300000n) * 1000000n,
210
+ sourceReadings: []
211
+ };
212
+ }
46
213
  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);
214
+ let grgShards = [];
215
+ let currentMode;
216
+ if (args.txHash && args.chainId != null && args.poolAddress) {
217
+ const txData = new TextEncoder().encode(args.txHash);
218
+ grgShards = GrgPipeline.processForward(txData, args.chainId, args.poolAddress).map((s) => Buffer.from(s).toString("hex"));
219
+ const syntheticBlock = {
220
+ timestamp: Number(pot.timestamp / 1000000n),
221
+ // ns → ms
222
+ txs: [args.txHash],
223
+ data: new TextEncoder().encode(args.txHash)
224
+ };
225
+ const syntheticTTTRecord = {
226
+ time: Number(pot.timestamp / 1000000n),
227
+ txOrder: [args.txHash],
228
+ grgPayload: []
229
+ };
230
+ currentMode = adaptiveSwitch.verifyBlock(syntheticBlock, syntheticTTTRecord, args.chainId, args.poolAddress);
231
+ } else {
232
+ currentMode = adaptiveSwitch.getCurrentMode();
233
+ }
49
234
  const signature = potSigner.signPot(potHash);
50
235
  const entry = {
51
236
  potHash,
52
237
  timestamp: pot.timestamp.toString(),
53
238
  stratum: pot.stratum,
54
- mode: adaptiveSwitch.getCurrentMode(),
239
+ mode: currentMode,
55
240
  chainId: args.chainId,
56
241
  poolAddress: args.poolAddress,
242
+ eventId: args.eventId,
243
+ prevEventId: args.prevEventId,
57
244
  createdAt: Date.now()
58
245
  };
246
+ if (potLog.length >= POT_LOG_MAX) {
247
+ const evicted = potLog.shift();
248
+ if (evicted.eventId) {
249
+ potByEventId.delete(evicted.eventId);
250
+ evictedEventIds.add(evicted.eventId);
251
+ if (evictedEventIds.size > EVICTED_MAX) {
252
+ const first = evictedEventIds.values().next().value;
253
+ if (first !== void 0) evictedEventIds.delete(first);
254
+ }
255
+ }
256
+ if (evicted.prevEventId) {
257
+ const siblings = potByPrevEventId.get(evicted.prevEventId);
258
+ if (siblings) {
259
+ const filtered = siblings.filter((e) => e !== evicted);
260
+ if (filtered.length === 0) potByPrevEventId.delete(evicted.prevEventId);
261
+ else potByPrevEventId.set(evicted.prevEventId, filtered);
262
+ }
263
+ }
264
+ }
59
265
  potLog.push(entry);
60
- if (potLog.length > POT_LOG_MAX) potLog.shift();
266
+ if (args.eventId) potByEventId.set(args.eventId, entry);
267
+ if (args.prevEventId) {
268
+ const bucket = potByPrevEventId.get(args.prevEventId) ?? [];
269
+ bucket.push(entry);
270
+ potByPrevEventId.set(args.prevEventId, bucket);
271
+ }
272
+ if (args.eventId) {
273
+ redis.setex(
274
+ `dag:${args.eventId}`,
275
+ 90 * 86400,
276
+ JSON.stringify({
277
+ eventId: args.eventId,
278
+ prevEventId: args.prevEventId ?? null,
279
+ potHash,
280
+ timestamp: pot.timestamp.toString(),
281
+ stratum: pot.stratum,
282
+ mode: currentMode,
283
+ createdAt: entry.createdAt,
284
+ chainId: args.chainId ?? null,
285
+ poolAddress: args.poolAddress ?? null
286
+ })
287
+ ).catch(() => {
288
+ });
289
+ }
290
+ updateLastPot(pot.timestamp, pot.stratum, pot.sources);
61
291
  return serialize({
62
292
  potHash,
293
+ eventId: args.eventId ?? null,
294
+ prevEventId: args.prevEventId ?? null,
63
295
  timestamp: pot.timestamp.toString(),
64
296
  stratum: pot.stratum,
65
297
  uncertainty: pot.uncertainty,
@@ -67,7 +299,8 @@ async function potGenerate(args) {
67
299
  sources: pot.sources,
68
300
  nonce: pot.nonce,
69
301
  expiresAt: pot.expiresAt.toString(),
70
- grgShards: grgShards.map((s) => Buffer.from(s).toString("hex")),
302
+ ...isOfflineFallback && { mode: "local" },
303
+ ...grgShards.length > 0 && { grgShards },
71
304
  signature: {
72
305
  issuerPubKey: signature.issuerPubKey,
73
306
  signature: signature.signature,
@@ -81,7 +314,7 @@ async function potVerify(args) {
81
314
  let valid = false;
82
315
  let reconstructedSize = 0;
83
316
  try {
84
- const recovered = import_openttt.GrgPipeline.processInverse(shards, 0, args.chainId, args.poolAddress);
317
+ const recovered = GrgPipeline.processInverse(shards, 0, args.chainId, args.poolAddress);
85
318
  valid = recovered.length > 0;
86
319
  reconstructedSize = recovered.length;
87
320
  } catch {
@@ -98,6 +331,31 @@ async function potVerify(args) {
98
331
  }
99
332
  async function potQuery(args) {
100
333
  (0, import_telemetry.telemetryIncrement)("pot_query");
334
+ const apiKey = resolvePaidApiKey();
335
+ if (apiKey) {
336
+ const { data, advisory } = await (0, import_server.delegateToServer)({
337
+ apiKey,
338
+ method: "GET",
339
+ path: "/pot/query",
340
+ query: {
341
+ eventId: args.eventId,
342
+ startTimeNs: args.startTime != null ? args.startTime * 1e6 : void 0,
343
+ endTimeNs: args.endTime != null ? args.endTime * 1e6 : void 0,
344
+ limit: args.limit
345
+ }
346
+ });
347
+ return applyAdvisory(data, advisory);
348
+ }
349
+ if (args.eventId) {
350
+ const entry = potByEventId.get(args.eventId);
351
+ return serialize({
352
+ local: entry ? [entry] : [],
353
+ subgraph: [],
354
+ found: !!entry,
355
+ totalLocal: potLog.length,
356
+ query: { eventId: args.eventId }
357
+ });
358
+ }
101
359
  const limit = Math.min(args.limit ?? 100, 1e3);
102
360
  const now = Date.now();
103
361
  const startTime = args.startTime ?? now - 864e5;
@@ -140,8 +398,56 @@ async function potQuery(args) {
140
398
  query: { startTime, endTime, limit }
141
399
  });
142
400
  }
401
+ async function potGraph(args) {
402
+ (0, import_telemetry.telemetryIncrement)("pot_graph");
403
+ const apiKey = resolvePaidApiKey();
404
+ if (apiKey) {
405
+ const { data, advisory } = await (0, import_server.delegateToServer)({
406
+ apiKey,
407
+ method: "GET",
408
+ path: "/pot/graph",
409
+ query: { eventId: args.eventId, depth: args.depth }
410
+ });
411
+ return applyAdvisory(data, advisory);
412
+ }
413
+ const maxDepth = Math.min(args.depth ?? 10, 100);
414
+ const backwardChain = [];
415
+ let cursor = potByEventId.get(args.eventId);
416
+ let d = 0;
417
+ while (cursor && d < maxDepth) {
418
+ backwardChain.unshift(cursor);
419
+ cursor = cursor.prevEventId ? potByEventId.get(cursor.prevEventId) : void 0;
420
+ d++;
421
+ }
422
+ const forwardChain = potByPrevEventId.get(args.eventId) ?? [];
423
+ const found = potByEventId.has(args.eventId);
424
+ const chainBroken = backwardChain.some((e) => e.eventId && evictedEventIds.has(e.eventId)) || cursor?.prevEventId != null && potByEventId.get(cursor.prevEventId) == null;
425
+ const chainRoot = backwardChain.length > 0 ? backwardChain[0] : null;
426
+ const isServerRestart = chainBroken && chainRoot !== null && chainRoot.prevEventId != null && !potByEventId.has(chainRoot.prevEventId) && !evictedEventIds.has(chainRoot.prevEventId);
427
+ const brokenAt = chainBroken ? isServerRestart ? "server_restart" : backwardChain[0]?.eventId ?? null : null;
428
+ const compressedBackward = backwardChain.map((e, i) => compressEntry(e, i + 1));
429
+ return serialize({
430
+ eventId: args.eventId,
431
+ found,
432
+ backwardChain: compressedBackward,
433
+ forwardChain,
434
+ chainLength: backwardChain.length + forwardChain.length,
435
+ reachableDepth: backwardChain.length,
436
+ chainBroken,
437
+ brokenAt
438
+ });
439
+ }
143
440
  async function potStats(args) {
144
441
  (0, import_telemetry.telemetryIncrement)("pot_stats");
442
+ const apiKey = resolvePaidApiKey();
443
+ if (apiKey) {
444
+ const { data, advisory } = await (0, import_server.delegateToServer)({
445
+ apiKey,
446
+ method: "GET",
447
+ path: "/pot/stats"
448
+ });
449
+ return applyAdvisory(data, advisory);
450
+ }
145
451
  const now = Date.now();
146
452
  const periodMs = {
147
453
  day: 864e5,
@@ -220,11 +526,72 @@ async function potHealth() {
220
526
  }
221
527
  });
222
528
  }
529
+ async function potCheckpoint(args) {
530
+ (0, import_telemetry.telemetryIncrement)("pot_checkpoint");
531
+ const apiKey = resolvePaidApiKey();
532
+ if (apiKey) {
533
+ const { data, advisory } = await (0, import_server.delegateToServer)({
534
+ apiKey,
535
+ method: "GET",
536
+ path: "/pot/checkpoint",
537
+ query: {
538
+ fromEventId: args.fromEventId,
539
+ toEventId: args.toEventId,
540
+ startTime: args.startTime,
541
+ endTime: args.endTime,
542
+ maxTokens: args.maxTokens
543
+ }
544
+ });
545
+ return applyAdvisory(data, advisory);
546
+ }
547
+ const now = Date.now();
548
+ const startTime = args.startTime ?? now - 36e5;
549
+ const endTime = args.endTime ?? now;
550
+ let entries;
551
+ if (args.fromEventId && args.toEventId) {
552
+ entries = [];
553
+ let cursor = potByEventId.get(args.fromEventId);
554
+ const maxDepth = 1e3;
555
+ let d = 0;
556
+ while (cursor && d < maxDepth) {
557
+ entries.push(cursor);
558
+ if (cursor.eventId === args.toEventId) break;
559
+ const nexts = potByPrevEventId.get(cursor.eventId ?? "") ?? [];
560
+ cursor = nexts[0];
561
+ d++;
562
+ }
563
+ } else {
564
+ entries = potLog.filter((e) => e.createdAt >= startTime && e.createdAt <= endTime);
565
+ }
566
+ const eventCount = entries.length;
567
+ const depthThreshold = args.maxTokens ? depthThresholdFromTokens(args.maxTokens, entries.length) : void 0;
568
+ const compressed = entries.map((e, i) => compressEntry(e, i + 1, depthThreshold));
569
+ const chainIntact = !entries.some((e) => e.eventId && evictedEventIds.has(e.eventId));
570
+ const nextCheckpointHint = Math.max(10, 240 - eventCount % 240);
571
+ const checkpointId = `ckpt_${now}_${eventCount}`;
572
+ const firstTs = entries[0]?.timestamp ?? null;
573
+ const lastTs = entries[entries.length - 1]?.timestamp ?? null;
574
+ return serialize({
575
+ checkpointId,
576
+ eventCount,
577
+ chainIntact,
578
+ nextCheckpointHint,
579
+ rollup: compressed,
580
+ summary: `${eventCount} events from ${firstTs ?? "?"} to ${lastTs ?? "?"}`,
581
+ generatedAt: now
582
+ });
583
+ }
223
584
  // Annotate the CommonJS export names for ESM import in node:
224
585
  0 && (module.exports = {
586
+ potCheckpoint,
225
587
  potGenerate,
588
+ potGraph,
226
589
  potHealth,
227
590
  potQuery,
228
591
  potStats,
229
- potVerify
592
+ potVerify,
593
+ redis,
594
+ restoreDagEntry,
595
+ tttsFreshnessSeal,
596
+ updateLastPot
230
597
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helm-protocol/ttt-mcp",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "MCP Server for OpenTTT — Proof of Time tools for AI agents",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -11,9 +11,26 @@
11
11
  "README.md"
12
12
  ],
13
13
  "scripts": {
14
- "build": "esbuild index.ts tools.ts telemetry.ts auth.ts --platform=node --target=node18 --format=cjs --outdir=dist",
14
+ "build": "esbuild index.ts tools.ts telemetry.ts auth.ts server.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
+ "typecheck": "NODE_OPTIONS='--max-old-space-size=4096' tsc --noEmit"
19
+ },
20
+ "jest": {
21
+ "preset": "ts-jest",
22
+ "testEnvironment": "node",
23
+ "testMatch": ["**/__tests__/**/*.test.ts"],
24
+ "moduleNameMapper": {
25
+ "^openttt$": "<rootDir>/node_modules/openttt/dist/index.js"
26
+ },
27
+ "transform": {
28
+ "^.+\\.tsx?$": ["ts-jest", {
29
+ "tsconfig": {
30
+ "module": "CommonJS"
31
+ }
32
+ }]
33
+ }
17
34
  },
18
35
  "keywords": [
19
36
  "mcp",
@@ -35,13 +52,17 @@
35
52
  "node": ">=18"
36
53
  },
37
54
  "dependencies": {
38
- "@modelcontextprotocol/sdk": "^1.12.1",
39
- "openttt": "^0.2.6",
55
+ "@modelcontextprotocol/sdk": "^1.27.1",
56
+ "ioredis": "^5.11.0",
57
+ "openttt": "^0.2.13",
40
58
  "zod": "^3.25.0"
41
59
  },
42
60
  "devDependencies": {
61
+ "@types/jest": "^30.0.0",
43
62
  "@types/node": "^20.11.19",
44
63
  "esbuild": "^0.27.4",
64
+ "jest": "^30.4.2",
65
+ "ts-jest": "^29.4.11",
45
66
  "typescript": "^5.3.3"
46
67
  },
47
68
  "mcpName": "io.github.Helm-Protocol/openttt-pot"