@guiie/buda-mcp 1.2.2 → 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 (79) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/PUBLISH_CHECKLIST.md +71 -63
  3. package/README.md +4 -4
  4. package/dist/http.js +39 -0
  5. package/dist/index.js +29 -0
  6. package/dist/tools/arbitrage.d.ts +35 -0
  7. package/dist/tools/arbitrage.d.ts.map +1 -0
  8. package/dist/tools/arbitrage.js +142 -0
  9. package/dist/tools/balances.d.ts.map +1 -1
  10. package/dist/tools/balances.js +24 -4
  11. package/dist/tools/calculate_position_size.d.ts +48 -0
  12. package/dist/tools/calculate_position_size.d.ts.map +1 -0
  13. package/dist/tools/calculate_position_size.js +111 -0
  14. package/dist/tools/compare_markets.d.ts.map +1 -1
  15. package/dist/tools/compare_markets.js +11 -10
  16. package/dist/tools/dead_mans_switch.d.ts +84 -0
  17. package/dist/tools/dead_mans_switch.d.ts.map +1 -0
  18. package/dist/tools/dead_mans_switch.js +236 -0
  19. package/dist/tools/market_sentiment.d.ts +30 -0
  20. package/dist/tools/market_sentiment.d.ts.map +1 -0
  21. package/dist/tools/market_sentiment.js +104 -0
  22. package/dist/tools/market_summary.d.ts +43 -0
  23. package/dist/tools/market_summary.d.ts.map +1 -0
  24. package/dist/tools/market_summary.js +81 -0
  25. package/dist/tools/markets.d.ts.map +1 -1
  26. package/dist/tools/markets.js +4 -2
  27. package/dist/tools/orderbook.d.ts.map +1 -1
  28. package/dist/tools/orderbook.js +14 -4
  29. package/dist/tools/orders.d.ts.map +1 -1
  30. package/dist/tools/orders.js +41 -3
  31. package/dist/tools/price_history.d.ts.map +1 -1
  32. package/dist/tools/price_history.js +5 -43
  33. package/dist/tools/simulate_order.d.ts +45 -0
  34. package/dist/tools/simulate_order.d.ts.map +1 -0
  35. package/dist/tools/simulate_order.js +139 -0
  36. package/dist/tools/spread.d.ts.map +1 -1
  37. package/dist/tools/spread.js +10 -8
  38. package/dist/tools/technical_indicators.d.ts +39 -0
  39. package/dist/tools/technical_indicators.d.ts.map +1 -0
  40. package/dist/tools/technical_indicators.js +223 -0
  41. package/dist/tools/ticker.d.ts.map +1 -1
  42. package/dist/tools/ticker.js +24 -3
  43. package/dist/tools/trades.d.ts.map +1 -1
  44. package/dist/tools/trades.js +17 -3
  45. package/dist/tools/volume.d.ts.map +1 -1
  46. package/dist/tools/volume.js +21 -3
  47. package/dist/types.d.ts +9 -0
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/utils.d.ts +23 -0
  50. package/dist/utils.d.ts.map +1 -0
  51. package/dist/utils.js +68 -0
  52. package/marketplace/README.md +1 -1
  53. package/marketplace/claude-listing.md +60 -14
  54. package/marketplace/gemini-tools.json +183 -9
  55. package/marketplace/openapi.yaml +335 -119
  56. package/package.json +1 -1
  57. package/server.json +2 -2
  58. package/src/http.ts +44 -0
  59. package/src/index.ts +34 -0
  60. package/src/tools/arbitrage.ts +202 -0
  61. package/src/tools/balances.ts +27 -4
  62. package/src/tools/calculate_position_size.ts +141 -0
  63. package/src/tools/compare_markets.ts +11 -10
  64. package/src/tools/dead_mans_switch.ts +314 -0
  65. package/src/tools/market_sentiment.ts +141 -0
  66. package/src/tools/market_summary.ts +124 -0
  67. package/src/tools/markets.ts +4 -2
  68. package/src/tools/orderbook.ts +15 -4
  69. package/src/tools/orders.ts +45 -4
  70. package/src/tools/price_history.ts +5 -57
  71. package/src/tools/simulate_order.ts +182 -0
  72. package/src/tools/spread.ts +10 -8
  73. package/src/tools/technical_indicators.ts +282 -0
  74. package/src/tools/ticker.ts +27 -3
  75. package/src/tools/trades.ts +18 -3
  76. package/src/tools/volume.ts +24 -3
  77. package/src/types.ts +12 -0
  78. package/src/utils.ts +73 -0
  79. package/test/unit.ts +758 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,74 @@ This project uses [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.4.0] – 2026-04-11
