@guiie/buda-mcp 1.1.2 → 1.2.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 +35 -0
- package/PUBLISH.md +5 -5
- package/PUBLISH_CHECKLIST.md +59 -62
- package/README.md +3 -3
- package/marketplace/README.md +1 -1
- package/marketplace/claude-listing.md +2 -2
- package/marketplace/gemini-tools.json +3 -3
- package/marketplace/openapi.yaml +7 -6
- package/package.json +5 -2
- package/scripts/sync-version.mjs +19 -0
- package/server.json +2 -2
- package/src/client.ts +77 -53
- package/src/http.ts +32 -150
- package/src/index.ts +2 -1
- package/src/tools/balances.ts +14 -4
- package/src/tools/cancel_order.ts +77 -43
- package/src/tools/compare_markets.ts +21 -4
- package/src/tools/markets.ts +27 -3
- package/src/tools/orderbook.ts +32 -3
- package/src/tools/orders.ts +41 -3
- package/src/tools/place_order.ts +134 -69
- package/src/tools/price_history.ts +50 -8
- package/src/tools/spread.ts +28 -3
- package/src/tools/ticker.ts +28 -3
- package/src/tools/trades.ts +37 -3
- package/src/tools/volume.ts +28 -3
- package/src/validation.ts +16 -0
- package/src/version.ts +8 -0
- package/test/run-all.ts +13 -1
- package/test/unit.ts +414 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,41 @@ This project uses [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.2.0] – 2026-04-11
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`src/validation.ts`** — new `validateMarketId` helper that enforces `/^[A-Z0-9]{2,10}-[A-Z0-9]{2,10}$/i` on all market ID inputs before URL interpolation. Returns a structured `isError: true` response with a helpful message on failure. Applied to all tools that accept `market_id`.
|
|
15
|
+
- **`.env.example`** — documents `BUDA_API_KEY` and `BUDA_API_SECRET` with comments and a link to the Buda token management page. Referenced in the README auth section.
|
|
16
|
+
- **`scripts/sync-version.mjs`** — reads `package.json` version and writes it to `server.json`. Run via `npm run sync-version` to keep the MCP registry manifest in sync after a version bump.
|
|
17
|
+
- **`test/unit.ts`** — 23 unit tests (no live API required):
|
|
18
|
+
- **HMAC signing** (3 tests): exact output verified for GET (no body) and POST (with base64 body); determinism check.
|
|
19
|
+
- **Cache deduplication** (3 tests): concurrent `getOrFetch` calls share the same in-flight promise; expiry triggers a new fetch; rejected fetcher clears the entry so the next call retries.
|
|
20
|
+
- **confirmation_token guard** (4 tests): `place_order` and `cancel_order` return `isError: true` without `"CONFIRM"`.
|
|
21
|
+
- **Input sanitization** (9 tests): malformed market IDs (path traversal, no hyphen, empty, oversized segments, special characters) all return a validation error; valid IDs (uppercase, lowercase, USDC) pass.
|
|
22
|
+
- **429 retry** (4 tests): mock 429→200 asserts fetch is called exactly twice and the 200 data is returned; double-429 asserts `BudaApiError` with `retryAfterMs`; `Retry-After: 2` header parsed as 2000 ms (RFC 7231 seconds); absent header defaults to 1000 ms.
|
|
23
|
+
- **`npm run test:unit`** and **`npm run test:integration`** scripts for running test subsets independently.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **Single version source-of-truth** — all version references now derive from `package.json` at startup:
|
|
28
|
+
- `src/version.ts` (new shared module): reads `package.json` via `readFileSync` + `fileURLToPath`.
|
|
29
|
+
- `src/client.ts`, `src/index.ts`, `src/http.ts` import `VERSION` from `src/version.ts`.
|
|
30
|
+
- `server.json` is kept in sync via `npm run sync-version`.
|
|
31
|
+
- **`http.ts` server-card generated programmatically** — the `/.well-known/mcp/server-card.json` endpoint now assembles tool schemas from each tool module's exported `toolSchema` constant instead of a 100-line hardcoded JSON block. Adding a tool only requires exporting its `toolSchema`.
|
|
32
|
+
- **All tool files** export a `toolSchema` constant `{ name, description, inputSchema }` used by both `register()` and the server-card endpoint, ensuring descriptions are always in sync.
|
|
33
|
+
- **`place_order.ts` and `cancel_order.ts`** expose their handler logic via exported `handlePlaceOrder` / `handleCancelOrder` functions, used by the register wrapper and directly testable in unit tests.
|
|
34
|
+
- **`get_price_history`** improvements:
|
|
35
|
+
- Shallow-window limitation moved to the **front** of the description (was buried in a `note` field).
|
|
36
|
+
- UTC bucket boundary format documented explicitly in tool description and `note` field.
|
|
37
|
+
- `limit` max raised from `100` to `1000`; description updated accordingly.
|
|
38
|
+
- **429 retry with Retry-After** — `BudaClient.get`, `.post`, and `.put` now retry once on a 429 response. The `Retry-After` header is parsed as integer seconds (per RFC 7231; Buda's docs describe 429 but do not document the header — the standard interpretation applies). Defaults to 1 second if the header is absent. A double-429 throws `BudaApiError` with `retryAfterMs` set.
|
|
39
|
+
- **`test/run-all.ts`** — integration tests now perform a 3-second connectivity pre-check at startup and skip gracefully (exit 0 with a message) when the Buda API is unreachable, preventing CI failures on networks without internet access.
|
|
40
|
+
- **`package.json`** version bumped to `1.2.0`.
|
|
41
|
+
- **`marketplace/`** and **`PUBLISH_CHECKLIST.md`** updated to reflect v1.2.0 changes.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
10
45
|
## [1.1.2] – 2026-04-10
|
|
11
46
|
|
|
12
47
|
### Fixed
|
package/PUBLISH.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Publishing checklist
|
|
1
|
+
# Publishing checklist — v1.2.0
|
|
2
2
|
|
|
3
3
|
Everything that could be automated is already done.
|
|
4
4
|
This file contains only what requires your manual action, in order.
|
|
@@ -63,7 +63,7 @@ mcp-publisher login github
|
|
|
63
63
|
mcp-publisher publish
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
You'll see: `✓ Successfully published — io.github.gtorreal/buda-mcp version 1.
|
|
66
|
+
You'll see: `✓ Successfully published — io.github.gtorreal/buda-mcp version 1.2.0`
|
|
67
67
|
|
|
68
68
|
### 2b. Put your MCP Registry token into GitHub Actions (for auto-publish on release)
|
|
69
69
|
|
|
@@ -177,9 +177,9 @@ For the Gemini Extensions marketplace (currently invite-only):
|
|
|
177
177
|
Once npm token and MCP registry token are set in GitHub Actions secrets (Steps 1c and 2b):
|
|
178
178
|
|
|
179
179
|
```bash
|
|
180
|
-
gh release create v1.
|
|
181
|
-
--title "v1.
|
|
182
|
-
--notes "
|
|
180
|
+
gh release create v1.2.0 \
|
|
181
|
+
--title "v1.2.0 — Input validation, 429 retry, unit tests" \
|
|
182
|
+
--notes "Input sanitization, 429 Retry-After support, get_price_history limit raised to 1000, 23 unit tests, single version source-of-truth." \
|
|
183
183
|
--latest
|
|
184
184
|
```
|
|
185
185
|
|
package/PUBLISH_CHECKLIST.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Publish Checklist — buda-mcp v1.
|
|
1
|
+
# Publish Checklist — buda-mcp v1.2.0
|
|
2
2
|
|
|
3
|
-
Steps to publish `v1.
|
|
3
|
+
Steps to publish `v1.2.0` to npm, the MCP registry, and notify community directories.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,12 +8,15 @@ Steps to publish `v1.1.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.2.0
|
|
12
12
|
|
|
13
13
|
# Build and test
|
|
14
14
|
npm run build
|
|
15
15
|
npm test
|
|
16
16
|
|
|
17
|
+
# Sync server.json version (already done, but run again to confirm)
|
|
18
|
+
npm run sync-version
|
|
19
|
+
|
|
17
20
|
# Verify no credentials are logged (audit)
|
|
18
21
|
grep -r "apiKey\|apiSecret\|BUDA_API" dist/ --include="*.js" | grep -v "process.env\|hasAuth\|X-SBTC-APIKEY\|authHeaders\|constructor"
|
|
19
22
|
# Should return empty or only header name strings — never credential values
|
|
@@ -36,17 +39,18 @@ Verify: https://www.npmjs.com/package/@guiie/buda-mcp
|
|
|
36
39
|
|
|
37
40
|
```bash
|
|
38
41
|
git add -A
|
|
39
|
-
git commit -m "chore: release v1.
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
50
54
|
git push origin main --tags
|
|
51
55
|
```
|
|
52
56
|
|
|
@@ -57,25 +61,27 @@ Then create a GitHub Release from the tag with the following release notes:
|
|
|
57
61
|
**Release notes template (GitHub):**
|
|
58
62
|
|
|
59
63
|
```
|
|
60
|
-
## buda-mcp v1.
|
|
64
|
+
## buda-mcp v1.2.0
|
|
61
65
|
|
|
62
66
|
### What's new
|
|
63
67
|
|
|
64
|
-
**
|
|
65
|
-
- `
|
|
66
|
-
- `
|
|
67
|
-
|
|
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`
|
|
71
|
+
|
|
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`.
|
|
68
75
|
|
|
69
|
-
**
|
|
70
|
-
-
|
|
71
|
-
- `
|
|
72
|
-
-
|
|
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
|
|
73
80
|
|
|
74
|
-
**
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
- Updated README with npx quickstart and per-tool examples
|
|
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`
|
|
79
85
|
|
|
80
86
|
```bash
|
|
81
87
|
npx @guiie/buda-mcp
|
|
@@ -86,16 +92,13 @@ npx @guiie/buda-mcp
|
|
|
86
92
|
|
|
87
93
|
## 4. MCP Registry update
|
|
88
94
|
|
|
89
|
-
The GitHub Actions workflow (`.github/workflows/publish.yml`) runs automatically on GitHub release.
|
|
95
|
+
The GitHub Actions workflow (`.github/workflows/publish.yml`) runs automatically on GitHub release. Verify at:
|
|
90
96
|
|
|
91
97
|
https://registry.modelcontextprotocol.io/servers/io.github.gtorreal/buda-mcp
|
|
92
98
|
|
|
93
99
|
If the workflow doesn't trigger, run manually:
|
|
94
100
|
|
|
95
101
|
```bash
|
|
96
|
-
# Download mcp-publisher from GitHub releases (check for latest version)
|
|
97
|
-
curl -L https://github.com/modelcontextprotocol/mcp-publisher/releases/latest/download/mcp-publisher-macos -o mcp-publisher
|
|
98
|
-
chmod +x mcp-publisher
|
|
99
102
|
MCP_REGISTRY_TOKEN=<token> ./mcp-publisher publish
|
|
100
103
|
```
|
|
101
104
|
|
|
@@ -111,23 +114,22 @@ Verify: https://smithery.ai/server/@guiie/buda-mcp
|
|
|
111
114
|
|
|
112
115
|
## 6. Notify mcp.so
|
|
113
116
|
|
|
114
|
-
**Method:** Submit via the mcp.so listing update form or open a PR to their repository.
|
|
115
|
-
|
|
116
117
|
**Email/message template:**
|
|
117
118
|
|
|
118
119
|
```
|
|
119
|
-
Subject: [Update] buda-mcp v1.
|
|
120
|
+
Subject: [Update] buda-mcp v1.2.0 — input sanitization, 429 retry, 23 unit tests
|
|
120
121
|
|
|
121
122
|
Hi mcp.so team,
|
|
122
123
|
|
|
123
|
-
I've released v1.
|
|
124
|
+
I've released v1.2.0 of buda-mcp (@guiie/buda-mcp on npm).
|
|
124
125
|
|
|
125
126
|
Key changes:
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
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
|
|
131
133
|
|
|
132
134
|
Links:
|
|
133
135
|
- npm: https://www.npmjs.com/package/@guiie/buda-mcp
|
|
@@ -143,31 +145,25 @@ Thank you!
|
|
|
143
145
|
|
|
144
146
|
## 7. Notify Glama.ai
|
|
145
147
|
|
|
146
|
-
**Method:** Use Glama's submission form at https://glama.ai/mcp/servers or open an issue/PR on their directory repository.
|
|
147
|
-
|
|
148
148
|
**Message template:**
|
|
149
149
|
|
|
150
150
|
```
|
|
151
|
-
Subject: [Update] buda-mcp v1.
|
|
151
|
+
Subject: [Update] buda-mcp v1.2.0
|
|
152
152
|
|
|
153
153
|
Hi Glama team,
|
|
154
154
|
|
|
155
|
-
buda-mcp has been updated to v1.
|
|
155
|
+
buda-mcp has been updated to v1.2.0.
|
|
156
156
|
|
|
157
157
|
Package: @guiie/buda-mcp (npm)
|
|
158
158
|
Registry: io.github.gtorreal/buda-mcp (MCP Registry)
|
|
159
|
-
Version: 1.
|
|
160
|
-
|
|
161
|
-
New tools added:
|
|
162
|
-
- get_spread: bid/ask spread for any market
|
|
163
|
-
- compare_markets: cross-currency price comparison for a base asset
|
|
164
|
-
- get_price_history: OHLCV candles from trade history (1h/4h/1d)
|
|
165
|
-
- get_balances, get_orders, place_order, cancel_order (authenticated, local-only)
|
|
159
|
+
Version: 1.2.0
|
|
166
160
|
|
|
167
|
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
-
|
|
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
|
|
171
167
|
|
|
172
168
|
Quick start:
|
|
173
169
|
npx @guiie/buda-mcp
|
|
@@ -182,11 +178,12 @@ Thank you!
|
|
|
182
178
|
|
|
183
179
|
## 8. Post-publish verification
|
|
184
180
|
|
|
185
|
-
- [ ] `npx @guiie/buda-mcp@1.
|
|
186
|
-
- [ ] `npm info @guiie/buda-mcp version` returns `1.
|
|
187
|
-
- [ ] GitHub release tag `v1.
|
|
188
|
-
- [ ] MCP Registry entry reflects v1.
|
|
189
|
-
- [ ] Smithery server card lists 8 public tools
|
|
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)
|
|
190
188
|
- [ ] mcp.so listing updated
|
|
191
189
|
- [ ] Glama.ai listing updated
|
|
192
|
-
- [ ] Railway deployment health check returns `"version":"1.1.0"` at `/health`
|
package/README.md
CHANGED
|
@@ -161,13 +161,13 @@ Side-by-side ticker data for all pairs of a given base currency (CLP, COP, PEN,
|
|
|
161
161
|
---
|
|
162
162
|
|
|
163
163
|
#### `get_price_history`
|
|
164
|
-
OHLCV candles derived from recent trade history (no native candlestick endpoint
|
|
164
|
+
OHLCV candles derived from recent trade history (Buda has no native candlestick endpoint — candles are aggregated client-side from raw trades). Candle timestamps are UTC bucket boundaries. Increasing `limit` gives deeper history at the cost of a slower response.
|
|
165
165
|
|
|
166
166
|
| Parameter | Type | Required | Description |
|
|
167
167
|
|-----------|------|----------|-------------|
|
|
168
168
|
| `market_id` | string | Yes | Market ID. |
|
|
169
169
|
| `period` | `1h` \| `4h` \| `1d` | No | Candle period (default `1h`). |
|
|
170
|
-
| `limit` | number | No | Raw trades to fetch before aggregation (default 100, max
|
|
170
|
+
| `limit` | number | No | Raw trades to fetch before aggregation (default 100, max 1000). |
|
|
171
171
|
|
|
172
172
|
**Example prompts:**
|
|
173
173
|
- *"Show me hourly price candles for BTC-CLP"*
|
|
@@ -252,7 +252,7 @@ In addition to tools, the server exposes two MCP Resources that clients can read
|
|
|
252
252
|
|
|
253
253
|
The server defaults to **public-only mode** — no API key needed, no breaking changes for existing users.
|
|
254
254
|
|
|
255
|
-
To enable authenticated tools, set environment variables before running:
|
|
255
|
+
To enable authenticated tools, copy `.env.example` to `.env` and fill in your credentials, then set them as environment variables before running:
|
|
256
256
|
|
|
257
257
|
```bash
|
|
258
258
|
BUDA_API_KEY=your_api_key BUDA_API_SECRET=your_api_secret npx @guiie/buda-mcp
|
package/marketplace/README.md
CHANGED
|
@@ -50,8 +50,8 @@ Side-by-side ticker data for all trading pairs of a given base currency across a
|
|
|
50
50
|
**Parameters:** `base_currency` *(required)* — e.g. `BTC`, `ETH`, `XRP`.
|
|
51
51
|
|
|
52
52
|
### `get_price_history`
|
|
53
|
-
OHLCV (open/high/low/close/volume) candles derived from recent trade history. Supports `1h`, `4h`, and `1d` periods.
|
|
54
|
-
**Parameters:** `market_id` *(required)*, `period` *(optional: `1h`/`4h`/`1d`, default `1h`)*, `limit` *(optional,
|
|
53
|
+
OHLCV (open/high/low/close/volume) candles derived from recent trade history (Buda has no native candlestick endpoint). Supports `1h`, `4h`, and `1d` periods. Candle timestamps are UTC bucket boundaries.
|
|
54
|
+
**Parameters:** `market_id` *(required)*, `period` *(optional: `1h`/`4h`/`1d`, default `1h`)*, `limit` *(optional, default 100, max 1000 trades — more = deeper history)*.
|
|
55
55
|
|
|
56
56
|
### Authenticated tools (require `BUDA_API_KEY` + `BUDA_API_SECRET`)
|
|
57
57
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "Gemini function declarations for buda-mcp v1.
|
|
2
|
+
"_comment": "Gemini function declarations for buda-mcp v1.2.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",
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"name": "get_price_history",
|
|
116
|
-
"description": "
|
|
116
|
+
"description": "IMPORTANT: Candles are aggregated client-side from raw trades (Buda has no native candlestick endpoint) — increase 'limit' for deeper history. Returns OHLCV (open/high/low/close/volume) price history for a Buda.com market. Candle timestamps are UTC bucket boundaries. Supports 1h, 4h, and 1d candle periods.",
|
|
117
117
|
"parameters": {
|
|
118
118
|
"type": "OBJECT",
|
|
119
119
|
"properties": {
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
},
|
|
128
128
|
"limit": {
|
|
129
129
|
"type": "INTEGER",
|
|
130
|
-
"description": "Number of raw trades to fetch before aggregation. Default is 100, maximum is
|
|
130
|
+
"description": "Number of raw trades to fetch before aggregation. Default is 100, maximum is 1000. More trades = deeper history."
|
|
131
131
|
}
|
|
132
132
|
},
|
|
133
133
|
"required": ["market_id"]
|
package/marketplace/openapi.yaml
CHANGED
|
@@ -11,7 +11,7 @@ info:
|
|
|
11
11
|
stdio server. Deploy locally with mcp-proxy:
|
|
12
12
|
mcp-proxy --port 8000 -- npx -y @guiie/buda-mcp
|
|
13
13
|
Or point `servers[0].url` at your hosted instance.
|
|
14
|
-
version: 1.
|
|
14
|
+
version: 1.2.0
|
|
15
15
|
contact:
|
|
16
16
|
url: https://github.com/gtorreal/buda-mcp
|
|
17
17
|
|
|
@@ -231,9 +231,10 @@ paths:
|
|
|
231
231
|
operationId: getPriceHistory
|
|
232
232
|
summary: OHLCV price history derived from trade history
|
|
233
233
|
description: |
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
234
|
+
IMPORTANT: Candles are aggregated client-side from raw trades (Buda has no native
|
|
235
|
+
candlestick endpoint) — increase 'limit' for deeper history but expect slower responses.
|
|
236
|
+
Returns OHLCV (open/high/low/close/volume) candles. Candle timestamps are UTC bucket
|
|
237
|
+
boundaries. Supports 1h, 4h, and 1d candle periods.
|
|
237
238
|
parameters:
|
|
238
239
|
- name: market_id
|
|
239
240
|
in: query
|
|
@@ -253,11 +254,11 @@ paths:
|
|
|
253
254
|
- name: limit
|
|
254
255
|
in: query
|
|
255
256
|
required: false
|
|
256
|
-
description: Number of raw trades to fetch before aggregation (default 100, max
|
|
257
|
+
description: Number of raw trades to fetch before aggregation (default 100, max 1000). More trades = deeper history.
|
|
257
258
|
schema:
|
|
258
259
|
type: integer
|
|
259
260
|
minimum: 1
|
|
260
|
-
maximum:
|
|
261
|
+
maximum: 1000
|
|
261
262
|
responses:
|
|
262
263
|
"200":
|
|
263
264
|
description: OHLCV candles
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guiie/buda-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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",
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
"start:stdio": "node dist/index.js",
|
|
15
15
|
"dev:http": "tsx src/http.ts",
|
|
16
16
|
"dev": "tsx src/index.ts",
|
|
17
|
-
"test": "
|
|
17
|
+
"test": "npm run test:unit && npm run test:integration",
|
|
18
|
+
"test:unit": "tsx test/unit.ts",
|
|
19
|
+
"test:integration": "tsx test/run-all.ts",
|
|
20
|
+
"sync-version": "node scripts/sync-version.mjs"
|
|
18
21
|
},
|
|
19
22
|
"engines": {
|
|
20
23
|
"node": ">=18"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the version from package.json and writes it into server.json.
|
|
3
|
+
* Run after bumping the version in package.json:
|
|
4
|
+
* node scripts/sync-version.mjs
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
7
|
+
import { dirname, join } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
|
|
10
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
+
|
|
12
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
13
|
+
const server = JSON.parse(readFileSync(join(root, "server.json"), "utf8"));
|
|
14
|
+
|
|
15
|
+
server.version = pkg.version;
|
|
16
|
+
server.packages[0].version = pkg.version;
|
|
17
|
+
|
|
18
|
+
writeFileSync(join(root, "server.json"), JSON.stringify(server, null, 2) + "\n");
|
|
19
|
+
console.log(`server.json synced to v${pkg.version}`);
|
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.
|
|
9
|
+
"version": "1.2.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@guiie/buda-mcp",
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.2.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|
package/src/client.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHmac } from "crypto";
|
|
2
|
+
import { VERSION } from "./version.js";
|
|
2
3
|
|
|
3
4
|
const BASE_URL = "https://www.buda.com/api/v2";
|
|
4
5
|
|
|
@@ -7,6 +8,7 @@ export class BudaApiError extends Error {
|
|
|
7
8
|
public readonly status: number,
|
|
8
9
|
public readonly path: string,
|
|
9
10
|
message: string,
|
|
11
|
+
public readonly retryAfterMs?: number,
|
|
10
12
|
) {
|
|
11
13
|
super(message);
|
|
12
14
|
this.name = "BudaApiError";
|
|
@@ -56,24 +58,51 @@ export class BudaClient {
|
|
|
56
58
|
};
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Parses the Retry-After header value into milliseconds.
|
|
63
|
+
* Per RFC 7231, Retry-After is an integer number of seconds.
|
|
64
|
+
* Defaults to 1000ms (1 second) if absent or unparseable.
|
|
65
|
+
*/
|
|
66
|
+
private parseRetryAfterMs(headers: Headers): number {
|
|
67
|
+
const raw = headers.get("Retry-After");
|
|
68
|
+
if (!raw) return 1000;
|
|
69
|
+
const secs = parseInt(raw, 10);
|
|
70
|
+
return isNaN(secs) ? 1000 : secs * 1000;
|
|
71
|
+
}
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Executes a fetch call with a single 429 retry.
|
|
75
|
+
* On the first 429, waits for Retry-After seconds (default 1s), then retries once.
|
|
76
|
+
* If the retry also returns 429, throws a BudaApiError with retryAfterMs set.
|
|
77
|
+
*/
|
|
78
|
+
private async fetchWithRetry(
|
|
79
|
+
url: URL,
|
|
80
|
+
options: RequestInit,
|
|
81
|
+
path: string,
|
|
82
|
+
): Promise<Response> {
|
|
83
|
+
const response = await fetch(url.toString(), options);
|
|
84
|
+
|
|
85
|
+
if (response.status !== 429) return response;
|
|
86
|
+
|
|
87
|
+
const retryAfterMs = this.parseRetryAfterMs(response.headers);
|
|
88
|
+
await new Promise((r) => setTimeout(r, retryAfterMs));
|
|
89
|
+
|
|
90
|
+
const retry = await fetch(url.toString(), options);
|
|
91
|
+
|
|
92
|
+
if (retry.status === 429) {
|
|
93
|
+
const retryAgainMs = this.parseRetryAfterMs(retry.headers);
|
|
94
|
+
throw new BudaApiError(
|
|
95
|
+
429,
|
|
96
|
+
path,
|
|
97
|
+
`Buda API rate limit exceeded. Retry after ${retryAgainMs}ms.`,
|
|
98
|
+
retryAgainMs,
|
|
99
|
+
);
|
|
66
100
|
}
|
|
67
101
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Accept: "application/json",
|
|
71
|
-
"User-Agent": "buda-mcp/1.1.1",
|
|
72
|
-
...this.authHeaders("GET", urlPath),
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const response = await fetch(url.toString(), { headers });
|
|
102
|
+
return retry;
|
|
103
|
+
}
|
|
76
104
|
|
|
105
|
+
private async handleResponse<T>(response: Response, path: string): Promise<T> {
|
|
77
106
|
if (!response.ok) {
|
|
78
107
|
let detail = response.statusText;
|
|
79
108
|
try {
|
|
@@ -84,10 +113,29 @@ export class BudaClient {
|
|
|
84
113
|
}
|
|
85
114
|
throw new BudaApiError(response.status, path, `Buda API ${response.status}: ${detail}`);
|
|
86
115
|
}
|
|
87
|
-
|
|
88
116
|
return response.json() as Promise<T>;
|
|
89
117
|
}
|
|
90
118
|
|
|
119
|
+
async get<T>(path: string, params?: Record<string, string | number>): Promise<T> {
|
|
120
|
+
const url = new URL(`${this.baseUrl}${path}.json`);
|
|
121
|
+
|
|
122
|
+
if (params) {
|
|
123
|
+
for (const [key, value] of Object.entries(params)) {
|
|
124
|
+
url.searchParams.set(key, String(value));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const urlPath = url.pathname + url.search;
|
|
129
|
+
const headers: Record<string, string> = {
|
|
130
|
+
Accept: "application/json",
|
|
131
|
+
"User-Agent": `buda-mcp/${VERSION}`,
|
|
132
|
+
...this.authHeaders("GET", urlPath),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const response = await this.fetchWithRetry(url, { headers }, path);
|
|
136
|
+
return this.handleResponse<T>(response, path);
|
|
137
|
+
}
|
|
138
|
+
|
|
91
139
|
async post<T>(path: string, payload: unknown): Promise<T> {
|
|
92
140
|
const url = new URL(`${this.baseUrl}${path}.json`);
|
|
93
141
|
const bodyStr = JSON.stringify(payload);
|
|
@@ -95,28 +143,16 @@ export class BudaClient {
|
|
|
95
143
|
const headers: Record<string, string> = {
|
|
96
144
|
Accept: "application/json",
|
|
97
145
|
"Content-Type": "application/json",
|
|
98
|
-
"User-Agent":
|
|
146
|
+
"User-Agent": `buda-mcp/${VERSION}`,
|
|
99
147
|
...this.authHeaders("POST", urlPath, bodyStr),
|
|
100
148
|
};
|
|
101
149
|
|
|
102
|
-
const response = await
|
|
103
|
-
|
|
104
|
-
headers,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (!response.ok) {
|
|
109
|
-
let detail = response.statusText;
|
|
110
|
-
try {
|
|
111
|
-
const body = (await response.json()) as { message?: string };
|
|
112
|
-
if (body.message) detail = body.message;
|
|
113
|
-
} catch {
|
|
114
|
-
// ignore parse error, use statusText
|
|
115
|
-
}
|
|
116
|
-
throw new BudaApiError(response.status, path, `Buda API ${response.status}: ${detail}`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return response.json() as Promise<T>;
|
|
150
|
+
const response = await this.fetchWithRetry(
|
|
151
|
+
url,
|
|
152
|
+
{ method: "POST", headers, body: bodyStr },
|
|
153
|
+
path,
|
|
154
|
+
);
|
|
155
|
+
return this.handleResponse<T>(response, path);
|
|
120
156
|
}
|
|
121
157
|
|
|
122
158
|
async put<T>(path: string, payload: unknown): Promise<T> {
|
|
@@ -126,27 +162,15 @@ export class BudaClient {
|
|
|
126
162
|
const headers: Record<string, string> = {
|
|
127
163
|
Accept: "application/json",
|
|
128
164
|
"Content-Type": "application/json",
|
|
129
|
-
"User-Agent":
|
|
165
|
+
"User-Agent": `buda-mcp/${VERSION}`,
|
|
130
166
|
...this.authHeaders("PUT", urlPath, bodyStr),
|
|
131
167
|
};
|
|
132
168
|
|
|
133
|
-
const response = await
|
|
134
|
-
|
|
135
|
-
headers,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (!response.ok) {
|
|
140
|
-
let detail = response.statusText;
|
|
141
|
-
try {
|
|
142
|
-
const body = (await response.json()) as { message?: string };
|
|
143
|
-
if (body.message) detail = body.message;
|
|
144
|
-
} catch {
|
|
145
|
-
// ignore parse error, use statusText
|
|
146
|
-
}
|
|
147
|
-
throw new BudaApiError(response.status, path, `Buda API ${response.status}: ${detail}`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return response.json() as Promise<T>;
|
|
169
|
+
const response = await this.fetchWithRetry(
|
|
170
|
+
url,
|
|
171
|
+
{ method: "PUT", headers, body: bodyStr },
|
|
172
|
+
path,
|
|
173
|
+
);
|
|
174
|
+
return this.handleResponse<T>(response, path);
|
|
151
175
|
}
|
|
152
176
|
}
|