@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.
Files changed (103) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/PUBLISH_CHECKLIST.md +39 -32
  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/http.js +25 -3
  7. package/dist/tools/account.js +1 -1
  8. package/dist/tools/arbitrage.js +1 -1
  9. package/dist/tools/balance.js +1 -1
  10. package/dist/tools/balances.js +1 -1
  11. package/dist/tools/banks.js +1 -1
  12. package/dist/tools/batch_orders.d.ts +1 -1
  13. package/dist/tools/batch_orders.d.ts.map +1 -1
  14. package/dist/tools/batch_orders.js +12 -2
  15. package/dist/tools/cancel_all_orders.d.ts +1 -1
  16. package/dist/tools/cancel_all_orders.d.ts.map +1 -1
  17. package/dist/tools/cancel_all_orders.js +10 -13
  18. package/dist/tools/cancel_order.d.ts +1 -1
  19. package/dist/tools/cancel_order.d.ts.map +1 -1
  20. package/dist/tools/cancel_order.js +9 -9
  21. package/dist/tools/cancel_order_by_client_id.d.ts +1 -1
  22. package/dist/tools/cancel_order_by_client_id.d.ts.map +1 -1
  23. package/dist/tools/cancel_order_by_client_id.js +9 -9
  24. package/dist/tools/compare_markets.d.ts +9 -0
  25. package/dist/tools/compare_markets.d.ts.map +1 -1
  26. package/dist/tools/compare_markets.js +63 -53
  27. package/dist/tools/dead_mans_switch.d.ts +1 -1
  28. package/dist/tools/dead_mans_switch.d.ts.map +1 -1
  29. package/dist/tools/dead_mans_switch.js +35 -3
  30. package/dist/tools/deposits.js +2 -2
  31. package/dist/tools/fees.js +1 -1
  32. package/dist/tools/lightning.d.ts +1 -1
  33. package/dist/tools/lightning.d.ts.map +1 -1
  34. package/dist/tools/lightning.js +11 -9
  35. package/dist/tools/market_sentiment.js +1 -1
  36. package/dist/tools/market_summary.js +1 -1
  37. package/dist/tools/markets.js +1 -1
  38. package/dist/tools/order_lookup.js +2 -2
  39. package/dist/tools/orderbook.js +1 -1
  40. package/dist/tools/orders.js +1 -1
  41. package/dist/tools/place_order.d.ts +1 -1
  42. package/dist/tools/place_order.d.ts.map +1 -1
  43. package/dist/tools/place_order.js +22 -4
  44. package/dist/tools/price_history.js +1 -1
  45. package/dist/tools/quotation.js +1 -1
  46. package/dist/tools/receive_addresses.d.ts +1 -1
  47. package/dist/tools/receive_addresses.d.ts.map +1 -1
  48. package/dist/tools/receive_addresses.js +11 -11
  49. package/dist/tools/remittance_recipients.js +2 -2
  50. package/dist/tools/remittances.d.ts +2 -2
  51. package/dist/tools/remittances.d.ts.map +1 -1
  52. package/dist/tools/remittances.js +19 -20
  53. package/dist/tools/simulate_order.js +1 -1
  54. package/dist/tools/spread.js +1 -1
  55. package/dist/tools/technical_indicators.js +1 -1
  56. package/dist/tools/ticker.js +1 -1
  57. package/dist/tools/trades.js +1 -1
  58. package/dist/tools/volume.js +1 -1
  59. package/dist/tools/withdrawals.d.ts +1 -1
  60. package/dist/tools/withdrawals.d.ts.map +1 -1
  61. package/dist/tools/withdrawals.js +10 -10
  62. package/dist/utils.d.ts +10 -0
  63. package/dist/utils.d.ts.map +1 -1
  64. package/dist/utils.js +25 -0
  65. package/package.json +1 -1
  66. package/server.json +2 -2
  67. package/src/audit.ts +24 -0
  68. package/src/http.ts +27 -3
  69. package/src/tools/account.ts +1 -1
  70. package/src/tools/arbitrage.ts +1 -1
  71. package/src/tools/balance.ts +1 -1
  72. package/src/tools/balances.ts +1 -1
  73. package/src/tools/banks.ts +1 -1
  74. package/src/tools/batch_orders.ts +12 -1
  75. package/src/tools/cancel_all_orders.ts +10 -12
  76. package/src/tools/cancel_order.ts +9 -8
  77. package/src/tools/cancel_order_by_client_id.ts +9 -8
  78. package/src/tools/compare_markets.ts +78 -61
  79. package/src/tools/dead_mans_switch.ts +37 -2
  80. package/src/tools/deposits.ts +2 -2
  81. package/src/tools/fees.ts +1 -1
  82. package/src/tools/lightning.ts +12 -9
  83. package/src/tools/market_sentiment.ts +1 -1
  84. package/src/tools/market_summary.ts +1 -1
  85. package/src/tools/markets.ts +1 -1
  86. package/src/tools/order_lookup.ts +2 -2
  87. package/src/tools/orderbook.ts +1 -1
  88. package/src/tools/orders.ts +1 -1
  89. package/src/tools/place_order.ts +24 -5
  90. package/src/tools/price_history.ts +1 -1
  91. package/src/tools/quotation.ts +1 -1
  92. package/src/tools/receive_addresses.ts +11 -10
  93. package/src/tools/remittance_recipients.ts +2 -2
  94. package/src/tools/remittances.ts +19 -18
  95. package/src/tools/simulate_order.ts +1 -1
  96. package/src/tools/spread.ts +1 -1
  97. package/src/tools/technical_indicators.ts +1 -1
  98. package/src/tools/ticker.ts +1 -1
  99. package/src/tools/trades.ts +1 -1
  100. package/src/tools/volume.ts +1 -1
  101. package/src/tools/withdrawals.ts +10 -9
  102. package/src/utils.ts +33 -0
  103. package/test/unit.ts +362 -4
