@guiie/buda-mcp 1.5.0 → 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.
- package/.cursor/rules/marketplace-docs-sync.mdc +32 -0
- package/CHANGELOG.md +75 -0
- package/PUBLISH_CHECKLIST.md +48 -89
- package/README.md +446 -78
- package/dist/audit.d.ts +21 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +14 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -1
- package/dist/http.js +65 -7
- package/dist/index.js +12 -3
- package/dist/tools/account.js +1 -1
- package/dist/tools/arbitrage.js +1 -1
- package/dist/tools/balance.js +1 -1
- package/dist/tools/balances.js +1 -1
- package/dist/tools/banks.js +1 -1
- package/dist/tools/batch_orders.d.ts +6 -1
- package/dist/tools/batch_orders.d.ts.map +1 -1
- package/dist/tools/batch_orders.js +47 -3
- package/dist/tools/cancel_all_orders.d.ts +1 -1
- package/dist/tools/cancel_all_orders.d.ts.map +1 -1
- package/dist/tools/cancel_all_orders.js +10 -13
- package/dist/tools/cancel_order.d.ts +1 -1
- package/dist/tools/cancel_order.d.ts.map +1 -1
- package/dist/tools/cancel_order.js +10 -10
- package/dist/tools/cancel_order_by_client_id.d.ts +1 -1
- package/dist/tools/cancel_order_by_client_id.d.ts.map +1 -1
- package/dist/tools/cancel_order_by_client_id.js +9 -9
- package/dist/tools/compare_markets.d.ts +9 -0
- package/dist/tools/compare_markets.d.ts.map +1 -1
- package/dist/tools/compare_markets.js +63 -53
- package/dist/tools/dead_mans_switch.d.ts +2 -2
- package/dist/tools/dead_mans_switch.d.ts.map +1 -1
- package/dist/tools/dead_mans_switch.js +68 -6
- package/dist/tools/deposits.js +2 -2
- package/dist/tools/fees.js +1 -1
- package/dist/tools/lightning.d.ts +1 -1
- package/dist/tools/lightning.d.ts.map +1 -1
- package/dist/tools/lightning.js +25 -9
- package/dist/tools/market_sentiment.js +1 -1
- package/dist/tools/market_summary.js +1 -1
- package/dist/tools/markets.js +1 -1
- package/dist/tools/order_lookup.js +2 -2
- package/dist/tools/orderbook.js +1 -1
- package/dist/tools/orders.js +1 -1
- package/dist/tools/place_order.d.ts +1 -1
- package/dist/tools/place_order.d.ts.map +1 -1
- package/dist/tools/place_order.js +53 -4
- package/dist/tools/price_history.js +1 -1
- package/dist/tools/quotation.js +1 -1
- package/dist/tools/receive_addresses.d.ts +6 -1
- package/dist/tools/receive_addresses.d.ts.map +1 -1
- package/dist/tools/receive_addresses.js +37 -13
- package/dist/tools/remittance_recipients.js +2 -2
- package/dist/tools/remittances.d.ts +7 -2
- package/dist/tools/remittances.d.ts.map +1 -1
- package/dist/tools/remittances.js +46 -23
- package/dist/tools/simulate_order.js +1 -1
- package/dist/tools/spread.js +1 -1
- package/dist/tools/technical_indicators.d.ts.map +1 -1
- package/dist/tools/technical_indicators.js +3 -2
- package/dist/tools/ticker.js +1 -1
- package/dist/tools/trades.js +1 -1
- package/dist/tools/volume.js +1 -1
- package/dist/tools/withdrawals.d.ts +1 -1
- package/dist/tools/withdrawals.d.ts.map +1 -1
- package/dist/tools/withdrawals.js +21 -11
- package/dist/utils.d.ts +10 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +29 -1
- package/dist/validation.d.ts +6 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +26 -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 +75 -4
- package/marketplace/gemini-tools.json +325 -2
- package/marketplace/openapi.yaml +160 -1
- package/package.json +2 -1
- package/server.json +2 -2
- package/src/audit.ts +24 -0
- package/src/client.ts +3 -1
- package/src/http.ts +75 -7
- package/src/index.ts +10 -3
- package/src/tools/account.ts +1 -1
- package/src/tools/arbitrage.ts +1 -1
- package/src/tools/balance.ts +1 -1
- package/src/tools/balances.ts +1 -1
- package/src/tools/banks.ts +1 -1
- package/src/tools/batch_orders.ts +52 -2
- package/src/tools/cancel_all_orders.ts +10 -12
- package/src/tools/cancel_order.ts +10 -9
- package/src/tools/cancel_order_by_client_id.ts +9 -8
- package/src/tools/compare_markets.ts +78 -61
- package/src/tools/dead_mans_switch.ts +76 -5
- package/src/tools/deposits.ts +2 -2
- package/src/tools/fees.ts +1 -1
- package/src/tools/lightning.ts +28 -9
- package/src/tools/market_sentiment.ts +1 -1
- package/src/tools/market_summary.ts +1 -1
- package/src/tools/markets.ts +1 -1
- package/src/tools/order_lookup.ts +2 -2
- package/src/tools/orderbook.ts +1 -1
- package/src/tools/orders.ts +1 -1
- package/src/tools/place_order.ts +56 -5
- package/src/tools/price_history.ts +1 -1
- package/src/tools/quotation.ts +1 -1
- package/src/tools/receive_addresses.ts +40 -13
- package/src/tools/remittance_recipients.ts +2 -2
- package/src/tools/remittances.ts +49 -22
- package/src/tools/simulate_order.ts +1 -1
- package/src/tools/spread.ts +1 -1
- package/src/tools/technical_indicators.ts +3 -2
- package/src/tools/ticker.ts +1 -1
- package/src/tools/trades.ts +1 -1
- package/src/tools/volume.ts +1 -1
- package/src/tools/withdrawals.ts +22 -10
- package/src/utils.ts +36 -1
- package/src/validation.ts +29 -0
- package/src/version.ts +11 -3
- package/test/unit.ts +623 -22
|
@@ -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
|
-
|
|
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) ----
|
|
@@ -23,7 +24,7 @@ async function cancelAllOrdersForMarket(marketId: string, client: BudaClient): P
|
|
|
23
24
|
const orders = data.orders ?? [];
|
|
24
25
|
await Promise.allSettled(
|
|
25
26
|
orders.map((order) =>
|
|
26
|
-
client.put<OrderResponse>(`/orders/${order.id}`, { state: "canceling" }),
|
|
27
|
+
client.put<OrderResponse>(`/orders/${order.id}`, { order: { state: "canceling" } }),
|
|
27
28
|
),
|
|
28
29
|
);
|
|
29
30
|
timers.delete(marketId);
|
|
@@ -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
|
|
|
@@ -155,9 +157,25 @@ export async function handleScheduleCancelAll(
|
|
|
155
157
|
};
|
|
156
158
|
}
|
|
157
159
|
|
|
160
|
+
if (!Number.isInteger(ttl_seconds) || ttl_seconds < 10 || ttl_seconds > 300) {
|
|
161
|
+
return {
|
|
162
|
+
content: [
|
|
163
|
+
{
|
|
164
|
+
type: "text",
|
|
165
|
+
text: JSON.stringify({
|
|
166
|
+
error: "ttl_seconds must be an integer between 10 and 300.",
|
|
167
|
+
code: "VALIDATION_ERROR",
|
|
168
|
+
}),
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
isError: true,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
158
175
|
const id = market_id.toLowerCase();
|
|
159
176
|
const entry = armTimer(id, ttl_seconds, client);
|
|
160
177
|
|
|
178
|
+
logAudit({ ts: new Date().toISOString(), tool: "schedule_cancel_all", transport, args_summary: { market_id: market_id.toUpperCase(), ttl_seconds }, success: true });
|
|
161
179
|
return {
|
|
162
180
|
content: [
|
|
163
181
|
{
|
|
@@ -269,7 +287,11 @@ export function handleDisarmCancelTimer(
|
|
|
269
287
|
|
|
270
288
|
// ---- Registration ----
|
|
271
289
|
|
|
272
|
-
export function register(
|
|
290
|
+
export function register(
|
|
291
|
+
server: McpServer,
|
|
292
|
+
client: BudaClient,
|
|
293
|
+
transport: "stdio" | "http" = "stdio",
|
|
294
|
+
): void {
|
|
273
295
|
server.tool(
|
|
274
296
|
toolSchema.name,
|
|
275
297
|
toolSchema.description,
|
|
@@ -287,7 +309,24 @@ export function register(server: McpServer, client: BudaClient): void {
|
|
|
287
309
|
.string()
|
|
288
310
|
.describe("Must equal exactly 'CONFIRM' (case-sensitive) to arm the switch."),
|
|
289
311
|
},
|
|
290
|
-
(args) =>
|
|
312
|
+
(args) => {
|
|
313
|
+
if (transport === "http") {
|
|
314
|
+
return Promise.resolve({
|
|
315
|
+
content: [{
|
|
316
|
+
type: "text" as const,
|
|
317
|
+
text: JSON.stringify({
|
|
318
|
+
error:
|
|
319
|
+
"schedule_cancel_all is not available on the HTTP transport. " +
|
|
320
|
+
"Timer state is lost on every server restart or deploy. " +
|
|
321
|
+
"Run buda-mcp in stdio mode on a persistent local process to use this feature.",
|
|
322
|
+
code: "TRANSPORT_NOT_SUPPORTED",
|
|
323
|
+
}),
|
|
324
|
+
}],
|
|
325
|
+
isError: true,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
return handleScheduleCancelAll(args, client);
|
|
329
|
+
},
|
|
291
330
|
);
|
|
292
331
|
|
|
293
332
|
server.tool(
|
|
@@ -298,7 +337,23 @@ export function register(server: McpServer, client: BudaClient): void {
|
|
|
298
337
|
.string()
|
|
299
338
|
.describe("Market ID whose timer should be renewed (e.g. 'BTC-CLP')."),
|
|
300
339
|
},
|
|
301
|
-
(args) =>
|
|
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
|
+
},
|
|
302
357
|
);
|
|
303
358
|
|
|
304
359
|
server.tool(
|
|
@@ -309,6 +364,22 @@ export function register(server: McpServer, client: BudaClient): void {
|
|
|
309
364
|
.string()
|
|
310
365
|
.describe("Market ID whose timer should be disarmed (e.g. 'BTC-CLP')."),
|
|
311
366
|
},
|
|
312
|
-
(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
|
+
},
|
|
313
384
|
);
|
|
314
385
|
}
|
package/src/tools/deposits.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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) }],
|
package/src/tools/lightning.ts
CHANGED
|
@@ -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,6 +93,22 @@ export async function handleLightningWithdrawal(
|
|
|
91
93
|
};
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
const BOLT11_RE = /^ln(bc|tb|bcrt)\d*[munp]?1[a-z0-9]{20,}$/i;
|
|
97
|
+
if (!BOLT11_RE.test(invoice)) {
|
|
98
|
+
return {
|
|
99
|
+
content: [{
|
|
100
|
+
type: "text",
|
|
101
|
+
text: JSON.stringify({
|
|
102
|
+
error:
|
|
103
|
+
"Invalid Lightning invoice format. " +
|
|
104
|
+
"Expected a BOLT-11 string starting with 'lnbc', 'lntb', or 'lnbcrt'.",
|
|
105
|
+
code: "INVALID_INVOICE",
|
|
106
|
+
}),
|
|
107
|
+
}],
|
|
108
|
+
isError: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
94
112
|
try {
|
|
95
113
|
const data = await client.post<LightningWithdrawalResponse>(
|
|
96
114
|
`/reserves/ln-btc/withdrawals`,
|
|
@@ -101,10 +119,10 @@ export async function handleLightningWithdrawal(
|
|
|
101
119
|
const amount = flattenAmount(lw.amount);
|
|
102
120
|
const fee = flattenAmount(lw.fee);
|
|
103
121
|
|
|
104
|
-
|
|
122
|
+
const result = {
|
|
105
123
|
content: [
|
|
106
124
|
{
|
|
107
|
-
type: "text",
|
|
125
|
+
type: "text" as const,
|
|
108
126
|
text: JSON.stringify(
|
|
109
127
|
{
|
|
110
128
|
id: lw.id,
|
|
@@ -122,15 +140,16 @@ export async function handleLightningWithdrawal(
|
|
|
122
140
|
},
|
|
123
141
|
],
|
|
124
142
|
};
|
|
143
|
+
logAudit({ ts: new Date().toISOString(), tool: "lightning_withdrawal", transport, args_summary: {}, success: true });
|
|
144
|
+
return result;
|
|
125
145
|
} catch (err) {
|
|
126
146
|
const msg =
|
|
127
147
|
err instanceof BudaApiError
|
|
128
|
-
? { error: err.message, code: err.status
|
|
148
|
+
? { error: err.message, code: err.status }
|
|
129
149
|
: { error: String(err), code: "UNKNOWN" };
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
};
|
|
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;
|
|
134
153
|
}
|
|
135
154
|
}
|
|
136
155
|
|
|
@@ -176,7 +195,7 @@ export async function handleCreateLightningInvoice(
|
|
|
176
195
|
} catch (err) {
|
|
177
196
|
const msg =
|
|
178
197
|
err instanceof BudaApiError
|
|
179
|
-
? { error: err.message, code: err.status
|
|
198
|
+
? { error: err.message, code: err.status }
|
|
180
199
|
: { error: String(err), code: "UNKNOWN" };
|
|
181
200
|
return {
|
|
182
201
|
content: [{ type: "text", text: JSON.stringify(msg) }],
|
|
@@ -192,7 +211,7 @@ export function register(server: McpServer, client: BudaClient): void {
|
|
|
192
211
|
{
|
|
193
212
|
invoice: z
|
|
194
213
|
.string()
|
|
195
|
-
.min(
|
|
214
|
+
.min(50)
|
|
196
215
|
.describe("BOLT-11 Lightning invoice string (starts with 'lnbc', 'lntb', etc.)."),
|
|
197
216
|
confirmation_token: z
|
|
198
217
|
.string()
|
|
@@ -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
|
|
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
|
|
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) }],
|
package/src/tools/markets.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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) }],
|
package/src/tools/orderbook.ts
CHANGED
|
@@ -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
|
|
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) }],
|
package/src/tools/orders.ts
CHANGED
|
@@ -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
|
|
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) }],
|
package/src/tools/place_order.ts
CHANGED
|
@@ -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,
|
|
@@ -191,6 +193,38 @@ export async function handlePlaceOrder(
|
|
|
191
193
|
};
|
|
192
194
|
}
|
|
193
195
|
|
|
196
|
+
if (gtd_timestamp !== undefined) {
|
|
197
|
+
const ts = new Date(gtd_timestamp).getTime();
|
|
198
|
+
if (isNaN(ts)) {
|
|
199
|
+
return {
|
|
200
|
+
content: [
|
|
201
|
+
{
|
|
202
|
+
type: "text",
|
|
203
|
+
text: JSON.stringify({
|
|
204
|
+
error: "gtd_timestamp must be a valid ISO 8601 datetime string.",
|
|
205
|
+
code: "VALIDATION_ERROR",
|
|
206
|
+
}),
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
isError: true,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (ts <= Date.now()) {
|
|
213
|
+
return {
|
|
214
|
+
content: [
|
|
215
|
+
{
|
|
216
|
+
type: "text",
|
|
217
|
+
text: JSON.stringify({
|
|
218
|
+
error: "gtd_timestamp must be a future datetime.",
|
|
219
|
+
code: "VALIDATION_ERROR",
|
|
220
|
+
}),
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
isError: true,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
194
228
|
let limitType = "gtc";
|
|
195
229
|
if (ioc) limitType = "ioc";
|
|
196
230
|
else if (fok) limitType = "fok";
|
|
@@ -211,18 +245,35 @@ export async function handlePlaceOrder(
|
|
|
211
245
|
payload,
|
|
212
246
|
);
|
|
213
247
|
|
|
214
|
-
|
|
215
|
-
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) }],
|
|
216
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;
|
|
217
259
|
} catch (err) {
|
|
218
260
|
const msg =
|
|
219
261
|
err instanceof BudaApiError
|
|
220
|
-
? { error: err.message, code: err.status
|
|
262
|
+
? { error: err.message, code: err.status }
|
|
221
263
|
: { error: String(err), code: "UNKNOWN" };
|
|
222
|
-
|
|
223
|
-
content: [{ type: "text", text: JSON.stringify(msg) }],
|
|
264
|
+
const result = {
|
|
265
|
+
content: [{ type: "text" as const, text: JSON.stringify(msg) }],
|
|
224
266
|
isError: true,
|
|
225
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;
|
|
226
277
|
}
|
|
227
278
|
}
|
|
228
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
|
|
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) }],
|
package/src/tools/quotation.ts
CHANGED
|
@@ -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
|
|
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) }],
|