@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.
- package/.cursor/rules/marketplace-docs-sync.mdc +32 -0
- package/CHANGELOG.md +79 -0
- package/PUBLISH_CHECKLIST.md +40 -88
- package/README.md +446 -78
- 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 +2 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +18 -1
- package/dist/http.js +97 -6
- package/dist/index.js +42 -3
- 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 +82 -0
- package/dist/tools/batch_orders.d.ts.map +1 -0
- package/dist/tools/batch_orders.js +188 -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.js +1 -1
- 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/dead_mans_switch.d.ts +1 -1
- package/dist/tools/dead_mans_switch.d.ts.map +1 -1
- package/dist/tools/dead_mans_switch.js +33 -3
- 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 +185 -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 +131 -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 +83 -0
- package/dist/tools/receive_addresses.d.ts.map +1 -0
- package/dist/tools/receive_addresses.js +185 -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 +120 -0
- package/dist/tools/remittances.d.ts.map +1 -0
- package/dist/tools/remittances.js +261 -0
- package/dist/tools/simulate_order.d.ts.map +1 -1
- package/dist/tools/simulate_order.js +2 -1
- package/dist/tools/technical_indicators.d.ts.map +1 -1
- package/dist/tools/technical_indicators.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 +225 -0
- package/dist/types.d.ts +155 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +4 -1
- package/dist/validation.d.ts +11 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +38 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +8 -1
- package/marketplace/README.md +1 -1
- package/marketplace/claude-listing.md +101 -2
- package/marketplace/gemini-tools.json +478 -1
- package/marketplace/openapi.yaml +160 -1
- package/package.json +2 -1
- package/server.json +2 -2
- package/src/cache.ts +1 -0
- package/src/client.ts +23 -1
- package/src/http.ts +105 -6
- package/src/index.ts +40 -3
- 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 +238 -0
- package/src/tools/cancel_all_orders.ts +117 -0
- package/src/tools/cancel_order.ts +1 -1
- package/src/tools/cancel_order_by_client_id.ts +132 -0
- package/src/tools/dead_mans_switch.ts +39 -3
- package/src/tools/deposits.ts +230 -0
- package/src/tools/fees.ts +91 -0
- package/src/tools/lightning.ts +247 -0
- package/src/tools/order_lookup.ts +139 -0
- package/src/tools/place_order.ts +151 -2
- package/src/tools/quotation.ts +124 -0
- package/src/tools/receive_addresses.ts +242 -0
- package/src/tools/remittance_recipients.ts +139 -0
- package/src/tools/remittances.ts +325 -0
- package/src/tools/simulate_order.ts +1 -0
- package/src/tools/technical_indicators.ts +2 -1
- package/src/tools/withdrawals.ts +287 -0
- package/src/types.ts +210 -0
- package/src/utils.ts +3 -1
- package/src/validation.ts +45 -0
- package/src/version.ts +11 -3
- package/test/run-all.ts +16 -0
- package/test/unit.ts +2149 -1
|
@@ -0,0 +1,238 @@
|
|
|
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
|
+
"Use max_notional to cap total exposure (computed as sum of amount × limit_price for limit orders; market orders contribute 0). " +
|
|
14
|
+
"IMPORTANT: Pass confirmation_token='CONFIRM' to execute. " +
|
|
15
|
+
"Requires BUDA_API_KEY and BUDA_API_SECRET.",
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: "object" as const,
|
|
18
|
+
properties: {
|
|
19
|
+
orders: {
|
|
20
|
+
type: "array",
|
|
21
|
+
description: "Array of 1–20 orders to place.",
|
|
22
|
+
items: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
market_id: { type: "string", description: "Market ID (e.g. 'BTC-CLP')." },
|
|
26
|
+
type: { type: "string", enum: ["Bid", "Ask"], description: "Order side." },
|
|
27
|
+
price_type: { type: "string", enum: ["limit", "market"], description: "Order type." },
|
|
28
|
+
amount: { type: "number", description: "Order size in base currency." },
|
|
29
|
+
limit_price: { type: "number", description: "Required when price_type is 'limit'." },
|
|
30
|
+
},
|
|
31
|
+
required: ["market_id", "type", "price_type", "amount"],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
max_notional: {
|
|
35
|
+
type: "number",
|
|
36
|
+
description:
|
|
37
|
+
"Optional spending cap: total notional (sum of amount × limit_price for limit orders). " +
|
|
38
|
+
"Batch is rejected before any API call if the sum exceeds this value. " +
|
|
39
|
+
"Market orders contribute 0 to the notional since their execution price is unknown.",
|
|
40
|
+
},
|
|
41
|
+
confirmation_token: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description:
|
|
44
|
+
"Safety confirmation. Must equal exactly 'CONFIRM' (case-sensitive) to execute.",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
required: ["orders", "confirmation_token"],
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const orderShape = z.object({
|
|
52
|
+
market_id: z.string(),
|
|
53
|
+
type: z.enum(["Bid", "Ask"]),
|
|
54
|
+
price_type: z.enum(["limit", "market"]),
|
|
55
|
+
amount: z.number().positive(),
|
|
56
|
+
limit_price: z.number().positive().optional(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
type SingleOrderInput = z.infer<typeof orderShape>;
|
|
60
|
+
|
|
61
|
+
type BatchResult = {
|
|
62
|
+
index: number;
|
|
63
|
+
market_id: string;
|
|
64
|
+
success: boolean;
|
|
65
|
+
order?: unknown;
|
|
66
|
+
error?: string;
|
|
67
|
+
code?: number | string;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type BatchOrdersArgs = {
|
|
71
|
+
orders: SingleOrderInput[];
|
|
72
|
+
max_notional?: number;
|
|
73
|
+
confirmation_token: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export async function handlePlaceBatchOrders(
|
|
77
|
+
args: BatchOrdersArgs,
|
|
78
|
+
client: BudaClient,
|
|
79
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
80
|
+
const { orders, max_notional, confirmation_token } = args;
|
|
81
|
+
|
|
82
|
+
if (confirmation_token !== "CONFIRM") {
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: JSON.stringify({
|
|
88
|
+
error:
|
|
89
|
+
"Orders not placed. confirmation_token must equal 'CONFIRM' to execute. " +
|
|
90
|
+
"Review all orders and set confirmation_token='CONFIRM' to proceed.",
|
|
91
|
+
code: "CONFIRMATION_REQUIRED",
|
|
92
|
+
preview: { order_count: orders.length },
|
|
93
|
+
}),
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
isError: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Pre-validate ALL orders before any API call
|
|
101
|
+
for (let i = 0; i < orders.length; i++) {
|
|
102
|
+
const order = orders[i];
|
|
103
|
+
const marketError = validateMarketId(order.market_id);
|
|
104
|
+
if (marketError) {
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: JSON.stringify({
|
|
110
|
+
error: `Order at index ${i}: ${marketError}`,
|
|
111
|
+
code: "INVALID_MARKET_ID",
|
|
112
|
+
index: i,
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
isError: true,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (order.price_type === "limit" && order.limit_price === undefined) {
|
|
120
|
+
return {
|
|
121
|
+
content: [
|
|
122
|
+
{
|
|
123
|
+
type: "text",
|
|
124
|
+
text: JSON.stringify({
|
|
125
|
+
error: `Order at index ${i}: limit_price is required when price_type is 'limit'.`,
|
|
126
|
+
code: "VALIDATION_ERROR",
|
|
127
|
+
index: i,
|
|
128
|
+
}),
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
isError: true,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Notional cap check (limit orders only; market orders have unknown execution price)
|
|
137
|
+
if (max_notional !== undefined) {
|
|
138
|
+
const totalNotional = orders.reduce((sum, o) => {
|
|
139
|
+
return sum + (o.price_type === "limit" && o.limit_price ? o.amount * o.limit_price : 0);
|
|
140
|
+
}, 0);
|
|
141
|
+
if (totalNotional > max_notional) {
|
|
142
|
+
return {
|
|
143
|
+
content: [{
|
|
144
|
+
type: "text",
|
|
145
|
+
text: JSON.stringify({
|
|
146
|
+
error: `Total notional ${totalNotional} exceeds max_notional cap of ${max_notional}. No orders were placed.`,
|
|
147
|
+
code: "NOTIONAL_CAP_EXCEEDED",
|
|
148
|
+
total_notional: totalNotional,
|
|
149
|
+
max_notional,
|
|
150
|
+
}),
|
|
151
|
+
}],
|
|
152
|
+
isError: true,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Execute sequentially
|
|
158
|
+
const results: BatchResult[] = [];
|
|
159
|
+
for (let i = 0; i < orders.length; i++) {
|
|
160
|
+
const order = orders[i];
|
|
161
|
+
try {
|
|
162
|
+
const payload: Record<string, unknown> = {
|
|
163
|
+
type: order.type,
|
|
164
|
+
price_type: order.price_type,
|
|
165
|
+
amount: order.amount,
|
|
166
|
+
};
|
|
167
|
+
if (order.price_type === "limit") {
|
|
168
|
+
payload.limit = { price: order.limit_price, type: "gtc" };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const data = await client.post<OrderResponse>(
|
|
172
|
+
`/markets/${order.market_id.toLowerCase()}/orders`,
|
|
173
|
+
payload,
|
|
174
|
+
);
|
|
175
|
+
results.push({ index: i, market_id: order.market_id, success: true, order: data.order });
|
|
176
|
+
} catch (err) {
|
|
177
|
+
const errInfo =
|
|
178
|
+
err instanceof BudaApiError
|
|
179
|
+
? { error: err.message, code: err.status as number | string }
|
|
180
|
+
: { error: String(err), code: "UNKNOWN" as const };
|
|
181
|
+
results.push({
|
|
182
|
+
index: i,
|
|
183
|
+
market_id: order.market_id,
|
|
184
|
+
success: false,
|
|
185
|
+
error: errInfo.error,
|
|
186
|
+
code: errInfo.code,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
192
|
+
const failed = results.filter((r) => !r.success).length;
|
|
193
|
+
|
|
194
|
+
const response: Record<string, unknown> = {
|
|
195
|
+
results,
|
|
196
|
+
total: orders.length,
|
|
197
|
+
succeeded,
|
|
198
|
+
failed,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
if (failed > 0 && succeeded > 0) {
|
|
202
|
+
response.warning = "Some orders failed. Already-placed orders were NOT rolled back.";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
207
|
+
isError: failed > 0 && succeeded === 0 ? true : undefined,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function register(server: McpServer, client: BudaClient): void {
|
|
212
|
+
server.tool(
|
|
213
|
+
toolSchema.name,
|
|
214
|
+
toolSchema.description,
|
|
215
|
+
{
|
|
216
|
+
orders: z
|
|
217
|
+
.array(orderShape)
|
|
218
|
+
.min(1)
|
|
219
|
+
.max(20)
|
|
220
|
+
.describe("Array of 1–20 orders to place."),
|
|
221
|
+
max_notional: z
|
|
222
|
+
.number()
|
|
223
|
+
.positive()
|
|
224
|
+
.optional()
|
|
225
|
+
.describe(
|
|
226
|
+
"Optional spending cap: total notional (sum of amount × limit_price for limit orders). " +
|
|
227
|
+
"Batch is rejected before any API call if the sum exceeds this value. " +
|
|
228
|
+
"Market orders contribute 0 to the notional since their execution price is unknown.",
|
|
229
|
+
),
|
|
230
|
+
confirmation_token: z
|
|
231
|
+
.string()
|
|
232
|
+
.describe(
|
|
233
|
+
"Safety confirmation. Must equal exactly 'CONFIRM' (case-sensitive) to execute.",
|
|
234
|
+
),
|
|
235
|
+
},
|
|
236
|
+
(args) => handlePlaceBatchOrders(args, client),
|
|
237
|
+
);
|
|
238
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
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 { CancelAllOrdersResponse } from "../types.js";
|
|
6
|
+
|
|
7
|
+
export const toolSchema = {
|
|
8
|
+
name: "cancel_all_orders",
|
|
9
|
+
description:
|
|
10
|
+
"Cancel all open orders on Buda.com, optionally filtered by a specific market. " +
|
|
11
|
+
"Pass market_id='*' to cancel across all markets, or a specific market ID (e.g. 'BTC-CLP'). " +
|
|
12
|
+
"IMPORTANT: This action is irreversible. Pass confirmation_token='CONFIRM' to execute. " +
|
|
13
|
+
"Requires BUDA_API_KEY and BUDA_API_SECRET.",
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: "object" as const,
|
|
16
|
+
properties: {
|
|
17
|
+
market_id: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "Market ID (e.g. 'BTC-CLP') or '*' to cancel orders across all markets.",
|
|
20
|
+
},
|
|
21
|
+
confirmation_token: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description:
|
|
24
|
+
"Safety confirmation. Must equal exactly 'CONFIRM' (case-sensitive) to execute. " +
|
|
25
|
+
"Any other value will reject the request without canceling.",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ["market_id", "confirmation_token"],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type CancelAllOrdersArgs = {
|
|
33
|
+
market_id: string;
|
|
34
|
+
confirmation_token: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export async function handleCancelAllOrders(
|
|
38
|
+
args: CancelAllOrdersArgs,
|
|
39
|
+
client: BudaClient,
|
|
40
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
41
|
+
const { market_id, confirmation_token } = args;
|
|
42
|
+
|
|
43
|
+
if (confirmation_token !== "CONFIRM") {
|
|
44
|
+
return {
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: JSON.stringify({
|
|
49
|
+
error:
|
|
50
|
+
"Orders not canceled. confirmation_token must equal 'CONFIRM' to execute. " +
|
|
51
|
+
"Review and set confirmation_token='CONFIRM' to proceed.",
|
|
52
|
+
code: "CONFIRMATION_REQUIRED",
|
|
53
|
+
preview: { market_id },
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
isError: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (market_id !== "*") {
|
|
62
|
+
const validationError = validateMarketId(market_id);
|
|
63
|
+
if (validationError) {
|
|
64
|
+
return {
|
|
65
|
+
content: [
|
|
66
|
+
{ type: "text", text: JSON.stringify({ error: validationError, code: "INVALID_MARKET_ID" }) },
|
|
67
|
+
],
|
|
68
|
+
isError: true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const params =
|
|
75
|
+
market_id !== "*" ? { market_id: market_id.toLowerCase() } : undefined;
|
|
76
|
+
|
|
77
|
+
const data = await client.delete<CancelAllOrdersResponse>(`/orders`, params);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: "text",
|
|
83
|
+
text: JSON.stringify({ canceled_count: data.canceled_count, market_id }),
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const msg =
|
|
89
|
+
err instanceof BudaApiError
|
|
90
|
+
? { error: err.message, code: err.status, path: err.path }
|
|
91
|
+
: { error: String(err), code: "UNKNOWN" };
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: "text", text: JSON.stringify(msg) }],
|
|
94
|
+
isError: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function register(server: McpServer, client: BudaClient): void {
|
|
100
|
+
server.tool(
|
|
101
|
+
toolSchema.name,
|
|
102
|
+
toolSchema.description,
|
|
103
|
+
{
|
|
104
|
+
market_id: z
|
|
105
|
+
.string()
|
|
106
|
+
.min(1)
|
|
107
|
+
.describe("Market ID (e.g. 'BTC-CLP') or '*' to cancel orders across all markets."),
|
|
108
|
+
confirmation_token: z
|
|
109
|
+
.string()
|
|
110
|
+
.describe(
|
|
111
|
+
"Safety confirmation. Must equal exactly 'CONFIRM' (case-sensitive) to execute. " +
|
|
112
|
+
"Any other value will reject the request without canceling.",
|
|
113
|
+
),
|
|
114
|
+
},
|
|
115
|
+
(args) => handleCancelAllOrders(args, client),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { BudaClient, BudaApiError } from "../client.js";
|
|
4
|
+
import { flattenAmount } from "../utils.js";
|
|
5
|
+
import type { OrderResponse, Order } from "../types.js";
|
|
6
|
+
|
|
7
|
+
export const toolSchema = {
|
|
8
|
+
name: "cancel_order_by_client_id",
|
|
9
|
+
description:
|
|
10
|
+
"Cancel an open order by its client-assigned ID on Buda.com. " +
|
|
11
|
+
"IMPORTANT: Pass confirmation_token='CONFIRM' to execute. " +
|
|
12
|
+
"Requires BUDA_API_KEY and BUDA_API_SECRET.",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: "object" as const,
|
|
15
|
+
properties: {
|
|
16
|
+
client_id: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "The client ID string assigned when placing the order.",
|
|
19
|
+
},
|
|
20
|
+
confirmation_token: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description:
|
|
23
|
+
"Safety confirmation. Must equal exactly 'CONFIRM' (case-sensitive) to execute. " +
|
|
24
|
+
"Any other value will reject the request without canceling.",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
required: ["client_id", "confirmation_token"],
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type CancelOrderByClientIdArgs = {
|
|
32
|
+
client_id: string;
|
|
33
|
+
confirmation_token: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function normalizeOrder(o: Order) {
|
|
37
|
+
const amount = flattenAmount(o.amount);
|
|
38
|
+
const originalAmount = flattenAmount(o.original_amount);
|
|
39
|
+
const tradedAmount = flattenAmount(o.traded_amount);
|
|
40
|
+
const totalExchanged = flattenAmount(o.total_exchanged);
|
|
41
|
+
const paidFee = flattenAmount(o.paid_fee);
|
|
42
|
+
const limitPrice = o.limit ? flattenAmount(o.limit) : null;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
id: o.id,
|
|
46
|
+
type: o.type,
|
|
47
|
+
state: o.state,
|
|
48
|
+
created_at: o.created_at,
|
|
49
|
+
market_id: o.market_id,
|
|
50
|
+
fee_currency: o.fee_currency,
|
|
51
|
+
price_type: o.price_type,
|
|
52
|
+
order_type: o.order_type,
|
|
53
|
+
client_id: o.client_id,
|
|
54
|
+
limit_price: limitPrice ? limitPrice.value : null,
|
|
55
|
+
limit_price_currency: limitPrice ? limitPrice.currency : null,
|
|
56
|
+
amount: amount.value,
|
|
57
|
+
amount_currency: amount.currency,
|
|
58
|
+
original_amount: originalAmount.value,
|
|
59
|
+
original_amount_currency: originalAmount.currency,
|
|
60
|
+
traded_amount: tradedAmount.value,
|
|
61
|
+
traded_amount_currency: tradedAmount.currency,
|
|
62
|
+
total_exchanged: totalExchanged.value,
|
|
63
|
+
total_exchanged_currency: totalExchanged.currency,
|
|
64
|
+
paid_fee: paidFee.value,
|
|
65
|
+
paid_fee_currency: paidFee.currency,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function handleCancelOrderByClientId(
|
|
70
|
+
args: CancelOrderByClientIdArgs,
|
|
71
|
+
client: BudaClient,
|
|
72
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
73
|
+
const { client_id, confirmation_token } = args;
|
|
74
|
+
|
|
75
|
+
if (confirmation_token !== "CONFIRM") {
|
|
76
|
+
return {
|
|
77
|
+
content: [
|
|
78
|
+
{
|
|
79
|
+
type: "text",
|
|
80
|
+
text: JSON.stringify({
|
|
81
|
+
error:
|
|
82
|
+
"Order not canceled. confirmation_token must equal 'CONFIRM' to execute. " +
|
|
83
|
+
"Verify the client ID and set confirmation_token='CONFIRM' to proceed.",
|
|
84
|
+
code: "CONFIRMATION_REQUIRED",
|
|
85
|
+
client_id,
|
|
86
|
+
}),
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
isError: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const data = await client.put<OrderResponse>(
|
|
95
|
+
`/orders/by-client-id/${encodeURIComponent(client_id)}`,
|
|
96
|
+
{ order: { state: "canceling" } },
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
content: [{ type: "text", text: JSON.stringify(normalizeOrder(data.order), null, 2) }],
|
|
101
|
+
};
|
|
102
|
+
} catch (err) {
|
|
103
|
+
const msg =
|
|
104
|
+
err instanceof BudaApiError
|
|
105
|
+
? { error: err.message, code: err.status, path: err.path }
|
|
106
|
+
: { error: String(err), code: "UNKNOWN" };
|
|
107
|
+
return {
|
|
108
|
+
content: [{ type: "text", text: JSON.stringify(msg) }],
|
|
109
|
+
isError: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function register(server: McpServer, client: BudaClient): void {
|
|
115
|
+
server.tool(
|
|
116
|
+
toolSchema.name,
|
|
117
|
+
toolSchema.description,
|
|
118
|
+
{
|
|
119
|
+
client_id: z
|
|
120
|
+
.string()
|
|
121
|
+
.min(1)
|
|
122
|
+
.describe("The client ID string assigned when placing the order."),
|
|
123
|
+
confirmation_token: z
|
|
124
|
+
.string()
|
|
125
|
+
.describe(
|
|
126
|
+
"Safety confirmation. Must equal exactly 'CONFIRM' (case-sensitive) to execute. " +
|
|
127
|
+
"Any other value will reject the request without canceling.",
|
|
128
|
+
),
|
|
129
|
+
},
|
|
130
|
+
(args) => handleCancelOrderByClientId(args, client),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -23,7 +23,7 @@ async function cancelAllOrdersForMarket(marketId: string, client: BudaClient): P
|
|
|
23
23
|
const orders = data.orders ?? [];
|
|
24
24
|
await Promise.allSettled(
|
|
25
25
|
orders.map((order) =>
|
|
26
|
-
client.put<OrderResponse>(`/orders/${order.id}`, { state: "canceling" }),
|
|
26
|
+
client.put<OrderResponse>(`/orders/${order.id}`, { order: { state: "canceling" } }),
|
|
27
27
|
),
|
|
28
28
|
);
|
|
29
29
|
timers.delete(marketId);
|
|
@@ -155,6 +155,21 @@ export async function handleScheduleCancelAll(
|
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
if (!Number.isInteger(ttl_seconds) || ttl_seconds < 10 || ttl_seconds > 300) {
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: "text",
|
|
163
|
+
text: JSON.stringify({
|
|
164
|
+
error: "ttl_seconds must be an integer between 10 and 300.",
|
|
165
|
+
code: "VALIDATION_ERROR",
|
|
166
|
+
}),
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
isError: true,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
158
173
|
const id = market_id.toLowerCase();
|
|
159
174
|
const entry = armTimer(id, ttl_seconds, client);
|
|
160
175
|
|
|
@@ -269,7 +284,11 @@ export function handleDisarmCancelTimer(
|
|
|
269
284
|
|
|
270
285
|
// ---- Registration ----
|
|
271
286
|
|
|
272
|
-
export function register(
|
|
287
|
+
export function register(
|
|
288
|
+
server: McpServer,
|
|
289
|
+
client: BudaClient,
|
|
290
|
+
transport: "stdio" | "http" = "stdio",
|
|
291
|
+
): void {
|
|
273
292
|
server.tool(
|
|
274
293
|
toolSchema.name,
|
|
275
294
|
toolSchema.description,
|
|
@@ -287,7 +306,24 @@ export function register(server: McpServer, client: BudaClient): void {
|
|
|
287
306
|
.string()
|
|
288
307
|
.describe("Must equal exactly 'CONFIRM' (case-sensitive) to arm the switch."),
|
|
289
308
|
},
|
|
290
|
-
(args) =>
|
|
309
|
+
(args) => {
|
|
310
|
+
if (transport === "http") {
|
|
311
|
+
return Promise.resolve({
|
|
312
|
+
content: [{
|
|
313
|
+
type: "text" as const,
|
|
314
|
+
text: JSON.stringify({
|
|
315
|
+
error:
|
|
316
|
+
"schedule_cancel_all is not available on the HTTP transport. " +
|
|
317
|
+
"Timer state is lost on every server restart or deploy. " +
|
|
318
|
+
"Run buda-mcp in stdio mode on a persistent local process to use this feature.",
|
|
319
|
+
code: "TRANSPORT_NOT_SUPPORTED",
|
|
320
|
+
}),
|
|
321
|
+
}],
|
|
322
|
+
isError: true,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return handleScheduleCancelAll(args, client);
|
|
326
|
+
},
|
|
291
327
|
);
|
|
292
328
|
|
|
293
329
|
server.tool(
|