@@ -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 { MemoryCache, CACHE_TTL } from "../cache.js";
5
+ import { validateCurrency } from "../validation.js";
5
6
  import type { AllTickersResponse } from "../types.js";
6
7
 
7
8
  export const toolSchema = {
@@ -24,6 +25,82 @@ export const toolSchema = {
24
25
  },
25
26
  };
26
27
 
28
+ export async function handleCompareMarkets(
29
+ args: { base_currency: string },
30
+ client: BudaClient,
31
+ cache: MemoryCache,
32
+ ): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
33
+ const { base_currency } = args;
34
+
35
+ const currencyError = validateCurrency(base_currency);
36
+ if (currencyError) {
37
+ return {
38
+ content: [{ type: "text", text: JSON.stringify({ error: currencyError, code: "INVALID_CURRENCY" }) }],
39
+ isError: true,
40
+ };
41
+ }
42
+
43
+ try {
44
+ const base = base_currency.toUpperCase();
45
+ const data = await cache.getOrFetch<AllTickersResponse>(
46
+ "tickers:all",
47
+ CACHE_TTL.TICKER,
48
+ () => client.get<AllTickersResponse>("/tickers"),
49
+ );
50
+
51
+ const matching = data.tickers.filter((t) => {
52
+ const [tickerBase] = t.market_id.split("-");
53
+ return tickerBase === base;
54
+ });
55
+
56
+ if (matching.length === 0) {
57
+ return {
58
+ content: [
59
+ {
60
+ type: "text",
61
+ text: JSON.stringify({
62
+ error: `No markets found for base currency '${base}'.`,
63
+ code: "NOT_FOUND",
64
+ }),
65
+ },
66
+ ],
67
+ isError: true,
68
+ };
69
+ }
70
+
71
+ const result = {
72
+ base_currency: base,
73
+ markets: matching.map((t) => ({
74
+ market_id: t.market_id,
75
+ last_price: parseFloat(t.last_price[0]),
76
+ last_price_currency: t.last_price[1],
77
+ best_bid: t.max_bid ? parseFloat(t.max_bid[0]) : null,
78
+ best_ask: t.min_ask ? parseFloat(t.min_ask[0]) : null,
79
+ volume_24h: t.volume ? parseFloat(t.volume[0]) : null,
80
+ price_change_24h: t.price_variation_24h
81
+ ? parseFloat((parseFloat(t.price_variation_24h) * 100).toFixed(4))
82
+ : null,
83
+ price_change_7d: t.price_variation_7d
84
+ ? parseFloat((parseFloat(t.price_variation_7d) * 100).toFixed(4))
85
+ : null,
86
+ })),
87
+ };
88
+
89
+ return {
90
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
91
+ };
92
+ } catch (err) {
93
+ const msg =
94
+ err instanceof BudaApiError
95
+ ? { error: err.message, code: err.status }
96
+ : { error: String(err), code: "UNKNOWN" };
97
+ return {
98
+ content: [{ type: "text", text: JSON.stringify(msg) }],
99
+ isError: true,
100
+ };
101
+ }
102
+ }
103
+
27
104
  export function register(server: McpServer, client: BudaClient, cache: MemoryCache): void {
28
105
  server.tool(
29
106
  toolSchema.name,
@@ -35,66 +112,6 @@ export function register(server: McpServer, client: BudaClient, cache: MemoryCac
35
112
  "Base currency to compare across all available markets (e.g. 'BTC', 'ETH', 'XRP').",
36
113
  ),
37
114
  },
38
- async ({ base_currency }) => {
39
- try {
40
- const base = base_currency.toUpperCase();
41
- const data = await cache.getOrFetch<AllTickersResponse>(
42
- "tickers:all",
43
- CACHE_TTL.TICKER,
44
- () => client.get<AllTickersResponse>("/tickers"),
45
- );
46
-
47
- const matching = data.tickers.filter((t) => {
48
- const [tickerBase] = t.market_id.split("-");
49
- return tickerBase === base;
50
- });
51
-
52
- if (matching.length === 0) {
53
- return {
54
- content: [
55
- {
56
- type: "text",
57
- text: JSON.stringify({
58
- error: `No markets found for base currency '${base}'.`,
59
- code: "NOT_FOUND",
60
- }),
61
- },
62
- ],
63
- isError: true,
64
- };
65
- }
66
-
67
- const result = {
68
- base_currency: base,
69
- markets: matching.map((t) => ({
70
- market_id: t.market_id,
71
- last_price: parseFloat(t.last_price[0]),
72
- last_price_currency: t.last_price[1],
73
- best_bid: t.max_bid ? parseFloat(t.max_bid[0]) : null,
74
- best_ask: t.min_ask ? parseFloat(t.min_ask[0]) : null,
75
- volume_24h: t.volume ? parseFloat(t.volume[0]) : null,
76
- price_change_24h: t.price_variation_24h
77
- ? parseFloat((parseFloat(t.price_variation_24h) * 100).toFixed(4))
78
- : null,
79
- price_change_7d: t.price_variation_7d
80
- ? parseFloat((parseFloat(t.price_variation_7d) * 100).toFixed(4))
81
- : null,
82
- })),
83
- };
84
-
85
- return {
86
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
87
- };
88
- } catch (err) {
89
- const msg =
90
- err instanceof BudaApiError
91
- ? { error: err.message, code: err.status, path: err.path }
92
- : { error: String(err), code: "UNKNOWN" };
93
- return {
94
- content: [{ type: "text", text: JSON.stringify(msg) }],
95
- isError: true,
96
- };
97
- }
98
- },
115
+ (args) => handleCompareMarkets(args, client, cache),
99
116
  );
