@guiie/buda-mcp 1.1.2 → 1.2.1

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 (77) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/PUBLISH.md +5 -5
  3. package/PUBLISH_CHECKLIST.md +59 -62
  4. package/README.md +3 -3
  5. package/dist/client.d.ts +15 -1
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +56 -52
  8. package/dist/http.js +30 -148
  9. package/dist/index.js +2 -1
  10. package/dist/tools/balances.d.ts +8 -0
  11. package/dist/tools/balances.d.ts.map +1 -1
  12. package/dist/tools/balances.js +11 -3
  13. package/dist/tools/cancel_order.d.ts +30 -0
  14. package/dist/tools/cancel_order.d.ts.map +1 -1
  15. package/dist/tools/cancel_order.js +59 -38
  16. package/dist/tools/compare_markets.d.ts +14 -0
  17. package/dist/tools/compare_markets.d.ts.map +1 -1
  18. package/dist/tools/compare_markets.js +17 -3
  19. package/dist/tools/markets.d.ts +13 -0
  20. package/dist/tools/markets.d.ts.map +1 -1
  21. package/dist/tools/markets.js +23 -2
  22. package/dist/tools/orderbook.d.ts +18 -0
  23. package/dist/tools/orderbook.d.ts.map +1 -1
  24. package/dist/tools/orderbook.js +28 -2
  25. package/dist/tools/orders.d.ts +26 -0
  26. package/dist/tools/orders.d.ts.map +1 -1
  27. package/dist/tools/orders.js +36 -2
  28. package/dist/tools/place_order.d.ts +50 -0
  29. package/dist/tools/place_order.d.ts.map +1 -1
  30. package/dist/tools/place_order.js +104 -58
  31. package/dist/tools/price_history.d.ts +22 -0
  32. package/dist/tools/price_history.d.ts.map +1 -1
  33. package/dist/tools/price_history.js +42 -7
  34. package/dist/tools/spread.d.ts +14 -0
  35. package/dist/tools/spread.d.ts.map +1 -1
  36. package/dist/tools/spread.js +24 -2
  37. package/dist/tools/ticker.d.ts +14 -0
  38. package/dist/tools/ticker.d.ts.map +1 -1
  39. package/dist/tools/ticker.js +24 -2
  40. package/dist/tools/trades.d.ts +22 -0
  41. package/dist/tools/trades.d.ts.map +1 -1
  42. package/dist/tools/trades.js +32 -2
  43. package/dist/tools/volume.d.ts +14 -0
  44. package/dist/tools/volume.d.ts.map +1 -1
  45. package/dist/tools/volume.js +24 -2
  46. package/dist/validation.d.ts +6 -0
  47. package/dist/validation.d.ts.map +1 -0
  48. package/dist/validation.js +14 -0
  49. package/dist/version.d.ts +2 -0
  50. package/dist/version.d.ts.map +1 -0
  51. package/dist/version.js +6 -0
  52. package/marketplace/README.md +1 -1
  53. package/marketplace/claude-listing.md +2 -2
  54. package/marketplace/gemini-tools.json +3 -3
  55. package/marketplace/openapi.yaml +7 -6
  56. package/package.json +5 -2
  57. package/scripts/sync-version.mjs +19 -0
  58. package/server.json +2 -2
  59. package/src/client.ts +77 -53
  60. package/src/http.ts +32 -150
  61. package/src/index.ts +2 -1
  62. package/src/tools/balances.ts +14 -4
  63. package/src/tools/cancel_order.ts +77 -43
  64. package/src/tools/compare_markets.ts +21 -4
  65. package/src/tools/markets.ts +27 -3
  66. package/src/tools/orderbook.ts +32 -3
  67. package/src/tools/orders.ts +41 -3
  68. package/src/tools/place_order.ts +134 -69
  69. package/src/tools/price_history.ts +50 -8
  70. package/src/tools/spread.ts +28 -3
  71. package/src/tools/ticker.ts +28 -3
  72. package/src/tools/trades.ts +37 -3
  73. package/src/tools/volume.ts +28 -3
  74. package/src/validation.ts +16 -0
  75. package/src/version.ts +8 -0
  76. package/test/run-all.ts +13 -1
  77. 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.0.0`
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.0.0 \
181
- --title "v1.0.0 — Initial release" \
182
- --notes "5 public market data tools for Buda.com: get_markets, get_ticker, get_orderbook, get_trades, get_market_volume." \
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
 
@@ -1,6 +1,6 @@
1
- # Publish Checklist — buda-mcp v1.1.0
1
+ # Publish Checklist — buda-mcp v1.2.0
2
2
 
3
- Steps to publish `v1.1.0` to npm, the MCP registry, and notify community directories.
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.1.0
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.1.0
40
-
41
- - 3 new public tools: get_spread, compare_markets, get_price_history
42
- - HMAC-SHA384 auth scaffold (BUDA_API_KEY / BUDA_API_SECRET)
43
- - 4 auth-gated tools: get_balances, get_orders, place_order, cancel_order
44
- - TTL caching (markets 60s, tickers 5s, orderbooks 3s)
45
- - MCP Resources: buda://markets, buda://ticker/{market}
46
- - Structured error responses for all tools
47
- - Updated README, marketplace files, CHANGELOG"
48
-
49
- git tag v1.1.0
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.1.0
64
+ ## buda-mcp v1.2.0
61
65
 
62
66
  ### What's new
63
67
 
64
- **3 new public tools**
65
- - `get_spread` bid/ask spread (absolute and %) for any market
66
- - `compare_markets` — side-by-side ticker data for a base currency across all quote currencies
67
- - `get_price_history` — OHLCV candles derived from recent trades (1h / 4h / 1d)
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
- **HMAC auth scaffold**
70
- - Set `BUDA_API_KEY` + `BUDA_API_SECRET` to unlock 4 authenticated tools
71
- - `get_balances`, `get_orders`, `place_order`, `cancel_order`
72
- - Public-only mode unchanged when no credentials are set
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
- **Platform improvements**
75
- - TTL caching: markets (60s), tickers (5s), order books (3s)
76
- - MCP Resources: `buda://markets` and `buda://ticker/{market}`
77
- - Structured `isError: true` responses for all tools
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. It runs `mcp publish` via `mcp-publisher`. Verify the registry entry at:
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.1.0 — new tools + auth
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.1.0 of buda-mcp (@guiie/buda-mcp on npm).
124
+ I've released v1.2.0 of buda-mcp (@guiie/buda-mcp on npm).
124
125
 
