@guiie/buda-mcp 1.3.0 → 1.4.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/PUBLISH_CHECKLIST.md +69 -70
  3. package/README.md +4 -4
  4. package/dist/http.js +17 -0
  5. package/dist/index.js +10 -0
  6. package/dist/tools/calculate_position_size.d.ts +48 -0
  7. package/dist/tools/calculate_position_size.d.ts.map +1 -0
  8. package/dist/tools/calculate_position_size.js +111 -0
  9. package/dist/tools/dead_mans_switch.d.ts +84 -0
  10. package/dist/tools/dead_mans_switch.d.ts.map +1 -0
  11. package/dist/tools/dead_mans_switch.js +236 -0
  12. package/dist/tools/market_sentiment.d.ts +30 -0
  13. package/dist/tools/market_sentiment.d.ts.map +1 -0
  14. package/dist/tools/market_sentiment.js +104 -0
  15. package/dist/tools/price_history.d.ts.map +1 -1
  16. package/dist/tools/price_history.js +2 -40
  17. package/dist/tools/simulate_order.d.ts +45 -0
  18. package/dist/tools/simulate_order.d.ts.map +1 -0
  19. package/dist/tools/simulate_order.js +139 -0
  20. package/dist/tools/technical_indicators.d.ts +39 -0
  21. package/dist/tools/technical_indicators.d.ts.map +1 -0
  22. package/dist/tools/technical_indicators.js +223 -0
  23. package/dist/types.d.ts +9 -0
  24. package/dist/types.d.ts.map +1 -1
  25. package/dist/utils.d.ts +7 -1
  26. package/dist/utils.d.ts.map +1 -1
  27. package/dist/utils.js +47 -0
  28. package/marketplace/README.md +1 -1
  29. package/marketplace/claude-listing.md +35 -1
  30. package/marketplace/gemini-tools.json +143 -1
  31. package/package.json +1 -1
  32. package/server.json +2 -2
  33. package/src/http.ts +17 -0
  34. package/src/index.ts +10 -0
  35. package/src/tools/calculate_position_size.ts +141 -0
  36. package/src/tools/dead_mans_switch.ts +314 -0
  37. package/src/tools/market_sentiment.ts +141 -0
  38. package/src/tools/price_history.ts +2 -54
  39. package/src/tools/simulate_order.ts +182 -0
  40. package/src/tools/technical_indicators.ts +282 -0
  41. package/src/types.ts +12 -0
  42. package/src/utils.ts +53 -1
  43. package/test/unit.ts +505 -1