11
+
12
+ ### Added
13
+
14
+ - **`simulate_order`** (`src/tools/simulate_order.ts`) — public tool that simulates a buy or sell order using live ticker and market data without placing a real order. Inputs: `market_id`, `side` (`buy`|`sell`), `amount`, optional `price` (omit for market order). Fetches ticker (cached) + market info (cached) in parallel to determine fill price, fee rate, and slippage. Uses the actual `taker_fee` from the market (0.8% crypto / 0.5% stablecoin). All responses include `simulation: true`. Exports `handleSimulateOrder` for unit testing.
15
+
16
+ - **`calculate_position_size`** (`src/tools/calculate_position_size.ts`) — public tool for Kelly-style position sizing from capital, risk %, entry price, and stop-loss. Fully client-side — no API calls. Infers `side` (`buy`/`sell`) from the stop vs entry relationship. Validates that stop ≠ entry. Returns `units`, `capital_at_risk`, `position_value`, `fee_impact` (0.8% conservative taker), and a plain-text `risk_reward_note`. Exports `handleCalculatePositionSize` for unit testing.
17
+
18
+ - **`get_market_sentiment`** (`src/tools/market_sentiment.ts`) — public tool computing a composite sentiment score (−100 to +100) from three weighted components: price variation 24h (40%), volume vs 7-day daily average (35%), bid/ask spread vs market-type baseline (25%). Spread baseline: 1.0% for crypto pairs, 0.3% for stablecoin pairs (USDT/USDC/DAI/TUSD). Returns `score`, `label` (`bearish`/`neutral`/`bullish`), `component_breakdown`, `data_timestamp`, and a `disclaimer`. Exports `handleMarketSentiment` for unit testing.
19
+
20
+ - **`get_technical_indicators`** (`src/tools/technical_indicators.ts`) — public tool computing RSI (14), MACD (12/26/9), Bollinger Bands (20, 2σ), SMA 20, and SMA 50 from Buda trade history. No external math libraries — all algorithms implemented inline. Uses at least 500 trades (minimum enforced). Returns signal interpretations: RSI overbought/oversold/neutral, MACD bullish/bearish crossover/neutral, BB above/below/within bands. Returns a structured `{ indicators: null, warning: "insufficient_data" }` object when fewer than 50 candles are available. Includes `disclaimer` field. Exports `handleTechnicalIndicators` for unit testing.
21
+
22
+ - **`schedule_cancel_all` + `renew_cancel_timer` + `disarm_cancel_timer`** (`src/tools/dead_mans_switch.ts`) — three auth-gated tools implementing an in-memory dead man's switch. `schedule_cancel_all` requires `confirmation_token="CONFIRM"`, `ttl_seconds` (10–300), and a `market_id`; arms a `setTimeout` that fetches all pending orders and cancels each one if not renewed. `renew_cancel_timer` resets the countdown for a market (no confirmation). `disarm_cancel_timer` clears the timer without cancelling orders (no confirmation). **WARNING: timer state is lost on server restart — not suitable for hosted deployments (e.g. Railway). Use only on locally-run instances.** Timer state is module-level and persists across HTTP requests within a process. Exports `handleScheduleCancelAll`, `handleRenewCancelTimer`, `handleDisarmCancelTimer` for unit testing.
23
+
24
+ - **`src/utils.ts` — `aggregateTradesToCandles(entries, period)`** — shared utility extracted from `get_price_history` logic. Takes raw Buda trade entries and a period string (`1h`/`4h`/`1d`), returns sorted `OhlcvCandle[]`. Used by both `get_price_history` and `get_technical_indicators`.
25
+
26
+ - **`src/types.ts` — `OhlcvCandle` interface** — exported for use across tools.
27
+
28
+ - **Unit tests (24 new, 59 total)** in `test/unit.ts`:
29
+ - **i. `simulate_order`** (5 tests): market buy fills at min_ask; market sell fills at max_bid; limit order_type_assumed; stablecoin 0.5% fee; invalid market_id.
30
+ - **j. `calculate_position_size`** (4 tests): buy scenario; sell scenario; stop == entry error; invalid market_id.
31
+ - **k. `get_market_sentiment`** (5 tests): disclaimer present; neutral label; bullish on strong positive variation; bearish on strong negative variation; invalid market_id.
32
+ - **l. `get_technical_indicators`** (4 tests): `aggregateTradesToCandles` OHLCV correctness; insufficient candles warning; sufficient candles with correct RSI signal; invalid market_id.
33
+ - **m. `schedule_cancel_all`** (6 tests): CONFIRM guard; invalid market_id; CONFIRM activates + expires_at; renew with no timer; disarm with no timer (no-op); disarm after arm clears timer.
34
+
35
+ ### Changed
36
+
37
+ - **`src/tools/price_history.ts`** — refactored to use the new shared `aggregateTradesToCandles()` from `utils.ts`. Behaviour is identical.
38
+
39
+ ---
40
+
41
+ ## [1.3.0] – 2026-04-11
42
+
43
+ ### Added
44
+
45
+ - **`src/utils.ts`** — shared `flattenAmount(amount: Amount)` helper (returns `{ value: number, currency: string }`) and `getLiquidityRating(spreadPct: number)` helper (`"high"` / `"medium"` / `"low"`) used across multiple tools and unit-testable in isolation.
46
+
47
+ - **`get_arbitrage_opportunities`** (`src/tools/arbitrage.ts`) — new public tool that detects cross-country price discrepancies for a given asset across Buda's CLP, COP, and PEN markets, normalised to USDC. Inputs: `base_currency` (e.g. `"BTC"`) and optional `threshold_pct` (default `0.5`). Algorithm: fetches all tickers, converts each local price to USDC via the current USDC-CLP / USDC-COP / USDC-PEN rates, computes all pairwise discrepancy percentages, filters by threshold, and sorts descending. Output includes a `fees_note` reminding callers that Buda's 0.8% taker fee per leg (~1.6% round-trip) must be deducted. Exports `handleArbitrageOpportunities` for unit testing.
48
+
49
+ - **`get_market_summary`** (`src/tools/market_summary.ts`) — new public tool that returns a single unified object with everything relevant about a market: `last_price`, `last_price_currency`, `bid`, `ask`, `spread_pct`, `volume_24h`, `volume_24h_currency`, `price_change_24h`, `price_change_7d`, and `liquidity_rating` (`"high"` when spread < 0.3%, `"medium"` when 0.3–1%, `"low"` when > 1%). Makes 2 API calls in parallel (ticker + volume); spread is derived from the ticker without a third call. Exports `handleMarketSummary` for unit testing.
50
+
51
+ - **`buda://summary/{market}`** MCP Resource — registered in both stdio (`src/index.ts`) and HTTP (`src/http.ts`) transports. Returns the same JSON as `get_market_summary`. Added to the server-card `resources` array.
52
+
53
+ - **Unit tests (12 new, 35 total)** in `test/unit.ts`:
54
+ - **f. Numeric flattening** (4 tests): `flattenAmount` returns a `number`, not a string; handles decimals and zero correctly; result is not an array.
55
+ - **g. `get_arbitrage_opportunities`** (3 tests): mocked tickers verify correct USDC-normalised discrepancy calculation (~3.95% for BTC CLP vs PEN at the given rates); threshold 5% correctly excludes the opportunity; returns `isError` when fewer than 2 markets have USDC rates.
56
+ - **h. `get_market_summary` / `getLiquidityRating`** (5 tests): boundary tests for all three liquidity tiers; end-to-end mock verifies `liquidity_rating: "high"` at 0.2% spread and that `last_price` is a number type.
57
+
58
+ ### Changed
59
+
60
+ - **Flat response schemas across all tools** — every tool that previously returned Buda `[amount_string, currency_string]` tuples now returns flat, typed fields. All numeric strings are cast to `parseFloat`; the currency is separated into a `_currency`-suffixed sibling field. Specific changes per tool:
61
+ - **`get_ticker`** — `last_price`, `min_ask`, `max_bid`, `volume` flattened; `price_variation_24h` / `price_variation_7d` cast to float.
62
+ - **`get_market_volume`** — all four `*_volume_*` Amount fields flattened with `_currency` suffix.
63
+ - **`get_orderbook`** — bids and asks converted from `[price, amount]` tuples to `{ price: float, amount: float }` objects.
64
+ - **`get_trades`** — entries converted from `[timestamp_ms, amount, price, direction]` tuples to `{ timestamp_ms: int, amount: float, price: float, direction: string }` objects.
65
+ - **`get_spread`** — `best_bid`, `best_ask`, `spread_absolute`, `last_price` → floats; `spread_percentage` → float (the "%" suffix is dropped); `currency` renamed to `price_currency`.
66
+ - **`compare_markets`** — per-market `last_price` + `last_price_currency` (was `last_price` + `currency`); `best_bid`, `best_ask`, `volume_24h` → floats; `price_change_*` → floats in percent (was strings like `"1.23%"`).
67
+ - **`get_price_history`** — OHLCV candle fields `open`, `high`, `low`, `close`, `volume` → floats (were strings).
68
+ - **`get_balances`** — all four Amount fields per balance entry (`amount`, `available_amount`, `frozen_amount`, `pending_withdraw_amount`) flattened with `_currency` suffix.
69
+ - **`get_orders`** — all Amount fields (`limit`, `amount`, `original_amount`, `traded_amount`, `total_exchanged`, `paid_fee`) flattened; `limit` renamed to `limit_price` / `limit_price_currency`.
70
+
71
+ - **Improved tool descriptions** — all 12 tool descriptions (10 public + 2 auth) rewritten to be specific about return shape, include units, and give a concrete example question an LLM might ask.
72
+
73
+ - **`package.json`** version bumped to `1.3.0`.
74
+ - **`marketplace/`** and **`PUBLISH_CHECKLIST.md`** updated to reflect v1.3.0 changes.
75
+
76
+ ---
77
+
10
78
  ## [1.2.0] – 2026-04-11
