@guiie/buda-mcp 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/cache.d.ts +1 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +1 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +16 -0
- package/dist/http.js +55 -0
- package/dist/index.js +30 -0
- package/dist/tools/account.d.ts +19 -0
- package/dist/tools/account.d.ts.map +1 -0
- package/dist/tools/account.js +49 -0
- package/dist/tools/balance.d.ts +29 -0
- package/dist/tools/balance.d.ts.map +1 -0
- package/dist/tools/balance.js +72 -0
- package/dist/tools/banks.d.ts +28 -0
- package/dist/tools/banks.d.ts.map +1 -0
- package/dist/tools/banks.js +68 -0
- package/dist/tools/batch_orders.d.ts +77 -0
- package/dist/tools/batch_orders.d.ts.map +1 -0
- package/dist/tools/batch_orders.js +154 -0
- package/dist/tools/cancel_all_orders.d.ts +34 -0
- package/dist/tools/cancel_all_orders.d.ts.map +1 -0
- package/dist/tools/cancel_all_orders.js +89 -0
- package/dist/tools/cancel_order_by_client_id.d.ts +34 -0
- package/dist/tools/cancel_order_by_client_id.d.ts.map +1 -0
- package/dist/tools/cancel_order_by_client_id.js +102 -0
- package/dist/tools/deposits.d.ts +83 -0
- package/dist/tools/deposits.d.ts.map +1 -0
- package/dist/tools/deposits.js +174 -0
- package/dist/tools/fees.d.ts +34 -0
- package/dist/tools/fees.d.ts.map +1 -0
- package/dist/tools/fees.js +72 -0
- package/dist/tools/lightning.d.ts +68 -0
- package/dist/tools/lightning.d.ts.map +1 -0
- package/dist/tools/lightning.js +171 -0
- package/dist/tools/order_lookup.d.ts +50 -0
- package/dist/tools/order_lookup.d.ts.map +1 -0
- package/dist/tools/order_lookup.js +112 -0
- package/dist/tools/place_order.d.ts +30 -0
- package/dist/tools/place_order.d.ts.map +1 -1
- package/dist/tools/place_order.js +100 -2
- package/dist/tools/quotation.d.ts +44 -0
- package/dist/tools/quotation.d.ts.map +1 -0
- package/dist/tools/quotation.js +99 -0
- package/dist/tools/receive_addresses.d.ts +78 -0
- package/dist/tools/receive_addresses.d.ts.map +1 -0
- package/dist/tools/receive_addresses.js +161 -0
- package/dist/tools/remittance_recipients.d.ts +54 -0
- package/dist/tools/remittance_recipients.d.ts.map +1 -0
- package/dist/tools/remittance_recipients.js +106 -0
- package/dist/tools/remittances.d.ts +115 -0
- package/dist/tools/remittances.d.ts.map +1 -0
- package/dist/tools/remittances.js +237 -0
- package/dist/tools/simulate_order.d.ts.map +1 -1
- package/dist/tools/simulate_order.js +2 -1
- package/dist/tools/withdrawals.d.ts +93 -0
- package/dist/tools/withdrawals.d.ts.map +1 -0
- package/dist/tools/withdrawals.js +215 -0
- package/dist/types.d.ts +155 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts +5 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +12 -0
- package/marketplace/README.md +1 -1
- package/marketplace/claude-listing.md +30 -2
- package/marketplace/gemini-tools.json +155 -1
- package/marketplace/openapi.yaml +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/cache.ts +1 -0
- package/src/client.ts +20 -0
- package/src/http.ts +55 -0
- package/src/index.ts +30 -0
- package/src/tools/account.ts +66 -0
- package/src/tools/balance.ts +94 -0
- package/src/tools/banks.ts +94 -0
- package/src/tools/batch_orders.ts +199 -0
- package/src/tools/cancel_all_orders.ts +117 -0
- package/src/tools/cancel_order_by_client_id.ts +132 -0
- package/src/tools/deposits.ts +230 -0
- package/src/tools/fees.ts +91 -0
- package/src/tools/lightning.ts +231 -0
- package/src/tools/order_lookup.ts +139 -0
- package/src/tools/place_order.ts +119 -2
- package/src/tools/quotation.ts +124 -0
- package/src/tools/receive_addresses.ts +216 -0
- package/src/tools/remittance_recipients.ts +139 -0
- package/src/tools/remittances.ts +299 -0
- package/src/tools/simulate_order.ts +1 -0
- package/src/tools/withdrawals.ts +276 -0
- package/src/types.ts +210 -0
- package/src/validation.ts +16 -0
- package/test/run-all.ts +16 -0
- package/test/unit.ts +1905 -0
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/gtorreal/buda-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.5.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@guiie/buda-mcp",
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.5.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|
package/src/cache.ts
CHANGED
package/src/client.ts
CHANGED
|
@@ -173,4 +173,24 @@ export class BudaClient {
|
|
|
173
173
|
);
|
|
174
174
|
return this.handleResponse<T>(response, path);
|
|
175
175
|
}
|
|
176
|
+
|
|
177
|
+
async delete<T>(path: string, params?: Record<string, string | number>): Promise<T> {
|
|
178
|
+
const url = new URL(`${this.baseUrl}${path}.json`);
|
|
179
|
+
|
|
180
|
+
if (params) {
|
|
181
|
+
for (const [key, value] of Object.entries(params)) {
|
|
182
|
+
url.searchParams.set(key, String(value));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const urlPath = url.pathname + url.search;
|
|
187
|
+
const headers: Record<string, string> = {
|
|
188
|
+
Accept: "application/json",
|
|
189
|
+
"User-Agent": `buda-mcp/${VERSION}`,
|
|
190
|
+
...this.authHeaders("DELETE", urlPath),
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const response = await this.fetchWithRetry(url, { method: "DELETE", headers }, path);
|
|
194
|
+
return this.handleResponse<T>(response, path);
|
|
195
|
+
}
|
|
176
196
|
}
|
package/src/http.ts
CHANGED
|
@@ -24,6 +24,21 @@ import * as positionSize from "./tools/calculate_position_size.js";
|
|
|
24
24
|
import * as marketSentiment from "./tools/market_sentiment.js";
|
|
25
25
|
import * as technicalIndicators from "./tools/technical_indicators.js";
|
|
26
26
|
import * as deadMansSwitch from "./tools/dead_mans_switch.js";
|
|
27
|
+
import * as banks from "./tools/banks.js";
|
|
28
|
+
import * as account from "./tools/account.js";
|
|
29
|
+
import * as balance from "./tools/balance.js";
|
|
30
|
+
import * as orderLookup from "./tools/order_lookup.js";
|
|
31
|
+
import * as networkFees from "./tools/fees.js";
|
|
32
|
+
import * as deposits from "./tools/deposits.js";
|
|
33
|
+
import * as withdrawals from "./tools/withdrawals.js";
|
|
34
|
+
import * as receiveAddresses from "./tools/receive_addresses.js";
|
|
35
|
+
import * as remittances from "./tools/remittances.js";
|
|
36
|
+
import * as remittanceRecipients from "./tools/remittance_recipients.js";
|
|
37
|
+
import * as quotation from "./tools/quotation.js";
|
|
38
|
+
import * as cancelAllOrders from "./tools/cancel_all_orders.js";
|
|
39
|
+
import * as cancelOrderByClientId from "./tools/cancel_order_by_client_id.js";
|
|
40
|
+
import * as batchOrders from "./tools/batch_orders.js";
|
|
41
|
+
import * as lightning from "./tools/lightning.js";
|
|
27
42
|
import { handleMarketSummary } from "./tools/market_summary.js";
|
|
28
43
|
|
|
29
44
|
const PORT = parseInt(process.env.PORT ?? "3000", 10);
|
|
@@ -50,9 +65,11 @@ const PUBLIC_TOOL_SCHEMAS = [
|
|
|
50
65
|
arbitrage.toolSchema,
|
|
51
66
|
marketSummary.toolSchema,
|
|
52
67
|
simulateOrder.toolSchema,
|
|
68
|
+
quotation.toolSchema,
|
|
53
69
|
positionSize.toolSchema,
|
|
54
70
|
marketSentiment.toolSchema,
|
|
55
71
|
technicalIndicators.toolSchema,
|
|
72
|
+
banks.toolSchema,
|
|
56
73
|
];
|
|
57
74
|
|
|
58
75
|
const AUTH_TOOL_SCHEMAS = [
|
|
@@ -63,6 +80,29 @@ const AUTH_TOOL_SCHEMAS = [
|
|
|
63
80
|
deadMansSwitch.toolSchema,
|
|
64
81
|
deadMansSwitch.renewToolSchema,
|
|
65
82
|
deadMansSwitch.disarmToolSchema,
|
|
83
|
+
account.toolSchema,
|
|
84
|
+
balance.toolSchema,
|
|
85
|
+
orderLookup.getOrderToolSchema,
|
|
86
|
+
orderLookup.getOrderByClientIdToolSchema,
|
|
87
|
+
networkFees.toolSchema,
|
|
88
|
+
deposits.getDepositHistoryToolSchema,
|
|
89
|
+
withdrawals.getWithdrawalHistoryToolSchema,
|
|
90
|
+
receiveAddresses.listReceiveAddressesToolSchema,
|
|
91
|
+
receiveAddresses.getReceiveAddressToolSchema,
|
|
92
|
+
remittances.listRemittancesToolSchema,
|
|
93
|
+
remittances.getRemittanceToolSchema,
|
|
94
|
+
remittances.quoteRemittanceToolSchema,
|
|
95
|
+
remittances.acceptRemittanceQuoteToolSchema,
|
|
96
|
+
remittanceRecipients.listToolSchema,
|
|
97
|
+
remittanceRecipients.getToolSchema,
|
|
98
|
+
receiveAddresses.createReceiveAddressToolSchema,
|
|
99
|
+
cancelAllOrders.toolSchema,
|
|
100
|
+
cancelOrderByClientId.toolSchema,
|
|
101
|
+
batchOrders.toolSchema,
|
|
102
|
+
withdrawals.createWithdrawalToolSchema,
|
|
103
|
+
deposits.createFiatDepositToolSchema,
|
|
104
|
+
lightning.lightningWithdrawalToolSchema,
|
|
105
|
+
lightning.createLightningInvoiceToolSchema,
|
|
66
106
|
];
|
|
67
107
|
|
|
68
108
|
function createServer(): McpServer {
|
|
@@ -82,9 +122,11 @@ function createServer(): McpServer {
|
|
|
82
122
|
arbitrage.register(server, client, reqCache);
|
|
83
123
|
marketSummary.register(server, client, reqCache);
|
|
84
124
|
simulateOrder.register(server, client, reqCache);
|
|
125
|
+
quotation.register(server, client);
|
|
85
126
|
positionSize.register(server);
|
|
86
127
|
marketSentiment.register(server, client, reqCache);
|
|
87
128
|
technicalIndicators.register(server, client);
|
|
129
|
+
banks.register(server, client, reqCache);
|
|
88
130
|
|
|
89
131
|
if (authEnabled) {
|
|
90
132
|
balances.register(server, client);
|
|
@@ -92,6 +134,19 @@ function createServer(): McpServer {
|
|
|
92
134
|
placeOrder.register(server, client);
|
|
93
135
|
cancelOrder.register(server, client);
|
|
94
136
|
deadMansSwitch.register(server, client);
|
|
137
|
+
account.register(server, client);
|
|
138
|
+
balance.register(server, client);
|
|
139
|
+
orderLookup.register(server, client);
|
|
140
|
+
networkFees.register(server, client);
|
|
141
|
+
deposits.register(server, client);
|
|
142
|
+
withdrawals.register(server, client);
|
|
143
|
+
receiveAddresses.register(server, client);
|
|
144
|
+
remittances.register(server, client);
|
|
145
|
+
remittanceRecipients.register(server, client);
|
|
146
|
+
cancelAllOrders.register(server, client);
|
|
147
|
+
cancelOrderByClientId.register(server, client);
|
|
148
|
+
batchOrders.register(server, client);
|
|
149
|
+
lightning.register(server, client);
|
|
95
150
|
}
|
|
96
151
|
|
|
97
152
|
// MCP Resources
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,21 @@ import * as positionSize from "./tools/calculate_position_size.js";
|
|
|
25
25
|
import * as marketSentiment from "./tools/market_sentiment.js";
|
|
26
26
|
import * as technicalIndicators from "./tools/technical_indicators.js";
|
|
27
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";
|
|
28
43
|
import { handleMarketSummary } from "./tools/market_summary.js";
|
|
29
44
|
|
|
30
45
|
const client = new BudaClient(
|
|
@@ -50,9 +65,11 @@ priceHistory.register(server, client, cache);
|
|
|
50
65
|
arbitrage.register(server, client, cache);
|
|
51
66
|
marketSummary.register(server, client, cache);
|
|
52
67
|
simulateOrder.register(server, client, cache);
|
|
68
|
+
quotation.register(server, client);
|
|
53
69
|
positionSize.register(server);
|
|
54
70
|
marketSentiment.register(server, client, cache);
|
|
55
71
|
technicalIndicators.register(server, client);
|
|
72
|
+
banks.register(server, client, cache);
|
|
56
73
|
|
|
57
74
|
// Auth-gated tools — only registered when API credentials are present
|
|
58
75
|
if (client.hasAuth()) {
|
|
@@ -61,6 +78,19 @@ if (client.hasAuth()) {
|
|
|
61
78
|
placeOrder.register(server, client);
|
|
62
79
|
cancelOrder.register(server, client);
|
|
63
80
|
deadMansSwitch.register(server, client);
|
|
81
|
+
account.register(server, client);
|
|
82
|
+
balance.register(server, client);
|
|
83
|
+
orderLookup.register(server, client);
|
|
84
|
+
networkFees.register(server, client);
|
|
85
|
+
deposits.register(server, client);
|
|
86
|
+
withdrawals.register(server, client);
|
|
87
|
+
receiveAddresses.register(server, client);
|
|
88
|
+
remittances.register(server, client);
|
|
89
|
+
remittanceRecipients.register(server, client);
|
|
90
|
+
cancelAllOrders.register(server, client);
|
|
91
|
+
cancelOrderByClientId.register(server, client);
|
|
92
|
+
batchOrders.register(server, client);
|
|
93
|
+
lightning.register(server, client);
|
|
64
94
|
}
|
|
65
95
|
|
|
66
96
|
// MCP Resources
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { BudaClient, BudaApiError } from "../client.js";
|
|
3
|
+
import { flattenAmount } from "../utils.js";
|
|
4
|
+
import type { MeResponse } from "../types.js";
|
|
5
|
+
|
|
6
|
+
export const toolSchema = {
|
|
7
|
+
name: "get_account_info",
|
|
8
|
+
description:
|
|
9
|
+
"Returns the authenticated user's profile on Buda.com. " +
|
|
10
|
+
"Fetches account details including email, display name, pubsub key, and monthly transacted amounts. " +
|
|
11
|
+
"Read-only; no side effects. " +
|
|
12
|
+
"Requires BUDA_API_KEY and BUDA_API_SECRET. " +
|
|
13
|
+
"Example: 'What is my account email and how much have I transacted this month?'",
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: "object" as const,
|
|
16
|
+
properties: {},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export async function handleGetAccountInfo(
|
|
21
|
+
client: BudaClient,
|
|
22
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
23
|
+
try {
|
|
24
|
+
const data = await client.get<MeResponse>("/me");
|
|
25
|
+
const me = data.me;
|
|
26
|
+
const monthly = flattenAmount(me.monthly_transacted);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: "text",
|
|
32
|
+
text: JSON.stringify(
|
|
33
|
+
{
|
|
34
|
+
id: me.id,
|
|
35
|
+
email: me.email,
|
|
36
|
+
name: me.name ?? null,
|
|
37
|
+
monthly_transacted: monthly.value,
|
|
38
|
+
monthly_transacted_currency: monthly.currency,
|
|
39
|
+
pubsub_key: me.pubsub_key ?? null,
|
|
40
|
+
},
|
|
41
|
+
null,
|
|
42
|
+
2,
|
|
43
|
+
),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
} catch (err) {
|
|
48
|
+
const msg =
|
|
49
|
+
err instanceof BudaApiError
|
|
50
|
+
? { error: err.message, code: err.status, path: err.path }
|
|
51
|
+
: { error: String(err), code: "UNKNOWN" };
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: "text", text: JSON.stringify(msg) }],
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function register(server: McpServer, client: BudaClient): void {
|
|
60
|
+
server.tool(
|
|
61
|
+
toolSchema.name,
|
|
62
|
+
toolSchema.description,
|
|
63
|
+
{},
|
|
64
|
+
() => handleGetAccountInfo(client),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { BudaClient, BudaApiError } from "../client.js";
|
|
4
|
+
import { validateCurrency } from "../validation.js";
|
|
5
|
+
import { flattenAmount } from "../utils.js";
|
|
6
|
+
import type { SingleBalanceResponse } from "../types.js";
|
|
7
|
+
|
|
8
|
+
export const toolSchema = {
|
|
9
|
+
name: "get_balance",
|
|
10
|
+
description:
|
|
11
|
+
"Returns the balance for a single currency for the authenticated Buda.com account. " +
|
|
12
|
+
"Fetches total, available, frozen, and pending-withdrawal amounts as floats with separate _currency fields. " +
|
|
13
|
+
"Requires BUDA_API_KEY and BUDA_API_SECRET. " +
|
|
14
|
+
"Example: 'How much ETH do I have available?'",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object" as const,
|
|
17
|
+
properties: {
|
|
18
|
+
currency: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Currency code (e.g. 'BTC', 'CLP', 'USDC').",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
required: ["currency"],
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type GetBalanceArgs = { currency: string };
|
|
28
|
+
|
|
29
|
+
export async function handleGetBalance(
|
|
30
|
+
args: GetBalanceArgs,
|
|
31
|
+
client: BudaClient,
|
|
32
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
33
|
+
const { currency } = args;
|
|
34
|
+
|
|
35
|
+
const validationError = validateCurrency(currency);
|
|
36
|
+
if (validationError) {
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: "text", text: JSON.stringify({ error: validationError, code: "INVALID_CURRENCY" }) }],
|
|
39
|
+
isError: true,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const data = await client.get<SingleBalanceResponse>(`/balances/${currency.toUpperCase()}`);
|
|
45
|
+
const b = data.balance;
|
|
46
|
+
const amount = flattenAmount(b.amount);
|
|
47
|
+
const available = flattenAmount(b.available_amount);
|
|
48
|
+
const frozen = flattenAmount(b.frozen_amount);
|
|
49
|
+
const pending = flattenAmount(b.pending_withdraw_amount);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: JSON.stringify(
|
|
56
|
+
{
|
|
57
|
+
id: b.id,
|
|
58
|
+
amount: amount.value,
|
|
59
|
+
amount_currency: amount.currency,
|
|
60
|
+
available_amount: available.value,
|
|
61
|
+
available_amount_currency: available.currency,
|
|
62
|
+
frozen_amount: frozen.value,
|
|
63
|
+
frozen_amount_currency: frozen.currency,
|
|
64
|
+
pending_withdraw_amount: pending.value,
|
|
65
|
+
pending_withdraw_amount_currency: pending.currency,
|
|
66
|
+
},
|
|
67
|
+
null,
|
|
68
|
+
2,
|
|
69
|
+
),
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
} catch (err) {
|
|
74
|
+
const msg =
|
|
75
|
+
err instanceof BudaApiError
|
|
76
|
+
? { error: err.message, code: err.status, path: err.path }
|
|
77
|
+
: { error: String(err), code: "UNKNOWN" };
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: "text", text: JSON.stringify(msg) }],
|
|
80
|
+
isError: true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function register(server: McpServer, client: BudaClient): void {
|
|
86
|
+
server.tool(
|
|
87
|
+
toolSchema.name,
|
|
88
|
+
toolSchema.description,
|
|
89
|
+
{
|
|
90
|
+
currency: z.string().min(2).max(10).describe("Currency code (e.g. 'BTC', 'CLP', 'USDC')."),
|
|
91
|
+
},
|
|
92
|
+
(args) => handleGetBalance(args, client),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { BudaClient, BudaApiError } from "../client.js";
|
|
4
|
+
import { MemoryCache, CACHE_TTL } from "../cache.js";
|
|
5
|
+
import { validateCurrency } from "../validation.js";
|
|
6
|
+
import type { BanksResponse } from "../types.js";
|
|
7
|
+
|
|
8
|
+
export const toolSchema = {
|
|
9
|
+
name: "get_available_banks",
|
|
10
|
+
description:
|
|
11
|
+
"Returns banks available for deposits and withdrawals of a fiat currency on Buda.com. " +
|
|
12
|
+
"Returns an empty banks array (not an error) if the currency has no associated banks " +
|
|
13
|
+
"(e.g. crypto currencies or unsupported fiat currencies). " +
|
|
14
|
+
"Results are cached for 60 seconds. " +
|
|
15
|
+
"Example: 'Which banks can I use for CLP deposits?'",
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: "object" as const,
|
|
18
|
+
properties: {
|
|
19
|
+
currency: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Currency code (e.g. 'CLP', 'COP', 'PEN').",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
required: ["currency"],
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export async function handleGetAvailableBanks(
|
|
29
|
+
args: { currency: string },
|
|
30
|
+
client: BudaClient,
|
|
31
|
+
cache: MemoryCache,
|
|
32
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
33
|
+
const { currency } = args;
|
|
34
|
+
|
|
35
|
+
const validationError = validateCurrency(currency);
|
|
36
|
+
if (validationError) {
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: "text", text: JSON.stringify({ error: validationError, code: "INVALID_CURRENCY" }) }],
|
|
39
|
+
isError: true,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const currencyUpper = currency.toUpperCase();
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const data = await cache.getOrFetch<BanksResponse>(
|
|
47
|
+
`banks:${currencyUpper}`,
|
|
48
|
+
CACHE_TTL.BANKS,
|
|
49
|
+
() => client.get<BanksResponse>(`/currencies/${currencyUpper}/banks`),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
content: [
|
|
54
|
+
{
|
|
55
|
+
type: "text",
|
|
56
|
+
text: JSON.stringify(
|
|
57
|
+
{
|
|
58
|
+
currency: currencyUpper,
|
|
59
|
+
banks: data.banks.map((b) => ({ id: b.id, name: b.name, country: b.country ?? null })),
|
|
60
|
+
},
|
|
61
|
+
null,
|
|
62
|
+
2,
|
|
63
|
+
),
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
} catch (err) {
|
|
68
|
+
// 404 means no banks exist for this currency — return empty list (not an error)
|
|
69
|
+
if (err instanceof BudaApiError && err.status === 404) {
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: "text", text: JSON.stringify({ currency: currencyUpper, banks: [] }, null, 2) }],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const msg =
|
|
75
|
+
err instanceof BudaApiError
|
|
76
|
+
? { error: err.message, code: err.status, path: err.path }
|
|
77
|
+
: { error: String(err), code: "UNKNOWN" };
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: "text", text: JSON.stringify(msg) }],
|
|
80
|
+
isError: true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function register(server: McpServer, client: BudaClient, cache: MemoryCache): void {
|
|
86
|
+
server.tool(
|
|
87
|
+
toolSchema.name,
|
|
88
|
+
toolSchema.description,
|
|
89
|
+
{
|
|
90
|
+
currency: z.string().min(2).max(10).describe("Currency code (e.g. 'CLP', 'COP', 'PEN')."),
|
|
91
|
+
},
|
|
92
|
+
(args) => handleGetAvailableBanks(args, client, cache),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { BudaClient, BudaApiError } from "../client.js";
|
|
4
|
+
import { validateMarketId } from "../validation.js";
|
|
5
|
+
import type { OrderResponse } from "../types.js";
|
|
6
|
+
|
|
7
|
+
export const toolSchema = {
|
|
8
|
+
name: "place_batch_orders",
|
|
9
|
+
description:
|
|
10
|
+
"Place multiple orders sequentially on Buda.com (up to 20). " +
|
|
11
|
+
"All orders are pre-validated before any API call — a validation failure stops execution with zero orders placed. " +
|
|
12
|
+
"Partial API failures do NOT roll back already-placed orders. " +
|
|
13
|
+
"IMPORTANT: Pass confirmation_token='CONFIRM' to execute. " +
|
|
14
|
+
"Requires BUDA_API_KEY and BUDA_API_SECRET.",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object" as const,
|
|
17
|
+
properties: {
|
|
18
|
+
orders: {
|
|
19
|
+
type: "array",
|
|
20
|
+
description: "Array of 1–20 orders to place.",
|
|
21
|
+
items: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
market_id: { type: "string", description: "Market ID (e.g. 'BTC-CLP')." },
|
|
25
|
+
type: { type: "string", enum: ["Bid", "Ask"], description: "Order side." },
|
|
26
|
+
price_type: { type: "string", enum: ["limit", "market"], description: "Order type." },
|
|
27
|
+
amount: { type: "number", description: "Order size in base currency." },
|
|
28
|
+
limit_price: { type: "number", description: "Required when price_type is 'limit'." },
|
|
29
|
+
},
|
|
30
|
+
required: ["market_id", "type", "price_type", "amount"],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
confirmation_token: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description:
|
|
36
|
+
"Safety confirmation. Must equal exactly 'CONFIRM' (case-sensitive) to execute.",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ["orders", "confirmation_token"],
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const orderShape = z.object({
|
|
44
|
+
market_id: z.string(),
|
|
45
|
+
type: z.enum(["Bid", "Ask"]),
|
|
46
|
+
price_type: z.enum(["limit", "market"]),
|
|
47
|
+
amount: z.number().positive(),
|
|
48
|
+
limit_price: z.number().positive().optional(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
type SingleOrderInput = z.infer<typeof orderShape>;
|
|
52
|
+
|
|
53
|
+
type BatchResult = {
|
|
54
|
+
index: number;
|
|
55
|
+
market_id: string;
|
|
56
|
+
success: boolean;
|
|
57
|
+
order?: unknown;
|
|
58
|
+
error?: string;
|
|
59
|
+
code?: number | string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type BatchOrdersArgs = {
|
|
63
|
+
orders: SingleOrderInput[];
|
|
64
|
+
confirmation_token: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export async function handlePlaceBatchOrders(
|
|
68
|
+
args: BatchOrdersArgs,
|
|
69
|
+
client: BudaClient,
|
|
70
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
71
|
+
const { orders, confirmation_token } = args;
|
|
72
|
+
|
|
73
|
+
if (confirmation_token !== "CONFIRM") {
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{
|
|
77
|
+
type: "text",
|
|
78
|
+
text: JSON.stringify({
|
|
79
|
+
error:
|
|
80
|
+
"Orders not placed. confirmation_token must equal 'CONFIRM' to execute. " +
|
|
81
|
+
"Review all orders and set confirmation_token='CONFIRM' to proceed.",
|
|
82
|
+
code: "CONFIRMATION_REQUIRED",
|
|
83
|
+
preview: { order_count: orders.length },
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
isError: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Pre-validate ALL orders before any API call
|
|
92
|
+
for (let i = 0; i < orders.length; i++) {
|
|
93
|
+
const order = orders[i];
|
|
94
|
+
const marketError = validateMarketId(order.market_id);
|
|
95
|
+
if (marketError) {
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: JSON.stringify({
|
|
101
|
+
error: `Order at index ${i}: ${marketError}`,
|
|
102
|
+
code: "INVALID_MARKET_ID",
|
|
103
|
+
index: i,
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
isError: true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (order.price_type === "limit" && order.limit_price === undefined) {
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: JSON.stringify({
|
|
116
|
+
error: `Order at index ${i}: limit_price is required when price_type is 'limit'.`,
|
|
117
|
+
code: "VALIDATION_ERROR",
|
|
118
|
+
index: i,
|
|
119
|
+
}),
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
isError: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Execute sequentially
|
|
128
|
+
const results: BatchResult[] = [];
|
|
129
|
+
for (let i = 0; i < orders.length; i++) {
|
|
130
|
+
const order = orders[i];
|
|
131
|
+
try {
|
|
132
|
+
const payload: Record<string, unknown> = {
|
|
133
|
+
type: order.type,
|
|
134
|
+
price_type: order.price_type,
|
|
135
|
+
amount: order.amount,
|
|
136
|
+
};
|
|
137
|
+
if (order.price_type === "limit") {
|
|
138
|
+
payload.limit = { price: order.limit_price, type: "gtc" };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const data = await client.post<OrderResponse>(
|
|
142
|
+
`/markets/${order.market_id.toLowerCase()}/orders`,
|
|
143
|
+
payload,
|
|
144
|
+
);
|
|
145
|
+
results.push({ index: i, market_id: order.market_id, success: true, order: data.order });
|
|
146
|
+
} catch (err) {
|
|
147
|
+
const errInfo =
|
|
148
|
+
err instanceof BudaApiError
|
|
149
|
+
? { error: err.message, code: err.status as number | string }
|
|
150
|
+
: { error: String(err), code: "UNKNOWN" as const };
|
|
151
|
+
results.push({
|
|
152
|
+
index: i,
|
|
153
|
+
market_id: order.market_id,
|
|
154
|
+
success: false,
|
|
155
|
+
error: errInfo.error,
|
|
156
|
+
code: errInfo.code,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
162
|
+
const failed = results.filter((r) => !r.success).length;
|
|
163
|
+
|
|
164
|
+
const response: Record<string, unknown> = {
|
|
165
|
+
results,
|
|
166
|
+
total: orders.length,
|
|
167
|
+
succeeded,
|
|
168
|
+
failed,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
if (failed > 0 && succeeded > 0) {
|
|
172
|
+
response.warning = "Some orders failed. Already-placed orders were NOT rolled back.";
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
177
|
+
isError: failed > 0 && succeeded === 0 ? true : undefined,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function register(server: McpServer, client: BudaClient): void {
|
|
182
|
+
server.tool(
|
|
183
|
+
toolSchema.name,
|
|
184
|
+
toolSchema.description,
|
|
185
|
+
{
|
|
186
|
+
orders: z
|
|
187
|
+
.array(orderShape)
|
|
188
|
+
.min(1)
|
|
189
|
+
.max(20)
|
|
190
|
+
.describe("Array of 1–20 orders to place."),
|
|
191
|
+
confirmation_token: z
|
|
192
|
+
.string()
|
|
193
|
+
.describe(
|
|
194
|
+
"Safety confirmation. Must equal exactly 'CONFIRM' (case-sensitive) to execute.",
|
|
195
|
+
),
|
|
196
|
+
},
|
|
197
|
+
(args) => handlePlaceBatchOrders(args, client),
|
|
198
|
+
);
|
|
199
|
+
}
|