@helm-protocol/ttt-mcp 0.1.9 → 0.2.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/LICENSE +18 -17
- package/README.md +151 -1
- package/dist/auth.js +55 -208
- package/dist/index.js +112 -22499
- package/dist/tools.js +6 -8
- package/package.json +4 -4
package/LICENSE
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
Business Source License 1.1
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Parameters
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
5
|
+
Licensor: Heime-Jorgen (Peter Jang)
|
|
6
|
+
Licensed Work: @helm-protocol/ttt-mcp
|
|
7
|
+
Change Date: 2029-05-28
|
|
8
|
+
Change License: Apache License, Version 2.0
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
Additional Use Grant:
|
|
11
|
+
The following uses are permitted without a commercial license:
|
|
12
|
+
- Non-commercial use (research, education, personal projects)
|
|
13
|
+
- Commercial use generating less than $10,000 USD/year in revenue
|
|
14
|
+
- Individual MEV bots or arbitrage bots operated for personal account
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
For all other commercial use, a license is required. See:
|
|
17
|
+
https://kenosian.com/pricing
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
Business Source License 1.1 terms apply. The full text is available at:
|
|
22
|
+
https://mariadb.com/bsl11/
|
package/README.md
CHANGED
|
@@ -144,6 +144,151 @@ Config file locations:
|
|
|
144
144
|
- Node.js >= 18
|
|
145
145
|
- Network access for time synthesis (HTTPS to time.nist.gov, time.google.com, time.cloudflare.com)
|
|
146
146
|
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Use Cases
|
|
150
|
+
|
|
151
|
+
### 1. MEV Bot — Transaction Ordering Proof
|
|
152
|
+
|
|
153
|
+
**Problem**: You got front-run. You know it happened. You can't prove it — mempool timestamps are per-node, unsigned, and non-authoritative. No evidence, no recourse.
|
|
154
|
+
|
|
155
|
+
**Solution**: Call `pot_generate` before submitting every transaction. The PoT receipt is cryptographically signed by three independent time sources (NIST, Google, Cloudflare), hashed on-chain to Base Sepolia TTT ERC-1155. If front-running occurs, you have a timestamped, on-chain-anchored record of your original submission that predates the attacker's block inclusion.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// Before tx submission
|
|
159
|
+
const pot = await client.callTool({ name: "pot_generate", arguments: { txHash: pendingTxHash, chainId: 8453 } });
|
|
160
|
+
// Store pot.potHash alongside your trade log
|
|
161
|
+
// If front-run: pot.potHash is your evidence, timestamped by NIST+Google+Cloudflare
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**V2 path**: When builder staking goes live, `S(V) ≥ V − c₀` makes reordering economically irrational for any V. Not just evidence — prevention.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
### 2. DEX Protocol — AdaptiveSwitch Sandwich Deterrence
|
|
169
|
+
|
|
170
|
+
**Problem**: Small-to-mid value sandwich attacks (V < ~$87) are constant background noise on any AMM. Each one is individually too small to litigate, collectively significant. No governance mechanism moves fast enough to respond.
|
|
171
|
+
|
|
172
|
+
**Solution**: Integrate `TTTHookSimple` (Uniswap V4 hook, Base Sepolia: `0x8C633b05b833a476925F7d9818da6E215760F2c7`). Honest builders who preserve PoT-verified ordering get `turbo` mode (~50ms path). Builders who tamper are flagged to `full` mode (~127ms + exponential backoff up to 320 blocks). The 77ms throughput differential makes reordering cost exceed opportunity value for the V* range. No vote. No committee. Economics.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// Query current switch state for a pool
|
|
176
|
+
const status = await client.callTool({ name: "pot_stats", arguments: { poolAddress: "0x..." } });
|
|
177
|
+
// status.adaptiveMode: "turbo" | "full"
|
|
178
|
+
// status.currentV_star: estimated MEV threshold being deterred
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Outcome**: ~80% reduction in sub-threshold sandwich attacks. Provable per-block audit trail.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
### 3. Hedge Fund / Prop Desk — MiFIR Art.22c Compliance
|
|
186
|
+
|
|
187
|
+
**Problem**: MiFIR Article 22c / RTS 25 requires microsecond-precision UTC-synchronized timestamps for every trade on regulated venues. The standard hardware solution (PTP/IEEE 1588 appliances) costs $50K–$500K and requires dedicated ops. Most DeFi-adjacent funds run manual reconciliation between two separate timestamp systems.
|
|
188
|
+
|
|
189
|
+
**Solution**: `pot_generate` produces an Ed25519-signed timestamp with uncertainty bound, confidence score, and multi-source attestation. The output is structurally compatible with RTS 25 audit record requirements. No hardware appliance. No dedicated ops. One API call per trade.
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
const audit = await client.callTool({
|
|
193
|
+
name: "pot_generate",
|
|
194
|
+
arguments: { txHash: tradeHash, chainId: 8453, metadata: { desk: "MACRO-1", trader: "algo-07" } }
|
|
195
|
+
});
|
|
196
|
+
// audit.timestamp: nanosecond precision
|
|
197
|
+
// audit.uncertainty: +/- ms bound (required field in RTS 25 record)
|
|
198
|
+
// audit.confidence: fraction of sources that agreed
|
|
199
|
+
// audit.ed25519_sig: non-repudiation signature
|
|
200
|
+
// Export to your compliance system — same format, every trade
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Outcome**: MiFIR-grade audit trail at ~$0.04/1K calls (DEX tier). Replaces $50K+ hardware setup. IETF standardized via `draft-helmprotocol-tttps-00`.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### 4. Liquidity Provider — Position Timeline for Dispute Resolution
|
|
208
|
+
|
|
209
|
+
**Problem**: LP enters and exits positions based on market conditions. When impermanent loss occurs due to a suspected protocol exploit or ordering manipulation, proving the sequence of events (position entry → exploit event → position exit) requires timestamped evidence that the current stack doesn't provide.
|
|
210
|
+
|
|
211
|
+
**Solution**: Stamp every LP action (add liquidity, remove liquidity, fee harvest) with a PoT receipt. The receipt chain creates an unforgeable causal timeline: each action's potHash references the previous, anchored on Base Sepolia. Legally defensible for tax documentation, insurance claims, and protocol dispute resolution.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// On liquidity add
|
|
215
|
+
const entryPot = await client.callTool({ name: "pot_generate", arguments: { txHash: addLiqTx, chainId: 8453 } });
|
|
216
|
+
|
|
217
|
+
// On liquidity remove
|
|
218
|
+
const exitPot = await client.callTool({ name: "pot_generate", arguments: { txHash: removeLiqTx, chainId: 8453 } });
|
|
219
|
+
|
|
220
|
+
// Verify the causal chain
|
|
221
|
+
const chain = await client.callTool({ name: "pot_verify", arguments: { potHash: exitPot.potHash, precedingHash: entryPot.potHash } });
|
|
222
|
+
// chain.valid: true means exit cryptographically followed entry
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
### 5. AI Agent Coordination — Multi-Agent Causal Ordering
|
|
228
|
+
|
|
229
|
+
**Problem**: When multiple AI agents interact in a pipeline (Agent A signals → Agent B acts → Agent C settles), the causal order matters for debugging, auditing, and liability. Agent logs are unverifiable—any agent can claim any timestamp.
|
|
230
|
+
|
|
231
|
+
**Solution**: Each agent calls `pot_generate` before acting. The resulting potHash chain is independently verifiable: "Agent A's signal at T₁ preceded Agent B's action at T₂" can be proven without trusting either agent's self-reported logs. The on-chain anchor makes the ordering dispute-proof.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// Agent A (signal generator)
|
|
235
|
+
const signalPot = await client.callTool({ name: "pot_generate", arguments: { txHash: signalId } });
|
|
236
|
+
|
|
237
|
+
// Agent B (executor) — references Agent A's pot
|
|
238
|
+
const execPot = await client.callTool({
|
|
239
|
+
name: "pot_generate",
|
|
240
|
+
arguments: { txHash: execId, precedingPotHash: signalPot.potHash }
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Any third party can verify the causal chain
|
|
244
|
+
const verified = await client.callTool({ name: "pot_verify", arguments: { potHash: execPot.potHash, precedingHash: signalPot.potHash } });
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Outcome**: Unforgeable causal chain across autonomous agents. Useful for multi-agent DeFi strategies, audit compliance, and cross-agent dispute resolution.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## TypeScript: MEV Bot Integration
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { McpClient } from "@modelcontextprotocol/sdk/client/mcp.js";
|
|
255
|
+
|
|
256
|
+
// Generate a Proof of Time for a transaction
|
|
257
|
+
const result = await client.callTool({
|
|
258
|
+
name: "pot_generate",
|
|
259
|
+
arguments: {
|
|
260
|
+
txHash: "0xabc123...",
|
|
261
|
+
chainId: 8453,
|
|
262
|
+
poolAddress: "0xdef456..."
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
// Returns: { potHash, timestamp, stratum, grg_shards }
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Python: Hedge Fund Audit
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
import subprocess, json
|
|
272
|
+
|
|
273
|
+
result = subprocess.run(
|
|
274
|
+
["npx", "-y", "@helm-protocol/ttt-mcp"],
|
|
275
|
+
input=json.dumps({
|
|
276
|
+
"tool": "pot_verify",
|
|
277
|
+
"potHash": "0x...",
|
|
278
|
+
"expectedChainId": 8453
|
|
279
|
+
}),
|
|
280
|
+
capture_output=True, text=True
|
|
281
|
+
)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Rate Limits & Pricing
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
Free Tier: 100 calls/day per IP — no API key needed
|
|
288
|
+
Paid Tier: Set TTT_API_KEY env var — unlimited
|
|
289
|
+
Commercial: peter@kenosian.com (hedge funds, DEX protocols, OTC desks)
|
|
290
|
+
```
|
|
291
|
+
|
|
147
292
|
## Learn More
|
|
148
293
|
|
|
149
294
|
- [OpenTTT SDK](https://www.npmjs.com/package/openttt) — The underlying SDK
|
|
@@ -152,4 +297,9 @@ Config file locations:
|
|
|
152
297
|
|
|
153
298
|
## License
|
|
154
299
|
|
|
155
|
-
|
|
300
|
+
BSL-1.1 — free for non-commercial use.
|
|
301
|
+
|
|
302
|
+
**Commercial use** (production bots, hedge funds, prop desks) requires a license.
|
|
303
|
+
→ [kenosian.com/pricing](https://kenosian.com/pricing)
|
|
304
|
+
|
|
305
|
+
Change Date: 2029-05-28 → Apache 2.0
|
package/dist/auth.js
CHANGED
|
@@ -28,230 +28,77 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var auth_exports = {};
|
|
30
30
|
__export(auth_exports, {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
FREE_TIER_LIMIT: () => FREE_TIER_LIMIT,
|
|
32
|
+
checkRateLimit: () => checkRateLimit,
|
|
33
|
+
resolveApiKey: () => resolveApiKey
|
|
33
34
|
});
|
|
34
35
|
module.exports = __toCommonJS(auth_exports);
|
|
35
|
-
var
|
|
36
|
-
var
|
|
37
|
-
var
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const authCache = /* @__PURE__ */ new Map();
|
|
49
|
-
function getCached(apiKey, toolName) {
|
|
50
|
-
const key = `${apiKey}:${toolName}`;
|
|
51
|
-
const entry = authCache.get(key);
|
|
52
|
-
if (!entry) return null;
|
|
53
|
-
if (Date.now() > entry.expiresAt) {
|
|
54
|
-
authCache.delete(key);
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
return entry;
|
|
58
|
-
}
|
|
59
|
-
function setCached(apiKey, toolName, allowed, planTier, cacheTtlMs) {
|
|
60
|
-
const key = `${apiKey}:${toolName}`;
|
|
61
|
-
authCache.set(key, {
|
|
62
|
-
allowed,
|
|
63
|
-
planTier,
|
|
64
|
-
expiresAt: Date.now() + cacheTtlMs
|
|
65
|
-
});
|
|
36
|
+
var fs = __toESM(require("fs"));
|
|
37
|
+
var path = __toESM(require("path"));
|
|
38
|
+
var os = __toESM(require("os"));
|
|
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");
|
|
42
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
43
|
+
function nextMidnightUtc() {
|
|
44
|
+
const now = /* @__PURE__ */ new Date();
|
|
45
|
+
const midnight = new Date(
|
|
46
|
+
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1)
|
|
47
|
+
);
|
|
48
|
+
return midnight.getTime();
|
|
66
49
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
openedAt: 0
|
|
73
|
-
};
|
|
74
|
-
function circuitRecordSuccess() {
|
|
75
|
-
circuit.consecutiveFailures = 0;
|
|
76
|
-
circuit.state = "CLOSED";
|
|
77
|
-
}
|
|
78
|
-
function circuitRecordFailure() {
|
|
79
|
-
circuit.consecutiveFailures += 1;
|
|
80
|
-
if (circuit.consecutiveFailures >= CIRCUIT_FAILURE_THRESHOLD) {
|
|
81
|
-
circuit.state = "OPEN";
|
|
82
|
-
circuit.openedAt = Date.now();
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function circuitCanAttempt() {
|
|
86
|
-
if (circuit.state === "CLOSED") return true;
|
|
87
|
-
if (circuit.state === "OPEN") {
|
|
88
|
-
if (Date.now() - circuit.openedAt >= CIRCUIT_OPEN_DURATION_MS) {
|
|
89
|
-
circuit.state = "HALF_OPEN";
|
|
90
|
-
return true;
|
|
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"));
|
|
91
55
|
}
|
|
92
|
-
|
|
56
|
+
} catch {
|
|
93
57
|
}
|
|
94
|
-
return
|
|
58
|
+
return { count: 0, resetAt: nextMidnightUtc() };
|
|
95
59
|
}
|
|
96
|
-
function
|
|
97
|
-
return new Promise((resolve, reject) => {
|
|
98
|
-
const requestId = (0, import_crypto2.randomUUID)();
|
|
99
|
-
const body = JSON.stringify({
|
|
100
|
-
api_key: apiKey,
|
|
101
|
-
tool_name: toolName,
|
|
102
|
-
request_id: requestId,
|
|
103
|
-
timestamp_ms: Date.now(),
|
|
104
|
-
client_version: CLIENT_VERSION
|
|
105
|
-
});
|
|
106
|
-
const url = new URL("/v1/auth/verify", serverUrl);
|
|
107
|
-
const options = {
|
|
108
|
-
hostname: url.hostname,
|
|
109
|
-
port: url.port || 443,
|
|
110
|
-
path: url.pathname,
|
|
111
|
-
method: "POST",
|
|
112
|
-
headers: {
|
|
113
|
-
"Content-Type": "application/json",
|
|
114
|
-
"Content-Length": Buffer.byteLength(body),
|
|
115
|
-
"X-Request-ID": requestId
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
const req = https.request(options, (res) => {
|
|
119
|
-
let data = "";
|
|
120
|
-
res.on("data", (chunk) => {
|
|
121
|
-
data += chunk.toString();
|
|
122
|
-
});
|
|
123
|
-
res.on("end", () => {
|
|
124
|
-
try {
|
|
125
|
-
const parsed = JSON.parse(data);
|
|
126
|
-
resolve(parsed);
|
|
127
|
-
} catch {
|
|
128
|
-
reject(new Error("Auth server returned non-JSON response"));
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
req.setTimeout(3e3, () => {
|
|
133
|
-
req.destroy(new Error("Auth server request timed out (3000ms)"));
|
|
134
|
-
});
|
|
135
|
-
req.on("error", (err) => {
|
|
136
|
-
reject(err);
|
|
137
|
-
});
|
|
138
|
-
req.write(body);
|
|
139
|
-
req.end();
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
async function serverSideGate(apiKey, toolName) {
|
|
143
|
-
if (!HELM_AUTH_SERVER_URL) {
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
const cached = getCached(apiKey, toolName);
|
|
147
|
-
if (cached !== null) {
|
|
148
|
-
if (!cached.allowed) {
|
|
149
|
-
return `Access denied by Helm Auth Server (plan: ${cached.planTier}, cached)`;
|
|
150
|
-
}
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
if (!circuitCanAttempt()) {
|
|
154
|
-
return "Helm Auth Server is temporarily unreachable. Access denied (fail-closed). Please retry in a few minutes.";
|
|
155
|
-
}
|
|
60
|
+
function writeUsageFile(entry) {
|
|
156
61
|
try {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
setCached(apiKey, toolName, resp.allowed, resp.plan_tier ?? "unknown", ttl);
|
|
160
|
-
circuitRecordSuccess();
|
|
161
|
-
if (!resp.allowed) {
|
|
162
|
-
const reason = resp.reason ? ` Reason: ${resp.reason}` : "";
|
|
163
|
-
return `Access denied by Helm Auth Server (plan: ${resp.plan_tier}).${reason}`;
|
|
164
|
-
}
|
|
165
|
-
return null;
|
|
62
|
+
if (!fs.existsSync(USAGE_DIR)) fs.mkdirSync(USAGE_DIR, { recursive: true });
|
|
63
|
+
fs.writeFileSync(USAGE_FILE, JSON.stringify(entry), "utf8");
|
|
166
64
|
} catch {
|
|
167
|
-
circuitRecordFailure();
|
|
168
|
-
return "Helm Auth Server is unreachable. Access denied (fail-closed). Please retry later.";
|
|
169
65
|
}
|
|
170
66
|
}
|
|
171
|
-
|
|
172
|
-
if (
|
|
173
|
-
return {
|
|
174
|
-
}
|
|
175
|
-
if (!apiKey.startsWith("hk-")) {
|
|
176
|
-
return { valid: false, error: "Invalid key format" };
|
|
67
|
+
function checkRateLimit(apiKey, clientIp) {
|
|
68
|
+
if (apiKey && apiKey.trim().length > 0) {
|
|
69
|
+
return { allowed: true, remaining: -1, tier: "paid" };
|
|
177
70
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
if (clientIp === "stdio") {
|
|
73
|
+
let entry2 = readUsageFile();
|
|
74
|
+
if (now >= entry2.resetAt) {
|
|
75
|
+
entry2 = { count: 0, resetAt: nextMidnightUtc() };
|
|
181
76
|
}
|
|
182
|
-
|
|
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" };
|
|
183
83
|
}
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
if (
|
|
187
|
-
|
|
84
|
+
const bucketKey = `ip:${clientIp}`;
|
|
85
|
+
let entry = buckets.get(bucketKey);
|
|
86
|
+
if (!entry || now >= entry.resetAt) {
|
|
87
|
+
entry = { count: 0, resetAt: nextMidnightUtc() };
|
|
88
|
+
buckets.set(bucketKey, entry);
|
|
188
89
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
Buffer.from(parts[1], "base64url").toString("utf8")
|
|
192
|
-
);
|
|
193
|
-
if (typeof payload.exp !== "number" || payload.exp < Math.floor(Date.now() / 1e3)) {
|
|
194
|
-
return { valid: false, error: "Key expired" };
|
|
195
|
-
}
|
|
196
|
-
if (payload.iss !== "helm-protocol") {
|
|
197
|
-
return { valid: false, error: "Invalid issuer" };
|
|
198
|
-
}
|
|
199
|
-
const publicKey = (0, import_crypto.createPublicKey)({
|
|
200
|
-
key: Buffer.from(HELM_PUBLIC_KEY_B64, "base64"),
|
|
201
|
-
format: "der",
|
|
202
|
-
type: "spki"
|
|
203
|
-
});
|
|
204
|
-
const sigInput = Buffer.from(`${parts[0]}.${parts[1]}`);
|
|
205
|
-
const sig = Buffer.from(parts[2], "base64url");
|
|
206
|
-
const isValid = (0, import_crypto.verify)(null, sigInput, publicKey, sig);
|
|
207
|
-
if (!isValid) {
|
|
208
|
-
return { valid: false, error: "Invalid signature" };
|
|
209
|
-
}
|
|
210
|
-
if (opts?.redisClient && payload.type === "short" && payload.jti) {
|
|
211
|
-
const redisKey = `helm:jti:${payload.jti}`;
|
|
212
|
-
const ttl = Math.max(
|
|
213
|
-
1,
|
|
214
|
-
payload.exp - Math.floor(Date.now() / 1e3)
|
|
215
|
-
);
|
|
216
|
-
const isNew = await opts.redisClient.set(redisKey, "1", {
|
|
217
|
-
NX: true,
|
|
218
|
-
EX: ttl
|
|
219
|
-
});
|
|
220
|
-
if (isNew === null) {
|
|
221
|
-
opts.onReplayDetected?.(payload.jti);
|
|
222
|
-
return { valid: false, error: "Replay detected" };
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (opts?.toolName) {
|
|
226
|
-
const gateError = await serverSideGate(apiKey, opts.toolName);
|
|
227
|
-
if (gateError !== null) {
|
|
228
|
-
return { valid: false, error: gateError };
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
return {
|
|
232
|
-
valid: true,
|
|
233
|
-
tier: typeof payload.tier === "number" ? payload.tier : 0,
|
|
234
|
-
orgId: payload.sub
|
|
235
|
-
};
|
|
236
|
-
} catch {
|
|
237
|
-
return { valid: false, error: "Verification error" };
|
|
90
|
+
if (entry.count >= FREE_TIER_LIMIT) {
|
|
91
|
+
return { allowed: false, remaining: 0, tier: "free" };
|
|
238
92
|
}
|
|
93
|
+
entry.count += 1;
|
|
94
|
+
return { allowed: true, remaining: FREE_TIER_LIMIT - entry.count, tier: "free" };
|
|
95
|
+
}
|
|
96
|
+
function resolveApiKey(headerValue) {
|
|
97
|
+
return headerValue?.trim() || process.env.TTT_API_KEY?.trim() || void 0;
|
|
239
98
|
}
|
|
240
|
-
const AUTH_ERROR_MESSAGE = `This tool requires a Helm Protocol API key.
|
|
241
|
-
|
|
242
|
-
To get access:
|
|
243
|
-
1. Contact enterprise@helmprotocol.io
|
|
244
|
-
2. Add the key to your MCP config:
|
|
245
|
-
{ "env": { "HELM_API_KEY": "hk-..." } }
|
|
246
|
-
|
|
247
|
-
Optional server-side validation:
|
|
248
|
-
Set HELM_AUTH_SERVER_URL=https://auth.helm-protocol.com for additional
|
|
249
|
-
server-side gate (quota enforcement, revocation, plan tier checks).
|
|
250
|
-
HTTPS is required \u2014 HTTP URLs are rejected at startup.
|
|
251
|
-
|
|
252
|
-
Free tools available without a key: pot_health, pot_stats`;
|
|
253
99
|
// Annotate the CommonJS export names for ESM import in node:
|
|
254
100
|
0 && (module.exports = {
|
|
255
|
-
|
|
256
|
-
|
|
101
|
+
FREE_TIER_LIMIT,
|
|
102
|
+
checkRateLimit,
|
|
103
|
+
resolveApiKey
|
|
257
104
|
});
|