@guiie/buda-mcp 1.5.1 → 1.5.2
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_CHECKLIST.md +39 -32
- package/dist/audit.d.ts +21 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +14 -0
- package/dist/http.js +25 -3
- package/dist/tools/account.js +1 -1
- package/dist/tools/arbitrage.js +1 -1
- package/dist/tools/balance.js +1 -1
- package/dist/tools/balances.js +1 -1
- package/dist/tools/banks.js +1 -1
- package/dist/tools/batch_orders.d.ts +1 -1
- package/dist/tools/batch_orders.d.ts.map +1 -1
- package/dist/tools/batch_orders.js +12 -2
- package/dist/tools/cancel_all_orders.d.ts +1 -1
- package/dist/tools/cancel_all_orders.d.ts.map +1 -1
- package/dist/tools/cancel_all_orders.js +10 -13
- package/dist/tools/cancel_order.d.ts +1 -1
- package/dist/tools/cancel_order.d.ts.map +1 -1
- package/dist/tools/cancel_order.js +9 -9
- package/dist/tools/cancel_order_by_client_id.d.ts +1 -1
- package/dist/tools/cancel_order_by_client_id.d.ts.map +1 -1
- package/dist/tools/cancel_order_by_client_id.js +9 -9
- package/dist/tools/compare_markets.d.ts +9 -0
- package/dist/tools/compare_markets.d.ts.map +1 -1
- package/dist/tools/compare_markets.js +63 -53
- package/dist/tools/dead_mans_switch.d.ts +1 -1
- package/dist/tools/dead_mans_switch.d.ts.map +1 -1
- package/dist/tools/dead_mans_switch.js +35 -3
- package/dist/tools/deposits.js +2 -2
- package/dist/tools/fees.js +1 -1
- package/dist/tools/lightning.d.ts +1 -1
- package/dist/tools/lightning.d.ts.map +1 -1
- package/dist/tools/lightning.js +11 -9
- package/dist/tools/market_sentiment.js +1 -1
- package/dist/tools/market_summary.js +1 -1
- package/dist/tools/markets.js +1 -1
- package/dist/tools/order_lookup.js +2 -2
- package/dist/tools/orderbook.js +1 -1
- package/dist/tools/orders.js +1 -1
- package/dist/tools/place_order.d.ts +1 -1
- package/dist/tools/place_order.d.ts.map +1 -1
- package/dist/tools/place_order.js +22 -4
- package/dist/tools/price_history.js +1 -1
- package/dist/tools/quotation.js +1 -1
- package/dist/tools/receive_addresses.d.ts +1 -1
- package/dist/tools/receive_addresses.d.ts.map +1 -1
- package/dist/tools/receive_addresses.js +11 -11
- package/dist/tools/remittance_recipients.js +2 -2
- package/dist/tools/remittances.d.ts +2 -2
- package/dist/tools/remittances.d.ts.map +1 -1
- package/dist/tools/remittances.js +19 -20
- package/dist/tools/simulate_order.js +1 -1
- package/dist/tools/spread.js +1 -1
- package/dist/tools/technical_indicators.js +1 -1
- package/dist/tools/ticker.js +1 -1
- package/dist/tools/trades.js +1 -1
- package/dist/tools/volume.js +1 -1
- package/dist/tools/withdrawals.d.ts +1 -1
- package/dist/tools/withdrawals.d.ts.map +1 -1
- package/dist/tools/withdrawals.js +10 -10
- package/dist/utils.d.ts +10 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +25 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/audit.ts +24 -0
- package/src/http.ts +27 -3
- package/src/tools/account.ts +1 -1
- package/src/tools/arbitrage.ts +1 -1
- package/src/tools/balance.ts +1 -1
- package/src/tools/balances.ts +1 -1
- package/src/tools/banks.ts +1 -1
- package/src/tools/batch_orders.ts +12 -1
- package/src/tools/cancel_all_orders.ts +10 -12
- package/src/tools/cancel_order.ts +9 -8
- package/src/tools/cancel_order_by_client_id.ts +9 -8
- package/src/tools/compare_markets.ts +78 -61
- package/src/tools/dead_mans_switch.ts +37 -2
- package/src/tools/deposits.ts +2 -2
- package/src/tools/fees.ts +1 -1
- package/src/tools/lightning.ts +12 -9
- package/src/tools/market_sentiment.ts +1 -1
- package/src/tools/market_summary.ts +1 -1
- package/src/tools/markets.ts +1 -1
- package/src/tools/order_lookup.ts +2 -2
- package/src/tools/orderbook.ts +1 -1
- package/src/tools/orders.ts +1 -1
- package/src/tools/place_order.ts +24 -5
- package/src/tools/price_history.ts +1 -1
- package/src/tools/quotation.ts +1 -1
- package/src/tools/receive_addresses.ts +11 -10
- package/src/tools/remittance_recipients.ts +2 -2
- package/src/tools/remittances.ts +19 -18
- package/src/tools/simulate_order.ts +1 -1
- package/src/tools/spread.ts +1 -1
- package/src/tools/technical_indicators.ts +1 -1
- package/src/tools/ticker.ts +1 -1
- package/src/tools/trades.ts +1 -1
- package/src/tools/volume.ts +1 -1
- package/src/tools/withdrawals.ts +10 -9
- package/src/utils.ts +33 -0
- package/test/unit.ts +362 -4
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { BudaApiError } from "../client.js";
|
|
3
3
|
import { flattenAmount } from "../utils.js";
|
|
4
4
|
import { validateCurrency } from "../validation.js";
|
|
5
|
+
import { logAudit } from "../audit.js";
|
|
5
6
|
export const listRemittancesToolSchema = {
|
|
6
7
|
name: "list_remittances",
|
|
7
8
|
description: "Returns all fiat remittance transfers for the authenticated Buda.com account. " +
|
|
@@ -130,7 +131,7 @@ export async function handleListRemittances(args, client) {
|
|
|
130
131
|
}
|
|
131
132
|
catch (err) {
|
|
132
133
|
const msg = err instanceof BudaApiError
|
|
133
|
-
? { error: err.message, code: err.status
|
|
134
|
+
? { error: err.message, code: err.status }
|
|
134
135
|
: { error: String(err), code: "UNKNOWN" };
|
|
135
136
|
return {
|
|
136
137
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
|
@@ -147,7 +148,7 @@ export async function handleGetRemittance(args, client) {
|
|
|
147
148
|
}
|
|
148
149
|
catch (err) {
|
|
149
150
|
const msg = err instanceof BudaApiError
|
|
150
|
-
? { error: err.message, code: err.status
|
|
151
|
+
? { error: err.message, code: err.status }
|
|
151
152
|
: { error: String(err), code: "UNKNOWN" };
|
|
152
153
|
return {
|
|
153
154
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
|
@@ -155,7 +156,7 @@ export async function handleGetRemittance(args, client) {
|
|
|
155
156
|
};
|
|
156
157
|
}
|
|
157
158
|
}
|
|
158
|
-
export async function handleQuoteRemittance(args, client) {
|
|
159
|
+
export async function handleQuoteRemittance(args, client, transport = "stdio") {
|
|
159
160
|
const { currency, amount, recipient_id, confirmation_token } = args;
|
|
160
161
|
if (confirmation_token !== "CONFIRM") {
|
|
161
162
|
return {
|
|
@@ -188,21 +189,20 @@ export async function handleQuoteRemittance(args, client) {
|
|
|
188
189
|
recipient_id,
|
|
189
190
|
},
|
|
190
191
|
});
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
const result = { content: [{ type: "text", text: JSON.stringify(normalizeRemittance(data.remittance), null, 2) }] };
|
|
193
|
+
logAudit({ ts: new Date().toISOString(), tool: "quote_remittance", transport, args_summary: { currency, amount, recipient_id }, success: true });
|
|
194
|
+
return result;
|
|
194
195
|
}
|
|
195
196
|
catch (err) {
|
|
196
197
|
const msg = err instanceof BudaApiError
|
|
197
|
-
? { error: err.message, code: err.status
|
|
198
|
+
? { error: err.message, code: err.status }
|
|
198
199
|
: { error: String(err), code: "UNKNOWN" };
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
};
|
|
200
|
+
const result = { content: [{ type: "text", text: JSON.stringify(msg) }], isError: true };
|
|
201
|
+
logAudit({ ts: new Date().toISOString(), tool: "quote_remittance", transport, args_summary: { currency, amount, recipient_id }, success: false, error_code: msg.code });
|
|
202
|
+
return result;
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
|
-
export async function handleAcceptRemittanceQuote(args, client) {
|
|
205
|
+
export async function handleAcceptRemittanceQuote(args, client, transport = "stdio") {
|
|
206
206
|
const { id, confirmation_token } = args;
|
|
207
207
|
if (confirmation_token !== "CONFIRM") {
|
|
208
208
|
return {
|
|
@@ -223,18 +223,17 @@ export async function handleAcceptRemittanceQuote(args, client) {
|
|
|
223
223
|
const data = await client.put(`/remittances/${id}`, {
|
|
224
224
|
remittance: { state: "confirming" },
|
|
225
225
|
});
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
226
|
+
const result = { content: [{ type: "text", text: JSON.stringify(normalizeRemittance(data.remittance), null, 2) }] };
|
|
227
|
+
logAudit({ ts: new Date().toISOString(), tool: "accept_remittance_quote", transport, args_summary: { remittance_id: id }, success: true });
|
|
228
|
+
return result;
|
|
229
229
|
}
|
|
230
230
|
catch (err) {
|
|
231
231
|
const msg = err instanceof BudaApiError
|
|
232
|
-
? { error: err.message, code: err.status
|
|
232
|
+
? { error: err.message, code: err.status }
|
|
233
233
|
: { error: String(err), code: "UNKNOWN" };
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
};
|
|
234
|
+
const result = { content: [{ type: "text", text: JSON.stringify(msg) }], isError: true };
|
|
235
|
+
logAudit({ ts: new Date().toISOString(), tool: "accept_remittance_quote", transport, args_summary: { remittance_id: id }, success: false, error_code: msg.code });
|
|
236
|
+
return result;
|
|
238
237
|
}
|
|
239
238
|
}
|
|
240
239
|
export function register(server, client) {
|
|
@@ -110,7 +110,7 @@ export async function handleSimulateOrder(args, client, cache) {
|
|
|
110
110
|
}
|
|
111
111
|
catch (err) {
|
|
112
112
|
const msg = err instanceof BudaApiError
|
|
113
|
-
? { error: err.message, code: err.status
|
|
113
|
+
? { error: err.message, code: err.status }
|
|
114
114
|
: { error: String(err), code: "UNKNOWN" };
|
|
115
115
|
return {
|
|
116
116
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
package/dist/tools/spread.js
CHANGED
|
@@ -67,7 +67,7 @@ export function register(server, client, cache) {
|
|
|
67
67
|
}
|
|
68
68
|
catch (err) {
|
|
69
69
|
const msg = err instanceof BudaApiError
|
|
70
|
-
? { error: err.message, code: err.status
|
|
70
|
+
? { error: err.message, code: err.status }
|
|
71
71
|
: { error: String(err), code: "UNKNOWN" };
|
|
72
72
|
return {
|
|
73
73
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
|
@@ -195,7 +195,7 @@ export async function handleTechnicalIndicators(args, client) {
|
|
|
195
195
|
}
|
|
196
196
|
catch (err) {
|
|
197
197
|
const msg = err instanceof BudaApiError
|
|
198
|
-
? { error: err.message, code: err.status
|
|
198
|
+
? { error: err.message, code: err.status }
|
|
199
199
|
: { error: String(err), code: "UNKNOWN" };
|
|
200
200
|
return {
|
|
201
201
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
package/dist/tools/ticker.js
CHANGED
|
@@ -60,7 +60,7 @@ export function register(server, client, cache) {
|
|
|
60
60
|
}
|
|
61
61
|
catch (err) {
|
|
62
62
|
const msg = err instanceof BudaApiError
|
|
63
|
-
? { error: err.message, code: err.status
|
|
63
|
+
? { error: err.message, code: err.status }
|
|
64
64
|
: { error: String(err), code: "UNKNOWN" };
|
|
65
65
|
return {
|
|
66
66
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
package/dist/tools/trades.js
CHANGED
|
@@ -76,7 +76,7 @@ export function register(server, client, _cache) {
|
|
|
76
76
|
}
|
|
77
77
|
catch (err) {
|
|
78
78
|
const msg = err instanceof BudaApiError
|
|
79
|
-
? { error: err.message, code: err.status
|
|
79
|
+
? { error: err.message, code: err.status }
|
|
80
80
|
: { error: String(err), code: "UNKNOWN" };
|
|
81
81
|
return {
|
|
82
82
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
package/dist/tools/volume.js
CHANGED
|
@@ -55,7 +55,7 @@ export function register(server, client, _cache) {
|
|
|
55
55
|
}
|
|
56
56
|
catch (err) {
|
|
57
57
|
const msg = err instanceof BudaApiError
|
|
58
|
-
? { error: err.message, code: err.status
|
|
58
|
+
? { error: err.message, code: err.status }
|
|
59
59
|
: { error: String(err), code: "UNKNOWN" };
|
|
60
60
|
return {
|
|
61
61
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
|
@@ -81,7 +81,7 @@ type CreateWithdrawalArgs = {
|
|
|
81
81
|
bank_account_id?: number;
|
|
82
82
|
confirmation_token: string;
|
|
83
83
|
};
|
|
84
|
-
export declare function handleCreateWithdrawal(args: CreateWithdrawalArgs, client: BudaClient): Promise<{
|
|
84
|
+
export declare function handleCreateWithdrawal(args: CreateWithdrawalArgs, client: BudaClient, transport?: "http" | "stdio"): Promise<{
|
|
85
85
|
content: Array<{
|
|
86
86
|
type: "text";
|
|
87
87
|
text: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withdrawals.d.ts","sourceRoot":"","sources":["../../src/tools/withdrawals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAgB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"withdrawals.d.ts","sourceRoot":"","sources":["../../src/tools/withdrawals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAgB,MAAM,cAAc,CAAC;AAMxD,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;CA8B1C,CAAC;AAEF,KAAK,wBAAwB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,mBAAmB,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAqBF,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,wBAAwB,EAC9B,MAAM,EAAE,UAAU,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,CA+ChF;AAED,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuBtC,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,oBAAoB,EAC1B,MAAM,EAAE,UAAU,EAClB,SAAS,GAAE,MAAM,GAAG,OAAiB,GACpC,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,CAkGhF;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CA+BpE"}
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { BudaApiError } from "../client.js";
|
|
3
3
|
import { validateCurrency, validateCryptoAddress } from "../validation.js";
|
|
4
4
|
import { flattenAmount } from "../utils.js";
|
|
5
|
+
import { logAudit } from "../audit.js";
|
|
5
6
|
export const getWithdrawalHistoryToolSchema = {
|
|
6
7
|
name: "get_withdrawal_history",
|
|
7
8
|
description: "Returns withdrawal history for a currency on the authenticated Buda.com account. " +
|
|
@@ -81,7 +82,7 @@ export async function handleGetWithdrawalHistory(args, client) {
|
|
|
81
82
|
}
|
|
82
83
|
catch (err) {
|
|
83
84
|
const msg = err instanceof BudaApiError
|
|
84
|
-
? { error: err.message, code: err.status
|
|
85
|
+
? { error: err.message, code: err.status }
|
|
85
86
|
: { error: String(err), code: "UNKNOWN" };
|
|
86
87
|
return {
|
|
87
88
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
|
@@ -112,7 +113,7 @@ export const createWithdrawalToolSchema = {
|
|
|
112
113
|
required: ["currency", "amount", "confirmation_token"],
|
|
113
114
|
},
|
|
114
115
|
};
|
|
115
|
-
export async function handleCreateWithdrawal(args, client) {
|
|
116
|
+
export async function handleCreateWithdrawal(args, client, transport = "stdio") {
|
|
116
117
|
const { currency, amount, address, network, bank_account_id, confirmation_token } = args;
|
|
117
118
|
if (confirmation_token !== "CONFIRM") {
|
|
118
119
|
return {
|
|
@@ -187,18 +188,17 @@ export async function handleCreateWithdrawal(args, client) {
|
|
|
187
188
|
payload.bank_account_id = bank_account_id;
|
|
188
189
|
}
|
|
189
190
|
const data = await client.post(`/currencies/${currency.toUpperCase()}/withdrawals`, payload);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
const result = { content: [{ type: "text", text: JSON.stringify(normalizeWithdrawal(data.withdrawal), null, 2) }] };
|
|
192
|
+
logAudit({ ts: new Date().toISOString(), tool: "create_withdrawal", transport, args_summary: { currency, amount, type: hasAddress ? "crypto" : "fiat" }, success: true });
|
|
193
|
+
return result;
|
|
193
194
|
}
|
|
194
195
|
catch (err) {
|
|
195
196
|
const msg = err instanceof BudaApiError
|
|
196
|
-
? { error: err.message, code: err.status
|
|
197
|
+
? { error: err.message, code: err.status }
|
|
197
198
|
: { error: String(err), code: "UNKNOWN" };
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
};
|
|
199
|
+
const result = { content: [{ type: "text", text: JSON.stringify(msg) }], isError: true };
|
|
200
|
+
logAudit({ ts: new Date().toISOString(), tool: "create_withdrawal", transport, args_summary: { currency, amount, type: hasAddress ? "crypto" : "fiat" }, success: false, error_code: msg.code });
|
|
201
|
+
return result;
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
export function register(server, client) {
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { Amount, OhlcvCandle } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Constant-time string comparison to prevent timing attacks on bearer tokens.
|
|
4
|
+
*/
|
|
5
|
+
export declare function safeTokenEqual(a: string, b: string): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Parses a raw string (from an environment variable) as an integer within [min, max].
|
|
8
|
+
* Returns the fallback when raw is undefined.
|
|
9
|
+
* Throws a descriptive Error if the value is non-numeric or out of range.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseEnvInt(raw: string | undefined, fallback: number, min: number, max: number, name: string): number;
|
|
2
12
|
/**
|
|
3
13
|
* Flattens a Buda API Amount tuple [value_string, currency] into a typed object.
|
|
4
14
|
* All numeric strings are cast to float via parseFloat.
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEtD;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAK5D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,GACX,MAAM,CASR;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAIjF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAI/E;AAWD;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EAC3C,MAAM,EAAE,MAAM,GACb,WAAW,EAAE,CAoCf"}
|
package/dist/utils.js
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
import { timingSafeEqual } from "crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Constant-time string comparison to prevent timing attacks on bearer tokens.
|
|
4
|
+
*/
|
|
5
|
+
export function safeTokenEqual(a, b) {
|
|
6
|
+
const aBuf = Buffer.from(a);
|
|
7
|
+
const bBuf = Buffer.from(b);
|
|
8
|
+
if (aBuf.length !== bBuf.length)
|
|
9
|
+
return false;
|
|
10
|
+
return timingSafeEqual(aBuf, bBuf);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Parses a raw string (from an environment variable) as an integer within [min, max].
|
|
14
|
+
* Returns the fallback when raw is undefined.
|
|
15
|
+
* Throws a descriptive Error if the value is non-numeric or out of range.
|
|
16
|
+
*/
|
|
17
|
+
export function parseEnvInt(raw, fallback, min, max, name) {
|
|
18
|
+
if (raw === undefined)
|
|
19
|
+
return fallback;
|
|
20
|
+
const n = parseInt(raw, 10);
|
|
21
|
+
if (isNaN(n) || n < min || n > max) {
|
|
22
|
+
throw new Error(`[buda-mcp] Invalid ${name} "${raw}". Must be an integer between ${min} and ${max}.`);
|
|
23
|
+
}
|
|
24
|
+
return n;
|
|
25
|
+
}
|
|
1
26
|
/**
|
|
2
27
|
* Flattens a Buda API Amount tuple [value_string, currency] into a typed object.
|
|
3
28
|
* All numeric strings are cast to float via parseFloat.
|
package/package.json
CHANGED
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.5.
|
|
9
|
+
"version": "1.5.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@guiie/buda-mcp",
|
|
14
|
-
"version": "1.5.
|
|
14
|
+
"version": "1.5.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|
package/src/audit.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured audit logging for destructive MCP tool calls.
|
|
3
|
+
*
|
|
4
|
+
* Writes newline-delimited JSON to stderr so it never pollutes the stdio MCP transport
|
|
5
|
+
* and is captured by Railway / any log aggregator attached to the process.
|
|
6
|
+
*
|
|
7
|
+
* Rules for args_summary:
|
|
8
|
+
* - Include: market_id, currency, price_type, type, amount ranges
|
|
9
|
+
* - NEVER include: confirmation_token, invoice, address, bank_account_id
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface AuditEvent {
|
|
13
|
+
ts: string;
|
|
14
|
+
tool: string;
|
|
15
|
+
transport: "http" | "stdio";
|
|
16
|
+
ip?: string;
|
|
17
|
+
args_summary: Record<string, unknown>;
|
|
18
|
+
success: boolean;
|
|
19
|
+
error_code?: string | number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function logAudit(event: AuditEvent): void {
|
|
23
|
+
process.stderr.write(JSON.stringify({ audit: true, ...event }) + "\n");
|
|
24
|
+
}
|
package/src/http.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mc
|
|
|
4
4
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
5
|
import { BudaClient } from "./client.js";
|
|
6
6
|
import { MemoryCache, CACHE_TTL } from "./cache.js";
|
|
7
|
+
import { safeTokenEqual, parseEnvInt } from "./utils.js";
|
|
7
8
|
import { VERSION } from "./version.js";
|
|
8
9
|
import { validateMarketId } from "./validation.js";
|
|
9
10
|
import type { MarketsResponse, TickerResponse } from "./types.js";
|
|
@@ -43,7 +44,13 @@ import * as batchOrders from "./tools/batch_orders.js";
|
|
|
43
44
|
import * as lightning from "./tools/lightning.js";
|
|
44
45
|
import { handleMarketSummary } from "./tools/market_summary.js";
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
let PORT: number;
|
|
48
|
+
try {
|
|
49
|
+
PORT = parseEnvInt(process.env.PORT, 3000, 1, 65535, "PORT");
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
47
54
|
|
|
48
55
|
const client = new BudaClient(
|
|
49
56
|
undefined,
|
|
@@ -224,6 +231,9 @@ function createServer(): McpServer {
|
|
|
224
231
|
}
|
|
225
232
|
|
|
226
233
|
const app = express();
|
|
234
|
+
// Required for correct client IP detection behind Railway's reverse proxy.
|
|
235
|
+
// Without this, express-rate-limit sees the proxy IP instead of the real client.
|
|
236
|
+
app.set("trust proxy", 1);
|
|
227
237
|
app.use(express.json());
|
|
228
238
|
|
|
229
239
|
const MCP_AUTH_TOKEN = process.env.MCP_AUTH_TOKEN;
|
|
@@ -237,9 +247,23 @@ if (authEnabled && !MCP_AUTH_TOKEN) {
|
|
|
237
247
|
process.exit(1);
|
|
238
248
|
}
|
|
239
249
|
|
|
250
|
+
if (MCP_AUTH_TOKEN && MCP_AUTH_TOKEN.length < 32) {
|
|
251
|
+
console.warn(
|
|
252
|
+
"[buda-mcp] WARNING: MCP_AUTH_TOKEN has fewer than 32 characters. Use a longer random secret.",
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let rateLimitMax: number;
|
|
257
|
+
try {
|
|
258
|
+
rateLimitMax = parseEnvInt(process.env.MCP_RATE_LIMIT, 120, 1, 10_000, "MCP_RATE_LIMIT");
|
|
259
|
+
} catch (err) {
|
|
260
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
|
|
240
264
|
const mcpRateLimiter = rateLimit({
|
|
241
265
|
windowMs: 60_000,
|
|
242
|
-
max:
|
|
266
|
+
max: rateLimitMax,
|
|
243
267
|
standardHeaders: true,
|
|
244
268
|
legacyHeaders: false,
|
|
245
269
|
message: { error: "Too many requests. Retry after 60 seconds.", code: "RATE_LIMITED" },
|
|
@@ -255,7 +279,7 @@ function mcpAuthMiddleware(
|
|
|
255
279
|
return;
|
|
256
280
|
}
|
|
257
281
|
const auth = req.headers.authorization ?? "";
|
|
258
|
-
if (auth
|
|
282
|
+
if (!safeTokenEqual(auth, `Bearer ${MCP_AUTH_TOKEN}`)) {
|
|
259
283
|
res.status(401).json({ error: "Unauthorized" });
|
|
260
284
|
return;
|
|
261
285
|
}
|
package/src/tools/account.ts
CHANGED
|
@@ -47,7 +47,7 @@ export async function handleGetAccountInfo(
|
|
|
47
47
|
} catch (err) {
|
|
48
48
|
const msg =
|
|
49
49
|
err instanceof BudaApiError
|
|
50
|
-
? { error: err.message, code: err.status
|
|
50
|
+
? { error: err.message, code: err.status }
|
|
51
51
|
: { error: String(err), code: "UNKNOWN" };
|
|
52
52
|
return {
|
|
53
53
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
package/src/tools/arbitrage.ts
CHANGED
|
@@ -171,7 +171,7 @@ export async function handleArbitrageOpportunities(
|
|
|
171
171
|
} catch (err) {
|
|
172
172
|
const msg =
|
|
173
173
|
err instanceof BudaApiError
|
|
174
|
-
? { error: err.message, code: err.status
|
|
174
|
+
? { error: err.message, code: err.status }
|
|
175
175
|
: { error: String(err), code: "UNKNOWN" };
|
|
176
176
|
return {
|
|
177
177
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
package/src/tools/balance.ts
CHANGED
|
@@ -73,7 +73,7 @@ export async function handleGetBalance(
|
|
|
73
73
|
} catch (err) {
|
|
74
74
|
const msg =
|
|
75
75
|
err instanceof BudaApiError
|
|
76
|
-
? { error: err.message, code: err.status
|
|
76
|
+
? { error: err.message, code: err.status }
|
|
77
77
|
: { error: String(err), code: "UNKNOWN" };
|
|
78
78
|
return {
|
|
79
79
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
package/src/tools/balances.ts
CHANGED
|
@@ -51,7 +51,7 @@ export function register(server: McpServer, client: BudaClient): void {
|
|
|
51
51
|
} catch (err) {
|
|
52
52
|
const msg =
|
|
53
53
|
err instanceof BudaApiError
|
|
54
|
-
? { error: err.message, code: err.status
|
|
54
|
+
? { error: err.message, code: err.status }
|
|
55
55
|
: { error: String(err), code: "UNKNOWN" };
|
|
56
56
|
return {
|
|
57
57
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
package/src/tools/banks.ts
CHANGED
|
@@ -73,7 +73,7 @@ export async function handleGetAvailableBanks(
|
|
|
73
73
|
}
|
|
74
74
|
const msg =
|
|
75
75
|
err instanceof BudaApiError
|
|
76
|
-
? { error: err.message, code: err.status
|
|
76
|
+
? { error: err.message, code: err.status }
|
|
77
77
|
: { error: String(err), code: "UNKNOWN" };
|
|
78
78
|
return {
|
|
79
79
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
|
@@ -2,6 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { BudaClient, BudaApiError } from "../client.js";
|
|
4
4
|
import { validateMarketId } from "../validation.js";
|
|
5
|
+
import { logAudit } from "../audit.js";
|
|
5
6
|
import type { OrderResponse } from "../types.js";
|
|
6
7
|
|
|
7
8
|
export const toolSchema = {
|
|
@@ -76,6 +77,7 @@ type BatchOrdersArgs = {
|
|
|
76
77
|
export async function handlePlaceBatchOrders(
|
|
77
78
|
args: BatchOrdersArgs,
|
|
78
79
|
client: BudaClient,
|
|
80
|
+
transport: "http" | "stdio" = "stdio",
|
|
79
81
|
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
80
82
|
const { orders, max_notional, confirmation_token } = args;
|
|
81
83
|
|
|
@@ -202,9 +204,18 @@ export async function handlePlaceBatchOrders(
|
|
|
202
204
|
response.warning = "Some orders failed. Already-placed orders were NOT rolled back.";
|
|
203
205
|
}
|
|
204
206
|
|
|
207
|
+
const isError = failed > 0 && succeeded === 0 ? true : undefined;
|
|
208
|
+
logAudit({
|
|
209
|
+
ts: new Date().toISOString(),
|
|
210
|
+
tool: "place_batch_orders",
|
|
211
|
+
transport,
|
|
212
|
+
args_summary: { order_count: orders.length, succeeded, failed },
|
|
213
|
+
success: !isError,
|
|
214
|
+
error_code: isError ? "PARTIAL_OR_FULL_FAILURE" : undefined,
|
|
215
|
+
});
|
|
205
216
|
return {
|
|
206
217
|
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
207
|
-
isError
|
|
218
|
+
isError,
|
|
208
219
|
};
|
|
209
220
|
}
|
|
210
221
|
|
|
@@ -2,6 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { BudaClient, BudaApiError } from "../client.js";
|
|
4
4
|
import { validateMarketId } from "../validation.js";
|
|
5
|
+
import { logAudit } from "../audit.js";
|
|
5
6
|
import type { CancelAllOrdersResponse } from "../types.js";
|
|
6
7
|
|
|
7
8
|
export const toolSchema = {
|
|
@@ -37,6 +38,7 @@ type CancelAllOrdersArgs = {
|
|
|
37
38
|
export async function handleCancelAllOrders(
|
|
38
39
|
args: CancelAllOrdersArgs,
|
|
39
40
|
client: BudaClient,
|
|
41
|
+
transport: "http" | "stdio" = "stdio",
|
|
40
42
|
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
41
43
|
const { market_id, confirmation_token } = args;
|
|
42
44
|
|
|
@@ -76,23 +78,19 @@ export async function handleCancelAllOrders(
|
|
|
76
78
|
|
|
77
79
|
const data = await client.delete<CancelAllOrdersResponse>(`/orders`, params);
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
content: [
|
|
81
|
-
{
|
|
82
|
-
type: "text",
|
|
83
|
-
text: JSON.stringify({ canceled_count: data.canceled_count, market_id }),
|
|
84
|
-
},
|
|
85
|
-
],
|
|
81
|
+
const result = {
|
|
82
|
+
content: [{ type: "text" as const, text: JSON.stringify({ canceled_count: data.canceled_count, market_id }) }],
|
|
86
83
|
};
|
|
84
|
+
logAudit({ ts: new Date().toISOString(), tool: "cancel_all_orders", transport, args_summary: { market_id }, success: true });
|
|
85
|
+
return result;
|
|
87
86
|
} catch (err) {
|
|
88
87
|
const msg =
|
|
89
88
|
err instanceof BudaApiError
|
|
90
|
-
? { error: err.message, code: err.status
|
|
89
|
+
? { error: err.message, code: err.status }
|
|
91
90
|
: { error: String(err), code: "UNKNOWN" };
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
};
|
|
91
|
+
const result = { content: [{ type: "text" as const, text: JSON.stringify(msg) }], isError: true as const };
|
|
92
|
+
logAudit({ ts: new Date().toISOString(), tool: "cancel_all_orders", transport, args_summary: { market_id }, success: false, error_code: msg.code });
|
|
93
|
+
return result;
|
|
96
94
|
}
|
|
97
95
|
}
|
|
98
96
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { BudaClient, BudaApiError } from "../client.js";
|
|
4
|
+
import { logAudit } from "../audit.js";
|
|
4
5
|
import type { OrderResponse } from "../types.js";
|
|
5
6
|
|
|
6
7
|
export const toolSchema = {
|
|
@@ -36,6 +37,7 @@ type CancelOrderArgs = {
|
|
|
36
37
|
export async function handleCancelOrder(
|
|
37
38
|
args: CancelOrderArgs,
|
|
38
39
|
client: BudaClient,
|
|
40
|
+
transport: "http" | "stdio" = "stdio",
|
|
39
41
|
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
40
42
|
const { order_id, confirmation_token } = args;
|
|
41
43
|
|
|
@@ -62,18 +64,17 @@ export async function handleCancelOrder(
|
|
|
62
64
|
order: { state: "canceling" },
|
|
63
65
|
});
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
const result = { content: [{ type: "text" as const, text: JSON.stringify(data.order, null, 2) }] };
|
|
68
|
+
logAudit({ ts: new Date().toISOString(), tool: "cancel_order", transport, args_summary: { order_id }, success: true });
|
|
69
|
+
return result;
|
|
68
70
|
} catch (err) {
|
|
69
71
|
const msg =
|
|
70
72
|
err instanceof BudaApiError
|
|
71
|
-
? { error: err.message, code: err.status
|
|
73
|
+
? { error: err.message, code: err.status }
|
|
72
74
|
: { error: String(err), code: "UNKNOWN" };
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
};
|
|
75
|
+
const result = { content: [{ type: "text" as const, text: JSON.stringify(msg) }], isError: true as const };
|
|
76
|
+
logAudit({ ts: new Date().toISOString(), tool: "cancel_order", transport, args_summary: { order_id }, success: false, error_code: msg.code });
|
|
77
|
+
return result;
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
|
|
@@ -2,6 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { BudaClient, BudaApiError } from "../client.js";
|
|
4
4
|
import { flattenAmount } from "../utils.js";
|
|
5
|
+
import { logAudit } from "../audit.js";
|
|
5
6
|
import type { OrderResponse, Order } from "../types.js";
|
|
6
7
|
|
|
7
8
|
export const toolSchema = {
|
|
@@ -69,6 +70,7 @@ function normalizeOrder(o: Order) {
|
|
|
69
70
|
export async function handleCancelOrderByClientId(
|
|
70
71
|
args: CancelOrderByClientIdArgs,
|
|
71
72
|
client: BudaClient,
|
|
73
|
+
transport: "http" | "stdio" = "stdio",
|
|
72
74
|
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
73
75
|
const { client_id, confirmation_token } = args;
|
|
74
76
|
|
|
@@ -96,18 +98,17 @@ export async function handleCancelOrderByClientId(
|
|
|
96
98
|
{ order: { state: "canceling" } },
|
|
97
99
|
);
|
|
98
100
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
const result = { content: [{ type: "text" as const, text: JSON.stringify(normalizeOrder(data.order), null, 2) }] };
|
|
102
|
+
logAudit({ ts: new Date().toISOString(), tool: "cancel_order_by_client_id", transport, args_summary: {}, success: true });
|
|
103
|
+
return result;
|
|
102
104
|
} catch (err) {
|
|
103
105
|
const msg =
|
|
104
106
|
err instanceof BudaApiError
|
|
105
|
-
? { error: err.message, code: err.status
|
|
107
|
+
? { error: err.message, code: err.status }
|
|
106
108
|
: { error: String(err), code: "UNKNOWN" };
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
};
|
|
109
|
+
const result = { content: [{ type: "text" as const, text: JSON.stringify(msg) }], isError: true as const };
|
|
110
|
+
logAudit({ ts: new Date().toISOString(), tool: "cancel_order_by_client_id", transport, args_summary: {}, success: false, error_code: msg.code });
|
|
111
|
+
return result;
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
|