@@ -0,0 +1,223 @@
1
+ import { z } from "zod";
2
+ import { BudaApiError } from "../client.js";
3
+ import { validateMarketId } from "../validation.js";
4
+ import { aggregateTradesToCandles } from "../utils.js";
5
+ export const toolSchema = {
6
+ name: "get_technical_indicators",
7
+ description: "Computes RSI (14), MACD (12/26/9), Bollinger Bands (20, 2σ), SMA 20, and SMA 50 " +
8
+ "from Buda trade history — no external data or libraries. " +
9
+ "Uses at least 500 trades for reliable results (set limit=1000 for maximum depth). " +
10
+ "Returns latest indicator values and signal interpretations (overbought/oversold, crossover, band position). " +
11
+ "If fewer than 50 candles are available after aggregation, returns a structured warning instead. " +
12
+ "Example: 'Is BTC-CLP RSI overbought on the 4-hour chart?'",
13
+ inputSchema: {
14
+ type: "object",
15
+ properties: {
16
+ market_id: {
17
+ type: "string",
18
+ description: "Market ID (e.g. 'BTC-CLP', 'ETH-BTC').",
19
+ },
20
+ period: {
21
+ type: "string",
22
+ description: "Candle period: '1h', '4h', or '1d'. Default: '1h'.",
23
+ },
24
+ limit: {
25
+ type: "number",
26
+ description: "Number of raw trades to fetch (default: 500, max: 1000). " +
27
+ "More trades = more candles = more reliable indicators.",
28
+ },
29
+ },
30
+ required: ["market_id"],
31
+ },
32
+ };
33
+ // ---- Pure math helpers ----
34
+ function sma(values, n) {
35
+ const slice = values.slice(-n);
36
+ return slice.reduce((sum, v) => sum + v, 0) / slice.length;
37
+ }
38
+ function ema(values, period) {
39
+ if (values.length < period)
40
+ return [];
41
+ const k = 2 / (period + 1);
42
+ const result = [];
43
+ // Seed with SMA of first `period` values
44
+ result.push(values.slice(0, period).reduce((s, v) => s + v, 0) / period);
45
+ for (let i = period; i < values.length; i++) {
46
+ result.push(values[i] * k + result[result.length - 1] * (1 - k));
47
+ }
48
+ return result;
49
+ }
50
+ function rsi(closes, period = 14) {
51
+ if (closes.length < period + 1)
52
+ return null;
53
+ const gains = [];
54
+ const losses = [];
55
+ for (let i = 1; i < closes.length; i++) {
56
+ const diff = closes[i] - closes[i - 1];
57
+ gains.push(diff > 0 ? diff : 0);
58
+ losses.push(diff < 0 ? -diff : 0);
59
+ }
60
+ // Use simple average for initial, then Wilder's smoothing
61
+ let avgGain = gains.slice(0, period).reduce((s, v) => s + v, 0) / period;
62
+ let avgLoss = losses.slice(0, period).reduce((s, v) => s + v, 0) / period;
63
+ for (let i = period; i < gains.length; i++) {
64
+ avgGain = (avgGain * (period - 1) + gains[i]) / period;
65
+ avgLoss = (avgLoss * (period - 1) + losses[i]) / period;
66
+ }
67
+ if (avgLoss === 0)
68
+ return 100;
69
+ const rs = avgGain / avgLoss;
70
+ return parseFloat((100 - 100 / (1 + rs)).toFixed(2));
71
+ }
72
+ function macd(closes, fast = 12, slow = 26, signalPeriod = 9) {
73
+ if (closes.length < slow + signalPeriod)
74
+ return null;
75
+ const ema12 = ema(closes, fast);
76
+ const ema26 = ema(closes, slow);
77
+ // Align: ema26 starts at index (slow-1) of closes; ema12 starts at index (fast-1)
78
+ // The MACD line length = ema26.length (shorter)
79
+ const offset = slow - fast;
80
+ const macdLine = ema26.map((e26, i) => ema12[i + offset] - e26);
81
+ const signalLine = ema(macdLine, signalPeriod);
82
+ if (signalLine.length === 0)
83
+ return null;
84
+ const lastMacd = macdLine[macdLine.length - 1];
85
+ const lastSignal = signalLine[signalLine.length - 1];
86
+ const histogram = lastMacd - lastSignal;
87
+ const prevHistogram = macdLine.length > 1 && signalLine.length > 1
88
+ ? macdLine[macdLine.length - 2] - signalLine[signalLine.length - 2]
89
+ : null;
90
+ return {
91
+ line: parseFloat(lastMacd.toFixed(2)),
92
+ signal: parseFloat(lastSignal.toFixed(2)),
93
+ histogram: parseFloat(histogram.toFixed(2)),
94
+ prev_histogram: prevHistogram !== null ? parseFloat(prevHistogram.toFixed(2)) : null,
95
+ };
96
+ }
97
+ function bollingerBands(closes, period = 20, stdMult = 2) {
98
+ if (closes.length < period)
99
+ return null;
100
+ const slice = closes.slice(-period);
101
+ const mid = slice.reduce((s, v) => s + v, 0) / period;
102
+ const variance = slice.reduce((s, v) => s + (v - mid) ** 2, 0) / period;
103
+ const std = Math.sqrt(variance);
104
+ return {
105
+ upper: parseFloat((mid + stdMult * std).toFixed(2)),
106
+ mid: parseFloat(mid.toFixed(2)),
107
+ lower: parseFloat((mid - stdMult * std).toFixed(2)),
108
+ };
109
+ }
110
+ // ---- Tool handler ----
111
+ const MIN_CANDLES = 50;
112
+ export async function handleTechnicalIndicators(args, client) {
113
+ const { market_id, period, limit } = args;
114
+ const validationError = validateMarketId(market_id);
115
+ if (validationError) {
116
+ return {
117
+ content: [{ type: "text", text: JSON.stringify({ error: validationError, code: "INVALID_MARKET_ID" }) }],
118
+ isError: true,
119
+ };
120
+ }
121
+ try {
122
+ const id = market_id.toLowerCase();
123
+ const tradesLimit = Math.max(limit ?? 500, 500);
124
+ const data = await client.get(`/markets/${id}/trades`, { limit: tradesLimit });
125
+ const candles = aggregateTradesToCandles(data.trades.entries, period);
126
+ const closes = candles.map((c) => c.close);
127
+ if (candles.length < MIN_CANDLES) {
128
+ return {
129
+ content: [
130
+ {
131
+ type: "text",
132
+ text: JSON.stringify({
133
+ market_id: market_id.toUpperCase(),
134
+ period,
135
+ indicators: null,
136
+ warning: "insufficient_data",
137
+ candles_available: candles.length,
138
+ minimum_required: MIN_CANDLES,
139
+ }),
140
+ },
141
+ ],
142
+ };
143
+ }
144
+ const rsiValue = rsi(closes, 14);
145
+ const macdResult = macd(closes, 12, 26, 9);
146
+ const bbResult = bollingerBands(closes, 20, 2);
147
+ const sma20 = parseFloat(sma(closes, 20).toFixed(2));
148
+ const sma50 = parseFloat(sma(closes, 50).toFixed(2));
149
+ const lastClose = closes[closes.length - 1];
150
+ // Signal interpretations
151
+ const rsiSignal = rsiValue !== null && rsiValue > 70
152
+ ? "overbought"
153
+ : rsiValue !== null && rsiValue < 30
154
+ ? "oversold"
155
+ : "neutral";
156
+ let macdSignal = "neutral";
157
+ if (macdResult && macdResult.prev_histogram !== null) {
158
+ if (macdResult.histogram > 0 && macdResult.prev_histogram <= 0) {
159
+ macdSignal = "bullish_crossover";
160
+ }
161
+ else if (macdResult.histogram < 0 && macdResult.prev_histogram >= 0) {
162
+ macdSignal = "bearish_crossover";
163
+ }
164
+ }
165
+ const bbSignal = bbResult && lastClose > bbResult.upper
166
+ ? "above_upper"
167
+ : bbResult && lastClose < bbResult.lower
168
+ ? "below_lower"
169
+ : "within_bands";
170
+ const result = {
171
+ market_id: market_id.toUpperCase(),
172
+ period,
173
+ candles_used: candles.length,
174
+ indicators: {
175
+ rsi: rsiValue,
176
+ macd: macdResult
177
+ ? { line: macdResult.line, signal: macdResult.signal, histogram: macdResult.histogram }
178
+ : null,
179
+ bollinger_bands: bbResult,
180
+ sma_20: sma20,
181
+ sma_50: sma50,
182
+ },
183
+ signals: {
184
+ rsi_signal: rsiSignal,
185
+ macd_signal: macdSignal,
186
+ bb_signal: bbSignal,
187
+ },
188
+ disclaimer: "Technical indicators are computed from Buda trade history. Not investment advice.",
189
+ };
190
+ return {
191
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
192
+ };
193
+ }
194
+ catch (err) {
195
+ const msg = err instanceof BudaApiError
196
+ ? { error: err.message, code: err.status, path: err.path }
197
+ : { error: String(err), code: "UNKNOWN" };
198
+ return {
199
+ content: [{ type: "text", text: JSON.stringify(msg) }],
200
+ isError: true,
201
+ };
202
+ }
203
+ }
204
+ export function register(server, client) {
205
+ server.tool(toolSchema.name, toolSchema.description, {
206
+ market_id: z
207
+ .string()
208
+ .describe("Market ID (e.g. 'BTC-CLP', 'ETH-BTC')."),
209
+ period: z
210
+ .enum(["1h", "4h", "1d"])
211
+ .default("1h")
212
+ .describe("Candle period: '1h', '4h', or '1d'. Default: '1h'."),
213
+ limit: z
214
+ .number()
215
+ .int()
216
+ .min(500)
217
+ .max(1000)
218
+ .optional()
219
+ .describe("Number of raw trades to fetch (default: 500, max: 1000). " +
220
+ "More trades = more candles = more reliable indicators."),
221
+ }, (args) => handleTechnicalIndicators(args, client));
222
+ }
223
+ //# sourceMappingURL=technical_indicators.js.map
package/dist/types.d.ts CHANGED
@@ -99,4 +99,13 @@ export interface OrdersResponse {
99
99
  total_pages: number;
100
100
  };
