bykaranteli-mcp 0.36.0 → 0.36.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/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  Notable changes to the `bykaranteli-mcp` package. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Dates are UTC and come from the git tag of each release (the version commit where no tag exists). The tool count is the number of tools the server registers in that release.
4
4
 
5
+ ## [0.36.2] - 2026-10-23
6
+
7
+ 68 tools.
8
+
9
+ ### Changed
10
+
11
+ - `@modelcontextprotocol/sdk` floor raised from `^1.12.0` to `^1.32.1`: the SDK's October 2026 advisories (OAuth client redirect handling, experimental task sessions, the DoS through deeply nested tool arguments, GHSA-46jr) are fixed from 1.32.0, and an install of this package now resolves at least that version.
12
+ - The stdio server sets `maxToolInputElements` to 2,000 (an SDK 1.32 option): a `tools/call` whose arguments carry more than two thousand array elements and object members combined is answered `-32602` before validation. The largest real input, an alert recipe, is a few dozen members. The hosted endpoint on bykaranteli.com applies the same ceiling.
13
+ - Tool input errors read as `<message> at <path>` lines (SDK 1.32 formats Zod issues this way, one per line) instead of the earlier raw issue dump; a `tools/call` without an `arguments` member is validated as `{}` (so a tool whose inputs are all optional answers instead of refusing).
14
+
15
+ ## [0.36.1] - 2026-10-21
16
+
17
+ 68 tools.
18
+
19
+ ### Changed
20
+
21
+ - `get_series`: the description and the `to` parameter state the public window rule the route enforces since 2026-10-20: a `to` before the newest 300 bars of the period needs member depth (Builder and higher plans) and answers 403 `history_member_only` otherwise, except daily and coarser price bars, which stay open to every plan. The handler is unchanged: the route's own sentence reaches the model as the tool error.
22
+ - `list_alert_recipes`: the description names `reachable` (whether a delivery channel is set up for the recipe) and `channels_available` (the delivery channels the account has connected), both in the answer since the web release of 2026-10-18.
23
+ - README: the 401 causes paragraph (`x-byk-mcp-auth`: `key_required`, `key_unusable`, `key_wrong_type`, `key_unrecognised`; the `WWW-Authenticate` challenge starts the OAuth sign-in).
24
+ - Short default answers, two more (the 0.35.0 rule; the package smoke test caught both at 46,000 and 47,000 characters): `get_factor_board` returns the first 40 rows without `limit` (`rows_total`, `rows_returned` and `trimmed` as on the other boards; `limit` up to 400 for the whole board), and `get_desk_notes` returns the newest 25 notes of the last 24 hours without arguments (`count` says how many came back; `limit` up to 100; `day` or `since` keep the route's 50).
25
+
5
26
  ## [0.36.0] - 2026-10-15
6
27
 
7
28
  68 tools.
package/README.md CHANGED
@@ -148,6 +148,7 @@ These tools read and change your own ByKaranteli account with the same key: the
148
148
  - How many recipes an account keeps follows its plan; past it, `create_alert_recipe` answers with the limit instead of saving.
149
149
  - Every call counts on the key's plan like any other request; writes are also rate limited per key, and each write is recorded on the account with the key that made it.
150
150
  - Recipes and watchlist changes show up at once on <https://bykaranteli.com/dashboard/alerts> and <https://bykaranteli.com/dashboard/watchlist>.
151
+ - If a tool call answers 401, the body's `error_description` says what to send and the `x-byk-mcp-auth` header names the cause: `key_required` (no credential at all), `key_unusable` (a header or the address carried no key: the `bk_...` placeholder, an unset variable, a non-Bearer scheme, an OAuth token of the wrong shape), `key_wrong_type` (a prepaid `bk_live_` key, which pays per call on /api/x402/* and does not open the MCP) or `key_unrecognised` (mistyped, expired or revoked). The `WWW-Authenticate` challenge starts the OAuth sign-in in clients that support it. This package forwards the REST body verbatim, so the same sentence reaches the model over stdio.
151
152
 
152
153
  ## Configuration
153
154
 
package/dist/index.js CHANGED
@@ -15,10 +15,14 @@ import { registerByKaranteliTools } from "./tools.js";
15
15
  // and sits one level above both src/ and dist/.
16
16
  const { version: VERSION } = createRequire(import.meta.url)("../package.json");
17
17
  const BASE_URL = (process.env.BYKARANTELI_BASE_URL ?? "https://bykaranteli.com").replace(/\/+$/, "");
18
+ /* GHSA-46jr (SDK 1.32.0): the combined number of array elements and object members one tools/call `arguments`
19
+ * payload may carry; above it the SDK answers -32602 before validation. The largest real input is an alert recipe
20
+ * (a few dozen members); the hosted endpoint sets the same ceiling. */
21
+ const MAX_TOOL_INPUT_ELEMENTS = 2_000;
18
22
  const server = new McpServer({
19
23
  name: "bykaranteli",
20
24
  version: VERSION,
21
- });
25
+ }, { maxToolInputElements: MAX_TOOL_INPUT_ELEMENTS });
22
26
  registerByKaranteliTools(server, { version: VERSION });
