@michaleffffff/mcp-trading-server 2.9.9 → 3.0.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/CHANGELOG.md +32 -0
- package/README.md +107 -8
- package/TOOL_EXAMPLES.md +593 -0
- package/dist/prompts/tradingGuide.js +10 -4
- package/dist/server.js +206 -15
- package/dist/services/marketService.js +101 -21
- package/dist/services/poolService.js +76 -2
- package/dist/services/tradeService.js +116 -11
- package/dist/tools/accountInfo.js +119 -49
- package/dist/tools/adjustMargin.js +1 -1
- package/dist/tools/cancelAllOrders.js +35 -3
- package/dist/tools/index.js +1 -2
- package/dist/tools/manageLiquidity.js +6 -2
- package/dist/tools/marketInfo.js +5 -4
- package/dist/tools/searchMarket.js +3 -3
- package/dist/tools/updateOrderTpSl.js +11 -9
- package/dist/utils/errorMessage.js +69 -0
- package/package.json +5 -2
- package/dist/tools/getBalances.js +0 -17
|
@@ -1,40 +1,132 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
|
+
import { getBalances, getMarginBalance } from "../services/balanceService.js";
|
|
3
4
|
import { getTradeFlowTypeDesc } from "../utils/mappings.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
function readErrorMessage(error) {
|
|
6
|
+
if (error instanceof Error)
|
|
7
|
+
return error.message;
|
|
8
|
+
if (typeof error === "string")
|
|
9
|
+
return error;
|
|
10
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
11
|
+
return String(error.message);
|
|
12
|
+
}
|
|
13
|
+
return String(error ?? "unknown error");
|
|
14
|
+
}
|
|
15
|
+
function assertSdkReadSuccess(result, actionName) {
|
|
16
|
+
if (!result || typeof result !== "object" || Array.isArray(result))
|
|
17
|
+
return;
|
|
18
|
+
if (!Object.prototype.hasOwnProperty.call(result, "code"))
|
|
19
|
+
return;
|
|
20
|
+
const code = Number(result.code);
|
|
21
|
+
if (!Number.isFinite(code) || code === 0)
|
|
22
|
+
return;
|
|
23
|
+
const payload = result.data;
|
|
24
|
+
const hasPayload = payload !== null &&
|
|
25
|
+
payload !== undefined &&
|
|
26
|
+
(!Array.isArray(payload) || payload.length > 0);
|
|
27
|
+
if (code > 0 && hasPayload) {
|
|
28
|
+
// Keep aligned with server-side read normalization:
|
|
29
|
+
// positive non-zero code with usable payload is warning-like.
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const message = String(result.msg ?? result.message ?? "unknown error");
|
|
33
|
+
throw new Error(`${actionName} failed: code=${code}, msg=${message}`);
|
|
34
|
+
}
|
|
35
|
+
function normalizeAccountInfo(result) {
|
|
36
|
+
if (!(result && result.code === 0 && Array.isArray(result.data))) {
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
const values = result.data;
|
|
40
|
+
return {
|
|
41
|
+
...result,
|
|
42
|
+
data: {
|
|
43
|
+
freeMargin: values[0],
|
|
44
|
+
walletBalance: values[1],
|
|
45
|
+
freeBaseAmount: values[2],
|
|
46
|
+
baseProfit: values[3],
|
|
47
|
+
quoteProfit: values[4],
|
|
48
|
+
reservedAmount: values[5],
|
|
49
|
+
releaseTime: values[6],
|
|
50
|
+
tradeableMargin: (BigInt(values[0]) + BigInt(values[1]) + (BigInt(values[6]) === 0n ? BigInt(values[4]) : 0n)).toString(),
|
|
51
|
+
baseProfitStatus: "base token to be unlocked",
|
|
52
|
+
quoteProfitStatus: BigInt(values[6]) > 0n ? "quote token to be unlocked" : "quote token unlocked/available"
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export const getAccountTool = {
|
|
57
|
+
name: "get_account",
|
|
58
|
+
description: "Unified account snapshot. Distinguishes wallet balance vs trading-account metrics. Provide poolId for full trading-account details.",
|
|
7
59
|
schema: {
|
|
8
|
-
poolId: z.string().describe("Pool ID"),
|
|
60
|
+
poolId: z.string().optional().describe("Optional Pool ID. Required to include trading-account metrics (account info + margin)."),
|
|
9
61
|
},
|
|
10
62
|
handler: async (args) => {
|
|
11
63
|
try {
|
|
12
64
|
const { client, address } = await resolveClient();
|
|
13
65
|
const chainId = getChainId();
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
66
|
+
const poolId = args.poolId?.trim();
|
|
67
|
+
const overview = {
|
|
68
|
+
meta: {
|
|
69
|
+
address,
|
|
70
|
+
chainId,
|
|
71
|
+
poolId: poolId ?? null,
|
|
72
|
+
partial: false,
|
|
73
|
+
},
|
|
74
|
+
wallet: {
|
|
75
|
+
scope: "wallet_balance",
|
|
76
|
+
description: "On-chain wallet quote-token balance (funds not yet deposited into trading account).",
|
|
77
|
+
quoteTokenBalance: null,
|
|
78
|
+
raw: null,
|
|
79
|
+
error: null,
|
|
80
|
+
},
|
|
81
|
+
tradingAccount: {
|
|
82
|
+
scope: "trading_account_balance",
|
|
83
|
+
description: poolId
|
|
84
|
+
? "Trading-account metrics for the selected pool."
|
|
85
|
+
: "Provide poolId to include trading-account metrics (account info + margin).",
|
|
86
|
+
poolId: poolId ?? null,
|
|
87
|
+
accountInfo: null,
|
|
88
|
+
marginBalance: null,
|
|
89
|
+
error: null,
|
|
90
|
+
},
|
|
91
|
+
warnings: [],
|
|
92
|
+
};
|
|
93
|
+
try {
|
|
94
|
+
const walletBalances = await getBalances(client, address);
|
|
95
|
+
assertSdkReadSuccess(walletBalances, "getWalletQuoteTokenBalance");
|
|
96
|
+
overview.wallet.quoteTokenBalance = walletBalances?.data ?? walletBalances;
|
|
97
|
+
overview.wallet.raw = walletBalances;
|
|
98
|
+
}
|
|
99
|
+
catch (walletError) {
|
|
100
|
+
overview.meta.partial = true;
|
|
101
|
+
overview.wallet.error = readErrorMessage(walletError);
|
|
102
|
+
overview.warnings.push(`wallet section failed: ${overview.wallet.error}`);
|
|
103
|
+
}
|
|
104
|
+
if (poolId) {
|
|
105
|
+
try {
|
|
106
|
+
const [accountInfoRaw, marginBalanceRaw] = await Promise.all([
|
|
107
|
+
client.account.getAccountInfo(chainId, address, poolId),
|
|
108
|
+
getMarginBalance(client, address, poolId),
|
|
109
|
+
]);
|
|
110
|
+
assertSdkReadSuccess(accountInfoRaw, "getAccountInfo");
|
|
111
|
+
assertSdkReadSuccess(marginBalanceRaw, "getAvailableMarginBalance");
|
|
112
|
+
overview.tradingAccount.accountInfo = normalizeAccountInfo(accountInfoRaw);
|
|
113
|
+
overview.tradingAccount.marginBalance = marginBalanceRaw;
|
|
114
|
+
}
|
|
115
|
+
catch (tradingError) {
|
|
116
|
+
overview.meta.partial = true;
|
|
117
|
+
overview.tradingAccount.error = readErrorMessage(tradingError);
|
|
118
|
+
overview.warnings.push(`tradingAccount section failed: ${overview.tradingAccount.error}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (overview.warnings.length === 0) {
|
|
122
|
+
delete overview.warnings;
|
|
123
|
+
}
|
|
124
|
+
const hasWalletData = overview.wallet.quoteTokenBalance !== null;
|
|
125
|
+
const hasTradingData = overview.tradingAccount.accountInfo !== null || overview.tradingAccount.marginBalance !== null;
|
|
126
|
+
if (!hasWalletData && !hasTradingData) {
|
|
127
|
+
throw new Error("Failed to build account snapshot: wallet and trading-account sections both unavailable.");
|
|
36
128
|
}
|
|
37
|
-
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data:
|
|
129
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: overview }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
38
130
|
}
|
|
39
131
|
catch (error) {
|
|
40
132
|
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
@@ -65,25 +157,3 @@ export const getTradeFlowTool = {
|
|
|
65
157
|
}
|
|
66
158
|
},
|
|
67
159
|
};
|
|
68
|
-
export const getMarginBalanceTool = {
|
|
69
|
-
name: "get_margin_balance",
|
|
70
|
-
description: "Get available margin balance for a specific pool.",
|
|
71
|
-
schema: {
|
|
72
|
-
poolId: z.string().describe("Pool ID"),
|
|
73
|
-
},
|
|
74
|
-
handler: async (args) => {
|
|
75
|
-
try {
|
|
76
|
-
const { client, address } = await resolveClient();
|
|
77
|
-
const chainId = getChainId();
|
|
78
|
-
const result = await client.account.getAvailableMarginBalance({
|
|
79
|
-
poolId: args.poolId,
|
|
80
|
-
chainId,
|
|
81
|
-
address,
|
|
82
|
-
});
|
|
83
|
-
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
};
|
|
@@ -8,7 +8,7 @@ export const adjustMarginTool = {
|
|
|
8
8
|
schema: {
|
|
9
9
|
poolId: z.string().describe("Pool ID"),
|
|
10
10
|
positionId: z.string().describe("Position ID"),
|
|
11
|
-
adjustAmount: z.
|
|
11
|
+
adjustAmount: z.union([z.string(), z.number()]).describe("Adjust amount. Human units are supported (e.g. '1' = 1 USDC). Use 'raw:<int>' for exact raw units."),
|
|
12
12
|
quoteToken: z.string().optional().describe("Quote token address"),
|
|
13
13
|
poolOracleType: z.coerce.number().optional().describe("Oracle type: 1 for Chainlink, 2 for Pyth"),
|
|
14
14
|
},
|
|
@@ -1,17 +1,49 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
3
|
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
4
|
+
function normalizeOrderIds(input) {
|
|
5
|
+
if (Array.isArray(input)) {
|
|
6
|
+
return input.map((id) => String(id).trim()).filter(Boolean);
|
|
7
|
+
}
|
|
8
|
+
if (typeof input === "string") {
|
|
9
|
+
const text = input.trim();
|
|
10
|
+
if (!text)
|
|
11
|
+
return [];
|
|
12
|
+
// Support toolchains that serialize arrays as JSON strings.
|
|
13
|
+
if (text.startsWith("[") && text.endsWith("]")) {
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(text);
|
|
16
|
+
if (Array.isArray(parsed)) {
|
|
17
|
+
return parsed.map((id) => String(id).trim()).filter(Boolean);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Fallback to comma/single parsing below.
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (text.includes(",")) {
|
|
25
|
+
return text.split(",").map((id) => id.trim()).filter(Boolean);
|
|
26
|
+
}
|
|
27
|
+
return [text];
|
|
28
|
+
}
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
4
31
|
export const cancelAllOrdersTool = {
|
|
5
32
|
name: "cancel_all_orders",
|
|
6
|
-
description: "Cancel
|
|
33
|
+
description: "Cancel open orders by orderIds. Accepts array, JSON-array string, comma-separated string, or single orderId.",
|
|
7
34
|
schema: {
|
|
8
|
-
orderIds: z
|
|
35
|
+
orderIds: z
|
|
36
|
+
.union([z.array(z.string()).min(1), z.string().min(1)])
|
|
37
|
+
.describe("Order IDs to cancel. Supports array or string (single/comma/JSON-array)."),
|
|
9
38
|
},
|
|
10
39
|
handler: async (args) => {
|
|
11
40
|
try {
|
|
12
41
|
const { client, signer } = await resolveClient();
|
|
13
42
|
const chainId = getChainId();
|
|
14
|
-
const orderIds = args.orderIds;
|
|
43
|
+
const orderIds = normalizeOrderIds(args.orderIds);
|
|
44
|
+
if (orderIds.length === 0) {
|
|
45
|
+
throw new Error("orderIds is required and must include at least one non-empty order id.");
|
|
46
|
+
}
|
|
15
47
|
const raw = await client.order.cancelAllOrders(orderIds, chainId);
|
|
16
48
|
const result = await finalizeMutationResult(raw, signer, "cancel_all_orders");
|
|
17
49
|
return {
|
package/dist/tools/index.js
CHANGED
|
@@ -28,10 +28,9 @@ export { createPerpMarketTool } from "./createPerpMarket.js";
|
|
|
28
28
|
export { manageLiquidityTool, getLpPriceTool } from "./manageLiquidity.js";
|
|
29
29
|
// Tools — 账户 & 查询
|
|
30
30
|
export { getPositionsTool } from "./getPositions.js";
|
|
31
|
-
export { getBalancesTool } from "./getBalances.js";
|
|
32
31
|
export { getOpenOrdersTool, getOrderHistoryTool } from "./orderQueries.js";
|
|
33
32
|
export { getPositionHistoryTool } from "./positionHistory.js";
|
|
34
|
-
export {
|
|
33
|
+
export { getAccountTool, getTradeFlowTool } from "./accountInfo.js";
|
|
35
34
|
export { getAccountVipInfoTool } from "./getAccountVipInfo.js";
|
|
36
35
|
export { accountDepositTool, accountWithdrawTool } from "./accountTransfer.js";
|
|
37
36
|
export { getMarketListTool } from "./getMarketList.js";
|
|
@@ -3,6 +3,7 @@ import { quoteDeposit, quoteWithdraw, baseDeposit, baseWithdraw, getLpPrice, } f
|
|
|
3
3
|
import { resolveClient } from "../auth/resolveClient.js";
|
|
4
4
|
import { resolvePool } from "../services/marketService.js";
|
|
5
5
|
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
6
|
+
import { extractErrorMessage } from "../utils/errorMessage.js";
|
|
6
7
|
export const manageLiquidityTool = {
|
|
7
8
|
name: "manage_liquidity",
|
|
8
9
|
description: "Add or withdraw liquidity from a BASE or QUOTE pool.",
|
|
@@ -40,11 +41,14 @@ export const manageLiquidityTool = {
|
|
|
40
41
|
if (!raw) {
|
|
41
42
|
throw new Error(`SDK returned an empty result for liquidity ${action}. This usually occurs if the pool is not in an Active state (state: 2) or if there is a contract-level restriction. Please check pool_info.`);
|
|
42
43
|
}
|
|
44
|
+
if (raw && typeof raw === "object" && "code" in raw && Number(raw.code) !== 0) {
|
|
45
|
+
throw new Error(`Liquidity ${action} failed: ${extractErrorMessage(raw)}`);
|
|
46
|
+
}
|
|
43
47
|
const data = await finalizeMutationResult(raw, signer, "manage_liquidity");
|
|
44
48
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
45
49
|
}
|
|
46
50
|
catch (error) {
|
|
47
|
-
return { content: [{ type: "text", text: `Error: ${error
|
|
51
|
+
return { content: [{ type: "text", text: `Error: ${extractErrorMessage(error)}` }], isError: true };
|
|
48
52
|
}
|
|
49
53
|
},
|
|
50
54
|
};
|
|
@@ -62,7 +66,7 @@ export const getLpPriceTool = {
|
|
|
62
66
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
63
67
|
}
|
|
64
68
|
catch (error) {
|
|
65
|
-
return { content: [{ type: "text", text: `Error: ${error
|
|
69
|
+
return { content: [{ type: "text", text: `Error: ${extractErrorMessage(error)}` }], isError: true };
|
|
66
70
|
}
|
|
67
71
|
},
|
|
68
72
|
};
|
package/dist/tools/marketInfo.js
CHANGED
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
3
|
import { getMarketDetail, resolvePool } from "../services/marketService.js";
|
|
4
4
|
import { getPoolInfo, getLiquidityInfo } from "../services/poolService.js";
|
|
5
|
+
import { extractErrorMessage } from "../utils/errorMessage.js";
|
|
5
6
|
export const getMarketDetailTool = {
|
|
6
7
|
name: "get_market_detail",
|
|
7
8
|
description: "Get detailed information for a specific trading pool (fee rates, open interest, etc.). Supports PoolId, Token Address, or Keywords.",
|
|
@@ -20,7 +21,7 @@ export const getMarketDetailTool = {
|
|
|
20
21
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
21
22
|
}
|
|
22
23
|
catch (error) {
|
|
23
|
-
return { content: [{ type: "text", text: `Error: ${error
|
|
24
|
+
return { content: [{ type: "text", text: `Error: ${extractErrorMessage(error)}` }], isError: true };
|
|
24
25
|
}
|
|
25
26
|
},
|
|
26
27
|
};
|
|
@@ -36,13 +37,13 @@ export const getPoolInfoTool = {
|
|
|
36
37
|
const { client } = await resolveClient();
|
|
37
38
|
const poolId = await resolvePool(client, args.poolId);
|
|
38
39
|
const chainId = args.chainId ?? getChainId();
|
|
39
|
-
const data = await getPoolInfo(poolId, chainId);
|
|
40
|
+
const data = await getPoolInfo(poolId, chainId, client);
|
|
40
41
|
if (!data)
|
|
41
42
|
throw new Error(`Pool info for ${poolId} returned undefined. The pool may not exist on chainId ${chainId}.`);
|
|
42
43
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
43
44
|
}
|
|
44
45
|
catch (error) {
|
|
45
|
-
return { content: [{ type: "text", text: `Error: ${error
|
|
46
|
+
return { content: [{ type: "text", text: `Error: ${extractErrorMessage(error)}` }], isError: true };
|
|
46
47
|
}
|
|
47
48
|
},
|
|
48
49
|
};
|
|
@@ -65,7 +66,7 @@ export const getLiquidityInfoTool = {
|
|
|
65
66
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
66
67
|
}
|
|
67
68
|
catch (error) {
|
|
68
|
-
return { content: [{ type: "text", text: `Error: ${error
|
|
69
|
+
return { content: [{ type: "text", text: `Error: ${extractErrorMessage(error)}` }], isError: true };
|
|
69
70
|
}
|
|
70
71
|
},
|
|
71
72
|
};
|
|
@@ -3,15 +3,15 @@ import { resolveClient } from "../auth/resolveClient.js";
|
|
|
3
3
|
import { searchMarket } from "../services/marketService.js";
|
|
4
4
|
export const searchMarketTool = {
|
|
5
5
|
name: "search_market",
|
|
6
|
-
description: "Search
|
|
6
|
+
description: "Search active markets by keyword. Leave keyword empty to list active markets.",
|
|
7
7
|
schema: {
|
|
8
|
-
keyword: z.string().describe('Search keyword, e.g. "BTC", "ETH"'),
|
|
8
|
+
keyword: z.string().optional().describe('Search keyword, e.g. "BTC", "ETH". Leave empty to return active markets.'),
|
|
9
9
|
limit: z.number().int().positive().optional().describe("Max results (default 100)"),
|
|
10
10
|
},
|
|
11
11
|
handler: async (args) => {
|
|
12
12
|
try {
|
|
13
13
|
const { client } = await resolveClient();
|
|
14
|
-
const activeMarkets = await searchMarket(client, args.keyword, args.limit);
|
|
14
|
+
const activeMarkets = await searchMarket(client, args.keyword ?? "", args.limit ?? 100);
|
|
15
15
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: activeMarkets }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
16
16
|
}
|
|
17
17
|
catch (error) {
|
|
@@ -2,20 +2,22 @@ import { z } from "zod";
|
|
|
2
2
|
import { resolveClient } from "../auth/resolveClient.js";
|
|
3
3
|
import { updateOrderTpSl } from "../services/tradeService.js";
|
|
4
4
|
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
5
|
+
import { extractErrorMessage } from "../utils/errorMessage.js";
|
|
5
6
|
export const updateOrderTpSlTool = {
|
|
6
7
|
name: "update_order_tp_sl",
|
|
7
8
|
description: "Update an existing take profit or stop loss order.",
|
|
8
9
|
schema: {
|
|
9
10
|
orderId: z.string().describe("The ID of the order to update"),
|
|
10
11
|
marketId: z.string().describe("The market ID (config hash) for the order"),
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
poolId: z.string().optional().describe("Optional poolId hint for decimal resolution."),
|
|
13
|
+
size: z.union([z.string(), z.number()]).describe("Order size (raw or human-readable)"),
|
|
14
|
+
price: z.union([z.string(), z.number()]).describe("Order price (raw or human-readable, 30 decimals)"),
|
|
15
|
+
tpPrice: z.union([z.string(), z.number()]).describe("TP price (raw or human-readable, 30 decimals)"),
|
|
16
|
+
tpSize: z.union([z.string(), z.number()]).describe("TP size (raw or human-readable)"),
|
|
17
|
+
slPrice: z.union([z.string(), z.number()]).describe("SL price (raw or human-readable, 30 decimals)"),
|
|
18
|
+
slSize: z.union([z.string(), z.number()]).describe("SL size (raw or human-readable)"),
|
|
19
|
+
useOrderCollateral: z.coerce.boolean().describe("Whether to use order collateral"),
|
|
20
|
+
isTpSlOrder: z.coerce.boolean().optional().describe("Whether this is a TP/SL order"),
|
|
19
21
|
quoteToken: z.string().describe("Quote token address"),
|
|
20
22
|
},
|
|
21
23
|
handler: async (args) => {
|
|
@@ -26,7 +28,7 @@ export const updateOrderTpSlTool = {
|
|
|
26
28
|
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
27
29
|
}
|
|
28
30
|
catch (error) {
|
|
29
|
-
return { content: [{ type: "text", text: `Error: ${error
|
|
31
|
+
return { content: [{ type: "text", text: `Error: ${extractErrorMessage(error)}` }], isError: true };
|
|
30
32
|
}
|
|
31
33
|
},
|
|
32
34
|
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const USELESS_MESSAGE_SET = new Set([
|
|
2
|
+
"",
|
|
3
|
+
"undefined",
|
|
4
|
+
"null",
|
|
5
|
+
"[object object]",
|
|
6
|
+
"{}",
|
|
7
|
+
]);
|
|
8
|
+
function cleanMessage(input) {
|
|
9
|
+
if (typeof input !== "string")
|
|
10
|
+
return null;
|
|
11
|
+
const text = input.trim();
|
|
12
|
+
if (!text)
|
|
13
|
+
return null;
|
|
14
|
+
if (USELESS_MESSAGE_SET.has(text.toLowerCase()))
|
|
15
|
+
return null;
|
|
16
|
+
return text;
|
|
17
|
+
}
|
|
18
|
+
function isRecord(value) {
|
|
19
|
+
return !!value && typeof value === "object";
|
|
20
|
+
}
|
|
21
|
+
function safeStringify(value) {
|
|
22
|
+
try {
|
|
23
|
+
return cleanMessage(JSON.stringify(value, (_, item) => (typeof item === "bigint" ? item.toString() : item)));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return cleanMessage(String(value ?? ""));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function isMeaningfulErrorMessage(message) {
|
|
30
|
+
return cleanMessage(message) !== null;
|
|
31
|
+
}
|
|
32
|
+
export function extractErrorMessage(error, fallback = "Unknown error") {
|
|
33
|
+
const visited = new Set();
|
|
34
|
+
const read = (value, depth) => {
|
|
35
|
+
if (depth > 6 || value === null || value === undefined)
|
|
36
|
+
return null;
|
|
37
|
+
if (typeof value === "string")
|
|
38
|
+
return cleanMessage(value);
|
|
39
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
40
|
+
return cleanMessage(String(value));
|
|
41
|
+
}
|
|
42
|
+
if (value instanceof Error) {
|
|
43
|
+
const message = cleanMessage(value.message);
|
|
44
|
+
if (message)
|
|
45
|
+
return message;
|
|
46
|
+
return read(value.cause, depth + 1);
|
|
47
|
+
}
|
|
48
|
+
if (!isRecord(value)) {
|
|
49
|
+
return cleanMessage(String(value));
|
|
50
|
+
}
|
|
51
|
+
if (visited.has(value))
|
|
52
|
+
return null;
|
|
53
|
+
visited.add(value);
|
|
54
|
+
const directKeys = ["message", "reason", "shortMessage", "msg", "detail", "error_description"];
|
|
55
|
+
for (const key of directKeys) {
|
|
56
|
+
const message = read(value[key], depth + 1);
|
|
57
|
+
if (message)
|
|
58
|
+
return message;
|
|
59
|
+
}
|
|
60
|
+
const nestedKeys = ["error", "data", "cause", "response", "info"];
|
|
61
|
+
for (const key of nestedKeys) {
|
|
62
|
+
const message = read(value[key], depth + 1);
|
|
63
|
+
if (message)
|
|
64
|
+
return message;
|
|
65
|
+
}
|
|
66
|
+
return safeStringify(value);
|
|
67
|
+
};
|
|
68
|
+
return read(error, 0) ?? fallback;
|
|
69
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michaleffffff/mcp-trading-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"myx-mcp": "dist/server.js"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
|
-
"dist"
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"TOOL_EXAMPLES.md",
|
|
12
|
+
"CHANGELOG.md"
|
|
10
13
|
],
|
|
11
14
|
"scripts": {
|
|
12
15
|
"build": "rm -rf dist && tsc",
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { resolveClient } from "../auth/resolveClient.js";
|
|
2
|
-
import { getBalances } from "../services/balanceService.js";
|
|
3
|
-
export const getBalancesTool = {
|
|
4
|
-
name: "get_balances",
|
|
5
|
-
description: "Get the account balances for different tokens.",
|
|
6
|
-
schema: {},
|
|
7
|
-
handler: async () => {
|
|
8
|
-
try {
|
|
9
|
-
const { client, address } = await resolveClient();
|
|
10
|
-
const data = await getBalances(client, address);
|
|
11
|
-
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
|
|
12
|
-
}
|
|
13
|
-
catch (error) {
|
|
14
|
-
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
};
|