11
79
 
12
80
  ### Added
@@ -1,6 +1,8 @@
1
- # Publish Checklist — buda-mcp v1.2.0
1
+ # Publish Checklist — buda-mcp v1.4.0
2
2
 
3
- Steps to publish `v1.2.0` to npm, the MCP registry, and notify community directories.
3
+ Steps to publish `v1.4.0` to npm, the MCP registry, and notify community directories.
4
+
5
+ > **Important for v1.4.0:** The new `schedule_cancel_all` tool uses in-memory timer state that is lost on server restart. This is prominently documented in the tool description, README auth section, and CHANGELOG. Do NOT encourage users to rely on this tool in hosted/Railway deployments.
4
6
 
5
7
  ---
6
8
 
@@ -8,7 +10,7 @@ Steps to publish `v1.2.0` to npm, the MCP registry, and notify community directo
8
10
 
9
11
  ```bash
10
12
  # Confirm version
11
- node -e "console.log(require('./package.json').version)" # should print 1.2.0
13
+ node -e "console.log(require('./package.json').version)" # should print 1.4.0
12
14
 
13
15
  # Build and test
14
16
  npm run build
@@ -39,49 +41,51 @@ Verify: https://www.npmjs.com/package/@guiie/buda-mcp
39
41
 
40
42
  ```bash
41
43
  git add -A