125
126
  Key changes:
126
- - 3 new public tools: get_spread, compare_markets, get_price_history (OHLCV)
127
- - Optional HMAC auth scaffold (BUDA_API_KEY / BUDA_API_SECRET) unlocks 4 private tools: get_balances, get_orders, place_order, cancel_order
128
- - TTL caching for all repeated data fetches
129
- - MCP Resources: buda://markets and buda://ticker/{market}
130
- - Structured error responses
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.1.0
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.1.0. Here's a summary of what's new:
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.1.0
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
- New capabilities:
168
- - MCP Resources protocol: buda://markets, buda://ticker/{market}
169
- - TTL caching (60s/5s/3s by data type)
170
- - Structured error responses (isError: true)
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.1.0` starts successfully
186
- - [ ] `npm info @guiie/buda-mcp version` returns `1.1.0`
187
- - [ ] GitHub release tag `v1.1.0` is visible
188
- - [ ] MCP Registry entry reflects v1.1.0
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 exists on Buda — candles are aggregated client-side from up to 100 raw trades).
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 100). |
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/dist/client.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  export declare class BudaApiError extends Error {
2
2
  readonly status: number;
3
3
  readonly path: string;
4
- constructor(status: number, path: string, message: string);
4
+ readonly retryAfterMs?: number | undefined;
5
+ constructor(status: number, path: string, message: string, retryAfterMs?: number | undefined);
5
6
  }
6
7
  export declare class BudaClient {
7
8
  private readonly baseUrl;
@@ -12,6 +13,19 @@ export declare class BudaClient {
12
13
  private nonce;
13
14
  private sign;
14
15
  private authHeaders;
16
+ /**
17
+ * Parses the Retry-After header value into milliseconds.
18
+ * Per RFC 7231, Retry-After is an integer number of seconds.
19
+ * Defaults to 1000ms (1 second) if absent or unparseable.
20
+ */
21
+ private parseRetryAfterMs;
22
+ /**
23
+ * Executes a fetch call with a single 429 retry.
24
+ * On the first 429, waits for Retry-After seconds (default 1s), then retries once.
25
+ * If the retry also returns 429, throws a BudaApiError with retryAfterMs set.
26
+ */
27
+ private fetchWithRetry;
28
+ private handleResponse;
15
29
  get<T>(path: string, params?: Record<string, string | number>): Promise<T>;
16
30
  post<T>(path: string, payload: unknown): Promise<T>;
17
31
  put<T>(path: string, payload: unknown): Promise<T>;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAIA,qBAAa,YAAa,SAAQ,KAAK;aAEnB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM;gBADZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAC5B,OAAO,EAAE,MAAM;CAKlB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;gBAG7C,OAAO,GAAE,MAAiB,EAC1B,MAAM,CAAC,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM;IAOpB,OAAO,IAAI,OAAO;IAIlB,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,IAAI;IASZ,OAAO,CAAC,WAAW;IAWb,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAgC1E,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IA+BnD,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;CA8BzD"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAKA,qBAAa,YAAa,SAAQ,KAAK;aAEnB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM;aAEZ,YAAY,CAAC,EAAE,MAAM;gBAHrB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAC5B,OAAO,EAAE,MAAM,EACC,YAAY,CAAC,EAAE,MAAM,YAAA;CAKxC;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;gBAG7C,OAAO,GAAE,MAAiB,EAC1B,MAAM,CAAC,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM;IAOpB,OAAO,IAAI,OAAO;IAIlB,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,IAAI;IASZ,OAAO,CAAC,WAAW;IAWnB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAOzB;;;;OAIG;YACW,cAAc;YA2Bd,cAAc;IActB,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAoB1E,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAmBnD,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;CAkBzD"}
package/dist/client.js CHANGED
@@ -1,12 +1,15 @@
1
1
  import { createHmac } from "crypto";
2
+ import { VERSION } from "./version.js";
2
3
  const BASE_URL = "https://www.buda.com/api/v2";
3
4
  export class BudaApiError extends Error {
4
5
  status;
5
6
  path;
6
- constructor(status, path, message) {
7
+ retryAfterMs;
8
+ constructor(status, path, message, retryAfterMs) {
7
9
  super(message);
8
10
  this.status = status;
9
11
  this.path = path;
12
+ this.retryAfterMs = retryAfterMs;
10
13
  this.name = "BudaApiError";
11
14
  }
12
15
  }
@@ -45,20 +48,37 @@ export class BudaClient {
45
48
  "X-SBTC-SIGNATURE": signature,
46
49
  };
47
50
  }
48
- async get(path, params) {
49
- const url = new URL(`${this.baseUrl}${path}.json`);
50
- if (params) {
51
- for (const [key, value] of Object.entries(params)) {
52
- url.searchParams.set(key, String(value));
53
- }
51
+ /**
52
+ * Parses the Retry-After header value into milliseconds.
53
+ * Per RFC 7231, Retry-After is an integer number of seconds.
54
+ * Defaults to 1000ms (1 second) if absent or unparseable.
55
+ */
56
+ parseRetryAfterMs(headers) {
57
+ const raw = headers.get("Retry-After");
58
+ if (!raw)
59
+ return 1000;
60
+ const secs = parseInt(raw, 10);
61
+ return isNaN(secs) ? 1000 : secs * 1000;
62
+ }
63
+ /**
64
+ * Executes a fetch call with a single 429 retry.
65
+ * On the first 429, waits for Retry-After seconds (default 1s), then retries once.
66
+ * If the retry also returns 429, throws a BudaApiError with retryAfterMs set.
67
+ */
68
+ async fetchWithRetry(url, options, path) {
69
+ const response = await fetch(url.toString(), options);
70
+ if (response.status !== 429)
71
+ return response;
72
+ const retryAfterMs = this.parseRetryAfterMs(response.headers);
73
+ await new Promise((r) => setTimeout(r, retryAfterMs));
74
+ const retry = await fetch(url.toString(), options);
75
+ if (retry.status === 429) {
76
+ const retryAgainMs = this.parseRetryAfterMs(retry.headers);
77
+ throw new BudaApiError(429, path, `Buda API rate limit exceeded. Retry after ${retryAgainMs}ms.`, retryAgainMs);
54
78
  }
55
- const urlPath = url.pathname + url.search;
56
- const headers = {
57
- Accept: "application/json",
58
- "User-Agent": "buda-mcp/1.1.1",
59
- ...this.authHeaders("GET", urlPath),
60
- };
61
- const response = await fetch(url.toString(), { headers });
79
+ return retry;
80
+ }
81
+ async handleResponse(response, path) {
62
82
  if (!response.ok) {
63
83
  let detail = response.statusText;
64
84
  try {
@@ -73,6 +93,22 @@ export class BudaClient {
73
93
  }
74
94
  return response.json();
75
95
  }
96
+ async get(path, params) {
97
+ const url = new URL(`${this.baseUrl}${path}.json`);
98
+ if (params) {
99
+ for (const [key, value] of Object.entries(params)) {
100
+ url.searchParams.set(key, String(value));
101
+ }
102
+ }
103
+ const urlPath = url.pathname + url.search;
104
+ const headers = {
105
+ Accept: "application/json",
106
+ "User-Agent": `buda-mcp/${VERSION}`,
107
+ ...this.authHeaders("GET", urlPath),
108
+ };
109
+ const response = await this.fetchWithRetry(url, { headers }, path);
110
+ return this.handleResponse(response, path);
111
+ }
76
112
  async post(path, payload) {
77
113
  const url = new URL(`${this.baseUrl}${path}.json`);
78
114
  const bodyStr = JSON.stringify(payload);
@@ -80,27 +116,11 @@ export class BudaClient {
80
116
  const headers = {
81
117
  Accept: "application/json",
82
118
  "Content-Type": "application/json",
83
- "User-Agent": "buda-mcp/1.1.1",
119
+ "User-Agent": `buda-mcp/${VERSION}`,
84
120
  ...this.authHeaders("POST", urlPath, bodyStr),
85
121
  };
86
- const response = await fetch(url.toString(), {
87
- method: "POST",
88
- headers,
89
- body: bodyStr,
90
- });
91
- if (!response.ok) {
92
- let detail = response.statusText;
93
- try {
94
- const body = (await response.json());
95
- if (body.message)
96
- detail = body.message;
97
- }
98
- catch {
99
- // ignore parse error, use statusText
100
- }
101
- throw new BudaApiError(response.status, path, `Buda API ${response.status}: ${detail}`);
102
- }
103
- return response.json();
122
+ const response = await this.fetchWithRetry(url, { method: "POST", headers, body: bodyStr }, path);
123
+ return this.handleResponse(response, path);
104
124
  }
105
125
  async put(path, payload) {
106
126
  const url = new URL(`${this.baseUrl}${path}.json`);
@@ -109,27 +129,11 @@ export class BudaClient {
109
129
  const headers = {
110
130
  Accept: "application/json",
111
131
  "Content-Type": "application/json",
112
- "User-Agent": "buda-mcp/1.1.1",
132
+ "User-Agent": `buda-mcp/${VERSION}`,
113
133
  ...this.authHeaders("PUT", urlPath, bodyStr),
114
134
  };
115
- const response = await fetch(url.toString(), {
116
- method: "PUT",
117
- headers,
118
- body: bodyStr,
119
- });
120
- if (!response.ok) {
121
- let detail = response.statusText;
122
- try {
123
- const body = (await response.json());
124
- if (body.message)
125
- detail = body.message;
126
- }
127
- catch {
128
- // ignore parse error, use statusText
129
- }
130
- throw new BudaApiError(response.status, path, `Buda API ${response.status}: ${detail}`);
131
- }
132
- return response.json();
135
+ const response = await this.fetchWithRetry(url, { method: "PUT", headers, body: bodyStr }, path);
136
+ return this.handleResponse(response, path);
133
137
  }
134
138
  }
135
139
  //# sourceMappingURL=client.js.map