@guiie/buda-mcp 1.2.2 → 1.3.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/CHANGELOG.md +37 -0
- package/PUBLISH_CHECKLIST.md +62 -53
- package/dist/http.js +22 -0
- package/dist/index.js +19 -0
- package/dist/tools/arbitrage.d.ts +35 -0
- package/dist/tools/arbitrage.d.ts.map +1 -0
- package/dist/tools/arbitrage.js +142 -0
- package/dist/tools/balances.d.ts.map +1 -1
- package/dist/tools/balances.js +24 -4
- package/dist/tools/compare_markets.d.ts.map +1 -1
- package/dist/tools/compare_markets.js +11 -10
- package/dist/tools/market_summary.d.ts +43 -0
- package/dist/tools/market_summary.d.ts.map +1 -0
- package/dist/tools/market_summary.js +81 -0
- package/dist/tools/markets.d.ts.map +1 -1
- package/dist/tools/markets.js +4 -2
- package/dist/tools/orderbook.d.ts.map +1 -1
- package/dist/tools/orderbook.js +14 -4
- package/dist/tools/orders.d.ts.map +1 -1
- package/dist/tools/orders.js +41 -3
- package/dist/tools/price_history.js +14 -14
- package/dist/tools/spread.d.ts.map +1 -1
- package/dist/tools/spread.js +10 -8
- package/dist/tools/ticker.d.ts.map +1 -1
- package/dist/tools/ticker.js +24 -3
- package/dist/tools/trades.d.ts.map +1 -1
- package/dist/tools/trades.js +17 -3
- package/dist/tools/volume.d.ts.map +1 -1
- package/dist/tools/volume.js +21 -3
- package/dist/utils.d.ts +17 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +21 -0
- package/marketplace/README.md +1 -1
- package/marketplace/claude-listing.md +26 -14
- package/marketplace/gemini-tools.json +41 -9
- package/marketplace/openapi.yaml +335 -119
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/http.ts +27 -0
- package/src/index.ts +24 -0
- package/src/tools/arbitrage.ts +202 -0
- package/src/tools/balances.ts +27 -4
- package/src/tools/compare_markets.ts +11 -10
- package/src/tools/market_summary.ts +124 -0
- package/src/tools/markets.ts +4 -2
- package/src/tools/orderbook.ts +15 -4
- package/src/tools/orders.ts +45 -4
- package/src/tools/price_history.ts +17 -17
- package/src/tools/spread.ts +10 -8
- package/src/tools/ticker.ts +27 -3
- package/src/tools/trades.ts +18 -3
- package/src/tools/volume.ts +24 -3
- package/src/utils.ts +21 -0
- package/test/unit.ts +254 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,43 @@ This project uses [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.3.0] – 2026-04-11
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`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.
|
|
15
|
+
|
|
16
|
+
- **`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.
|
|
17
|
+
|
|
18
|
+
- **`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.
|
|
19
|
+
|
|
20
|
+
- **`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.
|
|
21
|
+
|
|
22
|
+
- **Unit tests (12 new, 35 total)** in `test/unit.ts`:
|
|
23
|
+
- **f. Numeric flattening** (4 tests): `flattenAmount` returns a `number`, not a string; handles decimals and zero correctly; result is not an array.
|
|
24
|
+
- **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.
|
|
25
|
+
- **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.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **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:
|
|
30
|
+
- **`get_ticker`** — `last_price`, `min_ask`, `max_bid`, `volume` flattened; `price_variation_24h` / `price_variation_7d` cast to float.
|
|
31
|
+
- **`get_market_volume`** — all four `*_volume_*` Amount fields flattened with `_currency` suffix.
|
|
32
|
+
- **`get_orderbook`** — bids and asks converted from `[price, amount]` tuples to `{ price: float, amount: float }` objects.
|
|
33
|
+
- **`get_trades`** — entries converted from `[timestamp_ms, amount, price, direction]` tuples to `{ timestamp_ms: int, amount: float, price: float, direction: string }` objects.
|
|
34
|
+
- **`get_spread`** — `best_bid`, `best_ask`, `spread_absolute`, `last_price` → floats; `spread_percentage` → float (the "%" suffix is dropped); `currency` renamed to `price_currency`.
|
|
35
|
+
- **`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%"`).
|
|
36
|
+
- **`get_price_history`** — OHLCV candle fields `open`, `high`, `low`, `close`, `volume` → floats (were strings).
|
|
37
|
+
- **`get_balances`** — all four Amount fields per balance entry (`amount`, `available_amount`, `frozen_amount`, `pending_withdraw_amount`) flattened with `_currency` suffix.
|
|
38
|
+
- **`get_orders`** — all Amount fields (`limit`, `amount`, `original_amount`, `traded_amount`, `total_exchanged`, `paid_fee`) flattened; `limit` renamed to `limit_price` / `limit_price_currency`.
|
|
39
|
+
|
|
40
|
+
- **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.
|
|
41
|
+
|
|
42
|
+
- **`package.json`** version bumped to `1.3.0`.
|
|
43
|
+
- **`marketplace/`** and **`PUBLISH_CHECKLIST.md`** updated to reflect v1.3.0 changes.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
10
47
|
## [1.2.0] – 2026-04-11
|
|
11
48
|
|
|
12
49
|
### Added
|
package/PUBLISH_CHECKLIST.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Publish Checklist — buda-mcp v1.
|
|
1
|
+
# Publish Checklist — buda-mcp v1.3.0
|
|
2
2
|
|
|
3
|
-
Steps to publish `v1.
|
|
3
|
+
Steps to publish `v1.3.0` to npm, the MCP registry, and notify community directories.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ Steps to publish `v1.2.0` to npm, the MCP registry, and notify community directo
|
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
# Confirm version
|
|
11
|
-
node -e "console.log(require('./package.json').version)" # should print 1.
|
|
11
|
+
node -e "console.log(require('./package.json').version)" # should print 1.3.0
|
|
12
12
|
|
|
13
13
|
# Build and test
|
|
14
14
|
npm run build
|
|
@@ -39,18 +39,17 @@ Verify: https://www.npmjs.com/package/@guiie/buda-mcp
|
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
41
|
git add -A
|
|
42
|
-
git commit -m "chore: release v1.
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
git tag v1.2.0
|
|
42
|
+
git commit -m "chore: release v1.3.0
|
|
43
|
+
|
|
44
|
+
- Flatten all response schemas: all monetary amounts now floats with _currency fields
|
|
45
|
+
- get_arbitrage_opportunities: cross-country price discrepancy detection (USDC-normalized)
|
|
46
|
+
- get_market_summary: one-call market overview with liquidity_rating
|
|
47
|
+
- buda://summary/{market} MCP Resource
|
|
48
|
+
- Rewritten tool descriptions with concrete examples and units
|
|
49
|
+
- 35 unit tests (12 new: flattenAmount, arbitrage discrepancy, liquidity_rating thresholds)
|
|
50
|
+
- src/utils.ts: shared flattenAmount() and getLiquidityRating() helpers"
|
|
51
|
+
|
|
52
|
+
git tag v1.3.0
|
|
54
53
|
git push origin main --tags
|
|
55
54
|
```
|
|
56
55
|
|
|
@@ -61,27 +60,35 @@ Then create a GitHub Release from the tag with the following release notes:
|
|
|
61
60
|
**Release notes template (GitHub):**
|
|
62
61
|
|
|
63
62
|
```
|
|
64
|
-
## buda-mcp v1.
|
|
63
|
+
## buda-mcp v1.3.0 — Output Quality
|
|
65
64
|
|
|
66
65
|
### What's new
|
|
67
66
|
|
|
68
|
-
**
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
**Flat, typed response schemas (breaking change for field consumers)**
|
|
68
|
+
All tools now return floats instead of `["amount", "currency"]` arrays.
|
|
69
|
+
Every monetary Amount is split into a `value` (float) and `_currency` (string) field.
|
|
70
|
+
For example, `last_price: ["65000000", "CLP"]` → `last_price: 65000000, last_price_currency: "CLP"`.
|
|
71
|
+
Affected tools: get_ticker, get_market_volume, get_orderbook, get_trades, get_spread,
|
|
72
|
+
compare_markets, get_price_history, get_balances, get_orders.
|
|
73
|
+
|
|
74
|
+
**New tool: `get_market_summary`**
|
|
75
|
+
One-call summary: last price, bid/ask, spread %, 24h volume, price change 24h/7d, and
|
|
76
|
+
`liquidity_rating` ("high" < 0.3%, "medium" 0.3–1%, "low" > 1%). Best first tool to call.
|
|
77
|
+
|
|
78
|
+
**New tool: `get_arbitrage_opportunities`**
|
|
79
|
+
Detects cross-country price discrepancies for an asset across CLP/COP/PEN markets,
|
|
80
|
+
normalized to USDC. Includes pairwise discrepancy %, sorted by size.
|
|
81
|
+
Fees note: 0.8% taker fee per leg (~1.6% round-trip) included in every response.
|
|
71
82
|
|
|
72
|
-
**
|
|
73
|
-
|
|
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`.
|
|
83
|
+
**New MCP Resource: `buda://summary/{market}`**
|
|
84
|
+
Same data as get_market_summary, served as an MCP Resource in both stdio and HTTP transports.
|
|
75
85
|
|
|
76
|
-
**
|
|
77
|
-
|
|
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
|
|
86
|
+
**Improved tool descriptions**
|
|
87
|
+
All 12 tool descriptions rewritten: specific return types, units, and concrete example questions.
|
|
80
88
|
|
|
81
|
-
**Test suite**
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
- New scripts: `npm run test:unit`, `npm run test:integration`
|
|
89
|
+
**Test suite: 35 unit tests (was 23)**
|
|
90
|
+
New sections: flattenAmount type correctness, arbitrage discrepancy calculation with mock data,
|
|
91
|
+
liquidity_rating boundary tests.
|
|
85
92
|
|
|
86
93
|
```bash
|
|
87
94
|
npx @guiie/buda-mcp
|
|
@@ -117,19 +124,19 @@ Verify: https://smithery.ai/server/@guiie/buda-mcp
|
|
|
117
124
|
**Email/message template:**
|
|
118
125
|
|
|
119
126
|
```
|
|
120
|
-
Subject: [Update] buda-mcp v1.
|
|
127
|
+
Subject: [Update] buda-mcp v1.3.0 — flat schemas, arbitrage tool, market summary tool
|
|
121
128
|
|
|
122
129
|
Hi mcp.so team,
|
|
123
130
|
|
|
124
|
-
I've released v1.
|
|
131
|
+
I've released v1.3.0 of buda-mcp (@guiie/buda-mcp on npm).
|
|
125
132
|
|
|
126
133
|
Key changes:
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
-
|
|
134
|
+
- All tools now return flat typed objects: floats + _currency fields instead of [amount, currency] arrays
|
|
135
|
+
- New tool: get_market_summary — one-call overview with liquidity_rating (high/medium/low)
|
|
136
|
+
- New tool: get_arbitrage_opportunities — cross-country BTC/ETH/etc price discrepancy detection (USDC-normalized)
|
|
137
|
+
- New MCP Resource: buda://summary/{market}
|
|
138
|
+
- All tool descriptions rewritten with concrete example questions and units
|
|
139
|
+
- 35 unit tests (12 new)
|
|
133
140
|
|
|
134
141
|
Links:
|
|
135
142
|
- npm: https://www.npmjs.com/package/@guiie/buda-mcp
|
|
@@ -148,22 +155,23 @@ Thank you!
|
|
|
148
155
|
**Message template:**
|
|
149
156
|
|
|
150
157
|
```
|
|
151
|
-
Subject: [Update] buda-mcp v1.
|
|
158
|
+
Subject: [Update] buda-mcp v1.3.0
|
|
152
159
|
|
|
153
160
|
Hi Glama team,
|
|
154
161
|
|
|
155
|
-
buda-mcp has been updated to v1.
|
|
162
|
+
buda-mcp has been updated to v1.3.0.
|
|
156
163
|
|
|
157
164
|
Package: @guiie/buda-mcp (npm)
|
|
158
165
|
Registry: io.github.gtorreal/buda-mcp (MCP Registry)
|
|
159
|
-
Version: 1.
|
|
166
|
+
Version: 1.3.0
|
|
160
167
|
|
|
161
168
|
Changes:
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
169
|
+
- Flat response schemas: all monetary amounts now floats with _currency fields (LLM-friendly)
|
|
170
|
+
- New tool: get_market_summary (one-call overview, liquidity_rating)
|
|
171
|
+
- New tool: get_arbitrage_opportunities (cross-country USDC-normalized price discrepancy)
|
|
172
|
+
- New MCP Resource: buda://summary/{market}
|
|
173
|
+
- Rewritten descriptions with examples and units for all 12 tools
|
|
174
|
+
- 35 unit tests
|
|
167
175
|
|
|
168
176
|
Quick start:
|
|
169
177
|
npx @guiie/buda-mcp
|
|
@@ -178,12 +186,13 @@ Thank you!
|
|
|
178
186
|
|
|
179
187
|
## 8. Post-publish verification
|
|
180
188
|
|
|
181
|
-
- [ ] `npx @guiie/buda-mcp@1.
|
|
182
|
-
- [ ] `npm info @guiie/buda-mcp version` returns `1.
|
|
183
|
-
- [ ] GitHub release tag `v1.
|
|
184
|
-
- [ ] MCP Registry entry reflects v1.
|
|
185
|
-
- [ ] Smithery server card lists
|
|
186
|
-
- [ ] `GET /health` returns `"version":"1.
|
|
187
|
-
- [ ] `GET /.well-known/mcp/server-card.json` returns
|
|
189
|
+
- [ ] `npx @guiie/buda-mcp@1.3.0` starts successfully
|
|
190
|
+
- [ ] `npm info @guiie/buda-mcp version` returns `1.3.0`
|
|
191
|
+
- [ ] GitHub release tag `v1.3.0` is visible
|
|
192
|
+
- [ ] MCP Registry entry reflects v1.3.0
|
|
193
|
+
- [ ] Smithery server card lists 10 public tools (including get_market_summary, get_arbitrage_opportunities)
|
|
194
|
+
- [ ] `GET /health` returns `"version":"1.3.0"` on Railway deployment
|
|
195
|
+
- [ ] `GET /.well-known/mcp/server-card.json` returns 3 resources (including buda://summary/{market})
|
|
196
|
+
- [ ] get_ticker response has `last_price: <number>` not `last_price: ["...", "CLP"]`
|
|
188
197
|
- [ ] mcp.so listing updated
|
|
189
198
|
- [ ] Glama.ai listing updated
|
package/dist/http.js
CHANGED
|
@@ -12,10 +12,13 @@ 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 { handleMarketSummary } from "./tools/market_summary.js";
|
|
19
22
|
const PORT = parseInt(process.env.PORT ?? "3000", 10);
|
|
20
23
|
const client = new BudaClient(undefined, process.env.BUDA_API_KEY, process.env.BUDA_API_SECRET);
|
|
21
24
|
const authEnabled = client.hasAuth();
|
|
@@ -30,6 +33,8 @@ const PUBLIC_TOOL_SCHEMAS = [
|
|
|
30
33
|
spread.toolSchema,
|
|
31
34
|
compareMarkets.toolSchema,
|
|
32
35
|
priceHistory.toolSchema,
|
|
36
|
+
arbitrage.toolSchema,
|
|
37
|
+
marketSummary.toolSchema,
|
|
33
38
|
];
|
|
34
39
|
const AUTH_TOOL_SCHEMAS = [
|
|
35
40
|
balances.toolSchema,
|
|
@@ -49,6 +54,8 @@ function createServer() {
|
|
|
49
54
|
spread.register(server, client, reqCache);
|
|
50
55
|
compareMarkets.register(server, client, reqCache);
|
|
51
56
|
priceHistory.register(server, client, reqCache);
|
|
57
|
+
arbitrage.register(server, client, reqCache);
|
|
58
|
+
marketSummary.register(server, client, reqCache);
|
|
52
59
|
if (authEnabled) {
|
|
53
60
|
balances.register(server, client);
|
|
54
61
|
orders.register(server, client);
|
|
@@ -81,6 +88,20 @@ function createServer() {
|
|
|
81
88
|
],
|
|
82
89
|
};
|
|
83
90
|
});
|
|
91
|
+
server.resource("buda-summary", new ResourceTemplate("buda://summary/{market}", { list: undefined }), async (uri, params) => {
|
|
92
|
+
const marketId = params.market.toUpperCase();
|
|
93
|
+
const result = await handleMarketSummary({ market_id: marketId }, client, reqCache);
|
|
94
|
+
const text = result.content[0].text;
|
|
95
|
+
return {
|
|
96
|
+
contents: [
|
|
97
|
+
{
|
|
98
|
+
uri: uri.href,
|
|
99
|
+
mimeType: "application/json",
|
|
100
|
+
text,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
});
|
|
84
105
|
return server;
|
|
85
106
|
}
|
|
86
107
|
const app = express();
|
|
@@ -104,6 +125,7 @@ app.get("/.well-known/mcp/server-card.json", (_req, res) => {
|
|
|
104
125
|
resources: [
|
|
105
126
|
{ uri: "buda://markets", name: "All Buda.com markets", mimeType: "application/json" },
|
|
106
127
|
{ uri: "buda://ticker/{market}", name: "Ticker for a specific market", mimeType: "application/json" },
|
|
128
|
+
{ uri: "buda://summary/{market}", name: "Full market summary with liquidity rating", mimeType: "application/json" },
|
|
107
129
|
],
|
|
108
130
|
prompts: [],
|
|
109
131
|
});
|
package/dist/index.js
CHANGED
|
@@ -13,10 +13,13 @@ 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 { handleMarketSummary } from "./tools/market_summary.js";
|
|
20
23
|
const client = new BudaClient(undefined, process.env.BUDA_API_KEY, process.env.BUDA_API_SECRET);
|
|
21
24
|
const server = new McpServer({
|
|
22
25
|
name: "buda-mcp",
|
|
@@ -31,6 +34,8 @@ volume.register(server, client, cache);
|
|
|
31
34
|
spread.register(server, client, cache);
|
|
32
35
|
compareMarkets.register(server, client, cache);
|
|
33
36
|
priceHistory.register(server, client, cache);
|
|
37
|
+
arbitrage.register(server, client, cache);
|
|
38
|
+
marketSummary.register(server, client, cache);
|
|
34
39
|
// Auth-gated tools — only registered when API credentials are present
|
|
35
40
|
if (client.hasAuth()) {
|
|
36
41
|
balances.register(server, client);
|
|
@@ -64,6 +69,20 @@ server.resource("buda-ticker", new ResourceTemplate("buda://ticker/{market}", {
|
|
|
64
69
|
],
|
|
65
70
|
};
|
|
66
71
|
});
|
|
72
|
+
server.resource("buda-summary", new ResourceTemplate("buda://summary/{market}", { list: undefined }), async (uri, params) => {
|
|
73
|
+
const marketId = params.market.toUpperCase();
|
|
74
|
+
const result = await handleMarketSummary({ market_id: marketId }, client, cache);
|
|
75
|
+
const text = result.content[0].text;
|
|
76
|
+
return {
|
|
77
|
+
contents: [
|
|
78
|
+
{
|
|
79
|
+
uri: uri.href,
|
|
80
|
+
mimeType: "application/json",
|
|
81
|
+
text,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
});
|
|
67
86
|
const transport = new StdioServerTransport();
|
|
68
87
|
await server.connect(transport);
|
|
69
88
|
//# 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;
|
|
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"}
|
package/dist/tools/balances.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { BudaApiError } from "../client.js";
|
|
2
|
+
import { flattenAmount } from "../utils.js";
|
|
2
3
|
export const toolSchema = {
|
|
3
4
|
name: "get_balances",
|
|
4
|
-
description: "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
5
|
+
description: "Returns all currency balances for the authenticated Buda.com account as flat typed objects. " +
|
|
6
|
+
"Each currency entry includes total amount, available amount (not frozen), frozen amount, and " +
|
|
7
|
+
"pending withdrawal amount — all as floats with separate _currency fields. " +
|
|
8
|
+
"Requires BUDA_API_KEY and BUDA_API_SECRET. " +
|
|
9
|
+
"Example: 'How much BTC do I have available to trade right now?'",
|
|
7
10
|
inputSchema: {
|
|
8
11
|
type: "object",
|
|
9
12
|
properties: {},
|
|
@@ -13,8 +16,25 @@ export function register(server, client) {
|
|
|
13
16
|
server.tool(toolSchema.name, toolSchema.description, {}, async () => {
|
|
14
17
|
try {
|
|
15
18
|
const data = await client.get("/balances");
|
|
19
|
+
const result = data.balances.map((b) => {
|
|
20
|
+
const amount = flattenAmount(b.amount);
|
|
21
|
+
const available = flattenAmount(b.available_amount);
|
|
22
|
+
const frozen = flattenAmount(b.frozen_amount);
|
|
23
|
+
const pending = flattenAmount(b.pending_withdraw_amount);
|
|
24
|
+
return {
|
|
25
|
+
id: b.id,
|
|
26
|
+
amount: amount.value,
|
|
27
|
+
amount_currency: amount.currency,
|
|
28
|
+
available_amount: available.value,
|
|
29
|
+
available_amount_currency: available.currency,
|
|
30
|
+
frozen_amount: frozen.value,
|
|
31
|
+
frozen_amount_currency: frozen.currency,
|
|
32
|
+
pending_withdraw_amount: pending.value,
|
|
33
|
+
pending_withdraw_amount_currency: pending.currency,
|
|
34
|
+
};
|
|
35
|
+
});
|
|
16
36
|
return {
|
|
17
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
37
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
18
38
|
};
|
|
19
39
|
}
|
|
20
40
|
catch (err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compare_markets.d.ts","sourceRoot":"","sources":["../../src/tools/compare_markets.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;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"compare_markets.d.ts","sourceRoot":"","sources":["../../src/tools/compare_markets.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;;;;;;;;;;;;;CAkBtB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAyExF"}
|
|
@@ -3,9 +3,10 @@ import { BudaApiError } from "../client.js";
|
|
|
3
3
|
import { CACHE_TTL } from "../cache.js";
|
|
4
4
|
export const toolSchema = {
|
|
5
5
|
name: "compare_markets",
|
|
6
|
-
description: "
|
|
7
|
-
"supported quote currencies (CLP, COP, PEN, BTC, USDC, ETH). " +
|
|
8
|
-
"
|
|
6
|
+
description: "Returns side-by-side ticker data for all trading pairs of a given base currency across Buda.com's " +
|
|
7
|
+
"supported quote currencies (CLP, COP, PEN, BTC, USDC, ETH). All prices are floats; " +
|
|
8
|
+
"price_change_24h and price_change_7d are floats in percent (e.g. 1.23 means +1.23%). " +
|
|
9
|
+
"Example: 'In which country is Bitcoin currently most expensive on Buda?'",
|
|
9
10
|
inputSchema: {
|
|
10
11
|
type: "object",
|
|
11
12
|
properties: {
|
|
@@ -48,16 +49,16 @@ export function register(server, client, cache) {
|
|
|
48
49
|
base_currency: base,
|
|
49
50
|
markets: matching.map((t) => ({
|
|
50
51
|
market_id: t.market_id,
|
|
51
|
-
last_price: t.last_price[0],
|
|
52
|
-
|
|
53
|
-
best_bid: t.max_bid ? t.max_bid[0] : null,
|
|
54
|
-
best_ask: t.min_ask ? t.min_ask[0] : null,
|
|
55
|
-
volume_24h: t.volume ? t.volume[0] : null,
|
|
52
|
+
last_price: parseFloat(t.last_price[0]),
|
|
53
|
+
last_price_currency: t.last_price[1],
|
|
54
|
+
best_bid: t.max_bid ? parseFloat(t.max_bid[0]) : null,
|
|
55
|
+
best_ask: t.min_ask ? parseFloat(t.min_ask[0]) : null,
|
|
56
|
+
volume_24h: t.volume ? parseFloat(t.volume[0]) : null,
|
|
56
57
|
price_change_24h: t.price_variation_24h
|
|
57
|
-
? (parseFloat(t.price_variation_24h) * 100).toFixed(
|
|
58
|
+
? parseFloat((parseFloat(t.price_variation_24h) * 100).toFixed(4))
|
|
58
59
|
: null,
|
|
59
60
|
price_change_7d: t.price_variation_7d
|
|
60
|
-
? (parseFloat(t.price_variation_7d) * 100).toFixed(
|
|
61
|
+
? parseFloat((parseFloat(t.price_variation_7d) * 100).toFixed(4))
|
|
61
62
|
: null,
|
|
62
63
|
})),
|
|
63
64
|
};
|