42
- git commit -m "chore: release v1.2.0
43
-
44
- - Single version source-of-truth via src/version.ts (no more hardcoded strings)
45
- - Programmatic server-card in http.ts (toolSchema exported per tool)
46
- - .env.example with documented BUDA_API_KEY / BUDA_API_SECRET
47
- - Input sanitization: validateMarketId regex on all market_id inputs
48
- - 429 retry with Retry-After (seconds, per RFC 7231), default 1s
49
- - get_price_history: limit raised to 1000, UTC bucketing documented
50
- - 23 unit tests: HMAC signing, cache dedup, confirmation guard, sanitization, 429 retry
51
- - Integration tests: graceful skip when Buda API is unreachable"
52
-
53
- git tag v1.2.0
44
+ git commit -m "chore: release v1.4.0
45
+
46
+ - simulate_order: live order cost simulation (no order placed, simulation: true)
47
+ - calculate_position_size: Kelly-style sizing from capital/risk/entry/stop (client-side)
48
+ - get_market_sentiment: composite score -100..+100 from price/volume/spread
49
+ - get_technical_indicators: RSI/MACD/BB/SMA20/SMA50 from trade history (no libs)
50
+ - schedule_cancel_all + renew_cancel_timer + disarm_cancel_timer: in-memory dead man's switch (auth-gated)
51
+ - aggregateTradesToCandles() extracted to utils.ts (shared by price_history + technical_indicators)
52
+ - OhlcvCandle interface moved to types.ts
53
+ - 59 unit tests (24 new)"
54
+
55
+ git tag v1.4.0
54
56
  git push origin main --tags
55
57
  ```
56
58
 
57
- Then create a GitHub Release from the tag with the following release notes:
59
+ Then create a GitHub Release from the tag:
58
60
 
59
61
  ---
60
62
 
61
63
  **Release notes template (GitHub):**
62
64
 
63
65
  ```
64
- ## buda-mcp v1.2.0
66
+ ## buda-mcp v1.4.0 — Trading Tools
67
+
68
+ ### 5 new tools
69
+
70
+ **`simulate_order`** (public)
71
+ Simulates a buy or sell order using live ticker data — no order placed. Returns estimated fill price, fee (actual taker rate from market data: 0.8% crypto / 0.5% stablecoin), total cost, and slippage vs mid. All outputs include simulation: true.
65
72
 
66
- ### What's new
73
+ **`calculate_position_size`** (public)
74
+ Kelly-style position sizing from capital, risk %, entry, and stop-loss. Fully client-side. Returns units, capital_at_risk, position_value, fee_impact, and a plain-text risk note.
67
75
 
68
- **Bug fixes & maintenance**
69
- - Single version source-of-truth: all version strings now read from `package.json` at startup via `src/version.ts` no more drift between files
70
- - `http.ts` server-card assembled programmatically from exported `toolSchema` constants — adding a tool no longer requires touching `http.ts`
76
+ **`get_market_sentiment`** (public)
77
+ Composite sentiment score (−100 to +100) from price variation 24h (40%), volume vs 7d average (35%), and spread vs market-type baseline (25%). Returns score, label, component breakdown, and disclaimer.
71
78
 
72
- **Security / reliability**
73
- - Input sanitization: all `market_id` inputs validated against `/^[A-Z0-9]{2,10}-[A-Z0-9]{2,10}$/i` before URL interpolation rejects path traversal and malformed IDs with structured errors
74
- - 429 retry: `BudaClient` retries once on rate-limit responses, honoring the `Retry-After` header (seconds, per RFC 7231; defaults to 1s if absent). Double-429 throws `BudaApiError` with `retryAfterMs`.
79
+ **`get_technical_indicators`** (public)
80
+ RSI (14), MACD (12/26/9), Bollinger Bands (20, 2σ), SMA 20, SMA 50computed server-side from Buda trade history with no external libraries. Returns signal interpretations and structured warning if insufficient candles.
75
81
 
76
- **DX improvements**
77
- - `.env.example` added for easy credential setup
78
- - `get_price_history` limit raised from 100 to 1000 trades; UTC bucketing documented prominently
79
- - `npm run sync-version` syncs `server.json` from `package.json` automatically
82
+ **`schedule_cancel_all` + `renew_cancel_timer` + `disarm_cancel_timer`** (auth-gated)
83
+ In-memory dead man's switch: arms a timer that cancels all open orders if not renewed. WARNING: timer state is lost on server restart. Use only on locally-run instances.
80
84
 
81
- **Test suite**
82
- - 23 new unit tests (no live API needed): HMAC signing exactness, cache deduplication, confirmation_token guards, input sanitization, 429 retry behavior
83
- - Integration tests skip gracefully when Buda API is unreachable (CI-friendly)
84
- - New scripts: `npm run test:unit`, `npm run test:integration`
85
+ ### Infrastructure
86
+ - `aggregateTradesToCandles()` extracted to `utils.ts` shared by `get_price_history` and `get_technical_indicators`
87
+ - `OhlcvCandle` interface exported from `types.ts`
88
+ - 59 unit tests (was 35)
85
89
 
86
90
  ```bash
87
91
  npx @guiie/buda-mcp
@@ -96,12 +100,6 @@ The GitHub Actions workflow (`.github/workflows/publish.yml`) runs automatically
96
100
 
97
101
  https://registry.modelcontextprotocol.io/servers/io.github.gtorreal/buda-mcp
98
102
 
99
- If the workflow doesn't trigger, run manually:
100
-
101
- ```bash
102
- MCP_REGISTRY_TOKEN=<token> ./mcp-publisher publish
103
- ```
104
-
105
103
  ---
106
104
 
107
105
  ## 5. Smithery
@@ -117,19 +115,19 @@ Verify: https://smithery.ai/server/@guiie/buda-mcp
117
115
  **Email/message template:**
118
116
 
119
117
  ```
120
- Subject: [Update] buda-mcp v1.2.0 — input sanitization, 429 retry, 23 unit tests
118
+ Subject: [Update] buda-mcp v1.4.0 — simulate_order, technical indicators, sentiment, position sizing, dead man's switch
121
119
 
122
120
  Hi mcp.so team,
123
121
 
124
- I've released v1.2.0 of buda-mcp (@guiie/buda-mcp on npm).
122
+ I've released v1.4.0 of buda-mcp (@guiie/buda-mcp on npm).
125
123
 