23
27
  async function main() {
24
28
  // If the host dies while a response is in flight, exit quietly instead of
package/dist/tools.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Tool registrations for the ByKaranteli MCP server, transport-agnostic.
3
3
  * The stdio bin (index.ts) and the hosted Streamable HTTP endpoint on
4
4
  * bykaranteli.com both call registerByKaranteliTools on their own server
5
- * instance, so all 20 tools have exactly one definition.
5
+ * instance, so every tool has exactly one definition.
6
6
  */
7
7
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
8
  export type RegisterOptions = {
package/dist/tools.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Tool registrations for the ByKaranteli MCP server, transport-agnostic.
3
3
  * The stdio bin (index.ts) and the hosted Streamable HTTP endpoint on
4
4
  * bykaranteli.com both call registerByKaranteliTools on their own server
5
- * instance, so all 20 tools have exactly one definition.
5
+ * instance, so every tool has exactly one definition.
6
6
  */
7
7
  import { z } from "zod/v4";
8
8
  import { buildProvenance, pageForApiPath } from "./provenance.js";
@@ -771,12 +771,18 @@ export function registerByKaranteliTools(target, options) {
771
771
  });
772
772
  server.registerTool("get_factor_board", {
773
773
  title: "Factor board: what followed days like today across recorded metrics",
774
- 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.",
775
- inputSchema: {},
774
+ 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. Without limit the first 40 rows in the board's own order (rows_total counts every row, trimmed names the cut); pass limit up to 400 for the whole board.",
775
+ inputSchema: {
776
+ limit: z.coerce.number().int().min(1).max(400).optional().describe("Optional. Rows to return, 1..400 (default 40)"),
777
+ },
776
778
  annotations: READ_ONLY,
777
- }, async () => {
779
+ }, async ({ limit }) => {
778
780
  try {
779
- return ok(withProvenance(await fetchJson("/api/public/factors"), "/api/public/factors"));
781
+ /* 0.36.1: 79 rows of about 600 characters answered 46,000 characters without arguments; a first page of 40. */
782
+ const n = limit ?? 40;
783
+ return ok(compact(await fetchJson("/api/public/factors"), "/api/public/factors", (rec, t) => {
784
+ rec.rows = t.head(rec.rows, n, "rows", "pass limit up to 400");
785
+ }, "rows"));
780
786
  }
781
787
  catch (err) {
782
788
  return fail(err);
@@ -1379,13 +1385,13 @@ export function registerByKaranteliTools(target, options) {
1379
1385
  });
1380
1386
  server.registerTool("get_desk_notes", {
1381
1387
  title: "Desk notes: short dated observations the ByKaranteli desk wrote from its own recorded derivatives data",
1382
- description: "Call this when the user asks what happened in crypto derivatives today or on a date, what the desk observed, what moved in funding, open interest, liquidations, options, ETF flows or exchange premiums, or wants a dated, quotable sentence with its number and time. Returns the desk's notes newest first: each is one observation written by the desk writer (an AI model) from the platform's own recorded data and checked against it before publishing, with its publish time in UTC, the card's data time, the desk's 0 to 10 news value, the board it read from, the coins it names and its permanent URL. Information about the data, never advice. Default the last 24 hours; pass day for one UTC day, since for a window up to 7 days back, type for one card type (funding-arb, liqmap-btc, event-liq-burst, ...), lang for zh, ru or ko text.",
1388
+ description: "Call this when the user asks what happened in crypto derivatives today or on a date, what the desk observed, what moved in funding, open interest, liquidations, options, ETF flows or exchange premiums, or wants a dated, quotable sentence with its number and time. Returns the desk's notes newest first: each is one observation written by the desk writer (an AI model) from the platform's own recorded data and checked against it before publishing, with its publish time in UTC, the card's data time, the desk's 0 to 10 news value, the board it read from, the coins it names and its permanent URL. Information about the data, never advice. Default the newest 25 notes of the last 24 hours (count says how many came back; limit up to 100 for more); pass day for one UTC day, since for a window up to 7 days back (50 notes unless limit says otherwise), type for one card type (funding-arb, liqmap-btc, event-liq-burst, ...), lang for zh, ru or ko text.",
1383
1389
  inputSchema: {
1384
1390
  day: z.string().trim().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("One UTC day, YYYY-MM-DD"),
1385
1391
  since: z.string().trim().max(40).optional().describe("ISO time; notes published at or after it, at most 7 days back (ignored with day)"),
1386
1392
  type: z.string().trim().toLowerCase().regex(/^[a-z0-9][a-z0-9-]{0,60}$/).optional().describe("One card type, e.g. funding-arb, liqmap-btc, event-liq-burst"),
1387
1393
  lang: z.enum(["en", "zh", "ru", "ko"]).optional().describe("Text language (default en); a note without that translation comes back in English"),
1388
- limit: z.number().int().min(1).max(100).optional().describe("Notes to return, 1..100 (default 50)"),
1394
+ limit: z.number().int().min(1).max(100).optional().describe("Notes to return, 1..100 (default 25 for the last 24 hours, 50 with day or since)"),
1389
1395
  },
1390
1396
  annotations: READ_ONLY,
1391
1397
  }, async ({ day, since, type, lang, limit }) => {
@@ -1399,8 +1405,11 @@ export function registerByKaranteliTools(target, options) {
1399
1405
  q.set("type", type);
1400
1406
  if (lang && lang !== "en")
1401
1407
  q.set("lang", lang);
1408
+ /* 0.36.1: a day of forty notes answered 47,000 characters without arguments; the first page is 25. */
1402
1409
  if (limit)
1403
1410
  q.set("limit", String(limit));
1411
+ else if (!day && !since)
1412
+ q.set("limit", "25");
1404
1413
  const qs = q.toString();
1405
1414
  const path = `/api/public/desk${qs ? `?${qs}` : ""}`;
1406
1415
  return ok(withProvenance(await fetchJson(path), path));
@@ -1726,6 +1735,9 @@ export function registerByKaranteliTools(target, options) {
1726
1735
  const SERIES_PERIODS = ["1m", "3m", "5m", "10m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w", "1M"];
1727
1736
  const PERIOD_MS = { "1m": 60_000, "3m": 180_000, "5m": 300_000, "10m": 600_000, "15m": 900_000, "30m": 1_800_000, "1h": 3_600_000, "2h": 7_200_000, "4h": 14_400_000, "6h": 21_600_000, "8h": 28_800_000, "12h": 43_200_000, "1d": 86_400_000, "3d": 259_200_000, "1w": 604_800_000, "1M": 2_592_000_000 };
1728
1737
  const SERIES_MEMBER_BAR_LIMIT = 5000;
1738
+ /* The public depth of /api/series (web lib/series/limits.ts SERIES_ANON_BAR_LIMIT): the window a plan without member
1739
+ * depth may page back through with `to`; an older `to` answers 403 history_member_only (web route, 2026-10-20). */
1740
+ const SERIES_ANON_BAR_LIMIT = 300;
1729
1741
  const RECIPE_FIELD_KEYS = ["mark_price", "pressure_score", "funding_rate_pct", "oi24h_pct", "basis_pct", "price_change_24h_pct", "liq_cluster_distance_pct", "usdt_peg_min_usd", "usdc_peg_min_usd", "liq_1h_usd", "rsi_4h", "ls_ratio_global", "book_imbalance_2pct_pct", "withdrawal_paused_venues", "max_leverage_min", "borrow_apr_pct", "hl_whale_long_share_pct"];
1730
1742
  const RECIPE_OPS = [">=", ">", "<=", "<", "=="];
1731
1743
  const RECIPE_CHANNELS = ["telegram", "email", "push", "webhook"];
@@ -1815,14 +1827,14 @@ export function registerByKaranteliTools(target, options) {
1815
1827
  /* ---- 0.31.0: recorded series, alert text, alert recipes, watchlists ---- */
1816
1828
  server.registerTool("get_series", {
1817
1829
  title: "Recorded series: bars or points of one metric for one perpetual (price, OI, funding, CVD, liquidations, long/short, RSI and more)",
1818
- description: `Call this when the user wants the history of one recorded metric for one perpetual as a time series: price candles, volume, perp or spot CVD, open interest, funding (one venue, or OI-weighted across every venue as funding_agg), liquidations, long/short ratios, RSI, the Coinbase premium, US spot ETF flows, borrow rates or Hyperliquid whale net flow, for charting, backtesting or "what did X do over the last N days". Returns the /api/series answer (points as [t, v], or [t, o, h, l, c, v] for price, with unit, kind, source and source_kind, the bars served and whether member depth applied) plus provenance. metric is one of: ${SERIES_METRIC_KEYS.join(", ")} (unit, finest period and venue support of each: https://bykaranteli.com/api/series/metrics). The Free plan gets public depth: fewer bars and no bars under 15 minutes (1m, 3m, 5m, 10m); Builder and higher plans get member depth; the bar limits are in the same list. The answer names the venue it was read from (venue) and every venue a market-wide series sums (venues). Without from or limit, the newest 300 bars; pass from and to (ISO) for a window, or limit for more of the newest bars.`,
1830
+ description: `Call this when the user wants the history of one recorded metric for one perpetual as a time series: price candles, volume, perp or spot CVD, open interest, funding (one venue, or OI-weighted across every venue as funding_agg), liquidations, long/short ratios, RSI, the Coinbase premium, US spot ETF flows, borrow rates or Hyperliquid whale net flow, for charting, backtesting or "what did X do over the last N days". Returns the /api/series answer (points as [t, v], or [t, o, h, l, c, v] for price, with unit, kind, source and source_kind, the bars served and whether member depth applied) plus provenance. metric is one of: ${SERIES_METRIC_KEYS.join(", ")} (unit, finest period and venue support of each: https://bykaranteli.com/api/series/metrics). The Free plan gets public depth: fewer bars and no bars under 15 minutes (1m, 3m, 5m, 10m); Builder and higher plans get member depth; the bar limits are in the same list. The answer names the venue it was read from (venue) and every venue a market-wide series sums (venues). Without from or limit, the newest 300 bars; pass limit for more of the newest bars, or from and to (ISO) for a window. The public window is the newest ${SERIES_ANON_BAR_LIMIT} bars of the period: a to before it needs member depth (Builder and higher plans) and answers 403 history_member_only otherwise, except daily and coarser price bars, which stay open to every plan.`,
1819
1831
  inputSchema: {
1820
1832
  metric: z.string().describe("string, metric key, e.g. price, oi, funding, liquidations (list: /api/series/metrics)"),
1821
1833
  symbol: z.string().optional().describe("string, optional Binance USDT-M perp or coin, e.g. BTCUSDT or BTC (default BTCUSDT)"),
1822
1834
  period: z.enum(SERIES_PERIODS).optional().describe(`string, optional bar period: ${SERIES_PERIODS.join(" | ")} (default 1h; each metric has a finest period)`),
1823
1835
  venue: z.string().optional().describe("string, optional venue id for price, oi, funding or borrow, e.g. okx (default binance)"),
1824
1836
  from: z.string().optional().describe("string, optional ISO start, e.g. 2026-09-01T00:00:00Z"),
1825
- to: z.string().optional().describe("string, optional ISO end (default now)"),
1837
+ to: z.string().optional().describe(`string, optional ISO end (default now); a to before the newest ${SERIES_ANON_BAR_LIMIT} bars of the period needs member depth, except daily and coarser price bars`),
1826
1838
  limit: z.number().optional().describe(`number, optional newest bars, 10..${SERIES_MEMBER_BAR_LIMIT}; the plan's depth caps it (default 300 without from)`),
1827
1839
  },
1828
1840
  annotations: READ_ONLY,
@@ -1878,7 +1890,7 @@ export function registerByKaranteliTools(target, options) {
1878
1890
  });
1879
1891
  server.registerTool("list_alert_recipes", {
1880
1892
  title: "Your alert recipes: every alert on your account with its conditions, scope, channels and when it last fired",
1881
- description: "Call this when the user asks which alerts they have, whether an alert fired, or before deleting or changing one. Returns the account's alert recipes (id, name, scope, symbols, conditions, cooldown in hours, enabled, last fired, fire count, channels). Needs a signed-in account (OAuth sign-in or account key); reads nothing but this account.",
1893
+ description: "Call this when the user asks which alerts they have, whether an alert fired, or before deleting or changing one. Returns the account's alert recipes (id, name, scope, symbols, conditions, cooldown in hours, enabled, last fired, fire count, channels, reachable: whether a delivery channel is set up for it) and channels_available (the delivery channels the account has connected). Needs a signed-in account (OAuth sign-in or account key); reads nothing but this account.",
1882
1894
  inputSchema: {},
1883
1895
  annotations: ACCOUNT_READ,
1884
1896
  }, async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bykaranteli-mcp",
3
- "version": "0.36.0",
3
+ "version": "0.36.2",
4
4
  "description": "MCP server for live crypto derivatives data from bykaranteli.com: funding rates, funding arbitrage, open interest pressure, liquidations, options, ETF flows, Fear & Greed and BTC dominance. 68 read-only and account tools (the alert and watchlist tools act on your own account), free account key, paid depth via plans or x402.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -47,7 +47,7 @@
47
47
  "url": "https://github.com/bykarantelicom/bykaranteli-mcp/issues"
48
48
  },
49
49
  "dependencies": {
50
- "@modelcontextprotocol/sdk": "^1.12.0",
50
+ "@modelcontextprotocol/sdk": "^1.32.1",
51
51
  "zod": "^4.3.6"
52
52
  },
53
53
  "devDependencies": {