101
101
  }
102
+ export interface OhlcvCandle {
103
+ time: string;
104
+ open: number;
105
+ high: number;
106
+ low: number;
107
+ close: number;
108
+ volume: number;
109
+ trade_count: number;
110
+ }
102
111
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Amount } from "./types.js";
1
+ import type { Amount, OhlcvCandle } from "./types.js";
2
2
  /**
3
3
  * Flattens a Buda API Amount tuple [value_string, currency] into a typed object.
4
4
  * All numeric strings are cast to float via parseFloat.
@@ -14,4 +14,10 @@ export declare function flattenAmount(amount: Amount): {
14
14
  * > 1% → "low"
15
15
  */
16
16
  export declare function getLiquidityRating(spreadPct: number): "high" | "medium" | "low";
17
+ /**
18
+ * Aggregates raw Buda trade entries (newest-first) into OHLCV candles for the given period.
19
+ * Entries must be in the format [timestamp_ms, amount, price, direction].
20
+ * Returns candles sorted ascending by bucket start time.
21
+ */
22
+ export declare function aggregateTradesToCandles(entries: [string, string, string, string][], period: string): OhlcvCandle[];
17
23
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAEjF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAI/E"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEtD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAEjF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAI/E;AAQD;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EAC3C,MAAM,EAAE,MAAM,GACb,WAAW,EAAE,CAoCf"}
package/dist/utils.js CHANGED
@@ -18,4 +18,51 @@ export function getLiquidityRating(spreadPct) {
18
18
  return "medium";
19
19
  return "low";
20
20
  }