126
- Key changes:
127
- - Input sanitization: all market IDs validated against a strict regex before URL use
128
- - 429 rate-limit retry: honors Retry-After header (seconds, RFC 7231), defaults to 1s
129
- - get_price_history: limit raised to 1000 trades for deeper history
130
- - 23 unit tests added (no live API required): HMAC, cache dedup, confirmation guards, sanitization, retry
131
- - Single version source-of-truth (package.json all files via src/version.ts)
132
- - .env.example added for easy credential setup
124
+ Key changes (5 new tools + 3 sub-tools):
125
+ - simulate_order: live order cost simulation with actual fee rates (no order placed)
126
+ - calculate_position_size: Kelly-style position sizing (fully client-side)
127
+ - get_market_sentiment: composite score -100..+100 from price/volume/spread microstructure
128
+ - get_technical_indicators: RSI/MACD/Bollinger Bands/SMA (no external libs, from trade history)
129
+ - schedule_cancel_all / renew_cancel_timer / disarm_cancel_timer: in-memory dead man's switch (auth-gated)
130
+ - 59 unit tests (was 35)
133
131
 
134
132
  Links:
135
133
  - npm: https://www.npmjs.com/package/@guiie/buda-mcp
@@ -148,22 +146,23 @@ Thank you!
148
146
  **Message template:**
149
147
 
