bykaranteli-mcp 0.1.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/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/index.js +297 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ByKaranteli (bykaranteli.com)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# bykaranteli-mcp
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
Backed by 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 and BingX).
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
### Claude Code
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
claude mcp add bykaranteli -- npx -y bykaranteli-mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Claude Desktop
|
|
16
|
+
|
|
17
|
+
Add to `claude_desktop_config.json`:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"bykaranteli": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["-y", "bykaranteli-mcp"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Cursor / other MCP clients
|
|
31
|
+
|
|
32
|
+
Any stdio MCP client works: command `npx`, args `["-y", "bykaranteli-mcp"]`.
|
|
33
|
+
|
|
34
|
+
Requires Node.js 18 or newer.
|
|
35
|
+
|
|
36
|
+
## Tools
|
|
37
|
+
|
|
38
|
+
| Tool | What it answers |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `get_market_indices` | "What is the Fear & Greed index today?", "What is BTC dominance right now?" |
|
|
41
|
+
| `get_funding_heatmap` | "What are funding rates right now?", "What is SOL's funding?" |
|
|
42
|
+
| `get_funding_arbitrage` | "Any funding arb opportunities?", "Best venue to long/short BTC for carry?" |
|
|
43
|
+
| `get_pressure_scores` | "Which coins are over-leveraged / crowded right now?" |
|
|
44
|
+
| `get_top_movers` | "Biggest OI spikes today?", "Most extreme funding right now?" |
|
|
45
|
+
| `get_recent_signals` | "How did the signals do in the last 24h?" |
|
|
46
|
+
| `get_symbol_performance` | "Win rate on ETHUSDT over 90 days?" |
|
|
47
|
+
| `get_strategy_leaderboard` | "Which strategies are performing best?" |
|
|
48
|
+
|
|
49
|
+
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`.
|
|
50
|
+
|
|
51
|
+
## Configuration
|
|
52
|
+
|
|
53
|
+
| Env var | Default | Purpose |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `BYKARANTELI_BASE_URL` | `https://bykaranteli.com` | Override the API host (testing only) |
|
|
56
|
+
|
|
57
|
+
## Data notes
|
|
58
|
+
|
|
59
|
+
- Signal performance is a **live-only** track record: real published signals with SHA-256 receipts, evaluated net of fees, slippage and funding. Never backtests. Verify any signal at [bykaranteli.com/verify](https://bykaranteli.com/verify).
|
|
60
|
+
- Funding, OI and pressure data refresh every 15 to 30 minutes; indices every 30 minutes.
|
|
61
|
+
- Nothing here is financial advice. See [bykaranteli.com/risk-guide](https://bykaranteli.com/risk-guide).
|
|
62
|
+
|
|
63
|
+
## Development
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install
|
|
67
|
+
npm run build
|
|
68
|
+
node dist/index.js # speaks MCP over stdio
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT. Attribution appreciated: "ByKaranteli (bykaranteli.com)".
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* bykaranteli-mcp · MCP server for live crypto derivatives data
|
|
4
|
+
*
|
|
5
|
+
* Wraps the free, no-auth public JSON API at https://bykaranteli.com
|
|
6
|
+
* (docs: https://bykaranteli.com/developers). All data is Binance USDT-M
|
|
7
|
+
* perpetuals unless a tool says otherwise. Every response carries a
|
|
8
|
+
* generated-at timestamp; quote it when citing values.
|
|
9
|
+
*/
|
|
10
|
+
import { createRequire } from "node:module";
|
|
11
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
// Single source of truth for the version: package.json ships in the tarball
|
|
15
|
+
// and sits one level above both src/ and dist/.
|
|
16
|
+
const { version: VERSION } = createRequire(import.meta.url)("../package.json");
|
|
17
|
+
const BASE_URL = (process.env.BYKARANTELI_BASE_URL ?? "https://bykaranteli.com").replace(/\/+$/, "");
|
|
18
|
+
const USER_AGENT = `bykaranteli-mcp/${VERSION} (+https://github.com/bykarantelicom/bykaranteli-mcp)`;
|
|
19
|
+
const TIMEOUT_MS = 15_000;
|
|
20
|
+
// Read-only GET tools over an open-world public API, all of them.
|
|
21
|
+
const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
|
|
22
|
+
/** Invalid tool input (not a network problem): no retry hint in the error. */
|
|
23
|
+
class InputError extends Error {
|
|
24
|
+
}
|
|
25
|
+
async function fetchJson(path) {
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const timer = setTimeout(() => controller.abort(new Error(`${path} timed out after ${TIMEOUT_MS / 1000}s`)), TIMEOUT_MS);
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetch(`${BASE_URL}${path}`, {
|
|
30
|
+
signal: controller.signal,
|
|
31
|
+
headers: { accept: "application/json", "user-agent": USER_AGENT },
|
|
32
|
+
});
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw new Error(`${path} returned HTTP ${res.status}`);
|
|
35
|
+
}
|
|
36
|
+
return await res.json();
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function ok(data) {
|
|
43
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
44
|
+
}
|
|
45
|
+
function fail(err) {
|
|
46
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
47
|
+
const text = err instanceof InputError
|
|
48
|
+
? `Invalid input: ${message}`
|
|
49
|
+
: `Error fetching data from bykaranteli.com: ${message}. The API is free and unauthenticated; transient errors usually resolve on retry.`;
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: "text", text }],
|
|
52
|
+
isError: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const SYMBOL_RE = /^[A-Z0-9]{5,20}$/;
|
|
56
|
+
function normalizeSymbol(raw) {
|
|
57
|
+
// Accept the spellings models actually produce: "BTC/USDT", "btc-usdt",
|
|
58
|
+
// "BTC USDT", bare "BTC". Strip separators, then expand to the USDT perp.
|
|
59
|
+
const s = raw.trim().toUpperCase().replace(/[\s/_-]+/g, "");
|
|
60
|
+
const expanded = s.endsWith("USDT") ? s : `${s}USDT`;
|
|
61
|
+
if (!SYMBOL_RE.test(expanded)) {
|
|
62
|
+
throw new InputError(`"${raw}" is not a valid symbol. Use a Binance USDT-M perp symbol like BTCUSDT or a coin name like BTC.`);
|
|
63
|
+
}
|
|
64
|
+
return expanded;
|
|
65
|
+
}
|
|
66
|
+
const server = new McpServer({
|
|
67
|
+
name: "bykaranteli",
|
|
68
|
+
version: VERSION,
|
|
69
|
+
});
|
|
70
|
+
server.registerTool("get_market_indices", {
|
|
71
|
+
title: "Crypto market indices (Fear & Greed, BTC dominance, euphoria)",
|
|
72
|
+
description: "Call this when the user asks about overall crypto market sentiment or macro state: the Fear & Greed index (today and yesterday), Bitcoin dominance percentage, total market cap, or the Retail Euphoria composite. Live values refreshed about every 30 minutes.",
|
|
73
|
+
inputSchema: {},
|
|
74
|
+
annotations: READ_ONLY,
|
|
75
|
+
}, async () => {
|
|
76
|
+
try {
|
|
77
|
+
const d = (await fetchJson("/api/public/indices"));
|
|
78
|
+
// Trim heavy internals (weights/contributions) but keep the readable parts.
|
|
79
|
+
const euphoria = d.euphoria;
|
|
80
|
+
return ok({
|
|
81
|
+
generatedAt: d.generatedAt,
|
|
82
|
+
fearGreed: d.fearGreed,
|
|
83
|
+
global: d.global,
|
|
84
|
+
stablecoins: d.stablecoins,
|
|
85
|
+
euphoria: euphoria
|
|
86
|
+
? {
|
|
87
|
+
score: euphoria.score,
|
|
88
|
+
regime: euphoria.regime,
|
|
89
|
+
regimeLabel: euphoria.regimeLabel,
|
|
90
|
+
explainer: euphoria.explainer,
|
|
91
|
+
}
|
|
92
|
+
: undefined,
|
|
93
|
+
source: `${BASE_URL}/indices`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
return fail(err);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
server.registerTool("get_funding_heatmap", {
|
|
101
|
+
title: "Funding rates across top-30 Binance perps",
|
|
102
|
+
description: "Call this when the user asks for the full current funding table across the top-30 Binance perp universe, 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 top-30 Binance USDT-M perpetuals. Positive funding means longs pay shorts.",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
symbol: z
|
|
105
|
+
.string()
|
|
106
|
+
.optional()
|
|
107
|
+
.describe("Optional. Filter to one symbol, e.g. BTCUSDT or just BTC. Omit to get all 30 rows."),
|
|
108
|
+
},
|
|
109
|
+
annotations: READ_ONLY,
|
|
110
|
+
}, async ({ symbol }) => {
|
|
111
|
+
try {
|
|
112
|
+
const d = (await fetchJson("/api/public/heatmap"));
|
|
113
|
+
if (symbol) {
|
|
114
|
+
const want = normalizeSymbol(symbol);
|
|
115
|
+
const row = d.rows.find((r) => r.symbol === want);
|
|
116
|
+
if (!row) {
|
|
117
|
+
return ok({
|
|
118
|
+
generatedAt: d.generatedAt,
|
|
119
|
+
note: `${want} is not in the top-30 heatmap set. Tracked symbols: ${d.rows.map((r) => r.symbol).join(", ")}`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return ok({ generatedAt: d.generatedAt, row, source: `${BASE_URL}/heatmap` });
|
|
123
|
+
}
|
|
124
|
+
return ok({ ...d, source: `${BASE_URL}/heatmap` });
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
return fail(err);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
server.registerTool("get_funding_arbitrage", {
|
|
131
|
+
title: "Cross-exchange funding arbitrage opportunities",
|
|
132
|
+
description: "Call this when the user asks about funding arbitrage, funding rate differences between exchanges, or delta-neutral carry trades. Compares funding across Binance, OKX, Bybit, Gate, HTX and BingX for 12 major perps and returns the best long/short venue per symbol with gross and net annualized APR (net of taker fees and weekly rebalance cost).",
|
|
133
|
+
inputSchema: {},
|
|
134
|
+
annotations: READ_ONLY,
|
|
135
|
+
}, async () => {
|
|
136
|
+
try {
|
|
137
|
+
const d = (await fetchJson("/api/public/funding-arb"));
|
|
138
|
+
return ok({ ...d, source: `${BASE_URL}/funding-arb` });
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
return fail(err);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
server.registerTool("get_pressure_scores", {
|
|
145
|
+
title: "Derivatives pressure scores (funding + OI + basis composite)",
|
|
146
|
+
description: "Call this when the user asks which coins are crowded or over-leveraged, or asks for the pressure/derivatives-stress score of specific coins. For a quick top-10 ranking of the highest-stress coins right now, use get_top_movers instead. Each symbol gets a 0-100 composite score built from funding rate, 1h/4h/24h open interest deltas and basis, with a LONG/SHORT/NEUTRAL direction and a plain-language regime label.",
|
|
147
|
+
inputSchema: {
|
|
148
|
+
symbol: z
|
|
149
|
+
.string()
|
|
150
|
+
.optional()
|
|
151
|
+
.describe("Optional. Return only this symbol, e.g. BTCUSDT or BTC."),
|
|
152
|
+
limit: z.coerce
|
|
153
|
+
.number()
|
|
154
|
+
.int()
|
|
155
|
+
.min(1)
|
|
156
|
+
.max(50)
|
|
157
|
+
.optional()
|
|
158
|
+
.describe("Optional. Max rows to return when no symbol filter is set (default 20, sorted by score)."),
|
|
159
|
+
},
|
|
160
|
+
annotations: READ_ONLY,
|
|
161
|
+
}, async ({ symbol, limit }) => {
|
|
162
|
+
try {
|
|
163
|
+
const d = (await fetchJson("/api/public/pressure"));
|
|
164
|
+
const slim = (it) => ({
|
|
165
|
+
symbol: it.symbol,
|
|
166
|
+
score: it.score,
|
|
167
|
+
direction: it.direction,
|
|
168
|
+
regimeLabel: it.regimeLabel,
|
|
169
|
+
components: it.components,
|
|
170
|
+
explainer: it.explainer,
|
|
171
|
+
});
|
|
172
|
+
if (symbol) {
|
|
173
|
+
const want = normalizeSymbol(symbol);
|
|
174
|
+
const item = d.items.find((i) => i.symbol === want);
|
|
175
|
+
if (!item) {
|
|
176
|
+
return ok({
|
|
177
|
+
generatedAt: d.generatedAt,
|
|
178
|
+
note: `${want} is not in the tracked pressure universe right now.`,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return ok({ generatedAt: d.generatedAt, item: slim(item), source: `${BASE_URL}/pressure` });
|
|
182
|
+
}
|
|
183
|
+
return ok({
|
|
184
|
+
generatedAt: d.generatedAt,
|
|
185
|
+
items: d.items.slice(0, limit ?? 20).map(slim),
|
|
186
|
+
totalTracked: d.items.length,
|
|
187
|
+
source: `${BASE_URL}/pressure`,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
return fail(err);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
server.registerTool("get_top_movers", {
|
|
195
|
+
title: "Top movers: OI spikes, extreme funding, widest basis, highest stress",
|
|
196
|
+
description: "Call this when the user asks what is moving in crypto derivatives right now, which coins have the biggest open interest changes, the most extreme funding, the widest basis, or the highest derivatives stress. Returns four top-10 lists in one call.",
|
|
197
|
+
inputSchema: {},
|
|
198
|
+
annotations: READ_ONLY,
|
|
199
|
+
}, async () => {
|
|
200
|
+
try {
|
|
201
|
+
const d = (await fetchJson("/api/public/top-movers"));
|
|
202
|
+
return ok({ ...d, source: `${BASE_URL}/top-movers` });
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
return fail(err);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
server.registerTool("get_recent_signals", {
|
|
209
|
+
title: "Recent closed trading signals with verified outcomes",
|
|
210
|
+
description: "Call this when the user asks how the ByKaranteli signal engine is doing today, or wants recent closed LONG/SHORT signals with real outcomes (TP1, SL or TIMEOUT) and net basis-point results. Includes a 24h summary (wins, losses, net bps). Every signal is published with a SHA-256 receipt and results are net of fees, slippage and funding; live signals only, never backtests.",
|
|
211
|
+
inputSchema: {},
|
|
212
|
+
annotations: READ_ONLY,
|
|
213
|
+
}, async () => {
|
|
214
|
+
try {
|
|
215
|
+
const d = (await fetchJson("/api/public/recent"));
|
|
216
|
+
return ok({ ...d, source: `${BASE_URL}/signals` });
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
return fail(err);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
server.registerTool("get_symbol_performance", {
|
|
223
|
+
title: "Per-symbol signal performance and recent trades",
|
|
224
|
+
description: "Call this when the user asks how signals performed on a specific coin (win rate, profit factor, net PnL, best/worst trade) or wants that coin's recent closed signals. Data is the live verified track record for one Binance USDT-M perp over a 30, 90 or 180 day window.",
|
|
225
|
+
inputSchema: {
|
|
226
|
+
symbol: z.string().describe("The symbol, e.g. BTCUSDT, or a coin name like BTC."),
|
|
227
|
+
window_days: z
|
|
228
|
+
.preprocess((v) => (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v)) ? Number(v) : v), z.union([z.literal(30), z.literal(90), z.literal(180)]))
|
|
229
|
+
.optional()
|
|
230
|
+
.describe("Optional lookback window in days (30, 90 or 180). Default 90."),
|
|
231
|
+
},
|
|
232
|
+
annotations: READ_ONLY,
|
|
233
|
+
}, async ({ symbol, window_days }) => {
|
|
234
|
+
try {
|
|
235
|
+
const want = normalizeSymbol(symbol);
|
|
236
|
+
const w = window_days ?? 90;
|
|
237
|
+
let d;
|
|
238
|
+
try {
|
|
239
|
+
d = (await fetchJson(`/api/v1/public/symbols/${want}?window=${w}`));
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
// The API 404s when the symbol has no closed signals in the chosen
|
|
243
|
+
// window (data-dependent, not an invalid request). Say so plainly.
|
|
244
|
+
if (err instanceof Error && err.message.includes("HTTP 404")) {
|
|
245
|
+
return ok({
|
|
246
|
+
symbol: want,
|
|
247
|
+
window_days: w,
|
|
248
|
+
note: `No closed signals recorded for ${want} in the last ${w} days (or the symbol is not tracked). Try window_days: 180, or check ${BASE_URL}/symbols/${want}.`,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
throw err;
|
|
252
|
+
}
|
|
253
|
+
// daily_points can be long; the stats + recent signals carry the answer.
|
|
254
|
+
const daily = d.daily_points;
|
|
255
|
+
return ok({
|
|
256
|
+
...d,
|
|
257
|
+
daily_points: Array.isArray(daily) ? daily.slice(-30) : daily,
|
|
258
|
+
source: `${BASE_URL}/symbols/${want}`,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
return fail(err);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
server.registerTool("get_strategy_leaderboard", {
|
|
266
|
+
title: "Strategy leaderboard with verified live results",
|
|
267
|
+
description: "Call this when the user asks which trading strategies are performing best, or wants win rate, profit factor, drawdown and Sharpe per strategy. Rankings are computed from live closed signals only (no backtests), net of fees.",
|
|
268
|
+
inputSchema: {},
|
|
269
|
+
annotations: READ_ONLY,
|
|
270
|
+
}, async () => {
|
|
271
|
+
try {
|
|
272
|
+
const d = (await fetchJson("/api/v1/public/leaderboard"));
|
|
273
|
+
return ok({ ...d, source: `${BASE_URL}/leaderboard` });
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
return fail(err);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
async function main() {
|
|
280
|
+
// If the host dies while a response is in flight, exit quietly instead of
|
|
281
|
+
// crashing with an unhandled EPIPE from the stdout pipe. Non-EPIPE stream
|
|
282
|
+
// errors would be swallowed once a listener exists, so log and exit nonzero.
|
|
283
|
+
process.stdout.on("error", (err) => {
|
|
284
|
+
if (err.code === "EPIPE")
|
|
285
|
+
process.exit(0);
|
|
286
|
+
console.error("bykaranteli-mcp stdout error:", err);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
});
|
|
289
|
+
const transport = new StdioServerTransport();
|
|
290
|
+
await server.connect(transport);
|
|
291
|
+
// stdout is the JSON-RPC channel; log to stderr only.
|
|
292
|
+
console.error(`bykaranteli-mcp ready (base: ${BASE_URL})`);
|
|
293
|
+
}
|
|
294
|
+
main().catch((err) => {
|
|
295
|
+
console.error("bykaranteli-mcp fatal:", err);
|
|
296
|
+
process.exit(1);
|
|
297
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bykaranteli-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bykaranteli-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', 0o755)\"",
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"start": "node dist/index.js",
|
|
19
|
+
"prepare": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"model-context-protocol",
|
|
27
|
+
"crypto",
|
|
28
|
+
"funding-rate",
|
|
29
|
+
"open-interest",
|
|
30
|
+
"liquidation",
|
|
31
|
+
"fear-and-greed",
|
|
32
|
+
"btc-dominance",
|
|
33
|
+
"binance",
|
|
34
|
+
"derivatives",
|
|
35
|
+
"trading",
|
|
36
|
+
"claude"
|
|
37
|
+
],
|
|
38
|
+
"author": "ByKaranteli (https://bykaranteli.com)",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"homepage": "https://bykaranteli.com/developers",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/bykarantelicom/bykaranteli-mcp.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/bykarantelicom/bykaranteli-mcp/issues"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
50
|
+
"zod": "^3.25.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^20.0.0",
|
|
54
|
+
"typescript": "^5.5.0"
|
|
55
|
+
}
|
|
56
|
+
}
|