21
+ const PERIOD_MS = {
22
+ "1h": 60 * 60 * 1000,
23
+ "4h": 4 * 60 * 60 * 1000,
24
+ "1d": 24 * 60 * 60 * 1000,
25
+ };
26
+ /**
27
+ * Aggregates raw Buda trade entries (newest-first) into OHLCV candles for the given period.
28
+ * Entries must be in the format [timestamp_ms, amount, price, direction].
29
+ * Returns candles sorted ascending by bucket start time.
30
+ */
31
+ export function aggregateTradesToCandles(entries, period) {
32
+ const periodMs = PERIOD_MS[period];
33
+ if (!periodMs)
34
+ throw new Error(`Unknown period: ${period}`);
35
+ const sorted = [...entries].sort(([a], [b]) => parseInt(a, 10) - parseInt(b, 10));
36
+ const buckets = new Map();
37
+ for (const [tsMs, amount, price] of sorted) {
38
+ const ts = parseInt(tsMs, 10);
39
+ const bucketStart = Math.floor(ts / periodMs) * periodMs;
40
+ const p = parseFloat(price);
41
+ const v = parseFloat(amount);
42
+ if (!buckets.has(bucketStart)) {
43
+ buckets.set(bucketStart, {
44
+ time: new Date(bucketStart).toISOString(),
45
+ open: p,
46
+ high: p,
47
+ low: p,
48
+ close: p,
49
+ volume: v,
50
+ trade_count: 1,
51
+ });
52
+ }
53
+ else {
54
+ const candle = buckets.get(bucketStart);
55
+ if (p > candle.high)
56
+ candle.high = p;
57
+ if (p < candle.low)
58
+ candle.low = p;
59
+ candle.close = p;
60
+ candle.volume = parseFloat((candle.volume + v).toFixed(8));
61
+ candle.trade_count++;
62
+ }
63
+ }
64
+ return Array.from(buckets.entries())
65
+ .sort(([a], [b]) => a - b)
66
+ .map(([, candle]) => candle);
67
+ }
21
68
  //# sourceMappingURL=utils.js.map
@@ -1,4 +1,4 @@
1
- # Marketplace Submission Assets — v1.3.0
1
+ # Marketplace Submission Assets — v1.4.0
2
2
 
3
3
  Ready-to-use assets for submitting buda-mcp to every major AI marketplace.
4
4
  Replace `gtorreal` / `gtorreal` with your actual handles before submitting.