150
148
  ```
151
- Subject: [Update] buda-mcp v1.2.0
149
+ Subject: [Update] buda-mcp v1.4.0
152
150
 
153
151
  Hi Glama team,
154
152
 
155
- buda-mcp has been updated to v1.2.0.
153
+ buda-mcp has been updated to v1.4.0.
156
154
 
157
155
  Package: @guiie/buda-mcp (npm)
158
156
  Registry: io.github.gtorreal/buda-mcp (MCP Registry)
159
- Version: 1.2.0
157
+ Version: 1.4.0
160
158
 
161
- Changes:
162
- - Input validation on all market_id inputs (structured isError: true on failure)
163
- - 429 retry with Retry-After support (RFC 7231 seconds; default 1s)
164
- - get_price_history limit raised to 1000 trades; UTC bucket timestamps documented
165
- - 23 unit tests: HMAC signing, cache deduplication, confirmation guards, sanitization, retry
166
- - Single version source (package.json); .env.example added
159
+ Changes (5 new tools + 3 sub-tools):
160
+ - simulate_order: order simulation with live data, simulation: true always set
161
+ - calculate_position_size: client-side position sizing
162
+ - get_market_sentiment: composite sentiment score with disclaimers
163
+ - get_technical_indicators: RSI/MACD/BB/SMA from trade history
164
+ - schedule_cancel_all + renew/disarm: in-memory dead man's switch (auth-gated, local use only)
165
+ - 59 unit tests
167
166
 
168
167
  Quick start:
169
168
  npx @guiie/buda-mcp
@@ -178,12 +177,21 @@ Thank you!
178
177
 
179
178
  ## 8. Post-publish verification
180
179
 
181
- - [ ] `npx @guiie/buda-mcp@1.2.0` starts successfully
182
- - [ ] `npm info @guiie/buda-mcp version` returns `1.2.0`
183
- - [ ] GitHub release tag `v1.2.0` is visible
184
- - [ ] MCP Registry entry reflects v1.2.0
185
- - [ ] Smithery server card lists 8 public tools (with updated get_price_history description)
186
- - [ ] `GET /health` returns `"version":"1.2.0"` on Railway deployment
187
- - [ ] `GET /.well-known/mcp/server-card.json` returns tools with updated schemas (no hardcoded JSON)
180
+ - [ ] `npx @guiie/buda-mcp@1.4.0` starts successfully
181
+ - [ ] `npm info @guiie/buda-mcp version` returns `1.4.0`
182
+ - [ ] GitHub release tag `v1.4.0` is visible
183
+ - [ ] MCP Registry entry reflects v1.4.0
184
+ - [ ] Smithery server card lists 14 public tools (including 4 new: simulate_order, calculate_position_size, get_market_sentiment, get_technical_indicators)
185
+ - [ ] Smithery server card lists 7 auth tools (including schedule_cancel_all, renew_cancel_timer, disarm_cancel_timer)
186
+ - [ ] `GET /health` returns `"version":"1.4.0"` on Railway deployment
187
+ - [ ] simulate_order response includes `simulation: true`
188
+ - [ ] get_technical_indicators returns `warning: "insufficient_data"` for markets with few trades
189
+ - [ ] schedule_cancel_all requires `confirmation_token="CONFIRM"` (test with wrong token)
188
190
  - [ ] mcp.so listing updated
189
191
  - [ ] Glama.ai listing updated
192
+
193
+ ---
194
+
195
+ ## ARCHIVED: v1.3.0 checklist
196
+
197
+ See git tag `v1.3.0` for the v1.3.0 release notes and verification steps.
package/README.md CHANGED
@@ -285,11 +285,11 @@ Authentication uses HMAC-SHA384 signing per the [Buda API docs](https://api.buda
285
285
 
286
286
  | Quote | Country | Sample pairs |
287
287
  |-------|---------|-------------|
288
- | CLP | Chile | BTC-CLP, ETH-CLP, XRP-CLP |
289
- | COP | Colombia | BTC-COP, ETH-COP, XRP-COP |
288
+ | CLP | Chile | BTC-CLP, ETH-CLP, SOL-CLP |
289
+ | COP | Colombia | BTC-COP, ETH-COP, SOL-COP |
290
290
  | PEN | Peru | BTC-PEN, ETH-PEN |
291
- | USDC | USD-pegged | BTC-USDC, ETH-USDC |
292
- | BTC | Cross | ETH-BTC, XRP-BTC, BCH-BTC |
291
+ | USDC | USD-pegged | BTC-USDC, USDT-USDC |
292
+ | BTC | Cross | ETH-BTC, LTC-BTC, BCH-BTC |
293
293
 
294
294
  ---
295
295
 
package/dist/http.js CHANGED
@@ -12,10 +12,18 @@ import * as volume from "./tools/volume.js";
12
12
  import * as spread from "./tools/spread.js";
13
13
  import * as compareMarkets from "./tools/compare_markets.js";
14
14
  import * as priceHistory from "./tools/price_history.js";
15
+ import * as arbitrage from "./tools/arbitrage.js";
16
+ import * as marketSummary from "./tools/market_summary.js";
15
17
  import * as balances from "./tools/balances.js";
16
18
  import * as orders from "./tools/orders.js";
17
19
  import * as placeOrder from "./tools/place_order.js";
18
20
  import * as cancelOrder from "./tools/cancel_order.js";
21
+ import * as simulateOrder from "./tools/simulate_order.js";
22
+ import * as positionSize from "./tools/calculate_position_size.js";
23
+ import * as marketSentiment from "./tools/market_sentiment.js";
24
+ import * as technicalIndicators from "./tools/technical_indicators.js";
25
+ import * as deadMansSwitch from "./tools/dead_mans_switch.js";
26
+ import { handleMarketSummary } from "./tools/market_summary.js";
19
27
  const PORT = parseInt(process.env.PORT ?? "3000", 10);
20
28
  const client = new BudaClient(undefined, process.env.BUDA_API_KEY, process.env.BUDA_API_SECRET);
21
29
  const authEnabled = client.hasAuth();
@@ -30,12 +38,21 @@ const PUBLIC_TOOL_SCHEMAS = [
30
38
  spread.toolSchema,
31
39
  compareMarkets.toolSchema,
32
40
  priceHistory.toolSchema,
41
+ arbitrage.toolSchema,
42
+ marketSummary.toolSchema,
43
+ simulateOrder.toolSchema,
44
+ positionSize.toolSchema,
45
+ marketSentiment.toolSchema,
46
+ technicalIndicators.toolSchema,
33
47
  ];
34
48
  const AUTH_TOOL_SCHEMAS = [
35
49
  balances.toolSchema,
36
50
  orders.toolSchema,
37
51
  placeOrder.toolSchema,
38
52
  cancelOrder.toolSchema,
53
+ deadMansSwitch.toolSchema,
54
+ deadMansSwitch.renewToolSchema,
55
+ deadMansSwitch.disarmToolSchema,
39
56
  ];
40
57
  function createServer() {
41
58
  const server = new McpServer({ name: "buda-mcp", version: VERSION });
@@ -49,11 +66,18 @@ function createServer() {
49
66
  spread.register(server, client, reqCache);
50
67
  compareMarkets.register(server, client, reqCache);
51
68
  priceHistory.register(server, client, reqCache);
69
+ arbitrage.register(server, client, reqCache);
70
+ marketSummary.register(server, client, reqCache);
71
+ simulateOrder.register(server, client, reqCache);
72
+ positionSize.register(server);
73
+ marketSentiment.register(server, client, reqCache);
74
+ technicalIndicators.register(server, client);
52
75
  if (authEnabled) {
53
76
  balances.register(server, client);
54
77
  orders.register(server, client);
55
78
  placeOrder.register(server, client);
56
79
  cancelOrder.register(server, client);
80
+ deadMansSwitch.register(server, client);
57
81
  }
58
82
  // MCP Resources
59
83
  server.resource("buda-markets", "buda://markets", async (uri) => {
@@ -81,6 +105,20 @@ function createServer() {
81
105
  ],
82
106
  };
83
107
  });
108
+ server.resource("buda-summary", new ResourceTemplate("buda://summary/{market}", { list: undefined }), async (uri, params) => {
109
+ const marketId = params.market.toUpperCase();
110
+ const result = await handleMarketSummary({ market_id: marketId }, client, reqCache);
111
+ const text = result.content[0].text;
112
+ return {
113
+ contents: [
114
+ {
115
+ uri: uri.href,
116
+ mimeType: "application/json",
117
+ text,
118
+ },
119
+ ],
120
+ };
121
+ });
84
122
  return server;
85
123
  }
86
124
  const app = express();
@@ -104,6 +142,7 @@ app.get("/.well-known/mcp/server-card.json", (_req, res) => {
104
142
  resources: [
105
143
  { uri: "buda://markets", name: "All Buda.com markets", mimeType: "application/json" },
106
144
  { uri: "buda://ticker/{market}", name: "Ticker for a specific market", mimeType: "application/json" },
145
+ { uri: "buda://summary/{market}", name: "Full market summary with liquidity rating", mimeType: "application/json" },
107
146
  ],
108
147
  prompts: [],
109
148
  });
package/dist/index.js CHANGED
@@ -13,10 +13,18 @@ import * as volume from "./tools/volume.js";
13
13
  import * as spread from "./tools/spread.js";
14
14
  import * as compareMarkets from "./tools/compare_markets.js";
15
15
  import * as priceHistory from "./tools/price_history.js";
16
+ import * as arbitrage from "./tools/arbitrage.js";
17
+ import * as marketSummary from "./tools/market_summary.js";
16
18
  import * as balances from "./tools/balances.js";
17
19
  import * as orders from "./tools/orders.js";
18
20
  import * as placeOrder from "./tools/place_order.js";
19
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";
27
+ import { handleMarketSummary } from "./tools/market_summary.js";
20
28
  const client = new BudaClient(undefined, process.env.BUDA_API_KEY, process.env.BUDA_API_SECRET);
21
29
  const server = new McpServer({
22
30
  name: "buda-mcp",
@@ -31,12 +39,19 @@ volume.register(server, client, cache);
31
39
  spread.register(server, client, cache);
32
40
  compareMarkets.register(server, client, cache);
33
41
  priceHistory.register(server, client, cache);
42
+ arbitrage.register(server, client, cache);
43
+ marketSummary.register(server, client, cache);
44
+ simulateOrder.register(server, client, cache);
45
+ positionSize.register(server);
46
+ marketSentiment.register(server, client, cache);
47
+ technicalIndicators.register(server, client);
34
48
  // Auth-gated tools — only registered when API credentials are present
35
49
  if (client.hasAuth()) {
36
50
  balances.register(server, client);
37
51
  orders.register(server, client);
38
52
  placeOrder.register(server, client);
39
53
  cancelOrder.register(server, client);
54
+ deadMansSwitch.register(server, client);
40
55
  }
41
56
  // MCP Resources
42
57
  server.resource("buda-markets", "buda://markets", async (uri) => {
@@ -64,6 +79,20 @@ server.resource("buda-ticker", new ResourceTemplate("buda://ticker/{market}", {
64
79
  ],
65
80
  };
66
81
  });
82
+ server.resource("buda-summary", new ResourceTemplate("buda://summary/{market}", { list: undefined }), async (uri, params) => {
83
+ const marketId = params.market.toUpperCase();
84
+ const result = await handleMarketSummary({ market_id: marketId }, client, cache);
85
+ const text = result.content[0].text;
86
+ return {
87
+ contents: [
88
+ {
89
+ uri: uri.href,
90
+ mimeType: "application/json",
91
+ text,
92
+ },
93
+ ],
94
+ };
95
+ });
67
96
  const transport = new StdioServerTransport();
68
97
  await server.connect(transport);
69
98
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,35 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BudaClient } from "../client.js";
3
+ import { MemoryCache } from "../cache.js";
4
+ export declare const toolSchema: {
5
+ name: string;
6
+ description: string;
7
+ inputSchema: {
8
+ type: "object";
9
+ properties: {
10
+ base_currency: {
11
+ type: string;
12
+ description: string;
13
+ };
14
+ threshold_pct: {
15
+ type: string;
16
+ description: string;
17
+ };
18
+ };
19
+ required: string[];
20
+ };
21
+ };
22
+ interface ArbitrageInput {
23
+ base_currency: string;
24
+ threshold_pct?: number;
25
+ }
26
+ export declare function handleArbitrageOpportunities({ base_currency, threshold_pct }: ArbitrageInput, client: BudaClient, cache: MemoryCache): Promise<{
27
+ content: Array<{
28
+ type: "text";
29
+ text: string;
30
+ }>;
31
+ isError?: boolean;
32
+ }>;
33
+ export declare function register(server: McpServer, client: BudaClient, cache: MemoryCache): void;
34
+ export {};
35
+ //# sourceMappingURL=arbitrage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arbitrage.d.ts","sourceRoot":"","sources":["../../src/tools/arbitrage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAgB,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,WAAW,EAAa,MAAM,aAAa,CAAC;AAGrD,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;CAyBtB,CAAC;AAYF,UAAU,cAAc;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAsB,4BAA4B,CAChD,EAAE,aAAa,EAAE,aAAmB,EAAE,EAAE,cAAc,EACtD,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAgIhF;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAmBxF"}
@@ -0,0 +1,142 @@
1
+ import { z } from "zod";
2
+ import { BudaApiError } from "../client.js";
3
+ import { CACHE_TTL } from "../cache.js";
4
+ export const toolSchema = {
5
+ name: "get_arbitrage_opportunities",
6
+ description: "Detects cross-country price discrepancies for a given asset across Buda's CLP, COP, and PEN markets, " +
7
+ "normalized to USDC. Fetches all relevant tickers, converts each local price to USDC using the " +
8
+ "current USDC-CLP / USDC-COP / USDC-PEN rates, then computes pairwise discrepancy percentages. " +
9
+ "Results above threshold_pct are returned sorted by opportunity size. Note: Buda taker fee is 0.8% " +
10
+ "per leg (~1.6% round-trip) — always deduct fees before acting on any discrepancy. " +
11
+ "Example: 'Is there an arbitrage opportunity for BTC between Chile and Peru right now?'",
12
+ inputSchema: {
13
+ type: "object",
14
+ properties: {
15
+ base_currency: {
16
+ type: "string",
17
+ description: "Base asset to scan (e.g. 'BTC', 'ETH', 'XRP').",
18
+ },
19
+ threshold_pct: {
20
+ type: "number",
21
+ description: "Minimum price discrepancy percentage to include in results (default: 0.5). " +
22
+ "Buda taker fee is 0.8% per leg, so a round-trip requires > 1.6% to be profitable.",
23
+ },
24
+ },
25
+ required: ["base_currency"],
26
+ },
27
+ };
28
+ export async function handleArbitrageOpportunities({ base_currency, threshold_pct = 0.5 }, client, cache) {
29
+ try {
30
+ const base = base_currency.toUpperCase();
31
+ const data = await cache.getOrFetch("tickers:all", CACHE_TTL.TICKER, () => client.get("/tickers"));
32
+ const tickerMap = new Map();
33
+ for (const t of data.tickers) {
34
+ tickerMap.set(t.market_id, t);
35
+ }
36
+ // Find USDC conversion rates for each fiat currency
37
+ const usdcClpTicker = tickerMap.get("USDC-CLP");
38
+ const usdcCopTicker = tickerMap.get("USDC-COP");
39
+ const usdcPenTicker = tickerMap.get("USDC-PEN");
40
+ const marketPrices = [];
41
+ const candidates = [
42
+ { suffix: "CLP", usdcTicker: usdcClpTicker },
43
+ { suffix: "COP", usdcTicker: usdcCopTicker },
44
+ { suffix: "PEN", usdcTicker: usdcPenTicker },
45
+ ];
46
+ for (const { suffix, usdcTicker } of candidates) {
47
+ const marketId = `${base}-${suffix}`;
48
+ const baseTicker = tickerMap.get(marketId);
49
+ if (!baseTicker || !usdcTicker)
50
+ continue;
51
+ const localPrice = parseFloat(baseTicker.last_price[0]);
52
+ const usdcRate = parseFloat(usdcTicker.last_price[0]);
53
+ if (isNaN(localPrice) || isNaN(usdcRate) || usdcRate === 0)
54
+ continue;
55
+ marketPrices.push({
56
+ market_id: marketId,
57
+ local_price: localPrice,
58
+ usdc_rate: usdcRate,
59
+ price_usdc: localPrice / usdcRate,
60
+ });
61
+ }
62
+ if (marketPrices.length < 2) {
63
+ return {
64
+ content: [
65
+ {
66
+ type: "text",
67
+ text: JSON.stringify({
68
+ error: `Not enough markets found for base currency '${base}' to compute arbitrage. ` +
69
+ `Need at least 2 of: ${base}-CLP, ${base}-COP, ${base}-PEN with USDC rates available.`,
70
+ code: "INSUFFICIENT_MARKETS",
71
+ }),
72
+ },
73
+ ],
74
+ isError: true,
75
+ };
76
+ }
77
+ // Compute all pairwise discrepancies
78
+ const opportunities = [];
79
+ for (let i = 0; i < marketPrices.length; i++) {
80
+ for (let j = i + 1; j < marketPrices.length; j++) {
81
+ const a = marketPrices[i];
82
+ const b = marketPrices[j];
83
+ const minPrice = Math.min(a.price_usdc, b.price_usdc);
84
+ const discrepancyPct = (Math.abs(a.price_usdc - b.price_usdc) / minPrice) * 100;
85
+ if (discrepancyPct < threshold_pct)
86
+ continue;
87
+ const higherMarket = a.price_usdc > b.price_usdc ? a.market_id : b.market_id;
88
+ const lowerMarket = a.price_usdc < b.price_usdc ? a.market_id : b.market_id;
89
+ opportunities.push({
90
+ market_a: a.market_id,
91
+ market_b: b.market_id,
92
+ price_a_usdc: parseFloat(a.price_usdc.toFixed(4)),
93
+ price_b_usdc: parseFloat(b.price_usdc.toFixed(4)),
94
+ discrepancy_pct: parseFloat(discrepancyPct.toFixed(4)),
95
+ higher_market: higherMarket,
96
+ lower_market: lowerMarket,
97
+ });
98
+ }
99
+ }
100
+ opportunities.sort((a, b) => b.discrepancy_pct - a.discrepancy_pct);
101
+ const result = {
102
+ base_currency: base,
103
+ threshold_pct,
104
+ markets_analyzed: marketPrices.map((m) => ({
105
+ market_id: m.market_id,
106
+ price_usdc: parseFloat(m.price_usdc.toFixed(4)),
107
+ local_price: m.local_price,
108
+ usdc_rate: m.usdc_rate,
109
+ })),
110
+ opportunities_found: opportunities.length,
111
+ opportunities,
112
+ fees_note: "Buda taker fee is 0.8% per leg. A round-trip arbitrage (buy on one market, sell on another) " +
113
+ "costs approximately 1.6% in fees. Only discrepancies well above 1.6% are likely profitable.",
114
+ };
115
+ return {
116
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
117
+ };
118
+ }
119
+ catch (err) {
120
+ const msg = err instanceof BudaApiError
121
+ ? { error: err.message, code: err.status, path: err.path }
122
+ : { error: String(err), code: "UNKNOWN" };
123
+ return {
124
+ content: [{ type: "text", text: JSON.stringify(msg) }],
125
+ isError: true,
126
+ };
127
+ }
128
+ }
129
+ export function register(server, client, cache) {
130
+ server.tool(toolSchema.name, toolSchema.description, {
131
+ base_currency: z
132
+ .string()
133
+ .describe("Base asset to scan (e.g. 'BTC', 'ETH', 'XRP')."),
134
+ threshold_pct: z
135
+ .number()
136
+ .min(0)
137
+ .default(0.5)
138
+ .describe("Minimum price discrepancy percentage to include in results (default: 0.5). " +
139
+ "Buda taker fee is 0.8% per leg, so a round-trip requires > 1.6% to be profitable."),
140
+ }, (args) => handleArbitrageOpportunities(args, client, cache));
141
+ }
142
+ //# sourceMappingURL=arbitrage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"balances.d.ts","sourceRoot":"","sources":["../../src/tools/balances.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAgB,MAAM,cAAc,CAAC;AAGxD,eAAO,MAAM,UAAU;;;;;;;CAUtB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAuBpE"}
1
+ {"version":3,"file":"balances.d.ts","sourceRoot":"","sources":["../../src/tools/balances.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAgB,MAAM,cAAc,CAAC;AAIxD,eAAO,MAAM,UAAU;;;;;;;CAYtB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CA2CpE"}