@guiie/buda-mcp 1.4.2 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/.cursor/rules/marketplace-docs-sync.mdc +32 -0
  2. package/CHANGELOG.md +79 -0
  3. package/PUBLISH_CHECKLIST.md +40 -88
  4. package/README.md +446 -78
  5. package/dist/cache.d.ts +1 -0
  6. package/dist/cache.d.ts.map +1 -1
  7. package/dist/cache.js +1 -0
  8. package/dist/client.d.ts +2 -0
  9. package/dist/client.d.ts.map +1 -1
  10. package/dist/client.js +18 -1
  11. package/dist/http.js +97 -6
  12. package/dist/index.js +42 -3
  13. package/dist/tools/account.d.ts +19 -0
  14. package/dist/tools/account.d.ts.map +1 -0
  15. package/dist/tools/account.js +49 -0
  16. package/dist/tools/balance.d.ts +29 -0
  17. package/dist/tools/balance.d.ts.map +1 -0
  18. package/dist/tools/balance.js +72 -0
  19. package/dist/tools/banks.d.ts +28 -0
  20. package/dist/tools/banks.d.ts.map +1 -0
  21. package/dist/tools/banks.js +68 -0
  22. package/dist/tools/batch_orders.d.ts +82 -0
  23. package/dist/tools/batch_orders.d.ts.map +1 -0
  24. package/dist/tools/batch_orders.js +188 -0
  25. package/dist/tools/cancel_all_orders.d.ts +34 -0
  26. package/dist/tools/cancel_all_orders.d.ts.map +1 -0
  27. package/dist/tools/cancel_all_orders.js +89 -0
  28. package/dist/tools/cancel_order.js +1 -1
  29. package/dist/tools/cancel_order_by_client_id.d.ts +34 -0
  30. package/dist/tools/cancel_order_by_client_id.d.ts.map +1 -0
  31. package/dist/tools/cancel_order_by_client_id.js +102 -0
  32. package/dist/tools/dead_mans_switch.d.ts +1 -1
  33. package/dist/tools/dead_mans_switch.d.ts.map +1 -1
  34. package/dist/tools/dead_mans_switch.js +33 -3
  35. package/dist/tools/deposits.d.ts +83 -0
  36. package/dist/tools/deposits.d.ts.map +1 -0
  37. package/dist/tools/deposits.js +174 -0
  38. package/dist/tools/fees.d.ts +34 -0
  39. package/dist/tools/fees.d.ts.map +1 -0
  40. package/dist/tools/fees.js +72 -0
  41. package/dist/tools/lightning.d.ts +68 -0
  42. package/dist/tools/lightning.d.ts.map +1 -0
  43. package/dist/tools/lightning.js +185 -0
  44. package/dist/tools/order_lookup.d.ts +50 -0
  45. package/dist/tools/order_lookup.d.ts.map +1 -0
  46. package/dist/tools/order_lookup.js +112 -0
  47. package/dist/tools/place_order.d.ts +30 -0
  48. package/dist/tools/place_order.d.ts.map +1 -1
  49. package/dist/tools/place_order.js +131 -2
  50. package/dist/tools/quotation.d.ts +44 -0
  51. package/dist/tools/quotation.d.ts.map +1 -0
  52. package/dist/tools/quotation.js +99 -0
  53. package/dist/tools/receive_addresses.d.ts +83 -0
  54. package/dist/tools/receive_addresses.d.ts.map +1 -0
  55. package/dist/tools/receive_addresses.js +185 -0
  56. package/dist/tools/remittance_recipients.d.ts +54 -0
  57. package/dist/tools/remittance_recipients.d.ts.map +1 -0
  58. package/dist/tools/remittance_recipients.js +106 -0
  59. package/dist/tools/remittances.d.ts +120 -0
  60. package/dist/tools/remittances.d.ts.map +1 -0
  61. package/dist/tools/remittances.js +261 -0
  62. package/dist/tools/simulate_order.d.ts.map +1 -1
  63. package/dist/tools/simulate_order.js +2 -1
  64. package/dist/tools/technical_indicators.d.ts.map +1 -1
  65. package/dist/tools/technical_indicators.js +2 -1
  66. package/dist/tools/withdrawals.d.ts +93 -0
  67. package/dist/tools/withdrawals.d.ts.map +1 -0
  68. package/dist/tools/withdrawals.js +225 -0
  69. package/dist/types.d.ts +155 -0
  70. package/dist/types.d.ts.map +1 -1
  71. package/dist/utils.d.ts.map +1 -1
  72. package/dist/utils.js +4 -1
  73. package/dist/validation.d.ts +11 -0
  74. package/dist/validation.d.ts.map +1 -1
  75. package/dist/validation.js +38 -0
  76. package/dist/version.d.ts.map +1 -1
  77. package/dist/version.js +8 -1
  78. package/marketplace/README.md +1 -1
  79. package/marketplace/claude-listing.md +101 -2
  80. package/marketplace/gemini-tools.json +478 -1
  81. package/marketplace/openapi.yaml +160 -1
  82. package/package.json +2 -1
  83. package/server.json +2 -2
  84. package/src/cache.ts +1 -0
  85. package/src/client.ts +23 -1
  86. package/src/http.ts +105 -6
  87. package/src/index.ts +40 -3
  88. package/src/tools/account.ts +66 -0
  89. package/src/tools/balance.ts +94 -0
  90. package/src/tools/banks.ts +94 -0
  91. package/src/tools/batch_orders.ts +238 -0
  92. package/src/tools/cancel_all_orders.ts +117 -0
  93. package/src/tools/cancel_order.ts +1 -1
  94. package/src/tools/cancel_order_by_client_id.ts +132 -0
  95. package/src/tools/dead_mans_switch.ts +39 -3
  96. package/src/tools/deposits.ts +230 -0
  97. package/src/tools/fees.ts +91 -0
  98. package/src/tools/lightning.ts +247 -0
  99. package/src/tools/order_lookup.ts +139 -0
  100. package/src/tools/place_order.ts +151 -2
  101. package/src/tools/quotation.ts +124 -0
  102. package/src/tools/receive_addresses.ts +242 -0
  103. package/src/tools/remittance_recipients.ts +139 -0
  104. package/src/tools/remittances.ts +325 -0
  105. package/src/tools/simulate_order.ts +1 -0
  106. package/src/tools/technical_indicators.ts +2 -1
  107. package/src/tools/withdrawals.ts +287 -0
  108. package/src/types.ts +210 -0
  109. package/src/utils.ts +3 -1
  110. package/src/validation.ts +45 -0
  111. package/src/version.ts +11 -3
  112. package/test/run-all.ts +16 -0
  113. package/test/unit.ts +2149 -1
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAKA,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,QAAQ,CAAuC;IAEjD,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAyBtF,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAI7B,KAAK,IAAI,IAAI;CAGd;AAED,eAAO,MAAM,SAAS;;;;CAIZ,CAAC;AAEX,eAAO,MAAM,KAAK,aAAoB,CAAC"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAKA,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,QAAQ,CAAuC;IAEjD,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAyBtF,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAI7B,KAAK,IAAI,IAAI;CAGd;AAED,eAAO,MAAM,SAAS;;;;;CAKZ,CAAC;AAEX,eAAO,MAAM,KAAK,aAAoB,CAAC"}
package/dist/cache.js CHANGED
@@ -34,6 +34,7 @@ export const CACHE_TTL = {
34
34
  MARKETS: 60_000,
35
35
  TICKER: 5_000,
36
36
  ORDERBOOK: 3_000,
37
+ BANKS: 60_000,
37
38
  };