100
117
  }
@@ -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 { OrdersResponse, OrderResponse } from "../types.js";
6
7
 
7
8
  // ---- Module-level timer state (persists across HTTP requests / tool invocations) ----
@@ -125,6 +126,7 @@ type ScheduleArgs = {
125
126
  export async function handleScheduleCancelAll(
126
127
  args: ScheduleArgs,
127
128
  client: BudaClient,
129
+ transport: "http" | "stdio" = "stdio",
128
130
  ): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
129
131
  const { market_id, ttl_seconds, confirmation_token } = args;
130
132
 
@@ -173,6 +175,7 @@ export async function handleScheduleCancelAll(
173
175
  const id = market_id.toLowerCase();
174
176
  const entry = armTimer(id, ttl_seconds, client);
175
177
 
178
+ logAudit({ ts: new Date().toISOString(), tool: "schedule_cancel_all", transport, args_summary: { market_id: market_id.toUpperCase(), ttl_seconds }, success: true });
176
179
  return {
177
180
  content: [
178
181
  {
@@ -334,7 +337,23 @@ export function register(
334
337
  .string()
335
338
  .describe("Market ID whose timer should be renewed (e.g. 'BTC-CLP')."),
336
339
  },
337
- (args) => handleRenewCancelTimer(args, client),
340
+ (args) => {
341
+ if (transport === "http") {
342
+ return Promise.resolve({
343
+ content: [{
344
+ type: "text" as const,
345
+ text: JSON.stringify({
346
+ error:
347
+ "renew_cancel_timer is not available on the HTTP transport. " +
348
+ "Timer state is process-local and only meaningful in stdio mode on a persistent local process.",
349
+ code: "TRANSPORT_NOT_SUPPORTED",
350
+ }),
351
+ }],
352
+ isError: true,
353
+ });
354
+ }
355
+ return Promise.resolve(handleRenewCancelTimer(args, client));
356
+ },
338
357
  );
339
358
 
340
359
  server.tool(
@@ -345,6 +364,22 @@ export function register(
345
364
  .string()
346
365
  .describe("Market ID whose timer should be disarmed (e.g. 'BTC-CLP')."),
347
366
  },
348
- (args) => handleDisarmCancelTimer(args),
367
+ (args) => {
368
+ if (transport === "http") {
369
+ return Promise.resolve({
370
+ content: [{
371
+ type: "text" as const,
372
+ text: JSON.stringify({
373
+ error:
374
+ "disarm_cancel_timer is not available on the HTTP transport. " +
375
+ "Timer state is process-local and only meaningful in stdio mode on a persistent local process.",
376
+ code: "TRANSPORT_NOT_SUPPORTED",
377
+ }),
378
+ }],
379
+ isError: true,
380
+ });
381
+ }
382
+ return Promise.resolve(handleDisarmCancelTimer(args));
383
+ },
349
384
  );
350
385
  }
@@ -104,7 +104,7 @@ export async function handleGetDepositHistory(
104
104
  } catch (err) {
105
105
  const msg =
106
106
  err instanceof BudaApiError
107
- ? { error: err.message, code: err.status, path: err.path }
107
+ ? { error: err.message, code: err.status }
108
108
  : { error: String(err), code: "UNKNOWN" };
109
109
  return {
110
110
  content: [{ type: "text", text: JSON.stringify(msg) }],
@@ -189,7 +189,7 @@ export async function handleCreateFiatDeposit(
189
189
  } catch (err) {
190
190
  const msg =
191
191
  err instanceof BudaApiError
192
- ? { error: err.message, code: err.status, path: err.path }
192
+ ? { error: err.message, code: err.status }
193
193
  : { error: String(err), code: "UNKNOWN" };
194
194
  return {
195
195
  content: [{ type: "text", text: JSON.stringify(msg) }],
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) }],
@@ -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,18 +245,35 @@ 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
 
@@ -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,18 +195,17 @@ 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
 
@@ -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,18 +266,17 @@ 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
 
@@ -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) }],