@@ -14,7 +14,7 @@ Real-time market data from [Buda.com](https://www.buda.com/), the leading crypto
14
14
 
15
15
  Use this server to query live prices, spreads, order books, OHLCV candles, trade history, volume, and cross-market arbitrage opportunities for all BTC, ETH, and altcoin markets quoted in CLP, COP, PEN, and USDC. Optional API credentials unlock account tools for balances and order management.
16
16
 
17
- **v1.3.0 output quality improvements:** All response schemas are now flat and fully typed monetary amounts are returned as floats with separate `_currency` fields instead of `["amount", "currency"]` arrays, making responses directly usable by LLMs without parsing.
17
+ **v1.4.0** adds 5 new tools: `simulate_order`, `calculate_position_size`, `get_market_sentiment`, `get_technical_indicators`, and a dead man's switch (`schedule_cancel_all` + `renew_cancel_timer` + `disarm_cancel_timer`). All response schemas are flat and fully typed.
18
18
 
19
19
  ---
20
20
 
@@ -62,6 +62,22 @@ OHLCV (open/high/low/close/volume) candles derived from recent trade history (Bu
62
62
  Detects cross-country price discrepancies for a given asset across Buda's CLP, COP, and PEN markets, normalized to USDC. Returns pairwise discrepancies above `threshold_pct` sorted by size. Includes a `fees_note` reminding that Buda's 0.8% taker fee per leg (~1.6% round-trip) must be deducted.
63
63
  **Parameters:** `base_currency` *(required)* — e.g. `BTC`, `threshold_pct` *(optional, default 0.5)*.
64
64
 
65
+ ### `simulate_order`
66
+ Simulates a buy or sell order using live ticker data — no order is ever placed. Returns `estimated_fill_price`, `fee_amount`, `fee_currency`, `total_cost`, and `slippage_vs_mid_pct`. All outputs include `simulation: true`. Uses actual taker fee from market data (0.8% crypto / 0.5% stablecoin).
67
+ **Parameters:** `market_id` *(required)*, `side` (`buy`|`sell`) *(required)*, `amount` *(required)*, `price` *(optional — omit for market order simulation)*.
68
+
69
+ ### `calculate_position_size`
70
+ Calculates how many units to buy or sell so a stop-loss hit costs exactly `risk_pct`% of `capital`. Fully client-side — no API call. Returns `units`, `capital_at_risk`, `position_value`, `fee_impact`, and a plain-text `risk_reward_note`.
71
+ **Parameters:** `market_id`, `capital`, `risk_pct` (0.1–10), `entry_price`, `stop_loss_price` *(all required)*.
72
+
73
+ ### `get_market_sentiment`
74
+ Composite sentiment score (−100 to +100) from three components: 24h price variation (40%), volume vs 7-day daily average (35%), spread vs market-type baseline (25%). Returns `score`, `label` (`bearish`/`neutral`/`bullish`), `component_breakdown`, and a `disclaimer`.
75
+ **Parameters:** `market_id` *(required)*.
76
+
77
+ ### `get_technical_indicators`
78
+ RSI (14), MACD (12/26/9), Bollinger Bands (20, 2σ), SMA 20, and SMA 50 — computed server-side from Buda trade history (no external libraries). Returns latest values + signal interpretations. Returns a structured warning if fewer than 50 candles are available after aggregation. Includes `disclaimer`.
79
+ **Parameters:** `market_id` *(required)*, `period` (`1h`/`4h`/`1d`, default `1h`), `limit` *(optional, 500–1000)*.
80
+
65
81
  ### Authenticated tools (require `BUDA_API_KEY` + `BUDA_API_SECRET`)
66
82
 
67
83
  > **Important:** Authenticated instances must run locally only — never expose a server with API credentials publicly.
@@ -81,6 +97,19 @@ Place a limit or market order. Requires `confirmation_token="CONFIRM"` to preven
81
97
  Cancel an open order by ID. Requires `confirmation_token="CONFIRM"`.
82
98
  **Parameters:** `order_id`, `confirmation_token`.
83
99
 
100
+ ### `schedule_cancel_all`
101
+ **WARNING: timer state is lost on server restart. Use only on locally-run instances.**
102
+ Arms an in-memory dead man's switch: if not renewed within `ttl_seconds`, all open orders for the market are automatically cancelled. Requires `confirmation_token="CONFIRM"`.
103
+ **Parameters:** `market_id`, `ttl_seconds` (10–300), `confirmation_token`.
104
+
105
+ ### `renew_cancel_timer`
106
+ Resets the dead man's switch TTL for a market. No confirmation required. Must have an active timer.
107
+ **Parameters:** `market_id`.
108
+
109
+ ### `disarm_cancel_timer`
110
+ Disarms the dead man's switch without cancelling any orders. Safe to call even with no active timer.
111
+ **Parameters:** `market_id`.
112
+
84
113
  ---
85
114
 
86
115
  ## MCP Resources
@@ -106,6 +135,11 @@ Cancel an open order by ID. Requires `confirmation_token="CONFIRM"`.
106
135
  - *"Show me hourly BTC-CLP candles."*
107
136
  - *"What's my available BTC balance?"* *(authenticated)*
108
137
  - *"Show my open orders on BTC-CLP."* *(authenticated)*
138
+ - *"How much would it cost to buy 0.1 ETH on ETH-CLP right now?"*
139
+ - *"How many BTC can I buy with 1M CLP if I risk 2% with a stop at 78M?"*
140
+ - *"Is the BTC-CLP market bullish or bearish right now?"*
141
+ - *"Is BTC-CLP RSI overbought on the 4-hour chart?"*
142
+ - *"Arm a 60-second dead man's switch for my BTC-CLP orders."* *(authenticated)*
109
143
 
110
144
  ---
111
145
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "_comment": "Gemini function declarations for buda-mcp v1.3.0. Pass this array as the `tools[0].functionDeclarations` field when calling the Gemini API. See: https://ai.google.dev/gemini-api/docs/function-calling",
2
+ "_comment": "Gemini function declarations for buda-mcp v1.4.0. Pass this array as the `tools[0].functionDeclarations` field when calling the Gemini API. See: https://ai.google.dev/gemini-api/docs/function-calling",
3
3
  "functionDeclarations": [
4
4
  {
5
5
  "name": "get_markets",
@@ -164,6 +164,148 @@
164
164
  },
165
165
  "required": ["base_currency"]
166
166
  }
167
+ },
168
+ {
169
+ "name": "simulate_order",
170
+ "description": "Simulates a buy or sell order on Buda.com using live ticker data — no order is placed. Returns estimated fill price, fee, total cost, and slippage vs mid-price. Omit 'price' for a market order simulation; supply 'price' for a limit order simulation. All outputs are labelled simulation: true. Example: 'How much would it cost to buy 0.01 BTC on BTC-CLP right now?'",
171
+ "parameters": {
172
+ "type": "OBJECT",
173
+ "properties": {
174
+ "market_id": {
175
+ "type": "STRING",
176
+ "description": "Market identifier such as 'BTC-CLP' or 'ETH-BTC'. Case-insensitive."
177
+ },
178
+ "side": {
179
+ "type": "STRING",
180
+ "description": "'buy' or 'sell'."
181
+ },
182
+ "amount": {
183
+ "type": "NUMBER",
184
+ "description": "Order size in base currency (e.g. BTC for BTC-CLP)."
185
+ },
186
+ "price": {
187
+ "type": "NUMBER",
188
+ "description": "Limit price in quote currency. Omit for a market order simulation."
189
+ }
190
+ },
191
+ "required": ["market_id", "side", "amount"]
192
+ }
193
+ },
194
+ {
195
+ "name": "calculate_position_size",
196
+ "description": "Calculates position size based on capital, risk tolerance, entry price, and stop-loss. Determines units so that a stop-loss hit costs exactly risk_pct% of capital. Fully client-side — no API call. Example: 'How many BTC can I buy with 1M CLP at 2% risk, entry 80M, stop 78M?'",
197
+ "parameters": {
198
+ "type": "OBJECT",
199
+ "properties": {
200
+ "market_id": {
201
+ "type": "STRING",
202
+ "description": "Market identifier (e.g. 'BTC-CLP'). Used to derive the quote currency."
203
+ },
204
+ "capital": {
205
+ "type": "NUMBER",
206
+ "description": "Total available capital in the quote currency."
207
+ },
208
+ "risk_pct": {
209
+ "type": "NUMBER",
210
+ "description": "Percentage of capital to risk on this trade (0.1–10)."
211
+ },
212
+ "entry_price": {
213
+ "type": "NUMBER",
214
+ "description": "Planned entry price in quote currency."
215
+ },
216
+ "stop_loss_price": {
217
+ "type": "NUMBER",
218
+ "description": "Stop-loss price in quote currency. Must be below entry for buys, above for sells."
219
+ }
220
+ },
221
+ "required": ["market_id", "capital", "risk_pct", "entry_price", "stop_loss_price"]
222
+ }
223
+ },
224
+ {
225
+ "name": "get_market_sentiment",
226
+ "description": "Computes a composite sentiment score (−100 to +100) for a Buda.com market based on 24h price variation (40%), volume vs 7-day average (35%), and bid/ask spread vs baseline (25%). Returns score, label (bearish/neutral/bullish), and full component breakdown. Example: 'Is the BTC-CLP market currently bullish or bearish?'",
227
+ "parameters": {
228
+ "type": "OBJECT",
229
+ "properties": {
230
+ "market_id": {
231
+ "type": "STRING",
232
+ "description": "Market identifier such as 'BTC-CLP', 'ETH-BTC', or 'BTC-USDT'. Case-insensitive."
233
+ }
234
+ },
235
+ "required": ["market_id"]
236
+ }
237
+ },
238
+ {
239
+ "name": "get_technical_indicators",
240
+ "description": "Computes RSI (14), MACD (12/26/9), Bollinger Bands (20, 2σ), SMA 20, and SMA 50 from Buda trade history. No external libraries. Returns latest indicator values and signal interpretations. Returns a structured warning if fewer than 50 candles are available. Example: 'Is BTC-CLP RSI overbought on the 4-hour chart?'",
241
+ "parameters": {
242
+ "type": "OBJECT",
243
+ "properties": {
244
+ "market_id": {
245
+ "type": "STRING",
246
+ "description": "Market identifier such as 'BTC-CLP' or 'ETH-BTC'. Case-insensitive."
247
+ },
248
+ "period": {
249
+ "type": "STRING",
250
+ "description": "Candle period: '1h', '4h', or '1d'. Default is '1h'."
251
+ },
252
+ "limit": {
253
+ "type": "INTEGER",
254
+ "description": "Number of raw trades to fetch (500–1000). Default is 500."
255
+ }
256
+ },
257
+ "required": ["market_id"]
258
+ }
259
+ },
260
+ {
261
+ "name": "schedule_cancel_all",
262
+ "description": "WARNING: timer state is lost on server restart. Use only on locally-run instances. Arms an in-memory dead man's switch: if not renewed within ttl_seconds, all open orders for the market are automatically cancelled. Requires confirmation_token='CONFIRM'. Auth-gated.",
263
+ "parameters": {
264
+ "type": "OBJECT",
265
+ "properties": {
266
+ "market_id": {
267
+ "type": "STRING",
268
+ "description": "Market ID to protect (e.g. 'BTC-CLP')."
269
+ },
270
+ "ttl_seconds": {
271
+ "type": "INTEGER",
272
+ "description": "Seconds before all orders are cancelled if not renewed (10–300)."
273
+ },
274
+ "confirmation_token": {
275
+ "type": "STRING",
276
+ "description": "Must equal exactly 'CONFIRM' (case-sensitive) to arm the switch."
277
+ }
278
+ },
279
+ "required": ["market_id", "ttl_seconds", "confirmation_token"]
280
+ }
281
+ },
282
+ {
283
+ "name": "renew_cancel_timer",
284
+ "description": "Resets the dead man's switch TTL for a market, preventing automatic order cancellation. No confirmation required. Requires an active timer set by schedule_cancel_all. Auth-gated.",
285
+ "parameters": {
286
+ "type": "OBJECT",
287
+ "properties": {
288
+ "market_id": {
289
+ "type": "STRING",
290
+ "description": "Market ID whose timer should be renewed (e.g. 'BTC-CLP')."
291
+ }
292
+ },
293
+ "required": ["market_id"]
294
+ }
295
+ },
296
+ {
297
+ "name": "disarm_cancel_timer",
298
+ "description": "Disarms the dead man's switch for a market without cancelling any orders. No confirmation required. Safe to call even if no timer is active. Auth-gated.",
299
+ "parameters": {
300
+ "type": "OBJECT",
301
+ "properties": {
302
+ "market_id": {
303
+ "type": "STRING",
304
+ "description": "Market ID whose timer should be disarmed (e.g. 'BTC-CLP')."
305
+ }
306
+ },
307
+ "required": ["market_id"]
308
+ }
167
309
  }
168
310
  ]
