bykaranteli-mcp 0.8.0 → 0.9.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/README.md +17 -3
- package/dist/tools.js +279 -24
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
MCP (Model Context Protocol) server for **live crypto derivatives data**: funding rates, cross-exchange funding arbitrage, open interest pressure, Fear & Greed, BTC dominance and a verified signal track record.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
34 read-only tools over the free, no-auth public JSON API of [bykaranteli.com](https://bykaranteli.com/developers). No API key, no account, no rate-limit registration. Data covers Binance USDT-M perpetuals (funding arbitrage additionally compares OKX, Bybit, Gate, HTX, BingX, Kraken, MEXC and Bitget).
|
|
6
6
|
|
|
7
7
|
## Hosted endpoint (no install)
|
|
8
8
|
|
|
9
9
|
Paste `https://mcp.bykaranteli.com` as a custom connector in any MCP-capable
|
|
10
|
-
assistant. Same
|
|
10
|
+
assistant. Same 34 tools, nothing to install, no key.
|
|
11
11
|
|
|
12
12
|
## Quick start
|
|
13
13
|
|
|
@@ -62,6 +62,20 @@ Requires Node.js 18 or newer.
|
|
|
62
62
|
| `get_liquidation_cascades` | "What caused that flush?", "Who got liquidated this week?" |
|
|
63
63
|
| `get_open_interest` | "Is leverage entering the market?", "Are shorts building in XRP?" |
|
|
64
64
|
| `get_psi_charge` | "What liquidity state is the market in?", "Is parked money deploying?" |
|
|
65
|
+
| `get_theme_indices` | "Which crypto narrative is leading: AI, RWA, DePIN, memes, L2s, DeFi?" |
|
|
66
|
+
| `get_factor_board` | "Which indicators sit in an unusual band today, and what followed historically?" |
|
|
67
|
+
| `get_venue_markets` | "Total BTC open interest across exchanges?", "What is the DEX share of perp OI?", "Is USDT off peg anywhere?" |
|
|
68
|
+
| `get_lead_lag` | "Does Coinbase or Binance move first?" |
|
|
69
|
+
| `get_iv_surface` | "What is BTC implied vol by expiry?", "Is downside protection expensive (skew)?" |
|
|
70
|
+
| `get_whale_tape` | "Are whales buying or selling right now?" |
|
|
71
|
+
| `get_correlations` | "How correlated is SOL to BTC over 30 days?" |
|
|
72
|
+
| `get_new_listings` | "Which perpetuals were listed this week, and where first?" |
|
|
73
|
+
| `get_macro_liquidity` | "What is the Fed balance sheet / RRP / stablecoin supply doing?" |
|
|
74
|
+
| `get_network_health` | "Bitcoin hashrate, difficulty, fees, mempool right now?" |
|
|
75
|
+
| `get_altseason` | "Is it altseason?", "What is the altcoin season index?" |
|
|
76
|
+
| `get_metric_context` | "Is today's funding extreme historically?", "Where does this reading sit in its distribution?" |
|
|
77
|
+
| `get_quantum_exposure` | "How much Bitcoin is quantum-vulnerable?", "What is the P2PK exposure?" |
|
|
78
|
+
| `get_liqmap` | "Where are the BTC liquidation clusters?", "Where would leveraged longs get liquidated?" |
|
|
65
79
|
|
|
66
80
|
All responses are JSON and carry a `generatedAt` timestamp plus a `source` URL to the human-readable page. Symbols accept both `BTCUSDT` and bare `BTC`.
|
|
67
81
|
|
|
@@ -91,7 +105,7 @@ MIT. Attribution appreciated: "ByKaranteli (bykaranteli.com)".
|
|
|
91
105
|
|
|
92
106
|
## Paid depth (optional)
|
|
93
107
|
|
|
94
|
-
The
|
|
108
|
+
The 34 tools above are free and stay free. For recorded history and raw records
|
|
95
109
|
beyond the live snapshots, bykaranteli.com also exposes pay-per-call x402
|
|
96
110
|
endpoints (USDC on Solana or Base, priced per call, no account):
|
|
97
111
|
<https://bykaranteli.com/developers#x402> · machine catalog:
|
package/dist/tools.js
CHANGED
|
@@ -24,7 +24,10 @@ export function registerByKaranteliTools(server, options) {
|
|
|
24
24
|
headers: { accept: "application/json", "user-agent": USER_AGENT },
|
|
25
25
|
});
|
|
26
26
|
if (!res.ok) {
|
|
27
|
-
|
|
27
|
+
/* Carry the route's own error body (audit P3 #412): "HTTP 400" alone
|
|
28
|
+
* hid the actual reason (unknown metric, bad symbol...). */
|
|
29
|
+
const body = (await res.text().catch(() => "")).replace(/\s+/g, " ").slice(0, 2000);
|
|
30
|
+
throw new Error(`${path} returned HTTP ${res.status}${body ? `: ${body}` : ""}`);
|
|
28
31
|
}
|
|
29
32
|
return await res.json();
|
|
30
33
|
}
|
|
@@ -32,14 +35,29 @@ export function registerByKaranteliTools(server, options) {
|
|
|
32
35
|
clearTimeout(timer);
|
|
33
36
|
}
|
|
34
37
|
}
|
|
38
|
+
/* Every answer carries where it came from and when (README promise; audit
|
|
39
|
+
* P3 #416): routes that already emit these keep their own values. */
|
|
40
|
+
function withProvenance(data, path) {
|
|
41
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
42
|
+
return data;
|
|
43
|
+
const record = data;
|
|
44
|
+
return {
|
|
45
|
+
...record,
|
|
46
|
+
generatedAt: typeof record.generatedAt === "string" ? record.generatedAt : new Date().toISOString(),
|
|
47
|
+
source: typeof record.source === "string" ? record.source : `${PUBLIC_URL}${path.replace(/^\/api\/public/, "").split("?")[0] || "/"}`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
35
50
|
function ok(data) {
|
|
36
51
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
37
52
|
}
|
|
38
53
|
function fail(err) {
|
|
39
54
|
const message = err instanceof Error ? err.message : String(err);
|
|
55
|
+
const permanent = /returned HTTP 4\d\d/.test(message);
|
|
40
56
|
const text = err instanceof InputError
|
|
41
57
|
? `Invalid input: ${message}`
|
|
42
|
-
:
|
|
58
|
+
: permanent
|
|
59
|
+
? `Error fetching data from bykaranteli.com: ${message}. This is a permanent error for these inputs (wrong parameter or route), not a transient one; retrying will not help.`
|
|
60
|
+
: `Error fetching data from bykaranteli.com: ${message}. The API is free and unauthenticated; transient errors usually resolve on retry.`;
|
|
43
61
|
return {
|
|
44
62
|
content: [{ type: "text", text }],
|
|
45
63
|
isError: true,
|
|
@@ -88,7 +106,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
88
106
|
});
|
|
89
107
|
server.registerTool("get_liquidations", {
|
|
90
108
|
title: "Crypto liquidations: daily long/short totals per symbol and exchange",
|
|
91
|
-
description: "Call this when the user asks how much was liquidated in crypto futures, whether longs or shorts got flushed, or for liquidation history. Returns daily long and short liquidation totals in USD per symbol and exchange, recorded from ByKaranteli's own Binance, Bybit and
|
|
109
|
+
description: "Call this when the user asks how much was liquidated in crypto futures, whether longs or shorts got flushed, or for liquidation history. Returns daily long and short liquidation totals in USD per symbol and exchange, recorded from ByKaranteli's own Binance, Bybit, OKX, Gate, HTX and dYdX stream collectors (recorded events, a floor, not estimates). One row per finalized UTC day, symbol and exchange; history begins 2026-07-30 and grows daily.",
|
|
92
110
|
inputSchema: {
|
|
93
111
|
symbol: z
|
|
94
112
|
.string()
|
|
@@ -107,10 +125,70 @@ export function registerByKaranteliTools(server, options) {
|
|
|
107
125
|
const wantDays = days ?? 7;
|
|
108
126
|
const dates = [...new Set(rows.map((r) => String(r.date)))].sort().reverse().slice(0, wantDays);
|
|
109
127
|
const dateSet = new Set(dates);
|
|
110
|
-
|
|
128
|
+
/* Bare "BTC" is accepted like every other tool (audit P3 #414). */
|
|
129
|
+
const wantSymbol = symbol ? (symbol.endsWith("USDT") ? symbol : `${symbol}USDT`) : undefined;
|
|
130
|
+
const filtered = rows.filter((r) => dateSet.has(String(r.date)) && (!wantSymbol || String(r.symbol).toUpperCase() === wantSymbol));
|
|
131
|
+
/* Totals are computed over EVERY matching row before the row cap. A
|
|
132
|
+
* symbol-less 7-day call matches ~7,000 rows; returning only the first
|
|
133
|
+
* 400 (one day, alphabetically) used to understate the weekly total
|
|
134
|
+
* 11.5x with no warning in the payload (audit 2026-08-20 P1-67). */
|
|
135
|
+
const num = (v) => {
|
|
136
|
+
const n = Number(v);
|
|
137
|
+
return Number.isFinite(n) ? n : 0;
|
|
138
|
+
};
|
|
139
|
+
const byDate = new Map();
|
|
140
|
+
let longTotal = 0;
|
|
141
|
+
let shortTotal = 0;
|
|
142
|
+
let eventsTotal = 0;
|
|
143
|
+
for (const r of filtered) {
|
|
144
|
+
const date = String(r.date);
|
|
145
|
+
const long = num(r.long_liquidations_usd);
|
|
146
|
+
const short = num(r.short_liquidations_usd);
|
|
147
|
+
const events = num(r.events);
|
|
148
|
+
longTotal += long;
|
|
149
|
+
shortTotal += short;
|
|
150
|
+
eventsTotal += events;
|
|
151
|
+
const day = byDate.get(date) ?? { date, long_liquidations_usd: 0, short_liquidations_usd: 0, events: 0, rows: 0 };
|
|
152
|
+
day.long_liquidations_usd += long;
|
|
153
|
+
day.short_liquidations_usd += short;
|
|
154
|
+
day.events += events;
|
|
155
|
+
day.rows += 1;
|
|
156
|
+
byDate.set(date, day);
|
|
157
|
+
}
|
|
158
|
+
const MAX_ROWS = 400;
|
|
159
|
+
/* Largest rows first within the cap, newest day first. */
|
|
160
|
+
const ordered = [...filtered].sort((a, b) => {
|
|
161
|
+
const dd = String(b.date).localeCompare(String(a.date));
|
|
162
|
+
if (dd !== 0)
|
|
163
|
+
return dd;
|
|
164
|
+
return (num(b.long_liquidations_usd) + num(b.short_liquidations_usd)) - (num(a.long_liquidations_usd) + num(a.short_liquidations_usd));
|
|
165
|
+
});
|
|
166
|
+
const truncated = ordered.length > MAX_ROWS;
|
|
167
|
+
const baseNote = "Recorded from public exchange streams; totals are a floor (Binance throttles its stream). Live 24h view: " + PUBLIC_URL + "/liquidations";
|
|
111
168
|
return ok({
|
|
112
|
-
|
|
113
|
-
|
|
169
|
+
summary: {
|
|
170
|
+
window_days: wantDays,
|
|
171
|
+
dates_covered: dates,
|
|
172
|
+
totals: {
|
|
173
|
+
long_liquidations_usd: Math.round(longTotal),
|
|
174
|
+
short_liquidations_usd: Math.round(shortTotal),
|
|
175
|
+
total_liquidations_usd: Math.round(longTotal + shortTotal),
|
|
176
|
+
events: eventsTotal,
|
|
177
|
+
rows_matched: filtered.length,
|
|
178
|
+
},
|
|
179
|
+
by_date: [...byDate.values()].sort((a, b) => b.date.localeCompare(a.date)).map((d) => ({
|
|
180
|
+
...d,
|
|
181
|
+
long_liquidations_usd: Math.round(d.long_liquidations_usd),
|
|
182
|
+
short_liquidations_usd: Math.round(d.short_liquidations_usd),
|
|
183
|
+
})),
|
|
184
|
+
},
|
|
185
|
+
rows: ordered.slice(0, MAX_ROWS),
|
|
186
|
+
rows_returned: Math.min(ordered.length, MAX_ROWS),
|
|
187
|
+
rows_matched: filtered.length,
|
|
188
|
+
truncated,
|
|
189
|
+
note: truncated
|
|
190
|
+
? `rows is capped at ${MAX_ROWS} of ${filtered.length} matching rows (largest first). Use summary.totals and summary.by_date for complete figures, or pass a symbol filter. ` + baseNote
|
|
191
|
+
: baseNote,
|
|
114
192
|
source: `${PUBLIC_URL}/liquidations`,
|
|
115
193
|
});
|
|
116
194
|
}
|
|
@@ -131,9 +209,24 @@ export function registerByKaranteliTools(server, options) {
|
|
|
131
209
|
const d = (await fetchJson("/api/v1/public/datasets/etf-flows.json"));
|
|
132
210
|
const rows = Array.isArray(d.rows) ? d.rows : [];
|
|
133
211
|
const filtered = rows.filter((r) => !asset || String(r.asset) === asset);
|
|
212
|
+
/* Window by DATE like get_liquidations (audit D14-03): the old row-count
|
|
213
|
+
* slice could cut one asset's day in half and reported no totals and no
|
|
214
|
+
* truncation flag, so partial sums read as full ones. */
|
|
215
|
+
const wantDays = days ?? 10;
|
|
216
|
+
const dates = [...new Set(filtered.map((r) => String(r.date)))].sort().reverse().slice(0, wantDays);
|
|
217
|
+
const dateSet = new Set(dates);
|
|
218
|
+
const windowed = filtered.filter((r) => dateSet.has(String(r.date)));
|
|
219
|
+
const totals = {};
|
|
220
|
+
for (const r of windowed) {
|
|
221
|
+
const a = String(r.asset);
|
|
222
|
+
totals[a] = (totals[a] ?? 0) + (Number(r.net_inflow_usd) || 0);
|
|
223
|
+
}
|
|
134
224
|
return ok({
|
|
135
|
-
rows:
|
|
136
|
-
|
|
225
|
+
rows: windowed,
|
|
226
|
+
window_days: dates.length,
|
|
227
|
+
window_net_inflow_usd: totals,
|
|
228
|
+
truncated: dates.length < new Set(filtered.map((r) => String(r.date))).size,
|
|
229
|
+
note: "Finalized US trading days only; a positive net_inflow_usd means the funds bought more of the asset than they sold that day. window_net_inflow_usd sums the returned window per asset.",
|
|
137
230
|
source: `${PUBLIC_URL}/etf`,
|
|
138
231
|
});
|
|
139
232
|
}
|
|
@@ -142,8 +235,8 @@ export function registerByKaranteliTools(server, options) {
|
|
|
142
235
|
}
|
|
143
236
|
});
|
|
144
237
|
server.registerTool("get_funding_heatmap", {
|
|
145
|
-
title: "Funding rates across
|
|
146
|
-
description: "Call this when the user asks for the full current funding table across the
|
|
238
|
+
title: "Funding rates across the ~30 most traded Binance perps",
|
|
239
|
+
description: "Call this when the user asks for the full current funding table across the ~30 most traded Binance perps (28-30 rows; contracts without a live funding print are skipped), or the funding rate of one specific coin. For a pre-ranked top-10 of the most extreme funding rates, use get_top_movers instead. Returns per-symbol funding rate (per settlement interval), 24h open interest change and 24h price change for the most traded Binance USDT-M perpetuals. Positive funding means longs pay shorts.",
|
|
147
240
|
inputSchema: {
|
|
148
241
|
symbol: z
|
|
149
242
|
.string()
|
|
@@ -160,7 +253,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
160
253
|
if (!row) {
|
|
161
254
|
return ok({
|
|
162
255
|
generatedAt: d.generatedAt,
|
|
163
|
-
note: `${want} is not in the
|
|
256
|
+
note: `${want} is not in the heatmap set. Tracked symbols: ${d.rows.map((r) => r.symbol).join(", ")}`,
|
|
164
257
|
});
|
|
165
258
|
}
|
|
166
259
|
return ok({ generatedAt: d.generatedAt, row, source: `${PUBLIC_URL}/heatmap` });
|
|
@@ -299,6 +392,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
299
392
|
return ok({
|
|
300
393
|
...d,
|
|
301
394
|
daily_points: Array.isArray(daily) ? daily.slice(-30) : daily,
|
|
395
|
+
daily_points_truncated: Array.isArray(daily) ? daily.length > 30 : false,
|
|
302
396
|
source: `${PUBLIC_URL}/symbols/${want}`,
|
|
303
397
|
});
|
|
304
398
|
}
|
|
@@ -327,7 +421,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
327
421
|
annotations: READ_ONLY,
|
|
328
422
|
}, async () => {
|
|
329
423
|
try {
|
|
330
|
-
return ok(await fetchJson("/api/public/cot"));
|
|
424
|
+
return ok(withProvenance(await fetchJson("/api/public/cot"), "/api/public/cot"));
|
|
331
425
|
}
|
|
332
426
|
catch (err) {
|
|
333
427
|
return fail(err);
|
|
@@ -340,7 +434,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
340
434
|
annotations: READ_ONLY,
|
|
341
435
|
}, async () => {
|
|
342
436
|
try {
|
|
343
|
-
return ok(await fetchJson("/api/public/options"));
|
|
437
|
+
return ok(withProvenance(await fetchJson("/api/public/options"), "/api/public/options"));
|
|
344
438
|
}
|
|
345
439
|
catch (err) {
|
|
346
440
|
return fail(err);
|
|
@@ -353,7 +447,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
353
447
|
annotations: READ_ONLY,
|
|
354
448
|
}, async () => {
|
|
355
449
|
try {
|
|
356
|
-
return ok(await fetchJson("/api/public/premium"));
|
|
450
|
+
return ok(withProvenance(await fetchJson("/api/public/premium"), "/api/public/premium"));
|
|
357
451
|
}
|
|
358
452
|
catch (err) {
|
|
359
453
|
return fail(err);
|
|
@@ -366,7 +460,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
366
460
|
annotations: READ_ONLY,
|
|
367
461
|
}, async () => {
|
|
368
462
|
try {
|
|
369
|
-
return ok(await fetchJson("/api/public/flow"));
|
|
463
|
+
return ok(withProvenance(await fetchJson("/api/public/flow"), "/api/public/flow"));
|
|
370
464
|
}
|
|
371
465
|
catch (err) {
|
|
372
466
|
return fail(err);
|
|
@@ -379,7 +473,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
379
473
|
annotations: READ_ONLY,
|
|
380
474
|
}, async () => {
|
|
381
475
|
try {
|
|
382
|
-
return ok(await fetchJson("/api/public/options-flow"));
|
|
476
|
+
return ok(withProvenance(await fetchJson("/api/public/options-flow"), "/api/public/options-flow"));
|
|
383
477
|
}
|
|
384
478
|
catch (err) {
|
|
385
479
|
return fail(err);
|
|
@@ -392,7 +486,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
392
486
|
annotations: READ_ONLY,
|
|
393
487
|
}, async () => {
|
|
394
488
|
try {
|
|
395
|
-
return ok(await fetchJson("/api/public/slippage"));
|
|
489
|
+
return ok(withProvenance(await fetchJson("/api/public/slippage"), "/api/public/slippage"));
|
|
396
490
|
}
|
|
397
491
|
catch (err) {
|
|
398
492
|
return fail(err);
|
|
@@ -405,7 +499,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
405
499
|
annotations: READ_ONLY,
|
|
406
500
|
}, async () => {
|
|
407
501
|
try {
|
|
408
|
-
return ok(await fetchJson("/api/public/events"));
|
|
502
|
+
return ok(withProvenance(await fetchJson("/api/public/events"), "/api/public/events"));
|
|
409
503
|
}
|
|
410
504
|
catch (err) {
|
|
411
505
|
return fail(err);
|
|
@@ -418,7 +512,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
418
512
|
annotations: READ_ONLY,
|
|
419
513
|
}, async () => {
|
|
420
514
|
try {
|
|
421
|
-
return ok(await fetchJson("/api/public/incidents"));
|
|
515
|
+
return ok(withProvenance(await fetchJson("/api/public/incidents"), "/api/public/incidents"));
|
|
422
516
|
}
|
|
423
517
|
catch (err) {
|
|
424
518
|
return fail(err);
|
|
@@ -431,7 +525,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
431
525
|
annotations: READ_ONLY,
|
|
432
526
|
}, async () => {
|
|
433
527
|
try {
|
|
434
|
-
return ok(await fetchJson("/api/public/oi"));
|
|
528
|
+
return ok(withProvenance(await fetchJson("/api/public/oi"), "/api/public/oi"));
|
|
435
529
|
}
|
|
436
530
|
catch (err) {
|
|
437
531
|
return fail(err);
|
|
@@ -444,7 +538,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
444
538
|
annotations: READ_ONLY,
|
|
445
539
|
}, async () => {
|
|
446
540
|
try {
|
|
447
|
-
return ok(await fetchJson("/api/public/charge"));
|
|
541
|
+
return ok(withProvenance(await fetchJson("/api/public/charge"), "/api/public/charge"));
|
|
448
542
|
}
|
|
449
543
|
catch (err) {
|
|
450
544
|
return fail(err);
|
|
@@ -457,7 +551,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
457
551
|
annotations: READ_ONLY,
|
|
458
552
|
}, async () => {
|
|
459
553
|
try {
|
|
460
|
-
return ok(await fetchJson("/api/public/altseason"));
|
|
554
|
+
return ok(withProvenance(await fetchJson("/api/public/altseason"), "/api/public/altseason"));
|
|
461
555
|
}
|
|
462
556
|
catch (err) {
|
|
463
557
|
return fail(err);
|
|
@@ -470,7 +564,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
470
564
|
annotations: READ_ONLY,
|
|
471
565
|
}, async () => {
|
|
472
566
|
try {
|
|
473
|
-
return ok(await fetchJson("/api/public/quantum"));
|
|
567
|
+
return ok(withProvenance(await fetchJson("/api/public/quantum"), "/api/public/quantum"));
|
|
474
568
|
}
|
|
475
569
|
catch (err) {
|
|
476
570
|
return fail(err);
|
|
@@ -488,7 +582,168 @@ export function registerByKaranteliTools(server, options) {
|
|
|
488
582
|
annotations: READ_ONLY,
|
|
489
583
|
}, async ({ metric }) => {
|
|
490
584
|
try {
|
|
491
|
-
return ok(await fetchJson(`/api/public/context?metric=${encodeURIComponent(metric)}`));
|
|
585
|
+
return ok(withProvenance(await fetchJson(`/api/public/context?metric=${encodeURIComponent(metric)}`), `/api/public/context?metric=${encodeURIComponent(metric)}`));
|
|
586
|
+
}
|
|
587
|
+
catch (err) {
|
|
588
|
+
return fail(err);
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
server.registerTool("get_theme_indices", {
|
|
592
|
+
title: "Crypto narrative indices (AI, RWA, DePIN, meme, L1, L2, DeFi, quantum)",
|
|
593
|
+
description: "Call this when the user asks which crypto narrative or sector is leading, about rotation between AI, RWA, DePIN, memecoins, layer 1, layer 2, DeFi or quantum coins, or for a theme index. Returns eight equal-weight fixed-basket indices rebased to 100 on 2025-01-01 with 1d/7d/30d/90d/YTD returns, vs BTC, and the member lists; daily points are omitted unless include_points is true.",
|
|
594
|
+
inputSchema: { include_points: z.boolean().optional().describe("boolean, optional: include the daily index points (large)") },
|
|
595
|
+
annotations: READ_ONLY,
|
|
596
|
+
}, async (args) => {
|
|
597
|
+
try {
|
|
598
|
+
const path = args.include_points === true ? "/api/public/themes" : "/api/public/themes?points=0";
|
|
599
|
+
return ok(withProvenance(await fetchJson(path), path));
|
|
600
|
+
}
|
|
601
|
+
catch (err) {
|
|
602
|
+
return fail(err);
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
server.registerTool("get_factor_board", {
|
|
606
|
+
title: "Factor board: what followed days like today across recorded metrics",
|
|
607
|
+
description: "Call this when the user asks which indicators currently sit in an unusual band, whether a metric's current level historically preceded BTC moves, or for a cross-metric conditional overview. Returns every recorded metric in its historical band with the median 7-day BTC move that followed versus the base rate, with an n >= 30 gate; distributions, not forecasts.",
|
|
608
|
+
inputSchema: {},
|
|
609
|
+
annotations: READ_ONLY,
|
|
610
|
+
}, async () => {
|
|
611
|
+
try {
|
|
612
|
+
return ok(withProvenance(await fetchJson("/api/public/factors"), "/api/public/factors"));
|
|
613
|
+
}
|
|
614
|
+
catch (err) {
|
|
615
|
+
return fail(err);
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
server.registerTool("get_venue_markets", {
|
|
619
|
+
title: "Exchange coverage: OI, volume, funding and pegs across 43 exchanges",
|
|
620
|
+
description: "Call this when the user asks about total open interest across exchanges, which venues hold the most OI, DEX versus CEX share, funding dispersion between venues, or stablecoin pegs. Returns the latest 10-minute snapshot aggregates across 56 feeds on 43 exchanges; pass symbol for one coin's per-venue rows.",
|
|
621
|
+
inputSchema: { symbol: z.string().optional().describe("string, optional base asset, e.g. BTC") },
|
|
622
|
+
annotations: READ_ONLY,
|
|
623
|
+
}, async (args) => {
|
|
624
|
+
try {
|
|
625
|
+
const path = args.symbol ? `/api/public/venues/markets?symbol=${encodeURIComponent(args.symbol.toUpperCase())}` : "/api/public/venues/markets";
|
|
626
|
+
return ok(withProvenance(await fetchJson(path), path));
|
|
627
|
+
}
|
|
628
|
+
catch (err) {
|
|
629
|
+
return fail(err);
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
server.registerTool("get_lead_lag", {
|
|
633
|
+
title: "Venue lead-lag: who moves first (Coinbase, Kraken, Binance)",
|
|
634
|
+
description: "Call this when the user asks which exchange leads price discovery or whether spot or perp moves first. Returns per-pair daily cross-correlations of one-minute returns at lags -3..+3 and the lead asymmetry, with the share of days each venue led.",
|
|
635
|
+
inputSchema: {},
|
|
636
|
+
annotations: READ_ONLY,
|
|
637
|
+
}, async () => {
|
|
638
|
+
try {
|
|
639
|
+
return ok(withProvenance(await fetchJson("/api/public/venues/lead-lag"), "/api/public/venues/lead-lag"));
|
|
640
|
+
}
|
|
641
|
+
catch (err) {
|
|
642
|
+
return fail(err);
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
server.registerTool("get_iv_surface", {
|
|
646
|
+
title: "Options implied-volatility surface and 25-delta skew",
|
|
647
|
+
description: "Call this when the user asks about implied volatility by strike or expiry, skew, put versus call IV, term structure of IV, or whether downside protection is expensive. Returns the IV surface (expiry x moneyness), per-expiry ATM / 25-delta put and call IV, skew and butterfly, and the constant-30d history, from the daily Deribit chain.",
|
|
648
|
+
inputSchema: { currency: z.string().optional().describe("BTC or ETH, default BTC") },
|
|
649
|
+
annotations: READ_ONLY,
|
|
650
|
+
}, async (args) => {
|
|
651
|
+
try {
|
|
652
|
+
const path = `/api/public/options/surface?currency=${(args.currency ?? "BTC").toUpperCase() === "ETH" ? "ETH" : "BTC"}`;
|
|
653
|
+
return ok(withProvenance(await fetchJson(path), path));
|
|
654
|
+
}
|
|
655
|
+
catch (err) {
|
|
656
|
+
return fail(err);
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
server.registerTool("get_whale_tape", {
|
|
660
|
+
title: "Whale tape: $1M+ aggressive prints with 24h buy share",
|
|
661
|
+
description: "Call this when the user asks about whale trades, large market orders, or whether big players are buying or selling right now. Returns recent $1M+ aggressive prints recorded live from our own sockets and 24h aggregates with the buy share.",
|
|
662
|
+
inputSchema: {},
|
|
663
|
+
annotations: READ_ONLY,
|
|
664
|
+
}, async () => {
|
|
665
|
+
try {
|
|
666
|
+
return ok(withProvenance(await fetchJson("/api/public/whales"), "/api/public/whales"));
|
|
667
|
+
}
|
|
668
|
+
catch (err) {
|
|
669
|
+
return fail(err);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
server.registerTool("get_correlations", {
|
|
673
|
+
title: "Crypto correlation matrix",
|
|
674
|
+
description: "Call this when the user asks how correlated two coins are, for decorrelated pairs, or how tightly alts track BTC. Returns the 30-day rolling Pearson correlation matrix of daily returns across the top perpetuals.",
|
|
675
|
+
inputSchema: {},
|
|
676
|
+
annotations: READ_ONLY,
|
|
677
|
+
}, async () => {
|
|
678
|
+
try {
|
|
679
|
+
return ok(withProvenance(await fetchJson("/api/public/correlations"), "/api/public/correlations"));
|
|
680
|
+
}
|
|
681
|
+
catch (err) {
|
|
682
|
+
return fail(err);
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
server.registerTool("get_new_listings", {
|
|
686
|
+
title: "New and delisted perpetual contracts",
|
|
687
|
+
description: "Call this when the user asks what new perpetuals were listed, which exchange listed a coin first, or about delistings. Returns listings and delistings across six exchanges from the hourly scan.",
|
|
688
|
+
inputSchema: { days: z.number().int().min(1).max(30).optional().describe("Window in days, 1-30 (default 30). Longer listing history is the paid x402 dataset.") },
|
|
689
|
+
annotations: READ_ONLY,
|
|
690
|
+
}, async (args) => {
|
|
691
|
+
try {
|
|
692
|
+
const path = `/api/public/listings?days=${args.days && args.days > 0 ? Math.floor(args.days) : 30}`;
|
|
693
|
+
return ok(withProvenance(await fetchJson(path), path));
|
|
694
|
+
}
|
|
695
|
+
catch (err) {
|
|
696
|
+
return fail(err);
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
server.registerTool("get_macro_liquidity", {
|
|
700
|
+
title: "Macro liquidity: Fed funds, 10y, balance sheet, RRP, stablecoin supply",
|
|
701
|
+
description: "Call this when the user asks about macro liquidity, the Fed balance sheet, reverse repo, rates or stablecoin supply in relation to crypto. Returns the recorded daily series and latest values.",
|
|
702
|
+
inputSchema: { days: z.number().int().min(1).max(730).optional().describe("Window in days, 1-730 (default 365).") },
|
|
703
|
+
annotations: READ_ONLY,
|
|
704
|
+
}, async (args) => {
|
|
705
|
+
try {
|
|
706
|
+
const path = `/api/public/macro?days=${args.days && args.days > 0 ? Math.floor(args.days) : 365}`;
|
|
707
|
+
return ok(withProvenance(await fetchJson(path), path));
|
|
708
|
+
}
|
|
709
|
+
catch (err) {
|
|
710
|
+
return fail(err);
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
server.registerTool("get_network_health", {
|
|
714
|
+
title: "Bitcoin network health from our own node",
|
|
715
|
+
description: "Call this when the user asks about Bitcoin hashrate, difficulty, fees or mempool congestion. Returns the recorded daily series and latest values measured on ByKaranteli's own node.",
|
|
716
|
+
inputSchema: { days: z.number().int().min(1).max(730).optional().describe("Window in days, 1-730 (default 365).") },
|
|
717
|
+
annotations: READ_ONLY,
|
|
718
|
+
}, async (args) => {
|
|
719
|
+
try {
|
|
720
|
+
const path = `/api/public/network?days=${args.days && args.days > 0 ? Math.floor(args.days) : 365}`;
|
|
721
|
+
return ok(withProvenance(await fetchJson(path), path));
|
|
722
|
+
}
|
|
723
|
+
catch (err) {
|
|
724
|
+
return fail(err);
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
server.registerTool("get_liqmap", {
|
|
728
|
+
title: "LiqMap: estimated liquidation clusters with real prints overlaid",
|
|
729
|
+
description: "Call this when the user asks where liquidation clusters or liquidity pools sit for a perpetual, where leveraged longs/shorts would get liquidated, or for a liquidation heatmap reading. Returns the public LiqMap snapshot for one symbol: modeled liquidation levels by price, zone aggregates and real liquidation prints from six venues. Public tier serves the 24h view; other intervals are a member feature at the source page.",
|
|
730
|
+
inputSchema: {
|
|
731
|
+
symbol: z
|
|
732
|
+
.string()
|
|
733
|
+
.trim()
|
|
734
|
+
.toUpperCase()
|
|
735
|
+
.regex(/^[A-Z0-9]{2,20}$/)
|
|
736
|
+
.optional()
|
|
737
|
+
.describe("Symbol like BTCUSDT (bare BTC accepted). Default BTCUSDT."),
|
|
738
|
+
},
|
|
739
|
+
annotations: READ_ONLY,
|
|
740
|
+
}, async ({ symbol }) => {
|
|
741
|
+
try {
|
|
742
|
+
const raw = symbol ?? "BTCUSDT";
|
|
743
|
+
const sym = raw.endsWith("USDT") ? raw : `${raw}USDT`;
|
|
744
|
+
const path = `/api/liqmap/public?symbol=${encodeURIComponent(sym)}&timeframe=24h`;
|
|
745
|
+
const data = await fetchJson(path);
|
|
746
|
+
return ok({ ...data, source: `${PUBLIC_URL}/liqmap/${sym.replace(/USDT$/, "").toLowerCase()}` });
|
|
492
747
|
}
|
|
493
748
|
catch (err) {
|
|
494
749
|
return fail(err);
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bykaranteli-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "MCP server for live crypto derivatives data from bykaranteli.com: funding rates, funding arbitrage, open interest pressure, Fear & Greed, BTC dominance and verified signal performance. Free, no API key.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"bykaranteli-mcp": "dist/index.js"
|
|
8
8
|
},
|
|
9
|
-
"main": "dist/
|
|
9
|
+
"main": "dist/tools.js",
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"README.md",
|