@guiie/buda-mcp 1.5.1 → 1.5.3

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 (106) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/PUBLISH_CHECKLIST.md +56 -36
  3. package/dist/audit.d.ts +21 -0
  4. package/dist/audit.d.ts.map +1 -0
  5. package/dist/audit.js +14 -0
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +6 -1
  8. package/dist/http.js +47 -15
  9. package/dist/tools/account.js +1 -1
  10. package/dist/tools/arbitrage.js +1 -1
  11. package/dist/tools/balance.js +1 -1
  12. package/dist/tools/balances.js +1 -1
  13. package/dist/tools/banks.js +1 -1
  14. package/dist/tools/batch_orders.d.ts +2 -2
  15. package/dist/tools/batch_orders.d.ts.map +1 -1
  16. package/dist/tools/batch_orders.js +14 -4
  17. package/dist/tools/cancel_all_orders.d.ts +2 -2
  18. package/dist/tools/cancel_all_orders.d.ts.map +1 -1
  19. package/dist/tools/cancel_all_orders.js +12 -15
  20. package/dist/tools/cancel_order.d.ts +2 -2
  21. package/dist/tools/cancel_order.d.ts.map +1 -1
  22. package/dist/tools/cancel_order.js +11 -11
  23. package/dist/tools/cancel_order_by_client_id.d.ts +2 -2
  24. package/dist/tools/cancel_order_by_client_id.d.ts.map +1 -1
  25. package/dist/tools/cancel_order_by_client_id.js +11 -11
  26. package/dist/tools/compare_markets.d.ts +9 -0
  27. package/dist/tools/compare_markets.d.ts.map +1 -1
  28. package/dist/tools/compare_markets.js +63 -53
  29. package/dist/tools/dead_mans_switch.d.ts +1 -1
  30. package/dist/tools/dead_mans_switch.d.ts.map +1 -1
  31. package/dist/tools/dead_mans_switch.js +35 -3
  32. package/dist/tools/deposits.js +2 -2
  33. package/dist/tools/fees.js +1 -1
  34. package/dist/tools/lightning.d.ts +2 -2
  35. package/dist/tools/lightning.d.ts.map +1 -1
  36. package/dist/tools/lightning.js +13 -11
  37. package/dist/tools/market_sentiment.js +1 -1
  38. package/dist/tools/market_summary.js +1 -1
  39. package/dist/tools/markets.js +1 -1
  40. package/dist/tools/order_lookup.js +2 -2
  41. package/dist/tools/orderbook.js +1 -1
  42. package/dist/tools/orders.js +1 -1
  43. package/dist/tools/place_order.d.ts +2 -2
  44. package/dist/tools/place_order.d.ts.map +1 -1
  45. package/dist/tools/place_order.js +24 -6
  46. package/dist/tools/price_history.js +1 -1
  47. package/dist/tools/quotation.js +1 -1
  48. package/dist/tools/receive_addresses.d.ts +2 -2
  49. package/dist/tools/receive_addresses.d.ts.map +1 -1
  50. package/dist/tools/receive_addresses.js +13 -13
  51. package/dist/tools/remittance_recipients.js +2 -2
  52. package/dist/tools/remittances.d.ts +3 -3
  53. package/dist/tools/remittances.d.ts.map +1 -1
  54. package/dist/tools/remittances.js +22 -23
  55. package/dist/tools/simulate_order.js +1 -1
  56. package/dist/tools/spread.js +1 -1
  57. package/dist/tools/technical_indicators.js +1 -1
  58. package/dist/tools/ticker.js +1 -1
  59. package/dist/tools/trades.js +1 -1
  60. package/dist/tools/volume.js +1 -1
  61. package/dist/tools/withdrawals.d.ts +2 -2
  62. package/dist/tools/withdrawals.d.ts.map +1 -1
  63. package/dist/tools/withdrawals.js +12 -12
  64. package/dist/utils.d.ts +10 -0
  65. package/dist/utils.d.ts.map +1 -1
  66. package/dist/utils.js +25 -0
  67. package/package.json +2 -1
  68. package/server.json +2 -2
  69. package/src/audit.ts +24 -0
  70. package/src/client.ts +9 -1
  71. package/src/http.ts +50 -15
  72. package/src/tools/account.ts +1 -1
  73. package/src/tools/arbitrage.ts +1 -1
  74. package/src/tools/balance.ts +1 -1
  75. package/src/tools/balances.ts +1 -1
  76. package/src/tools/banks.ts +1 -1
  77. package/src/tools/batch_orders.ts +18 -3
  78. package/src/tools/cancel_all_orders.ts +16 -14
  79. package/src/tools/cancel_order.ts +15 -10
  80. package/src/tools/cancel_order_by_client_id.ts +15 -10
  81. package/src/tools/compare_markets.ts +78 -61
  82. package/src/tools/dead_mans_switch.ts +37 -2
  83. package/src/tools/deposits.ts +2 -2
  84. package/src/tools/fees.ts +1 -1
  85. package/src/tools/lightning.ts +18 -11
  86. package/src/tools/market_sentiment.ts +1 -1
  87. package/src/tools/market_summary.ts +1 -1
  88. package/src/tools/markets.ts +1 -1
  89. package/src/tools/order_lookup.ts +2 -2
  90. package/src/tools/orderbook.ts +1 -1
  91. package/src/tools/orders.ts +1 -1
  92. package/src/tools/place_order.ts +30 -7
  93. package/src/tools/price_history.ts +1 -1
  94. package/src/tools/quotation.ts +1 -1
  95. package/src/tools/receive_addresses.ts +17 -12
  96. package/src/tools/remittance_recipients.ts +2 -2
  97. package/src/tools/remittances.ts +26 -21
  98. package/src/tools/simulate_order.ts +1 -1
  99. package/src/tools/spread.ts +1 -1
  100. package/src/tools/technical_indicators.ts +1 -1
  101. package/src/tools/ticker.ts +1 -1
  102. package/src/tools/trades.ts +1 -1
  103. package/src/tools/volume.ts +1 -1
  104. package/src/tools/withdrawals.ts +16 -11
  105. package/src/utils.ts +33 -0
  106. package/test/unit.ts +362 -4
