bykaranteli-mcp 0.7.3 → 0.9.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 +2 -2
- package/dist/tools.js +277 -19
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ MCP (Model Context Protocol) server for **live crypto derivatives data**: fundin
|
|
|
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 23 tools, nothing to install, no key.
|
|
11
11
|
|
|
12
12
|
## Quick start
|
|
13
13
|
|
|
@@ -91,7 +91,7 @@ MIT. Attribution appreciated: "ByKaranteli (bykaranteli.com)".
|
|
|
91
91
|
|
|
92
92
|
## Paid depth (optional)
|
|
93
93
|
|
|
94
|
-
The
|
|
94
|
+
The 23 tools above are free and stay free. For recorded history and raw records
|
|
95
95
|
beyond the live snapshots, bykaranteli.com also exposes pay-per-call x402
|
|
96
96
|
endpoints (USDC on Solana or Base, priced per call, no account):
|
|
97
97
|
<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, 240);
|
|
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 and HTX 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
|
}
|
|
@@ -142,8 +220,8 @@ export function registerByKaranteliTools(server, options) {
|
|
|
142
220
|
}
|
|
143
221
|
});
|
|
144
222
|
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
|
|
223
|
+
title: "Funding rates across the ~30 most traded Binance perps",
|
|
224
|
+
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
225
|
inputSchema: {
|
|
148
226
|
symbol: z
|
|
149
227
|
.string()
|
|
@@ -160,7 +238,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
160
238
|
if (!row) {
|
|
161
239
|
return ok({
|
|
162
240
|
generatedAt: d.generatedAt,
|
|
163
|
-
note: `${want} is not in the
|
|
241
|
+
note: `${want} is not in the heatmap set. Tracked symbols: ${d.rows.map((r) => r.symbol).join(", ")}`,
|
|
164
242
|
});
|
|
165
243
|
}
|
|
166
244
|
return ok({ generatedAt: d.generatedAt, row, source: `${PUBLIC_URL}/heatmap` });
|
|
@@ -327,7 +405,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
327
405
|
annotations: READ_ONLY,
|
|
328
406
|
}, async () => {
|
|
329
407
|
try {
|
|
330
|
-
return ok(await fetchJson("/api/public/cot"));
|
|
408
|
+
return ok(withProvenance(await fetchJson("/api/public/cot"), "/api/public/cot"));
|
|
331
409
|
}
|
|
332
410
|
catch (err) {
|
|
333
411
|
return fail(err);
|
|
@@ -340,7 +418,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
340
418
|
annotations: READ_ONLY,
|
|
341
419
|
}, async () => {
|
|
342
420
|
try {
|
|
343
|
-
return ok(await fetchJson("/api/public/options"));
|
|
421
|
+
return ok(withProvenance(await fetchJson("/api/public/options"), "/api/public/options"));
|
|
344
422
|
}
|
|
345
423
|
catch (err) {
|
|
346
424
|
return fail(err);
|
|
@@ -353,7 +431,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
353
431
|
annotations: READ_ONLY,
|
|
354
432
|
}, async () => {
|
|
355
433
|
try {
|
|
356
|
-
return ok(await fetchJson("/api/public/premium"));
|
|
434
|
+
return ok(withProvenance(await fetchJson("/api/public/premium"), "/api/public/premium"));
|
|
357
435
|
}
|
|
358
436
|
catch (err) {
|
|
359
437
|
return fail(err);
|
|
@@ -366,7 +444,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
366
444
|
annotations: READ_ONLY,
|
|
367
445
|
}, async () => {
|
|
368
446
|
try {
|
|
369
|
-
return ok(await fetchJson("/api/public/flow"));
|
|
447
|
+
return ok(withProvenance(await fetchJson("/api/public/flow"), "/api/public/flow"));
|
|
370
448
|
}
|
|
371
449
|
catch (err) {
|
|
372
450
|
return fail(err);
|
|
@@ -379,7 +457,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
379
457
|
annotations: READ_ONLY,
|
|
380
458
|
}, async () => {
|
|
381
459
|
try {
|
|
382
|
-
return ok(await fetchJson("/api/public/options-flow"));
|
|
460
|
+
return ok(withProvenance(await fetchJson("/api/public/options-flow"), "/api/public/options-flow"));
|
|
383
461
|
}
|
|
384
462
|
catch (err) {
|
|
385
463
|
return fail(err);
|
|
@@ -392,7 +470,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
392
470
|
annotations: READ_ONLY,
|
|
393
471
|
}, async () => {
|
|
394
472
|
try {
|
|
395
|
-
return ok(await fetchJson("/api/public/slippage"));
|
|
473
|
+
return ok(withProvenance(await fetchJson("/api/public/slippage"), "/api/public/slippage"));
|
|
396
474
|
}
|
|
397
475
|
catch (err) {
|
|
398
476
|
return fail(err);
|
|
@@ -405,7 +483,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
405
483
|
annotations: READ_ONLY,
|
|
406
484
|
}, async () => {
|
|
407
485
|
try {
|
|
408
|
-
return ok(await fetchJson("/api/public/events"));
|
|
486
|
+
return ok(withProvenance(await fetchJson("/api/public/events"), "/api/public/events"));
|
|
409
487
|
}
|
|
410
488
|
catch (err) {
|
|
411
489
|
return fail(err);
|
|
@@ -418,7 +496,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
418
496
|
annotations: READ_ONLY,
|
|
419
497
|
}, async () => {
|
|
420
498
|
try {
|
|
421
|
-
return ok(await fetchJson("/api/public/incidents"));
|
|
499
|
+
return ok(withProvenance(await fetchJson("/api/public/incidents"), "/api/public/incidents"));
|
|
422
500
|
}
|
|
423
501
|
catch (err) {
|
|
424
502
|
return fail(err);
|
|
@@ -431,7 +509,7 @@ export function registerByKaranteliTools(server, options) {
|
|
|
431
509
|
annotations: READ_ONLY,
|
|
432
510
|
}, async () => {
|
|
433
511
|
try {
|
|
434
|
-
return ok(await fetchJson("/api/public/oi"));
|
|
512
|
+
return ok(withProvenance(await fetchJson("/api/public/oi"), "/api/public/oi"));
|
|
435
513
|
}
|
|
436
514
|
catch (err) {
|
|
437
515
|
return fail(err);
|
|
@@ -444,7 +522,187 @@ export function registerByKaranteliTools(server, options) {
|
|
|
444
522
|
annotations: READ_ONLY,
|
|
445
523
|
}, async () => {
|
|
446
524
|
try {
|
|
447
|
-
return ok(await fetchJson("/api/public/charge"));
|
|
525
|
+
return ok(withProvenance(await fetchJson("/api/public/charge"), "/api/public/charge"));
|
|
526
|
+
}
|
|
527
|
+
catch (err) {
|
|
528
|
+
return fail(err);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
server.registerTool("get_altseason", {
|
|
532
|
+
title: "Altcoin Season Index (live + recorded history)",
|
|
533
|
+
description: "Call this when the user asks whether it is altseason, how altcoins are doing against Bitcoin, or about market rotation. Returns the live Altcoin Season Index (share of the top 50 Binance perpetual altcoins beating BTC over the trailing 90 days; >=75 altseason, <=25 bitcoin season), the strongest and weakest large alts, and the recorded daily history (never reconstructed).",
|
|
534
|
+
inputSchema: {},
|
|
535
|
+
annotations: READ_ONLY,
|
|
536
|
+
}, async () => {
|
|
537
|
+
try {
|
|
538
|
+
return ok(withProvenance(await fetchJson("/api/public/altseason"), "/api/public/altseason"));
|
|
539
|
+
}
|
|
540
|
+
catch (err) {
|
|
541
|
+
return fail(err);
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
server.registerTool("get_quantum_exposure", {
|
|
545
|
+
title: "Quantum-exposed Bitcoin (daily first-party measurement)",
|
|
546
|
+
description: "Call this when the user asks how much Bitcoin is vulnerable to a quantum computer, about quantum-exposed supply, P2PK coins, or Satoshi-era exposure. Returns the latest daily measurement from ByKaranteli's own Bitcoin Core node: exposed BTC and its share of held value and UTXO count, composition by script family, dormancy cohorts, the dormant-P2PK watch set, and provenance hashes (base_height, base_hash, txoutset_hash) so any figure can be re-verified against any node.",
|
|
547
|
+
inputSchema: {},
|
|
548
|
+
annotations: READ_ONLY,
|
|
549
|
+
}, async () => {
|
|
550
|
+
try {
|
|
551
|
+
return ok(withProvenance(await fetchJson("/api/public/quantum"), "/api/public/quantum"));
|
|
552
|
+
}
|
|
553
|
+
catch (err) {
|
|
554
|
+
return fail(err);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
server.registerTool("get_metric_context", {
|
|
558
|
+
title: "Historical context for any recorded metric (conditional distribution)",
|
|
559
|
+
description: "Call this when the user asks whether a metric's current reading is high or low, or what happened after similar readings. Buckets today's value against the metric's own recorded daily history and returns the median forward BTC return and up-share per bucket at +1/+3/+7 days, with the all-days base rate alongside. Honesty rules: buckets under 30 days are suppressed, and most metrics do NOT separate from the base rate; the interpretation says so plainly. History, not a forecast. Metrics include coinbase_premium_pct, kraken_btc_premium_pct, dvol_btc, fear_greed, funding_btc_daily_pct, etf_btc_net_flow_usd, vpin_btc, altseason_index, stablecoin_total_mcap_busd, fred_dff, fred_dgs10, fred_walcl_busd, fred_rrp_busd and the btc_* network series.",
|
|
560
|
+
inputSchema: {
|
|
561
|
+
metric: z
|
|
562
|
+
.string()
|
|
563
|
+
.regex(/^[a-z0-9_]{2,50}$/)
|
|
564
|
+
.describe("Metric key, e.g. coinbase_premium_pct, fear_greed, altseason_index, stablecoin_total_mcap_busd."),
|
|
565
|
+
},
|
|
566
|
+
annotations: READ_ONLY,
|
|
567
|
+
}, async ({ metric }) => {
|
|
568
|
+
try {
|
|
569
|
+
return ok(withProvenance(await fetchJson(`/api/public/context?metric=${encodeURIComponent(metric)}`), `/api/public/context?metric=${encodeURIComponent(metric)}`));
|
|
570
|
+
}
|
|
571
|
+
catch (err) {
|
|
572
|
+
return fail(err);
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
server.registerTool("get_theme_indices", {
|
|
576
|
+
title: "Crypto narrative indices (AI, RWA, DePIN, meme, L1, L2, DeFi, quantum)",
|
|
577
|
+
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.",
|
|
578
|
+
inputSchema: { include_points: z.boolean().optional().describe("boolean, optional: include the daily index points (large)") },
|
|
579
|
+
annotations: READ_ONLY,
|
|
580
|
+
}, async (args) => {
|
|
581
|
+
try {
|
|
582
|
+
const path = args.include_points === true ? "/api/public/themes" : "/api/public/themes?points=0";
|
|
583
|
+
return ok(withProvenance(await fetchJson(path), path));
|
|
584
|
+
}
|
|
585
|
+
catch (err) {
|
|
586
|
+
return fail(err);
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
server.registerTool("get_factor_board", {
|
|
590
|
+
title: "Factor board: what followed days like today across recorded metrics",
|
|
591
|
+
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.",
|
|
592
|
+
inputSchema: {},
|
|
593
|
+
annotations: READ_ONLY,
|
|
594
|
+
}, async () => {
|
|
595
|
+
try {
|
|
596
|
+
return ok(withProvenance(await fetchJson("/api/public/factors"), "/api/public/factors"));
|
|
597
|
+
}
|
|
598
|
+
catch (err) {
|
|
599
|
+
return fail(err);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
server.registerTool("get_venue_markets", {
|
|
603
|
+
title: "Exchange coverage: OI, volume, funding and pegs across 43 exchanges",
|
|
604
|
+
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.",
|
|
605
|
+
inputSchema: { symbol: z.string().optional().describe("string, optional base asset, e.g. BTC") },
|
|
606
|
+
annotations: READ_ONLY,
|
|
607
|
+
}, async (args) => {
|
|
608
|
+
try {
|
|
609
|
+
const path = args.symbol ? `/api/public/venues/markets?symbol=${encodeURIComponent(args.symbol.toUpperCase())}` : "/api/public/venues/markets";
|
|
610
|
+
return ok(withProvenance(await fetchJson(path), path));
|
|
611
|
+
}
|
|
612
|
+
catch (err) {
|
|
613
|
+
return fail(err);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
server.registerTool("get_lead_lag", {
|
|
617
|
+
title: "Venue lead-lag: who moves first (Coinbase, Kraken, Binance)",
|
|
618
|
+
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.",
|
|
619
|
+
inputSchema: {},
|
|
620
|
+
annotations: READ_ONLY,
|
|
621
|
+
}, async () => {
|
|
622
|
+
try {
|
|
623
|
+
return ok(withProvenance(await fetchJson("/api/public/venues/lead-lag"), "/api/public/venues/lead-lag"));
|
|
624
|
+
}
|
|
625
|
+
catch (err) {
|
|
626
|
+
return fail(err);
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
server.registerTool("get_iv_surface", {
|
|
630
|
+
title: "Options implied-volatility surface and 25-delta skew",
|
|
631
|
+
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.",
|
|
632
|
+
inputSchema: { currency: z.string().optional().describe("BTC or ETH, default BTC") },
|
|
633
|
+
annotations: READ_ONLY,
|
|
634
|
+
}, async (args) => {
|
|
635
|
+
try {
|
|
636
|
+
const path = `/api/public/options/surface?currency=${(args.currency ?? "BTC").toUpperCase() === "ETH" ? "ETH" : "BTC"}`;
|
|
637
|
+
return ok(withProvenance(await fetchJson(path), path));
|
|
638
|
+
}
|
|
639
|
+
catch (err) {
|
|
640
|
+
return fail(err);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
server.registerTool("get_whale_tape", {
|
|
644
|
+
title: "Whale tape: $1M+ aggressive prints with 24h buy share",
|
|
645
|
+
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.",
|
|
646
|
+
inputSchema: {},
|
|
647
|
+
annotations: READ_ONLY,
|
|
648
|
+
}, async () => {
|
|
649
|
+
try {
|
|
650
|
+
return ok(withProvenance(await fetchJson("/api/public/whales"), "/api/public/whales"));
|
|
651
|
+
}
|
|
652
|
+
catch (err) {
|
|
653
|
+
return fail(err);
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
server.registerTool("get_correlations", {
|
|
657
|
+
title: "Crypto correlation matrix",
|
|
658
|
+
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.",
|
|
659
|
+
inputSchema: {},
|
|
660
|
+
annotations: READ_ONLY,
|
|
661
|
+
}, async () => {
|
|
662
|
+
try {
|
|
663
|
+
return ok(withProvenance(await fetchJson("/api/public/correlations"), "/api/public/correlations"));
|
|
664
|
+
}
|
|
665
|
+
catch (err) {
|
|
666
|
+
return fail(err);
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
server.registerTool("get_new_listings", {
|
|
670
|
+
title: "New and delisted perpetual contracts",
|
|
671
|
+
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.",
|
|
672
|
+
inputSchema: { days: z.number().optional().describe("number, optional window in days, default 30") },
|
|
673
|
+
annotations: READ_ONLY,
|
|
674
|
+
}, async (args) => {
|
|
675
|
+
try {
|
|
676
|
+
const path = `/api/public/listings?days=${args.days && args.days > 0 ? Math.floor(args.days) : 30}`;
|
|
677
|
+
return ok(withProvenance(await fetchJson(path), path));
|
|
678
|
+
}
|
|
679
|
+
catch (err) {
|
|
680
|
+
return fail(err);
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
server.registerTool("get_macro_liquidity", {
|
|
684
|
+
title: "Macro liquidity: Fed funds, 10y, balance sheet, RRP, stablecoin supply",
|
|
685
|
+
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.",
|
|
686
|
+
inputSchema: { days: z.number().optional().describe("number, optional window in days, default 365") },
|
|
687
|
+
annotations: READ_ONLY,
|
|
688
|
+
}, async (args) => {
|
|
689
|
+
try {
|
|
690
|
+
const path = `/api/public/macro?days=${args.days && args.days > 0 ? Math.floor(args.days) : 365}`;
|
|
691
|
+
return ok(withProvenance(await fetchJson(path), path));
|
|
692
|
+
}
|
|
693
|
+
catch (err) {
|
|
694
|
+
return fail(err);
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
server.registerTool("get_network_health", {
|
|
698
|
+
title: "Bitcoin network health from our own node",
|
|
699
|
+
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.",
|
|
700
|
+
inputSchema: { days: z.number().optional().describe("number, optional window in days, default 365") },
|
|
701
|
+
annotations: READ_ONLY,
|
|
702
|
+
}, async (args) => {
|
|
703
|
+
try {
|
|
704
|
+
const path = `/api/public/network?days=${args.days && args.days > 0 ? Math.floor(args.days) : 365}`;
|
|
705
|
+
return ok(withProvenance(await fetchJson(path), path));
|
|
448
706
|
}
|
|
449
707
|
catch (err) {
|
|
450
708
|
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.0",
|
|
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",
|