169
311
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guiie/buda-mcp",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "mcpName": "io.github.gtorreal/buda-mcp",
5
5
  "description": "MCP server for Buda.com's public cryptocurrency exchange API (Chile, Colombia, Peru)",
6
6
  "type": "module",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/gtorreal/buda-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "1.3.0",
9
+ "version": "1.4.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@guiie/buda-mcp",
14
- "version": "1.3.0",
14
+ "version": "1.4.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }
package/src/http.ts CHANGED
@@ -19,6 +19,11 @@ import * as balances from "./tools/balances.js";
19
19
  import * as orders from "./tools/orders.js";
20
20
  import * as placeOrder from "./tools/place_order.js";
21
21
  import * as cancelOrder from "./tools/cancel_order.js";
22
+ import * as simulateOrder from "./tools/simulate_order.js";
23
+ import * as positionSize from "./tools/calculate_position_size.js";
24
+ import * as marketSentiment from "./tools/market_sentiment.js";
25
+ import * as technicalIndicators from "./tools/technical_indicators.js";
26
+ import * as deadMansSwitch from "./tools/dead_mans_switch.js";
22
27
  import { handleMarketSummary } from "./tools/market_summary.js";
23
28
 
24
29
  const PORT = parseInt(process.env.PORT ?? "3000", 10);