38
39
  export const cache = new MemoryCache();
39
40
  //# sourceMappingURL=cache.js.map
package/dist/client.d.ts CHANGED
@@ -10,6 +10,7 @@ export declare class BudaClient {
10
10
  private readonly apiSecret;
11
11
  constructor(baseUrl?: string, apiKey?: string, apiSecret?: string);
12
12
  hasAuth(): boolean;
13
+ private _nonceCounter;
13
14
  private nonce;
14
15
  private sign;
15
16
  private authHeaders;
@@ -29,5 +30,6 @@ export declare class BudaClient {
29
30
  get<T>(path: string, params?: Record<string, string | number>): Promise<T>;
30
31
  post<T>(path: string, payload: unknown): Promise<T>;
31
32
  put<T>(path: string, payload: unknown): Promise<T>;
33
+ delete<T>(path: string, params?: Record<string, string | number>): Promise<T>;
32
34
  }
33
35
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAKA,qBAAa,YAAa,SAAQ,KAAK;aAEnB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM;aAEZ,YAAY,CAAC,EAAE,MAAM;gBAHrB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAC5B,OAAO,EAAE,MAAM,EACC,YAAY,CAAC,EAAE,MAAM,YAAA;CAKxC;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;gBAG7C,OAAO,GAAE,MAAiB,EAC1B,MAAM,CAAC,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM;IAOpB,OAAO,IAAI,OAAO;IAIlB,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,IAAI;IASZ,OAAO,CAAC,WAAW;IAWnB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAOzB;;;;OAIG;YACW,cAAc;YA2Bd,cAAc;IActB,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAoB1E,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAmBnD,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;CAkBzD"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAKA,qBAAa,YAAa,SAAQ,KAAK;aAEnB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM;aAEZ,YAAY,CAAC,EAAE,MAAM;gBAHrB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAC5B,OAAO,EAAE,MAAM,EACC,YAAY,CAAC,EAAE,MAAM,YAAA;CAKxC;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;gBAG7C,OAAO,GAAE,MAAiB,EAC1B,MAAM,CAAC,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM;IAOpB,OAAO,IAAI,OAAO;IAIlB,OAAO,CAAC,aAAa,CAAK;IAE1B,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,IAAI;IASZ,OAAO,CAAC,WAAW;IAWnB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAOzB;;;;OAIG;YACW,cAAc;YA2Bd,cAAc;IActB,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAoB1E,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAmBnD,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAmBlD,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAmBpF"}
package/dist/client.js CHANGED
@@ -25,8 +25,9 @@ export class BudaClient {
25
25
  hasAuth() {
26
26
  return Boolean(this.apiKey && this.apiSecret);
27
27
  }
28
+ _nonceCounter = 0;
28
29
  nonce() {
29
- return String(Math.floor(Date.now() * 1000));
30
+ return String(Date.now() * 1000 + (this._nonceCounter++ % 1000));
30
31
  }
31
32
  sign(method, pathWithQuery, body, nonce) {
32
33
  const encodedBody = body ? Buffer.from(body).toString("base64") : "";
@@ -135,5 +136,21 @@ export class BudaClient {
135
136
  const response = await this.fetchWithRetry(url, { method: "PUT", headers, body: bodyStr }, path);
136
137
  return this.handleResponse(response, path);
137
138
  }
139
+ async delete(path, params) {
140
+ const url = new URL(`${this.baseUrl}${path}.json`);
141
+ if (params) {
142
+ for (const [key, value] of Object.entries(params)) {
143
+ url.searchParams.set(key, String(value));
144
+ }
145
+ }
146
+ const urlPath = url.pathname + url.search;
147
+ const headers = {
148
+ Accept: "application/json",
149
+ "User-Agent": `buda-mcp/${VERSION}`,
150
+ ...this.authHeaders("DELETE", urlPath),
151
+ };
152
+ const response = await this.fetchWithRetry(url, { method: "DELETE", headers }, path);
153
+ return this.handleResponse(response, path);
154
+ }
138
155
  }
139
156
  //# sourceMappingURL=client.js.map
package/dist/http.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import express from "express";
2
+ import rateLimit from "express-rate-limit";
2
3
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
3
4
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
5
  import { BudaClient } from "./client.js";
5
6
  import { MemoryCache, CACHE_TTL } from "./cache.js";
6
7
  import { VERSION } from "./version.js";
8
+ import { validateMarketId } from "./validation.js";
7
9
  import * as markets from "./tools/markets.js";
8
10
  import * as ticker from "./tools/ticker.js";
9
11
  import * as orderbook from "./tools/orderbook.js";
@@ -23,6 +25,21 @@ import * as positionSize from "./tools/calculate_position_size.js";
23
25
  import * as marketSentiment from "./tools/market_sentiment.js";
24
26
  import * as technicalIndicators from "./tools/technical_indicators.js";
25
27
  import * as deadMansSwitch from "./tools/dead_mans_switch.js";
28
+ import * as banks from "./tools/banks.js";
29
+ import * as account from "./tools/account.js";
30
+ import * as balance from "./tools/balance.js";
31
+ import * as orderLookup from "./tools/order_lookup.js";
32
+ import * as networkFees from "./tools/fees.js";
33
+ import * as deposits from "./tools/deposits.js";
34
+ import * as withdrawals from "./tools/withdrawals.js";
35
+ import * as receiveAddresses from "./tools/receive_addresses.js";
36
+ import * as remittances from "./tools/remittances.js";
37
+ import * as remittanceRecipients from "./tools/remittance_recipients.js";
38
+ import * as quotation from "./tools/quotation.js";
39
+ import * as cancelAllOrders from "./tools/cancel_all_orders.js";
40
+ import * as cancelOrderByClientId from "./tools/cancel_order_by_client_id.js";
41
+ import * as batchOrders from "./tools/batch_orders.js";
42
+ import * as lightning from "./tools/lightning.js";
26
43
  import { handleMarketSummary } from "./tools/market_summary.js";
27
44
  const PORT = parseInt(process.env.PORT ?? "3000", 10);
28
45
  const client = new BudaClient(undefined, process.env.BUDA_API_KEY, process.env.BUDA_API_SECRET);
@@ -41,9 +58,11 @@ const PUBLIC_TOOL_SCHEMAS = [
41
58
  arbitrage.toolSchema,
42
59
  marketSummary.toolSchema,
43
60
  simulateOrder.toolSchema,
61
+ quotation.toolSchema,
44
62
  positionSize.toolSchema,
45
63
  marketSentiment.toolSchema,
46
64
  technicalIndicators.toolSchema,
65
+ banks.toolSchema,
47
66
  ];
48
67
  const AUTH_TOOL_SCHEMAS = [
49
68
  balances.toolSchema,
@@ -53,6 +72,29 @@ const AUTH_TOOL_SCHEMAS = [
53
72
  deadMansSwitch.toolSchema,
54
73
  deadMansSwitch.renewToolSchema,
55
74
  deadMansSwitch.disarmToolSchema,
75
+ account.toolSchema,
76
+ balance.toolSchema,
77
+ orderLookup.getOrderToolSchema,
78
+ orderLookup.getOrderByClientIdToolSchema,
79
+ networkFees.toolSchema,
80
+ deposits.getDepositHistoryToolSchema,
81
+ withdrawals.getWithdrawalHistoryToolSchema,
82
+ receiveAddresses.listReceiveAddressesToolSchema,
83
+ receiveAddresses.getReceiveAddressToolSchema,
84
+ remittances.listRemittancesToolSchema,
85
+ remittances.getRemittanceToolSchema,
86
+ remittances.quoteRemittanceToolSchema,
87
+ remittances.acceptRemittanceQuoteToolSchema,
88
+ remittanceRecipients.listToolSchema,
89
+ remittanceRecipients.getToolSchema,
90
+ receiveAddresses.createReceiveAddressToolSchema,
91
+ cancelAllOrders.toolSchema,
92
+ cancelOrderByClientId.toolSchema,
93
+ batchOrders.toolSchema,
94
+ withdrawals.createWithdrawalToolSchema,
95
+ deposits.createFiatDepositToolSchema,
96
+ lightning.lightningWithdrawalToolSchema,
97
+ lightning.createLightningInvoiceToolSchema,
56
98
  ];
57
99
  function createServer() {
58
100
  const server = new McpServer({ name: "buda-mcp", version: VERSION });
@@ -69,15 +111,30 @@ function createServer() {
69
111
  arbitrage.register(server, client, reqCache);
70
112
  marketSummary.register(server, client, reqCache);
71
113
  simulateOrder.register(server, client, reqCache);
114
+ quotation.register(server, client);
72
115
  positionSize.register(server);
73
116
  marketSentiment.register(server, client, reqCache);
74
117
  technicalIndicators.register(server, client);
118
+ banks.register(server, client, reqCache);
75
119
  if (authEnabled) {
76
120
  balances.register(server, client);
77
121
  orders.register(server, client);
78
122
  placeOrder.register(server, client);
79
123
  cancelOrder.register(server, client);
80
- deadMansSwitch.register(server, client);
124
+ deadMansSwitch.register(server, client, "http");
125
+ account.register(server, client);
126
+ balance.register(server, client);
127
+ orderLookup.register(server, client);
128
+ networkFees.register(server, client);
129
+ deposits.register(server, client);
130
+ withdrawals.register(server, client);
131
+ receiveAddresses.register(server, client);
132
+ remittances.register(server, client);
133
+ remittanceRecipients.register(server, client);
134
+ cancelAllOrders.register(server, client);
135
+ cancelOrderByClientId.register(server, client);
136
+ batchOrders.register(server, client);
137
+ lightning.register(server, client);
81
138
  }
82
139
  // MCP Resources
83
140
  server.resource("buda-markets", "buda://markets", async (uri) => {
@@ -93,7 +150,11 @@ function createServer() {
93
150
  };
94
151
  });
95
152
  server.resource("buda-ticker", new ResourceTemplate("buda://ticker/{market}", { list: undefined }), async (uri, params) => {
96
- const marketId = params.market.toLowerCase();
153
+ const raw = params.market;
154
+ const validationError = validateMarketId(raw);
155
+ if (validationError)
156
+ throw new Error(validationError);
157
+ const marketId = raw.toLowerCase();
97
158
  const data = await reqCache.getOrFetch(`ticker:${marketId}`, CACHE_TTL.TICKER, () => client.get(`/markets/${marketId}/ticker`));
98
159
  return {
99
160
  contents: [
@@ -106,9 +167,13 @@ function createServer() {
106
167
  };
107
168
  });
108
169
  server.resource("buda-summary", new ResourceTemplate("buda://summary/{market}", { list: undefined }), async (uri, params) => {
109
- const marketId = params.market.toUpperCase();
170
+ const raw = params.market;
171
+ const validationError = validateMarketId(raw);
172
+ if (validationError)
173
+ throw new Error(validationError);
174
+ const marketId = raw.toUpperCase();
110
175
  const result = await handleMarketSummary({ market_id: marketId }, client, reqCache);
111
- const text = result.content[0].text;
176
+ const text = result.content[0]?.text ?? JSON.stringify({ error: "No content returned" });
112
177
  return {
113
178
  contents: [
114
179
  {
@@ -123,6 +188,32 @@ function createServer() {
123
188
  }
124
189
  const app = express();
125
190
  app.use(express.json());
191
+ const MCP_AUTH_TOKEN = process.env.MCP_AUTH_TOKEN;
192
+ if (authEnabled && !MCP_AUTH_TOKEN) {
193
+ console.error("[buda-mcp] FATAL: BUDA_API_KEY/BUDA_API_SECRET are set but MCP_AUTH_TOKEN is not.\n" +
194
+ " The /mcp endpoint would be publicly accessible with full account access.\n" +
195
+ " Set MCP_AUTH_TOKEN to a long random secret, or run in stdio mode instead.");
196
+ process.exit(1);
197
+ }
198
+ const mcpRateLimiter = rateLimit({
199
+ windowMs: 60_000,
200
+ max: parseInt(process.env.MCP_RATE_LIMIT ?? "120", 10),
201
+ standardHeaders: true,
202
+ legacyHeaders: false,
203
+ message: { error: "Too many requests. Retry after 60 seconds.", code: "RATE_LIMITED" },
204
+ });
205
+ function mcpAuthMiddleware(req, res, next) {
206
+ if (!MCP_AUTH_TOKEN) {
207
+ next();
208
+ return;
209
+ }
210
+ const auth = req.headers.authorization ?? "";
211
+ if (auth !== `Bearer ${MCP_AUTH_TOKEN}`) {
212
+ res.status(401).json({ error: "Unauthorized" });
213
+ return;
214
+ }
215
+ next();
216
+ }
126
217
  // Health check for Railway / uptime monitors
127
218
  app.get("/health", (_req, res) => {
128
219
  res.json({
@@ -148,7 +239,7 @@ app.get("/.well-known/mcp/server-card.json", (_req, res) => {
148
239
  });
149
240
  });
150
241
  // Stateless StreamableHTTP — new server instance per request (no session state needed)
151
- app.post("/mcp", async (req, res) => {
242
+ app.post("/mcp", mcpRateLimiter, mcpAuthMiddleware, async (req, res) => {
152
243
  const transport = new StreamableHTTPServerTransport({
153
244
  sessionIdGenerator: undefined,
154
245
  });
@@ -160,7 +251,7 @@ app.post("/mcp", async (req, res) => {
160
251
  await transport.handleRequest(req, res, req.body);
161
252
  });
162
253
  // SSE upgrade for clients that prefer streaming
163
- app.get("/mcp", async (req, res) => {
254
+ app.get("/mcp", mcpRateLimiter, mcpAuthMiddleware, async (req, res) => {
164
255
  const transport = new StreamableHTTPServerTransport({
165
256
  sessionIdGenerator: undefined,
166
257
  });
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { BudaClient } from "./client.js";
6
6
  import { cache, CACHE_TTL } from "./cache.js";
7
7
  import { VERSION } from "./version.js";
8
+ import { validateMarketId } from "./validation.js";
8
9
  import * as markets from "./tools/markets.js";
9
10
  import * as ticker from "./tools/ticker.js";
10
11
  import * as orderbook from "./tools/orderbook.js";
@@ -24,6 +25,21 @@ import * as positionSize from "./tools/calculate_position_size.js";
24
25
  import * as marketSentiment from "./tools/market_sentiment.js";
25
26
  import * as technicalIndicators from "./tools/technical_indicators.js";
26
27
  import * as deadMansSwitch from "./tools/dead_mans_switch.js";
28
+ import * as banks from "./tools/banks.js";
29
+ import * as account from "./tools/account.js";
30
+ import * as balance from "./tools/balance.js";
31
+ import * as orderLookup from "./tools/order_lookup.js";
32
+ import * as networkFees from "./tools/fees.js";
33
+ import * as deposits from "./tools/deposits.js";
34
+ import * as withdrawals from "./tools/withdrawals.js";
35
+ import * as receiveAddresses from "./tools/receive_addresses.js";
36
+ import * as remittances from "./tools/remittances.js";
37
+ import * as remittanceRecipients from "./tools/remittance_recipients.js";
38
+ import * as quotation from "./tools/quotation.js";
39
+ import * as cancelAllOrders from "./tools/cancel_all_orders.js";
40
+ import * as cancelOrderByClientId from "./tools/cancel_order_by_client_id.js";
41
+ import * as batchOrders from "./tools/batch_orders.js";
42
+ import * as lightning from "./tools/lightning.js";
27
43
  import { handleMarketSummary } from "./tools/market_summary.js";
28
44
  const client = new BudaClient(undefined, process.env.BUDA_API_KEY, process.env.BUDA_API_SECRET);
29
45
  const server = new McpServer({
@@ -42,9 +58,11 @@ priceHistory.register(server, client, cache);
42
58
  arbitrage.register(server, client, cache);
43
59
  marketSummary.register(server, client, cache);
44
60
  simulateOrder.register(server, client, cache);
61
+ quotation.register(server, client);
45
62
  positionSize.register(server);
46
63
  marketSentiment.register(server, client, cache);
47
64
  technicalIndicators.register(server, client);
65
+ banks.register(server, client, cache);
48
66
  // Auth-gated tools — only registered when API credentials are present
49
67
  if (client.hasAuth()) {
50
68
  balances.register(server, client);
@@ -52,6 +70,19 @@ if (client.hasAuth()) {
52
70
  placeOrder.register(server, client);
53
71
  cancelOrder.register(server, client);
54
72
  deadMansSwitch.register(server, client);
73
+ account.register(server, client);
74
+ balance.register(server, client);
75
+ orderLookup.register(server, client);
76
+ networkFees.register(server, client);
77
+ deposits.register(server, client);
78
+ withdrawals.register(server, client);
79
+ receiveAddresses.register(server, client);
80
+ remittances.register(server, client);
81
+ remittanceRecipients.register(server, client);
82
+ cancelAllOrders.register(server, client);
83
+ cancelOrderByClientId.register(server, client);
84
+ batchOrders.register(server, client);
85
+ lightning.register(server, client);
55
86
  }
56
87
  // MCP Resources
57
88
  server.resource("buda-markets", "buda://markets", async (uri) => {
@@ -67,7 +98,11 @@ server.resource("buda-markets", "buda://markets", async (uri) => {
67
98
  };
68
99
  });
69
100
  server.resource("buda-ticker", new ResourceTemplate("buda://ticker/{market}", { list: undefined }), async (uri, params) => {
70
- const marketId = params.market.toLowerCase();
101
+ const raw = params.market;
102
+ const validationError = validateMarketId(raw);
103
+ if (validationError)
104
+ throw new Error(validationError);
105
+ const marketId = raw.toLowerCase();
71
106
  const data = await cache.getOrFetch(`ticker:${marketId}`, CACHE_TTL.TICKER, () => client.get(`/markets/${marketId}/ticker`));
72
107
  return {
73
108
  contents: [
@@ -80,9 +115,13 @@ server.resource("buda-ticker", new ResourceTemplate("buda://ticker/{market}", {
80
115
  };
81
116
  });
82
117
  server.resource("buda-summary", new ResourceTemplate("buda://summary/{market}", { list: undefined }), async (uri, params) => {
83
- const marketId = params.market.toUpperCase();
118
+ const raw = params.market;
119
+ const validationError = validateMarketId(raw);
120
+ if (validationError)
121
+ throw new Error(validationError);
122
+ const marketId = raw.toUpperCase();
84
123
  const result = await handleMarketSummary({ market_id: marketId }, client, cache);
85
- const text = result.content[0].text;
124
+ const text = result.content[0]?.text ?? JSON.stringify({ error: "No content returned" });
86
125
  return {
87
126
  contents: [
88
127
  {
@@ -0,0 +1,19 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BudaClient } from "../client.js";
3
+ export declare const toolSchema: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: {
7
+ type: "object";
8
+ properties: {};
9
+ };
10
+ };
11
+ export declare function handleGetAccountInfo(client: BudaClient): Promise<{
12
+ content: Array<{
13
+ type: "text";
14
+ text: string;
15
+ }>;
16
+ isError?: boolean;
17
+ }>;
18
+ export declare function register(server: McpServer, client: BudaClient): void;
19
+ //# sourceMappingURL=account.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../../src/tools/account.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAgB,MAAM,cAAc,CAAC;AAIxD,eAAO,MAAM,UAAU;;;;;;;CAYtB,CAAC;AAEF,wBAAsB,oBAAoB,CACxC,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,CAmChF;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAOpE"}
@@ -0,0 +1,49 @@
1
+ import { BudaApiError } from "../client.js";
2
+ import { flattenAmount } from "../utils.js";
3
+ export const toolSchema = {
4
+ name: "get_account_info",
5
+ description: "Returns the authenticated user's profile on Buda.com. " +
6
+ "Fetches account details including email, display name, pubsub key, and monthly transacted amounts. " +
7
+ "Read-only; no side effects. " +
8
+ "Requires BUDA_API_KEY and BUDA_API_SECRET. " +
9
+ "Example: 'What is my account email and how much have I transacted this month?'",
10
+ inputSchema: {
11
+ type: "object",
12
+ properties: {},
13
+ },
14
+ };
15
+ export async function handleGetAccountInfo(client) {
16
+ try {
17
+ const data = await client.get("/me");
18
+ const me = data.me;
19
+ const monthly = flattenAmount(me.monthly_transacted);
20
+ return {
21
+ content: [
22
+ {
23
+ type: "text",
24
+ text: JSON.stringify({
25
+ id: me.id,
26
+ email: me.email,
27
+ name: me.name ?? null,
28
+ monthly_transacted: monthly.value,
29
+ monthly_transacted_currency: monthly.currency,
30
+ pubsub_key: me.pubsub_key ?? null,
31
+ }, null, 2),
32
+ },
33
+ ],
34
+ };
35
+ }
36
+ catch (err) {
37
+ const msg = err instanceof BudaApiError
38
+ ? { error: err.message, code: err.status, path: err.path }
39
+ : { error: String(err), code: "UNKNOWN" };
40
+ return {
41
+ content: [{ type: "text", text: JSON.stringify(msg) }],
42
+ isError: true,
43
+ };
44
+ }
45
+ }
46
+ export function register(server, client) {
47
+ server.tool(toolSchema.name, toolSchema.description, {}, () => handleGetAccountInfo(client));
48
+ }
49
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1,29 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BudaClient } from "../client.js";
3
+ export declare const toolSchema: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: {
7
+ type: "object";
8
+ properties: {
9
+ currency: {
10
+ type: string;
11
+ description: string;
12
+ };
13
+ };
14
+ required: string[];
15
+ };
16
+ };
17
+ type GetBalanceArgs = {
18
+ currency: string;
19
+ };
20
+ export declare function handleGetBalance(args: GetBalanceArgs, client: BudaClient): Promise<{
21
+ content: Array<{
22
+ type: "text";
23
+ text: string;
24
+ }>;
25
+ isError?: boolean;
26
+ }>;
27
+ export declare function register(server: McpServer, client: BudaClient): void;
28
+ export {};
29
+ //# sourceMappingURL=balance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"balance.d.ts","sourceRoot":"","sources":["../../src/tools/balance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAgB,MAAM,cAAc,CAAC;AAKxD,eAAO,MAAM,UAAU;;;;;;;;;;;;;CAiBtB,CAAC;AAEF,KAAK,cAAc,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3C,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,cAAc,EACpB,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,CAmDhF;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CASpE"}
@@ -0,0 +1,72 @@
1
+ import { z } from "zod";
2
+ import { BudaApiError } from "../client.js";
3
+ import { validateCurrency } from "../validation.js";
4
+ import { flattenAmount } from "../utils.js";
5
+ export const toolSchema = {
6
+ name: "get_balance",
7
+ description: "Returns the balance for a single currency for the authenticated Buda.com account. " +
8
+ "Fetches total, available, frozen, and pending-withdrawal amounts as floats with separate _currency fields. " +
9
+ "Requires BUDA_API_KEY and BUDA_API_SECRET. " +
10
+ "Example: 'How much ETH do I have available?'",
11
+ inputSchema: {
12
+ type: "object",
13
+ properties: {
14
+ currency: {
15
+ type: "string",
16
+ description: "Currency code (e.g. 'BTC', 'CLP', 'USDC').",
17
+ },
18
+ },
19
+ required: ["currency"],
20
+ },
21
+ };
22
+ export async function handleGetBalance(args, client) {
23
+ const { currency } = args;
24
+ const validationError = validateCurrency(currency);
25
+ if (validationError) {
26
+ return {
27
+ content: [{ type: "text", text: JSON.stringify({ error: validationError, code: "INVALID_CURRENCY" }) }],
28
+ isError: true,
29
+ };
30
+ }
31
+ try {
32
+ const data = await client.get(`/balances/${currency.toUpperCase()}`);
33
+ const b = data.balance;
34
+ const amount = flattenAmount(b.amount);
35
+ const available = flattenAmount(b.available_amount);
36
+ const frozen = flattenAmount(b.frozen_amount);
37
+ const pending = flattenAmount(b.pending_withdraw_amount);
38
+ return {
39
+ content: [
40
+ {
41
+ type: "text",
42
+ text: JSON.stringify({
43
+ id: b.id,
44
+ amount: amount.value,
45
+ amount_currency: amount.currency,
46
+ available_amount: available.value,
47
+ available_amount_currency: available.currency,
48
+ frozen_amount: frozen.value,
49
+ frozen_amount_currency: frozen.currency,
50
+ pending_withdraw_amount: pending.value,
51
+ pending_withdraw_amount_currency: pending.currency,
52
+ }, null, 2),
53
+ },
54
+ ],
55
+ };
56
+ }
57
+ catch (err) {
58
+ const msg = err instanceof BudaApiError
59
+ ? { error: err.message, code: err.status, path: err.path }
60
+ : { error: String(err), code: "UNKNOWN" };
61
+ return {
62
+ content: [{ type: "text", text: JSON.stringify(msg) }],
63
+ isError: true,
64
+ };
65
+ }
66
+ }
67
+ export function register(server, client) {
68
+ server.tool(toolSchema.name, toolSchema.description, {
69
+ currency: z.string().min(2).max(10).describe("Currency code (e.g. 'BTC', 'CLP', 'USDC')."),
70
+ }, (args) => handleGetBalance(args, client));
71
+ }
72
+ //# sourceMappingURL=balance.js.map
@@ -0,0 +1,28 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BudaClient } from "../client.js";
3
+ import { MemoryCache } from "../cache.js";
4
+ export declare const toolSchema: {
5
+ name: string;
6
+ description: string;
7
+ inputSchema: {
8
+ type: "object";
9
+ properties: {
10
+ currency: {
11
+ type: string;
12
+ description: string;
13
+ };
14
+ };
15
+ required: string[];
16
+ };
17
+ };
18
+ export declare function handleGetAvailableBanks(args: {
19
+ currency: string;
20
+ }, client: BudaClient, cache: MemoryCache): Promise<{
21
+ content: Array<{
22
+ type: "text";
23
+ text: string;
24
+ }>;
25
+ isError?: boolean;
26
+ }>;
27
+ export declare function register(server: McpServer, client: BudaClient, cache: MemoryCache): void;
28
+ //# sourceMappingURL=banks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banks.d.ts","sourceRoot":"","sources":["../../src/tools/banks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAgB,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,WAAW,EAAa,MAAM,aAAa,CAAC;AAIrD,eAAO,MAAM,UAAU;;;;;;;;;;;;;CAkBtB,CAAC;AAEF,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,EAC1B,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,WAAW,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,CAmDhF;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CASxF"}
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ import { BudaApiError } from "../client.js";
3
+ import { CACHE_TTL } from "../cache.js";
4
+ import { validateCurrency } from "../validation.js";
5
+ export const toolSchema = {
6
+ name: "get_available_banks",
7
+ description: "Returns banks available for deposits and withdrawals of a fiat currency on Buda.com. " +
8
+ "Returns an empty banks array (not an error) if the currency has no associated banks " +
9
+ "(e.g. crypto currencies or unsupported fiat currencies). " +
10
+ "Results are cached for 60 seconds. " +
11
+ "Example: 'Which banks can I use for CLP deposits?'",
12
+ inputSchema: {
13
+ type: "object",
14
+ properties: {
15
+ currency: {
16
+ type: "string",
17
+ description: "Currency code (e.g. 'CLP', 'COP', 'PEN').",
18
+ },
19
+ },
20
+ required: ["currency"],
21
+ },
22
+ };
23
+ export async function handleGetAvailableBanks(args, client, cache) {
24
+ const { currency } = args;
25
+ const validationError = validateCurrency(currency);
26
+ if (validationError) {
27
+ return {
28
+ content: [{ type: "text", text: JSON.stringify({ error: validationError, code: "INVALID_CURRENCY" }) }],
29
+ isError: true,
30
+ };
31
+ }
32
+ const currencyUpper = currency.toUpperCase();
33
+ try {
34
+ const data = await cache.getOrFetch(`banks:${currencyUpper}`, CACHE_TTL.BANKS, () => client.get(`/currencies/${currencyUpper}/banks`));
35
+ return {
36
+ content: [
37
+ {
38
+ type: "text",
39
+ text: JSON.stringify({
40
+ currency: currencyUpper,
41
+ banks: data.banks.map((b) => ({ id: b.id, name: b.name, country: b.country ?? null })),
42
+ }, null, 2),
43
+ },
44
+ ],
45
+ };
46
+ }
47
+ catch (err) {
48
+ // 404 means no banks exist for this currency — return empty list (not an error)
49
+ if (err instanceof BudaApiError && err.status === 404) {
50
+ return {
51
+ content: [{ type: "text", text: JSON.stringify({ currency: currencyUpper, banks: [] }, null, 2) }],
52
+ };
53
+ }
54
+ const msg = err instanceof BudaApiError
55
+ ? { error: err.message, code: err.status, path: err.path }
56
+ : { error: String(err), code: "UNKNOWN" };
57
+ return {
58
+ content: [{ type: "text", text: JSON.stringify(msg) }],
59
+ isError: true,
60
+ };
61
+ }
62
+ }
63
+ export function register(server, client, cache) {
64
+ server.tool(toolSchema.name, toolSchema.description, {
65
+ currency: z.string().min(2).max(10).describe("Currency code (e.g. 'CLP', 'COP', 'PEN')."),
66
+ }, (args) => handleGetAvailableBanks(args, client, cache));
67
+ }
68
+ //# sourceMappingURL=banks.js.map