@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.
- package/CHANGELOG.md +35 -0
- package/PUBLISH.md +5 -5
- package/PUBLISH_CHECKLIST.md +59 -62
- package/README.md +3 -3
- package/dist/client.d.ts +15 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +56 -52
- package/dist/http.js +30 -148
- package/dist/index.js +2 -1
- package/dist/tools/balances.d.ts +8 -0
- package/dist/tools/balances.d.ts.map +1 -1
- package/dist/tools/balances.js +11 -3
- package/dist/tools/cancel_order.d.ts +30 -0
- package/dist/tools/cancel_order.d.ts.map +1 -1
- package/dist/tools/cancel_order.js +59 -38
- package/dist/tools/compare_markets.d.ts +14 -0
- package/dist/tools/compare_markets.d.ts.map +1 -1
- package/dist/tools/compare_markets.js +17 -3
- package/dist/tools/markets.d.ts +13 -0
- package/dist/tools/markets.d.ts.map +1 -1
- package/dist/tools/markets.js +23 -2
- package/dist/tools/orderbook.d.ts +18 -0
- package/dist/tools/orderbook.d.ts.map +1 -1
- package/dist/tools/orderbook.js +28 -2
- package/dist/tools/orders.d.ts +26 -0
- package/dist/tools/orders.d.ts.map +1 -1
- package/dist/tools/orders.js +36 -2
- package/dist/tools/place_order.d.ts +50 -0
- package/dist/tools/place_order.d.ts.map +1 -1
- package/dist/tools/place_order.js +104 -58
- package/dist/tools/price_history.d.ts +22 -0
- package/dist/tools/price_history.d.ts.map +1 -1
- package/dist/tools/price_history.js +42 -7
- package/dist/tools/spread.d.ts +14 -0
- package/dist/tools/spread.d.ts.map +1 -1
- package/dist/tools/spread.js +24 -2
- package/dist/tools/ticker.d.ts +14 -0
- package/dist/tools/ticker.d.ts.map +1 -1
- package/dist/tools/ticker.js +24 -2
- package/dist/tools/trades.d.ts +22 -0
- package/dist/tools/trades.d.ts.map +1 -1
- package/dist/tools/trades.js +32 -2
- package/dist/tools/volume.d.ts +14 -0
- package/dist/tools/volume.d.ts.map +1 -1
- package/dist/tools/volume.js +24 -2
- package/dist/validation.d.ts +6 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +14 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +6 -0
- 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/dist/tools/volume.js
CHANGED
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { BudaApiError } from "../client.js";
|
|
3
|
+
import { validateMarketId } from "../validation.js";
|
|
4
|
+
export const toolSchema = {
|
|
5
|
+
name: "get_market_volume",
|
|
6
|
+
description: "Get 24h and 7-day transacted volume for a Buda.com market. " +
|
|
7
|
+
"Returns ask (sell) and bid (buy) volumes in the market's base currency.",
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
market_id: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Market ID (e.g. 'BTC-CLP', 'ETH-BTC').",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
required: ["market_id"],
|
|
17
|
+
},
|
|
18
|
+
};
|
|
3
19
|
export function register(server, client, _cache) {
|
|
4
|
-
server.tool(
|
|
5
|
-
"Returns ask (sell) and bid (buy) volumes in the market's base currency.", {
|
|
20
|
+
server.tool(toolSchema.name, toolSchema.description, {
|
|
6
21
|
market_id: z
|
|
7
22
|
.string()
|
|
8
23
|
.describe("Market ID (e.g. 'BTC-CLP', 'ETH-BTC')."),
|
|
9
24
|
}, async ({ market_id }) => {
|
|
10
25
|
try {
|
|
26
|
+
const validationError = validateMarketId(market_id);
|
|
27
|
+
if (validationError) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text: JSON.stringify({ error: validationError, code: "INVALID_MARKET_ID" }) }],
|
|
30
|
+
isError: true,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
11
33
|
const data = await client.get(`/markets/${market_id.toLowerCase()}/volume`);
|
|
12
34
|
return {
|
|
13
35
|
content: [{ type: "text", text: JSON.stringify(data.volume, null, 2) }],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS1D"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const MARKET_ID_RE = /^[A-Z0-9]{2,10}-[A-Z0-9]{2,10}$/i;
|
|
2
|
+
/**
|
|
3
|
+
* Validates a market ID against the expected BASE-QUOTE format.
|
|
4
|
+
* Returns an error message string if invalid, or null if valid.
|
|
5
|
+
*/
|
|
6
|
+
export function validateMarketId(id) {
|
|
7
|
+
if (!MARKET_ID_RE.test(id)) {
|
|
8
|
+
return (`Invalid market ID "${id}". ` +
|
|
9
|
+
`Expected format: BASE-QUOTE with 2–10 alphanumeric characters per part ` +
|
|
10
|
+
`(e.g. "BTC-CLP", "ETH-BTC").`);
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,OAAO,EAAE,MAEb,CAAC"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
const _dir = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
export const VERSION = JSON.parse(readFileSync(join(_dir, "../package.json"), "utf8")).version;
|
|
6
|
+
//# sourceMappingURL=version.js.map
|
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.1
|
|
3
|
+
"version": "1.2.1",
|
|
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
|
}
|
package/src/http.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mc
|
|
|
3
3
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
4
|
import { BudaClient } from "./client.js";
|
|
5
5
|
import { MemoryCache, CACHE_TTL } from "./cache.js";
|
|
6
|
+
import { VERSION } from "./version.js";
|
|
6
7
|
import type { MarketsResponse, TickerResponse } from "./types.js";
|
|
7
8
|
import * as markets from "./tools/markets.js";
|
|
8
9
|
import * as ticker from "./tools/ticker.js";
|
|
@@ -27,8 +28,28 @@ const client = new BudaClient(
|
|
|
27
28
|
|
|
28
29
|
const authEnabled = client.hasAuth();
|
|
29
30
|
|
|
31
|
+
// Schemas for the Smithery server-card — assembled from the same definitions used in register().
|
|
32
|
+
// Adding a new tool only requires exporting its toolSchema; no changes needed here.
|
|
33
|
+
const PUBLIC_TOOL_SCHEMAS = [
|
|
34
|
+
markets.toolSchema,
|
|
35
|
+
ticker.toolSchema,
|
|
36
|
+
orderbook.toolSchema,
|
|
37
|
+
trades.toolSchema,
|
|
38
|
+
volume.toolSchema,
|
|
39
|
+
spread.toolSchema,
|
|
40
|
+
compareMarkets.toolSchema,
|
|
41
|
+
priceHistory.toolSchema,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const AUTH_TOOL_SCHEMAS = [
|
|
45
|
+
balances.toolSchema,
|
|
46
|
+
orders.toolSchema,
|
|
47
|
+
placeOrder.toolSchema,
|
|
48
|
+
cancelOrder.toolSchema,
|
|
49
|
+
];
|
|
50
|
+
|
|
30
51
|
function createServer(): McpServer {
|
|
31
|
-
const server = new McpServer({ name: "buda-mcp", version:
|
|
52
|
+
const server = new McpServer({ name: "buda-mcp", version: VERSION });
|
|
32
53
|
|
|
33
54
|
// Per-request cache so caching works correctly for stateless HTTP
|
|
34
55
|
const reqCache = new MemoryCache();
|
|
@@ -101,160 +122,21 @@ app.use(express.json());
|
|
|
101
122
|
|
|
102
123
|
// Health check for Railway / uptime monitors
|
|
103
124
|
app.get("/health", (_req, res) => {
|
|
104
|
-
res.json({
|
|
125
|
+
res.json({
|
|
126
|
+
status: "ok",
|
|
127
|
+
server: "buda-mcp",
|
|
128
|
+
version: VERSION,
|
|
129
|
+
auth_mode: authEnabled ? "authenticated" : "public",
|
|
130
|
+
});
|
|
105
131
|
});
|
|
106
132
|
|
|
107
|
-
// Smithery static server card —
|
|
133
|
+
// Smithery static server card — assembled programmatically from tool definitions.
|
|
134
|
+
// Adding a new tool only requires exporting its toolSchema; this handler needs no changes.
|
|
108
135
|
app.get("/.well-known/mcp/server-card.json", (_req, res) => {
|
|
109
|
-
const publicTools = [
|
|
110
|
-
{
|
|
111
|
-
name: "get_markets",
|
|
112
|
-
description: "List all available trading pairs on Buda.com, or get details for a specific market.",
|
|
113
|
-
inputSchema: {
|
|
114
|
-
type: "object",
|
|
115
|
-
properties: {
|
|
116
|
-
market_id: { type: "string", description: "Optional market ID (e.g. BTC-CLP)" },
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
name: "get_ticker",
|
|
122
|
-
description: "Get current price, bid/ask, volume, and price change for a Buda.com market.",
|
|
123
|
-
inputSchema: {
|
|
124
|
-
type: "object",
|
|
125
|
-
properties: {
|
|
126
|
-
market_id: { type: "string", description: "Market ID (e.g. BTC-CLP)" },
|
|
127
|
-
},
|
|
128
|
-
required: ["market_id"],
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
name: "get_orderbook",
|
|
133
|
-
description: "Get the full order book (bids and asks) for a Buda.com market.",
|
|
134
|
-
inputSchema: {
|
|
135
|
-
type: "object",
|
|
136
|
-
properties: {
|
|
137
|
-
market_id: { type: "string", description: "Market ID (e.g. BTC-CLP)" },
|
|
138
|
-
limit: { type: "number", description: "Max levels per side" },
|
|
139
|
-
},
|
|
140
|
-
required: ["market_id"],
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
name: "get_trades",
|
|
145
|
-
description: "Get recent trade history for a Buda.com market.",
|
|
146
|
-
inputSchema: {
|
|
147
|
-
type: "object",
|
|
148
|
-
properties: {
|
|
149
|
-
market_id: { type: "string", description: "Market ID (e.g. BTC-CLP)" },
|
|
150
|
-
limit: { type: "number", description: "Number of trades (max 100)" },
|
|
151
|
-
timestamp: { type: "number", description: "Unix timestamp for pagination" },
|
|
152
|
-
},
|
|
153
|
-
required: ["market_id"],
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
name: "get_market_volume",
|
|
158
|
-
description: "Get 24h and 7-day transacted volume for a Buda.com market.",
|
|
159
|
-
inputSchema: {
|
|
160
|
-
type: "object",
|
|
161
|
-
properties: {
|
|
162
|
-
market_id: { type: "string", description: "Market ID (e.g. BTC-CLP)" },
|
|
163
|
-
},
|
|
164
|
-
required: ["market_id"],
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
name: "get_spread",
|
|
169
|
-
description: "Calculate bid/ask spread (absolute and percentage) for a Buda.com market.",
|
|
170
|
-
inputSchema: {
|
|
171
|
-
type: "object",
|
|
172
|
-
properties: {
|
|
173
|
-
market_id: { type: "string", description: "Market ID (e.g. BTC-CLP)" },
|
|
174
|
-
},
|
|
175
|
-
required: ["market_id"],
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
name: "compare_markets",
|
|
180
|
-
description: "Compare ticker data for all trading pairs of a given base currency side by side.",
|
|
181
|
-
inputSchema: {
|
|
182
|
-
type: "object",
|
|
183
|
-
properties: {
|
|
184
|
-
base_currency: { type: "string", description: "Base currency (e.g. BTC, ETH)" },
|
|
185
|
-
},
|
|
186
|
-
required: ["base_currency"],
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
name: "get_price_history",
|
|
191
|
-
description: "Get OHLCV price history for a market, derived from recent trade history.",
|
|
192
|
-
inputSchema: {
|
|
193
|
-
type: "object",
|
|
194
|
-
properties: {
|
|
195
|
-
market_id: { type: "string", description: "Market ID (e.g. BTC-CLP)" },
|
|
196
|
-
period: { type: "string", enum: ["1h", "4h", "1d"], description: "Candle period" },
|
|
197
|
-
limit: { type: "number", description: "Raw trades to fetch (max 100)" },
|
|
198
|
-
},
|
|
199
|
-
required: ["market_id"],
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
];
|
|
203
|
-
|
|
204
|
-
const authTools = authEnabled
|
|
205
|
-
? [
|
|
206
|
-
{
|
|
207
|
-
name: "get_balances",
|
|
208
|
-
description: "Get all currency balances for the authenticated account.",
|
|
209
|
-
inputSchema: { type: "object", properties: {} },
|
|
210
|
-
},
|
|
211
|
-
{
|
|
212
|
-
name: "get_orders",
|
|
213
|
-
description: "Get orders for a given market.",
|
|
214
|
-
inputSchema: {
|
|
215
|
-
type: "object",
|
|
216
|
-
properties: {
|
|
217
|
-
market_id: { type: "string" },
|
|
218
|
-
state: { type: "string" },
|
|
219
|
-
},
|
|
220
|
-
required: ["market_id"],
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
name: "place_order",
|
|
225
|
-
description: "Place a limit or market order. Requires confirmation_token='CONFIRM'.",
|
|
226
|
-
inputSchema: {
|
|
227
|
-
type: "object",
|
|
228
|
-
properties: {
|
|
229
|
-
market_id: { type: "string" },
|
|
230
|
-
type: { type: "string", enum: ["Bid", "Ask"] },
|
|
231
|
-
price_type: { type: "string", enum: ["limit", "market"] },
|
|
232
|
-
amount: { type: "number" },
|
|
233
|
-
limit_price: { type: "number" },
|
|
234
|
-
confirmation_token: { type: "string" },
|
|
235
|
-
},
|
|
236
|
-
required: ["market_id", "type", "price_type", "amount", "confirmation_token"],
|
|
237
|
-
},
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
name: "cancel_order",
|
|
241
|
-
description: "Cancel an order by ID. Requires confirmation_token='CONFIRM'.",
|
|
242
|
-
inputSchema: {
|
|
243
|
-
type: "object",
|
|
244
|
-
properties: {
|
|
245
|
-
order_id: { type: "number" },
|
|
246
|
-
confirmation_token: { type: "string" },
|
|
247
|
-
},
|
|
248
|
-
required: ["order_id", "confirmation_token"],
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
]
|
|
252
|
-
: [];
|
|
253
|
-
|
|
254
136
|
res.json({
|
|
255
|
-
serverInfo: { name: "buda-mcp", version:
|
|
137
|
+
serverInfo: { name: "buda-mcp", version: VERSION },
|
|
256
138
|
authentication: { required: authEnabled },
|
|
257
|
-
tools: [...
|
|
139
|
+
tools: [...PUBLIC_TOOL_SCHEMAS, ...(authEnabled ? AUTH_TOOL_SCHEMAS : [])],
|
|
258
140
|
resources: [
|
|
259
141
|
{ uri: "buda://markets", name: "All Buda.com markets", mimeType: "application/json" },
|
|
260
142
|
{ uri: "buda://ticker/{market}", name: "Ticker for a specific market", mimeType: "application/json" },
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
3
3
|
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
4
|
import { BudaClient } from "./client.js";
|
|
5
5
|
import { cache, CACHE_TTL } from "./cache.js";
|
|
6
|
+
import { VERSION } from "./version.js";
|
|
6
7
|
import type { MarketsResponse, TickerResponse } from "./types.js";
|
|
7
8
|
import * as markets from "./tools/markets.js";
|
|
8
9
|
import * as ticker from "./tools/ticker.js";
|
|
@@ -25,7 +26,7 @@ const client = new BudaClient(
|
|
|
25
26
|
|
|
26
27
|
const server = new McpServer({
|
|
27
28
|
name: "buda-mcp",
|
|
28
|
-
version:
|
|
29
|
+
version: VERSION,
|
|
29
30
|
});
|
|
30
31
|
|
|
31
32
|
// Public tools
|
package/src/tools/balances.ts
CHANGED
|
@@ -2,12 +2,22 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { BudaClient, BudaApiError } from "../client.js";
|
|
3
3
|
import type { BalancesResponse } from "../types.js";
|
|
4
4
|
|
|
5
|
+
export const toolSchema = {
|
|
6
|
+
name: "get_balances",
|
|
7
|
+
description:
|
|
8
|
+
"Get all currency balances for the authenticated Buda.com account. " +
|
|
9
|
+
"Returns total, available, frozen, and pending withdrawal amounts per currency. " +
|
|
10
|
+
"Requires BUDA_API_KEY and BUDA_API_SECRET environment variables.",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: "object" as const,
|
|
13
|
+
properties: {},
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
5
17
|
export function register(server: McpServer, client: BudaClient): void {
|
|
6
18
|
server.tool(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"Returns total, available, frozen, and pending withdrawal amounts per currency. " +
|
|
10
|
-
"Requires BUDA_API_KEY and BUDA_API_SECRET environment variables.",
|
|
19
|
+
toolSchema.name,
|
|
20
|
+
toolSchema.description,
|
|
11
21
|
{},
|
|
12
22
|
async () => {
|
|
13
23
|
try {
|