@@ -44,6 +49,10 @@ const PUBLIC_TOOL_SCHEMAS = [
44
49
  priceHistory.toolSchema,
45
50
  arbitrage.toolSchema,
46
51
  marketSummary.toolSchema,
52
+ simulateOrder.toolSchema,
53
+ positionSize.toolSchema,
54
+ marketSentiment.toolSchema,
55
+ technicalIndicators.toolSchema,
47
56
  ];
48
57
 
49
58
  const AUTH_TOOL_SCHEMAS = [
@@ -51,6 +60,9 @@ const AUTH_TOOL_SCHEMAS = [
51
60
  orders.toolSchema,
52
61
  placeOrder.toolSchema,
53
62
  cancelOrder.toolSchema,
63
+ deadMansSwitch.toolSchema,
64
+ deadMansSwitch.renewToolSchema,
65
+ deadMansSwitch.disarmToolSchema,
54
66
  ];
55
67
 
56
68
  function createServer(): McpServer {
@@ -69,12 +81,17 @@ function createServer(): McpServer {
69
81
  priceHistory.register(server, client, reqCache);
70
82
  arbitrage.register(server, client, reqCache);
71
83
  marketSummary.register(server, client, reqCache);
84
+ simulateOrder.register(server, client, reqCache);
85
+ positionSize.register(server);
86
+ marketSentiment.register(server, client, reqCache);
87
+ technicalIndicators.register(server, client);
72
88
 
73
89
  if (authEnabled) {
74
90
  balances.register(server, client);
75
91
  orders.register(server, client);
76
92
  placeOrder.register(server, client);
77
93
  cancelOrder.register(server, client);
94
+ deadMansSwitch.register(server, client);
78
95
  }
79
96
 
80
97
  // MCP Resources
package/src/index.ts CHANGED
@@ -20,6 +20,11 @@ import * as balances from "./tools/balances.js";
20
20
  import * as orders from "./tools/orders.js";
21
21
  import * as placeOrder from "./tools/place_order.js";
22
22
  import * as cancelOrder from "./tools/cancel_order.js";
23
+ import * as simulateOrder from "./tools/simulate_order.js";
24
+ import * as positionSize from "./tools/calculate_position_size.js";
25
+ import * as marketSentiment from "./tools/market_sentiment.js";
26
+ import * as technicalIndicators from "./tools/technical_indicators.js";
27
+ import * as deadMansSwitch from "./tools/dead_mans_switch.js";
23
28
  import { handleMarketSummary } from "./tools/market_summary.js";
24
29
 
25
30
  const client = new BudaClient(
@@ -44,6 +49,10 @@ compareMarkets.register(server, client, cache);
44
49
  priceHistory.register(server, client, cache);
45
50
  arbitrage.register(server, client, cache);
46
51
  marketSummary.register(server, client, cache);
52
+ simulateOrder.register(server, client, cache);
53
+ positionSize.register(server);
54
+ marketSentiment.register(server, client, cache);
55
+ technicalIndicators.register(server, client);
47
56
 
48
57
  // Auth-gated tools — only registered when API credentials are present
49
58
  if (client.hasAuth()) {
@@ -51,6 +60,7 @@ if (client.hasAuth()) {
51
60
  orders.register(server, client);
52
61
  placeOrder.register(server, client);
53
62
  cancelOrder.register(server, client);
63
+ deadMansSwitch.register(server, client);
54
64
  }
55
65
 
56
66
  // MCP Resources