package/src/tools/fees.ts CHANGED
@@ -69,7 +69,7 @@ export async function handleGetNetworkFees(
69
69
  } catch (err) {
70
70
  const msg =
71
71
  err instanceof BudaApiError
72
- ? { error: err.message, code: err.status, path: err.path }
72
+ ? { error: err.message, code: err.status }
73
73
  : { error: String(err), code: "UNKNOWN" };
74
74
  return {
75
75
  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 { flattenAmount } from "../utils.js";
5
+ import { logAudit } from "../audit.js";
5
6
  import type { LightningWithdrawalResponse, LightningInvoiceResponse } from "../types.js";
6
7
 
7
8
  export const lightningWithdrawalToolSchema = {
@@ -69,6 +70,7 @@ type CreateLightningInvoiceArgs = {
69
70
  export async function handleLightningWithdrawal(
70
71
  args: LightningWithdrawalArgs,
71
72
  client: BudaClient,
73
+ transport: "http" | "stdio" = "stdio",
72
74
  ): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
73
75
  const { invoice, confirmation_token } = args;
74
76
 
@@ -91,7 +93,7 @@ export async function handleLightningWithdrawal(
91
93
  };
92
94
  }
93
95
 
94
- const BOLT11_RE = /^ln(bc|tb|bcrt)\d/i;
96
+ const BOLT11_RE = /^ln(bc|tb|bcrt)\d*[munp]?1[a-z0-9]{20,}$/i;
95
97
  if (!BOLT11_RE.test(invoice)) {
96
98
  return {
97
99
  content: [{
@@ -117,10 +119,10 @@ export async function handleLightningWithdrawal(
117
119
  const amount = flattenAmount(lw.amount);
118
120
  const fee = flattenAmount(lw.fee);
119
121
 
120
- return {
122
+ const result = {
121
123
  content: [
122
124
  {
123
- type: "text",
125
+ type: "text" as const,
124
126
  text: JSON.stringify(
125
127
  {
126
128
  id: lw.id,
@@ -138,15 +140,16 @@ export async function handleLightningWithdrawal(
138
140
  },
139
141
  ],
140
142
  };
143
+ logAudit({ ts: new Date().toISOString(), tool: "lightning_withdrawal", transport, args_summary: {}, success: true });
144
+ return result;
141
145
  } catch (err) {
142
146
  const msg =
143
147
  err instanceof BudaApiError
144
- ? { error: err.message, code: err.status, path: err.path }
148
+ ? { error: err.message, code: err.status }
145
149
  : { error: String(err), code: "UNKNOWN" };
146
- return {
147
- content: [{ type: "text", text: JSON.stringify(msg) }],
148
- isError: true,
149
- };
150
+ const result = { content: [{ type: "text" as const, text: JSON.stringify(msg) }], isError: true as const };
151
+ logAudit({ ts: new Date().toISOString(), tool: "lightning_withdrawal", transport, args_summary: {}, success: false, error_code: msg.code });
152
+ return result;
150
153
  }
151
154
  }
152
155
 
@@ -192,7 +195,7 @@ export async function handleCreateLightningInvoice(
192
195
  } catch (err) {
193
196
  const msg =
194
197
  err instanceof BudaApiError
195
- ? { error: err.message, code: err.status, path: err.path }
198
+ ? { error: err.message, code: err.status }
196
199
  : { error: String(err), code: "UNKNOWN" };
197
200
  return {
198
201
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -201,7 +204,11 @@ export async function handleCreateLightningInvoice(
201
204
  }
202
205
  }
203
206
 
204
- export function register(server: McpServer, client: BudaClient): void {
207
+ export function register(
208
+ server: McpServer,
209
+ client: BudaClient,
210
+ transport: "http" | "stdio" = "stdio",
211
+ ): void {
205
212
  server.tool(
206
213
  lightningWithdrawalToolSchema.name,
207
214
  lightningWithdrawalToolSchema.description,
@@ -217,7 +224,7 @@ export function register(server: McpServer, client: BudaClient): void {
217
224
  "Any other value will reject the request without paying.",
218
225
  ),
219
226
  },
220
- (args) => handleLightningWithdrawal(args, client),
227
+ (args) => handleLightningWithdrawal(args, client, transport),
221
228
  );
222
229
 
223
230
  server.tool(
@@ -118,7 +118,7 @@ export async function handleMarketSentiment(
118
118
  } catch (err) {
119
119
  const msg =
120
120
  err instanceof BudaApiError
121
- ? { error: err.message, code: err.status, path: err.path }
121
+ ? { error: err.message, code: err.status }
122
122
  : { error: String(err), code: "UNKNOWN" };
123
123
  return {
124
124
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -101,7 +101,7 @@ export async function handleMarketSummary(
101
101
  } catch (err) {
102
102
  const msg =
103
103
  err instanceof BudaApiError
104
- ? { error: err.message, code: err.status, path: err.path }
104
+ ? { error: err.message, code: err.status }
105
105
  : { error: String(err), code: "UNKNOWN" };
106
106
  return {
107
107
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -68,7 +68,7 @@ export function register(server: McpServer, client: BudaClient, cache: MemoryCac
68
68
  } catch (err) {
69
69
  const msg =
70
70
  err instanceof BudaApiError
71
- ? { error: err.message, code: err.status, path: err.path }
71
+ ? { error: err.message, code: err.status }
72
72
  : { error: String(err), code: "UNKNOWN" };
73
73
  return {
74
74
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -88,7 +88,7 @@ export async function handleGetOrder(
88
88
  } catch (err) {
89
89
  const msg =
90
90
  err instanceof BudaApiError
91
- ? { error: err.message, code: err.status, path: err.path }
91
+ ? { error: err.message, code: err.status }
92
92
  : { error: String(err), code: "UNKNOWN" };
93
93
  return {
94
94
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -109,7 +109,7 @@ export async function handleGetOrderByClientId(
109
109
  } catch (err) {
110
110
  const msg =
111
111
  err instanceof BudaApiError
112
- ? { error: err.message, code: err.status, path: err.path }
112
+ ? { error: err.message, code: err.status }
113
113
  : { error: String(err), code: "UNKNOWN" };
114
114
  return {
115
115
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -83,7 +83,7 @@ export function register(server: McpServer, client: BudaClient, cache: MemoryCac
83
83
  } catch (err) {
84
84
  const msg =
85
85
  err instanceof BudaApiError
86
- ? { error: err.message, code: err.status, path: err.path }
86
+ ? { error: err.message, code: err.status }
87
87
  : { error: String(err), code: "UNKNOWN" };
88
88
  return {
89
89
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -135,7 +135,7 @@ export function register(server: McpServer, client: BudaClient): void {
135
135
  } catch (err) {
136
136
  const msg =
137
137
  err instanceof BudaApiError
138
- ? { error: err.message, code: err.status, path: err.path }
138
+ ? { error: err.message, code: err.status }
139
139
  : { error: String(err), code: "UNKNOWN" };
140
140
  return {
141
141
  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 = {
@@ -91,6 +92,7 @@ type PlaceOrderArgs = {
91
92
  export async function handlePlaceOrder(
92
93
  args: PlaceOrderArgs,
93
94
  client: BudaClient,
95
+ transport: "http" | "stdio" = "stdio",
94
96
  ): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
95
97
  const {
96
98
  market_id,
@@ -243,22 +245,43 @@ export async function handlePlaceOrder(
243
245
  payload,
244
246
  );
245
247
 
246
- return {
247
- content: [{ type: "text", text: JSON.stringify(data.order, null, 2) }],
248
+ const result = {
249
+ content: [{ type: "text" as const, text: JSON.stringify(data.order, null, 2) }],
248
250
  };
251
+ logAudit({
252
+ ts: new Date().toISOString(),
253
+ tool: "place_order",
254
+ transport,
255
+ args_summary: { market_id, type, price_type, amount },
256
+ success: true,
257
+ });
258
+ return result;
249
259
  } catch (err) {
250
260
  const msg =
251
261
  err instanceof BudaApiError
252
- ? { error: err.message, code: err.status, path: err.path }
262
+ ? { error: err.message, code: err.status }
253
263
  : { error: String(err), code: "UNKNOWN" };
254
- return {
255
- content: [{ type: "text", text: JSON.stringify(msg) }],
264
+ const result = {
265
+ content: [{ type: "text" as const, text: JSON.stringify(msg) }],
256
266
  isError: true,
257
267
  };
268
+ logAudit({
269
+ ts: new Date().toISOString(),
270
+ tool: "place_order",
271
+ transport,
272
+ args_summary: { market_id, type, price_type, amount },
273
+ success: false,
274
+ error_code: msg.code,
275
+ });
276
+ return result;
258
277
  }
259
278
  }
260
279
 
261
- export function register(server: McpServer, client: BudaClient): void {
280
+ export function register(
281
+ server: McpServer,
282
+ client: BudaClient,
283
+ transport: "http" | "stdio" = "stdio",
284
+ ): void {
262
285
  server.tool(
263
286
  toolSchema.name,
264
287
  toolSchema.description,
@@ -316,6 +339,6 @@ export function register(server: McpServer, client: BudaClient): void {
316
339
  "Any other value will reject the request without placing an order.",
317
340
  ),
318
341
  },
319
- (args) => handlePlaceOrder(args, client),
342
+ (args) => handlePlaceOrder(args, client, transport),
320
343
  );
321
344
  }
@@ -108,7 +108,7 @@ export function register(server: McpServer, client: BudaClient, _cache: MemoryCa
108
108
  } catch (err) {
109
109
  const msg =
110
110
  err instanceof BudaApiError
111
- ? { error: err.message, code: err.status, path: err.path }
111
+ ? { error: err.message, code: err.status }
112
112
  : { error: String(err), code: "UNKNOWN" };
113
113
  return {
114
114
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -100,7 +100,7 @@ export async function handleGetRealQuotation(
100
100
  } catch (err) {
101
101
  const msg =
102
102
  err instanceof BudaApiError
103
- ? { error: err.message, code: err.status, path: err.path }
103
+ ? { error: err.message, code: err.status }
104
104
  : { error: String(err), code: "UNKNOWN" };
105
105
  return {
106
106
  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 { validateCurrency } from "../validation.js";
5
+ import { logAudit } from "../audit.js";
5
6
  import type { ReceiveAddressesResponse, SingleReceiveAddressResponse, ReceiveAddress } from "../types.js";
6
7
 
7
8
  export const createReceiveAddressToolSchema = {
@@ -114,7 +115,7 @@ export async function handleListReceiveAddresses(
114
115
  } catch (err) {
115
116
  const msg =
116
117
  err instanceof BudaApiError
117
- ? { error: err.message, code: err.status, path: err.path }
118
+ ? { error: err.message, code: err.status }
118
119
  : { error: String(err), code: "UNKNOWN" };
119
120
  return {
120
121
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -147,7 +148,7 @@ export async function handleGetReceiveAddress(
147
148
  } catch (err) {
148
149
  const msg =
149
150
  err instanceof BudaApiError
150
- ? { error: err.message, code: err.status, path: err.path }
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) }],
@@ -159,6 +160,7 @@ export async function handleGetReceiveAddress(
159
160
  export async function handleCreateReceiveAddress(
160
161
  args: { currency: string; confirmation_token: string },
161
162
  client: BudaClient,
163
+ transport: "http" | "stdio" = "stdio",
162
164
  ): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
163
165
  const { currency, confirmation_token } = args;
164
166
 
@@ -193,22 +195,25 @@ export async function handleCreateReceiveAddress(
193
195
  `/currencies/${currency.toUpperCase()}/receive_addresses`,
194
196
  {},
195
197
  );
196
- return {
197
- content: [{ type: "text", text: JSON.stringify(normalizeAddress(data.receive_address), null, 2) }],
198
- };
198
+ const result = { content: [{ type: "text" as const, text: JSON.stringify(normalizeAddress(data.receive_address), null, 2) }] };
199
+ logAudit({ ts: new Date().toISOString(), tool: "create_receive_address", transport, args_summary: { currency }, success: true });
200
+ return result;
199
201
  } catch (err) {
200
202
  const msg =
201
203
  err instanceof BudaApiError
202
- ? { error: err.message, code: err.status, path: err.path }
204
+ ? { error: err.message, code: err.status }
203
205
  : { error: String(err), code: "UNKNOWN" };
204
- return {
205
- content: [{ type: "text", text: JSON.stringify(msg) }],
206
- isError: true,
207
- };
206
+ const result = { content: [{ type: "text" as const, text: JSON.stringify(msg) }], isError: true as const };
207
+ logAudit({ ts: new Date().toISOString(), tool: "create_receive_address", transport, args_summary: { currency }, success: false, error_code: msg.code });
208
+ return result;
208
209
  }
209
210
  }
210
211
 
211
- export function register(server: McpServer, client: BudaClient): void {
212
+ export function register(
213
+ server: McpServer,
214
+ client: BudaClient,
215
+ transport: "http" | "stdio" = "stdio",
216
+ ): void {
212
217
  server.tool(
213
218
  listReceiveAddressesToolSchema.name,
214
219
  listReceiveAddressesToolSchema.description,
@@ -237,6 +242,6 @@ export function register(server: McpServer, client: BudaClient): void {
237
242
  .string()
238
243
  .describe("Safety confirmation. Must equal exactly 'CONFIRM' (case-sensitive) to generate a new address."),
239
244
  },
240
- (args) => handleCreateReceiveAddress(args, client),
245
+ (args) => handleCreateReceiveAddress(args, client, transport),
241
246
  );
242
247
  }
@@ -87,7 +87,7 @@ export async function handleListRemittanceRecipients(
87
87
  } catch (err) {
88
88
  const msg =
89
89
  err instanceof BudaApiError
90
- ? { error: err.message, code: err.status, path: err.path }
90
+ ? { error: err.message, code: err.status }
91
91
  : { error: String(err), code: "UNKNOWN" };
92
92
  return {
93
93
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -108,7 +108,7 @@ export async function handleGetRemittanceRecipient(
108
108
  } catch (err) {
109
109
  const msg =
110
110
  err instanceof BudaApiError
111
- ? { error: err.message, code: err.status, path: err.path }
111
+ ? { error: err.message, code: err.status }
112
112
  : { error: String(err), code: "UNKNOWN" };
113
113
  return {
114
114
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { BudaClient, BudaApiError } from "../client.js";
4
4
  import { flattenAmount } from "../utils.js";
5
5
  import { validateCurrency } from "../validation.js";
6
+ import { logAudit } from "../audit.js";
6
7
  import type { RemittancesResponse, SingleRemittanceResponse, Remittance } from "../types.js";
7
8
 
8
9
  export const listRemittancesToolSchema = {
@@ -152,7 +153,7 @@ export async function handleListRemittances(
152
153
  } catch (err) {
153
154
  const msg =
154
155
  err instanceof BudaApiError
155
- ? { error: err.message, code: err.status, path: err.path }
156
+ ? { error: err.message, code: err.status }
156
157
  : { error: String(err), code: "UNKNOWN" };
157
158
  return {
158
159
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -173,7 +174,7 @@ export async function handleGetRemittance(
173
174
  } catch (err) {
174
175
  const msg =
175
176
  err instanceof BudaApiError
176
- ? { error: err.message, code: err.status, path: err.path }
177
+ ? { error: err.message, code: err.status }
177
178
  : { error: String(err), code: "UNKNOWN" };
178
179
  return {
179
180
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -185,6 +186,7 @@ export async function handleGetRemittance(
185
186
  export async function handleQuoteRemittance(
186
187
  args: { currency: string; amount: number; recipient_id: number; confirmation_token: string },
187
188
  client: BudaClient,
189
+ transport: "http" | "stdio" = "stdio",
188
190
  ): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
189
191
  const { currency, amount, recipient_id, confirmation_token } = args;
190
192
 
@@ -222,24 +224,24 @@ export async function handleQuoteRemittance(
222
224
  recipient_id,
223
225
  },
224
226
  });
225
- return {
226
- content: [{ type: "text", text: JSON.stringify(normalizeRemittance(data.remittance), null, 2) }],
227
- };
227
+ const result = { content: [{ type: "text" as const, text: JSON.stringify(normalizeRemittance(data.remittance), null, 2) }] };
228
+ logAudit({ ts: new Date().toISOString(), tool: "quote_remittance", transport, args_summary: { currency, amount, recipient_id }, success: true });
229
+ return result;
228
230
  } catch (err) {
229
231
  const msg =
230
232
  err instanceof BudaApiError
231
- ? { error: err.message, code: err.status, path: err.path }
233
+ ? { error: err.message, code: err.status }
232
234
  : { error: String(err), code: "UNKNOWN" };
233
- return {
234
- content: [{ type: "text", text: JSON.stringify(msg) }],
235
- isError: true,
236
- };
235
+ const result = { content: [{ type: "text" as const, text: JSON.stringify(msg) }], isError: true as const };
236
+ logAudit({ ts: new Date().toISOString(), tool: "quote_remittance", transport, args_summary: { currency, amount, recipient_id }, success: false, error_code: msg.code });
237
+ return result;
237
238
  }
238
239
  }
239
240
 
240
241
  export async function handleAcceptRemittanceQuote(
241
242
  args: { id: number; confirmation_token: string },
242
243
  client: BudaClient,
244
+ transport: "http" | "stdio" = "stdio",
243
245
  ): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
244
246
  const { id, confirmation_token } = args;
245
247
 
@@ -264,22 +266,25 @@ export async function handleAcceptRemittanceQuote(
264
266
  const data = await client.put<SingleRemittanceResponse>(`/remittances/${id}`, {
265
267
  remittance: { state: "confirming" },
266
268
  });
267
- return {
268
- content: [{ type: "text", text: JSON.stringify(normalizeRemittance(data.remittance), null, 2) }],
269
- };
269
+ const result = { content: [{ type: "text" as const, text: JSON.stringify(normalizeRemittance(data.remittance), null, 2) }] };
270
+ logAudit({ ts: new Date().toISOString(), tool: "accept_remittance_quote", transport, args_summary: { remittance_id: id }, success: true });
271
+ return result;
270
272
  } catch (err) {
271
273
  const msg =
272
274
  err instanceof BudaApiError
273
- ? { error: err.message, code: err.status, path: err.path }
275
+ ? { error: err.message, code: err.status }
274
276
  : { error: String(err), code: "UNKNOWN" };
275
- return {
276
- content: [{ type: "text", text: JSON.stringify(msg) }],
277
- isError: true,
278
- };
277
+ const result = { content: [{ type: "text" as const, text: JSON.stringify(msg) }], isError: true as const };
278
+ logAudit({ ts: new Date().toISOString(), tool: "accept_remittance_quote", transport, args_summary: { remittance_id: id }, success: false, error_code: msg.code });
279
+ return result;
279
280
  }
280
281
  }
281
282
 
282
- export function register(server: McpServer, client: BudaClient): void {
283
+ export function register(
284
+ server: McpServer,
285
+ client: BudaClient,
286
+ transport: "http" | "stdio" = "stdio",
287
+ ): void {
283
288
  server.tool(
284
289
  listRemittancesToolSchema.name,
285
290
  listRemittancesToolSchema.description,
@@ -310,7 +315,7 @@ export function register(server: McpServer, client: BudaClient): void {
310
315
  .string()
311
316
  .describe("Safety confirmation. Must equal exactly 'CONFIRM' (case-sensitive) to create the quote."),
312
317
  },
313
- (args) => handleQuoteRemittance(args, client),
318
+ (args) => handleQuoteRemittance(args, client, transport),
314
319
  );
315
320
 
316
321
  server.tool(
@@ -320,6 +325,6 @@ export function register(server: McpServer, client: BudaClient): void {
320
325
  id: z.number().int().positive().describe("The numeric ID of the remittance quote to accept."),
321
326
  confirmation_token: z.string().describe("Must be 'CONFIRM' to proceed. Any other value aborts."),
322
327
  },
323
- (args) => handleAcceptRemittanceQuote(args, client),
328
+ (args) => handleAcceptRemittanceQuote(args, client, transport),
324
329
  );
325
330
  }
@@ -148,7 +148,7 @@ export async function handleSimulateOrder(
148
148
  } catch (err) {
149
149
  const msg =
150
150
  err instanceof BudaApiError
151
- ? { error: err.message, code: err.status, path: err.path }
151
+ ? { error: err.message, code: err.status }
152
152
  : { error: String(err), code: "UNKNOWN" };
153
153
  return {
154
154
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -86,7 +86,7 @@ export function register(server: McpServer, client: BudaClient, cache: MemoryCac
86
86
  } catch (err) {
87
87
  const msg =
88
88
  err instanceof BudaApiError
89
- ? { error: err.message, code: err.status, path: err.path }
89
+ ? { error: err.message, code: err.status }
90
90
  : { error: String(err), code: "UNKNOWN" };
91
91
  return {
92
92
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -247,7 +247,7 @@ export async function handleTechnicalIndicators(
247
247
  } catch (err) {
248
248
  const msg =
249
249
  err instanceof BudaApiError
250
- ? { error: err.message, code: err.status, path: err.path }
250
+ ? { error: err.message, code: err.status }
251
251
  : { error: String(err), code: "UNKNOWN" };
252
252
  return {
253
253
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -77,7 +77,7 @@ export function register(server: McpServer, client: BudaClient, cache: MemoryCac
77
77
  } catch (err) {
78
78
  const msg =
79
79
  err instanceof BudaApiError
80
- ? { error: err.message, code: err.status, path: err.path }
80
+ ? { error: err.message, code: err.status }
81
81
  : { error: String(err), code: "UNKNOWN" };
82
82
  return {
83
83
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -94,7 +94,7 @@ export function register(server: McpServer, client: BudaClient, _cache: MemoryCa
94
94
  } catch (err) {
95
95
  const msg =
96
96
  err instanceof BudaApiError
97
- ? { error: err.message, code: err.status, path: err.path }
97
+ ? { error: err.message, code: err.status }
98
98
  : { error: String(err), code: "UNKNOWN" };
99
99
  return {
100
100
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -71,7 +71,7 @@ export function register(server: McpServer, client: BudaClient, _cache: MemoryCa
71
71
  } catch (err) {
72
72
  const msg =
73
73
  err instanceof BudaApiError
74
- ? { error: err.message, code: err.status, path: err.path }
74
+ ? { error: err.message, code: err.status }
75
75
  : { error: String(err), code: "UNKNOWN" };
76
76
  return {
77
77
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { BudaClient, BudaApiError } from "../client.js";
4
4
  import { validateCurrency, validateCryptoAddress } from "../validation.js";
5
5
  import { flattenAmount } from "../utils.js";
6
+ import { logAudit } from "../audit.js";
6
7
  import type { WithdrawalsResponse, SingleWithdrawalResponse, Withdrawal } from "../types.js";
7
8
 
8
9
  export const getWithdrawalHistoryToolSchema = {
@@ -106,7 +107,7 @@ export async function handleGetWithdrawalHistory(
106
107
  } catch (err) {
107
108
  const msg =
108
109
  err instanceof BudaApiError
109
- ? { error: err.message, code: err.status, path: err.path }
110
+ ? { error: err.message, code: err.status }
110
111
  : { error: String(err), code: "UNKNOWN" };
111
112
  return {
112
113
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -152,6 +153,7 @@ type CreateWithdrawalArgs = {
152
153
  export async function handleCreateWithdrawal(
153
154
  args: CreateWithdrawalArgs,
154
155
  client: BudaClient,
156
+ transport: "http" | "stdio" = "stdio",
155
157
  ): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
156
158
  const { currency, amount, address, network, bank_account_id, confirmation_token } = args;
157
159
 
@@ -238,22 +240,25 @@ export async function handleCreateWithdrawal(
238
240
  payload,
239
241
  );
240
242
 
241
- return {
242
- content: [{ type: "text", text: JSON.stringify(normalizeWithdrawal(data.withdrawal), null, 2) }],
243
- };
243
+ const result = { content: [{ type: "text" as const, text: JSON.stringify(normalizeWithdrawal(data.withdrawal), null, 2) }] };
244
+ logAudit({ ts: new Date().toISOString(), tool: "create_withdrawal", transport, args_summary: { currency, amount, type: hasAddress ? "crypto" : "fiat" }, success: true });
245
+ return result;
244
246
  } catch (err) {
245
247
  const msg =
246
248
  err instanceof BudaApiError
247
- ? { error: err.message, code: err.status, path: err.path }
249
+ ? { error: err.message, code: err.status }
248
250
  : { error: String(err), code: "UNKNOWN" };
249
- return {
250
- content: [{ type: "text", text: JSON.stringify(msg) }],
251
- isError: true,
252
- };
251
+ const result = { content: [{ type: "text" as const, text: JSON.stringify(msg) }], isError: true as const };
252
+ logAudit({ ts: new Date().toISOString(), tool: "create_withdrawal", transport, args_summary: { currency, amount, type: hasAddress ? "crypto" : "fiat" }, success: false, error_code: msg.code });
253
+ return result;
253
254
  }
254
255
  }
255
256
 
256
- export function register(server: McpServer, client: BudaClient): void {
257
+ export function register(
258
+ server: McpServer,
259
+ client: BudaClient,
260
+ transport: "http" | "stdio" = "stdio",
261
+ ): void {
257
262
  server.tool(
258
263
  getWithdrawalHistoryToolSchema.name,
259
264
  getWithdrawalHistoryToolSchema.description,
@@ -282,6 +287,6 @@ export function register(server: McpServer, client: BudaClient): void {
282
287
  .string()
283
288
  .describe("Safety confirmation. Must equal exactly 'CONFIRM' (case-sensitive) to execute."),
284
289
  },
285
- (args) => handleCreateWithdrawal(args, client),
290
+ (args) => handleCreateWithdrawal(args, client, transport),
286
291
  );
287
292
  }
package/src/utils.ts CHANGED
@@ -1,5 +1,38 @@
1
+ import { timingSafeEqual } from "crypto";
1
2
  import type { Amount, OhlcvCandle } from "./types.js";
2
3
 
4
+ /**
5
+ * Constant-time string comparison to prevent timing attacks on bearer tokens.
6
+ */
7
+ export function safeTokenEqual(a: string, b: string): boolean {
8
+ const aBuf = Buffer.from(a);
9
+ const bBuf = Buffer.from(b);
10
+ if (aBuf.length !== bBuf.length) return false;
11
+ return timingSafeEqual(aBuf, bBuf);
12
+ }
13
+
14
+ /**
15
+ * Parses a raw string (from an environment variable) as an integer within [min, max].
16
+ * Returns the fallback when raw is undefined.
17
+ * Throws a descriptive Error if the value is non-numeric or out of range.
18
+ */
19
+ export function parseEnvInt(
20
+ raw: string | undefined,
21
+ fallback: number,
22
+ min: number,
23
+ max: number,
24
+ name: string,
25
+ ): number {
26
+ if (raw === undefined) return fallback;
27
+ const n = parseInt(raw, 10);
28
+ if (isNaN(n) || n < min || n > max) {
29
+ throw new Error(
30
+ `[buda-mcp] Invalid ${name} "${raw}". Must be an integer between ${min} and ${max}.`,
31
+ );
32
+ }
33
+ return n;
34
+ }
35
+
3
36
  /**
4
37
  * Flattens a Buda API Amount tuple [value_string, currency] into a typed object.
5
38
  * All numeric strings are cast to float via parseFloat.