@michaleffffff/mcp-trading-server 2.3.0 → 2.3.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.
@@ -1,23 +1,26 @@
1
1
  import { MyxClient } from "@myx-trade/sdk";
2
2
  import { JsonRpcProvider, Wallet } from "ethers";
3
+ import { normalizeAddress } from "../utils/address.js";
3
4
  let cached = null;
4
5
  export async function resolveClient() {
5
6
  if (cached)
6
7
  return cached;
7
8
  const rpcUrl = process.env.RPC_URL || "https://rpc.sepolia.linea.build";
8
9
  const privateKey = process.env.PRIVATE_KEY;
9
- const brokerAddress = process.env.BROKER_ADDRESS || "0xAc6C93eaBDc3DBE4e1B0176914dc2a16f8Fd1800";
10
+ const brokerAddressRaw = process.env.BROKER_ADDRESS || "0xAc6C93eaBDc3DBE4e1B0176914dc2a16f8Fd1800";
10
11
  const chainId = Number(process.env.CHAIN_ID) || 59141;
11
- const quoteToken = process.env.QUOTE_TOKEN_ADDRESS || "0xD984fd34f91F92DA0586e1bE82E262fF27DC431b";
12
+ const quoteTokenRaw = process.env.QUOTE_TOKEN_ADDRESS || "0xD984fd34f91F92DA0586e1bE82E262fF27DC431b";
12
13
  const quoteDecimals = Number(process.env.QUOTE_TOKEN_DECIMALS) || 6;
13
14
  if (!rpcUrl)
14
15
  throw new Error("RPC_URL env var is required.");
15
16
  if (!privateKey)
16
17
  throw new Error("PRIVATE_KEY env var is required.");
17
- if (!brokerAddress)
18
+ if (!brokerAddressRaw)
18
19
  throw new Error("BROKER_ADDRESS env var is required.");
19
- if (!quoteToken)
20
+ if (!quoteTokenRaw)
20
21
  throw new Error("QUOTE_TOKEN_ADDRESS env var is required.");
22
+ const brokerAddress = normalizeAddress(brokerAddressRaw, "BROKER_ADDRESS");
23
+ const quoteToken = normalizeAddress(quoteTokenRaw, "QUOTE_TOKEN_ADDRESS");
21
24
  const provider = new JsonRpcProvider(rpcUrl);
22
25
  const signer = new Wallet(privateKey, provider);
23
26
  // Inject the EIP-1193 mock so SDK can sign transactions seamlessly
@@ -41,10 +44,10 @@ export function getChainId() {
41
44
  export function getQuoteToken() {
42
45
  if (cached)
43
46
  return cached.quoteToken;
44
- const token = process.env.QUOTE_TOKEN_ADDRESS || "0xD984fd34f91F92DA0586e1bE82E262fF27DC431b";
45
- if (!token)
47
+ const tokenRaw = process.env.QUOTE_TOKEN_ADDRESS || "0xD984fd34f91F92DA0586e1bE82E262fF27DC431b";
48
+ if (!tokenRaw)
46
49
  throw new Error("QUOTE_TOKEN_ADDRESS env var is required.");
47
- return token;
50
+ return normalizeAddress(tokenRaw, "QUOTE_TOKEN_ADDRESS");
48
51
  }
49
52
  export function getQuoteDecimals() {
50
53
  if (cached)
package/dist/server.js CHANGED
@@ -35,27 +35,41 @@ const allResources = Object.values(baseResources);
35
35
  const allPrompts = Object.values(basePrompts);
36
36
  // 将 Zod schema 转换为 JSON Schema (tool listing 用)
37
37
  function zodSchemaToJsonSchema(zodSchema) {
38
+ const toPropSchema = (value) => {
39
+ const def = value?._def;
40
+ const typeName = def?.typeName;
41
+ if (typeName === "ZodOptional" || typeName === "ZodNullable") {
42
+ return toPropSchema(def?.innerType);
43
+ }
44
+ if (typeName === "ZodString")
45
+ return { type: "string" };
46
+ if (typeName === "ZodNumber")
47
+ return { type: "number" };
48
+ if (typeName === "ZodBoolean")
49
+ return { type: "boolean" };
50
+ if (typeName === "ZodArray") {
51
+ return { type: "array", items: toPropSchema(def?.type) };
52
+ }
53
+ if (typeName === "ZodLiteral") {
54
+ return { const: def?.value };
55
+ }
56
+ if (typeName === "ZodUnion") {
57
+ const options = def?.options || [];
58
+ return { anyOf: options.map((opt) => toPropSchema(opt)) };
59
+ }
60
+ if (typeName === "ZodObject") {
61
+ return { type: "object" };
62
+ }
63
+ return { type: "string" };
64
+ };
38
65
  const properties = {};
39
66
  const required = [];
40
67
  for (const [key, value] of Object.entries(zodSchema)) {
41
68
  const desc = value?._def?.description || "";
42
69
  const isOptional = value?.isOptional?.() || value?._def?.typeName === "ZodOptional";
43
- // 提取内部类型 (如果是 optional)
44
- const innerDef = isOptional ? value?._def?.innerType?._def : value?._def;
45
- const typeName = innerDef?.typeName;
46
- let jsonType = "string";
47
- if (typeName === "ZodNumber")
48
- jsonType = "number";
49
- else if (typeName === "ZodBoolean")
50
- jsonType = "boolean";
51
- else if (typeName === "ZodArray")
52
- jsonType = "array";
53
- const prop = { type: jsonType };
70
+ const prop = toPropSchema(value);
54
71
  if (desc)
55
72
  prop.description = desc;
56
- if (jsonType === "array") {
57
- prop.items = { type: "string" };
58
- }
59
73
  properties[key] = prop;
60
74
  if (!isOptional)
61
75
  required.push(key);
@@ -67,7 +81,7 @@ function zodSchemaToJsonSchema(zodSchema) {
67
81
  };
68
82
  }
69
83
  // ─── MCP Server ───
70
- const server = new Server({ name: "myx-mcp-trading-server", version: "2.3.0" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
84
+ const server = new Server({ name: "myx-mcp-trading-server", version: "2.3.1" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
71
85
  // List tools
72
86
  server.setRequestHandler(ListToolsRequestSchema, async () => {
73
87
  return {
@@ -89,9 +103,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
89
103
  };
90
104
  }
91
105
  try {
92
- let validatedArgs = args;
106
+ let validatedArgs = args ?? {};
93
107
  if (tool.schema) {
94
- validatedArgs = z.object(tool.schema).parse(args);
108
+ validatedArgs = z.object(tool.schema).parse(args ?? {});
95
109
  }
96
110
  logger.toolExecution(name, validatedArgs);
97
111
  const result = await tool.handler(validatedArgs);
@@ -115,7 +129,7 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
115
129
  resources: allResources.map((r) => ({
116
130
  uri: r.uri,
117
131
  name: r.name,
118
- mimetype: r.mimetype,
132
+ mimeType: r.mimetype,
119
133
  description: r.description
120
134
  }))
121
135
  };
@@ -167,7 +181,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
167
181
  async function main() {
168
182
  const transport = new StdioServerTransport();
169
183
  await server.connect(transport);
170
- logger.info("🚀 MYX Trading MCP Server v2.3.0 running (stdio, pure on-chain, prod ready)");
184
+ logger.info("🚀 MYX Trading MCP Server v2.3.1 running (stdio, pure on-chain, prod ready)");
171
185
  }
172
186
  main().catch((err) => {
173
187
  logger.error("Fatal Server Startup Error", err);
@@ -12,7 +12,7 @@ export async function getOraclePrice(client, poolId) {
12
12
  const chainId = getChainId();
13
13
  return client.utils.getOraclePrice(poolId, chainId);
14
14
  }
15
- export async function searchMarket(client, keyword, limit = 100) {
15
+ export async function searchMarket(client, keyword, limit = 1000) {
16
16
  const chainId = getChainId();
17
17
  const searchRes = await client.markets.searchMarket({ chainId, searchKey: keyword, limit });
18
18
  const raw = searchRes;
@@ -29,8 +29,8 @@ export async function searchMarket(client, keyword, limit = 100) {
29
29
  else if (raw && Array.isArray(raw)) {
30
30
  dataList = raw;
31
31
  }
32
- // Filter active markets
33
- const activeMarkets = dataList.filter(m => m.state > 0);
32
+ // Filter tradable markets (state=2 => Active)
33
+ const activeMarkets = dataList.filter(m => Number(m.state) === 2);
34
34
  // Get tickers for these pools to get price and change24h
35
35
  let tickers = [];
36
36
  if (activeMarkets.length > 0) {
@@ -59,7 +59,11 @@ export async function searchMarket(client, keyword, limit = 100) {
59
59
  }
60
60
  export async function getMarketDetail(client, poolId) {
61
61
  const chainId = getChainId();
62
- return client.markets.getMarketDetail({ chainId, poolId });
62
+ const res = await client.markets.getMarketDetail({ chainId, poolId });
63
+ // Ensure it's returned as { data: ... } for consistency with other services if needed,
64
+ // but looking at existing tools, they often stringify the whole result.
65
+ // Let's just normalize it to always have the data if it's missing.
66
+ return res?.marketId ? { data: res } : res;
63
67
  }
64
68
  /**
65
69
  * 获取所有池子列表
@@ -2,6 +2,7 @@ import { Direction, OrderType, TriggerType } from "@myx-trade/sdk";
2
2
  import { parseUnits } from "ethers";
3
3
  import { getChainId, getQuoteToken, getQuoteDecimals } from "../auth/resolveClient.js";
4
4
  import { ensureUnits, parseHumanUnits } from "../utils/units.js";
5
+ import { normalizeAddress } from "../utils/address.js";
5
6
  /**
6
7
  * 将人类可读数值转为指定精度的整数字串
7
8
  * e.g. toUnits("100", 6) => "100000000"
@@ -9,6 +10,12 @@ import { ensureUnits, parseHumanUnits } from "../utils/units.js";
9
10
  export function toUnits(human, decimals) {
10
11
  return parseUnits(human, decimals).toString();
11
12
  }
13
+ function resolveDirection(direction) {
14
+ if (direction !== 0 && direction !== 1) {
15
+ throw new Error("direction must be 0 (LONG) or 1 (SHORT).");
16
+ }
17
+ return direction === 0 ? Direction.LONG : Direction.SHORT;
18
+ }
12
19
  /**
13
20
  * 开仓 / 加仓
14
21
  */
@@ -18,7 +25,7 @@ export async function openPosition(client, address, args) {
18
25
  throw new Error(`Security Exception: collateralAmount (${args.collateralAmount}) exceeds MAX_TRADE_AMOUNT (${maxTradeAmount}). Update .env if you need to trade larger sizes.`);
19
26
  }
20
27
  const chainId = getChainId();
21
- const quoteToken = args.quoteToken || getQuoteToken();
28
+ const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
22
29
  const quoteDecimals = args.quoteDecimals ?? getQuoteDecimals();
23
30
  const rawLeverage = args.leverage ?? 10;
24
31
  if (!Number.isFinite(rawLeverage) || rawLeverage <= 0) {
@@ -53,7 +60,7 @@ export async function openPosition(client, address, args) {
53
60
  const exponent = BigInt(18 + 30 - quoteDecimals);
54
61
  size = ((collateralBig * leverageBig * (10n ** exponent)) / priceBig).toString();
55
62
  }
56
- const dir = args.direction === 0 ? Direction.LONG : Direction.SHORT;
63
+ const dir = resolveDirection(args.direction);
57
64
  // 根据方向决定 triggerType: 做多分级(如果现价>限价则是低买,需等跌,用 LTE;反之亦然,这里简化为通用 LTE(多)/GTE(空))
58
65
  const triggerType = args.price
59
66
  ? (args.direction === 0 ? TriggerType.LTE : TriggerType.GTE)
@@ -72,7 +79,7 @@ export async function openPosition(client, address, args) {
72
79
  price: orderPrice30,
73
80
  timeInForce: 0,
74
81
  postOnly: false,
75
- slippagePct: args.slippagePct || "100", // BPS
82
+ slippagePct: args.slippagePct ?? "100", // BPS
76
83
  executionFeeToken: quoteToken,
77
84
  leverage,
78
85
  };
@@ -89,7 +96,7 @@ export async function openPosition(client, address, args) {
89
96
  const tradingFeeStr = (args.tradingFee || "0").toString();
90
97
  // 获取 marketId,SDK 0.1.267 要求 as 3rd parameter
91
98
  const marketDetailRes = await client.markets.getMarketDetail({ chainId, poolId: args.poolId });
92
- const marketId = marketDetailRes?.data?.marketId;
99
+ const marketId = marketDetailRes?.marketId || marketDetailRes?.data?.marketId;
93
100
  if (!marketId) {
94
101
  throw new Error(`Could not find marketId for poolId: ${args.poolId}`);
95
102
  }
@@ -100,12 +107,12 @@ export async function openPosition(client, address, args) {
100
107
  */
101
108
  export async function closePosition(client, address, args) {
102
109
  const chainId = getChainId();
103
- const quoteToken = args.quoteToken || getQuoteToken();
110
+ const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
104
111
  if (args.direction === undefined || args.direction === null) {
105
112
  throw new Error("direction is required (0=LONG, 1=SHORT), must match position direction.");
106
113
  }
107
114
  const price30 = parseHumanUnits(args.price, 30, "price");
108
- const dir = args.direction === 0 ? Direction.LONG : Direction.SHORT;
115
+ const dir = resolveDirection(args.direction);
109
116
  return client.order.createDecreaseOrder({
110
117
  chainId,
111
118
  address,
@@ -118,7 +125,7 @@ export async function closePosition(client, address, args) {
118
125
  size: args.size,
119
126
  price: price30,
120
127
  postOnly: false,
121
- slippagePct: args.slippagePct || "100",
128
+ slippagePct: args.slippagePct ?? "100",
122
129
  executionFeeToken: quoteToken,
123
130
  leverage: args.leverage,
124
131
  });
@@ -128,11 +135,11 @@ export async function closePosition(client, address, args) {
128
135
  */
129
136
  export async function setPositionTpSl(client, address, args) {
130
137
  const chainId = getChainId();
131
- const quoteToken = args.quoteToken || getQuoteToken();
138
+ const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
132
139
  if (!args.tpPrice && !args.slPrice) {
133
140
  throw new Error("At least one of tpPrice or slPrice must be provided.");
134
141
  }
135
- const dir = args.direction === 0 ? Direction.LONG : Direction.SHORT;
142
+ const dir = resolveDirection(args.direction);
136
143
  const params = {
137
144
  chainId,
138
145
  address,
@@ -160,7 +167,7 @@ export async function setPositionTpSl(client, address, args) {
160
167
  */
161
168
  export async function adjustMargin(client, address, args) {
162
169
  const chainId = getChainId();
163
- const quoteToken = args.quoteToken || getQuoteToken();
170
+ const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
164
171
  const quoteDecimals = args.quoteDecimals ?? getQuoteDecimals();
165
172
  return client.position.adjustCollateral({
166
173
  poolId: args.poolId,
@@ -197,7 +204,7 @@ export async function closeAllPositions(client, address) {
197
204
  else {
198
205
  slippagePrice30 = (BigInt(currentPrice30) * 110n) / 100n;
199
206
  }
200
- const sizeWei = parseUnits(pos.size, 18).toString();
207
+ const sizeWei = ensureUnits(pos.size, 18, "size");
201
208
  const res = await client.order.createDecreaseOrder({
202
209
  chainId,
203
210
  address,
@@ -223,7 +230,7 @@ export async function closeAllPositions(client, address) {
223
230
  */
224
231
  export async function updateOrderTpSl(client, address, args) {
225
232
  const chainId = getChainId();
226
- const quoteToken = args.quoteToken || getQuoteToken();
233
+ const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
227
234
  const params = {
228
235
  orderId: args.orderId,
229
236
  tpSize: args.tpSize || "0",
@@ -47,7 +47,8 @@ export const getTradeFlowTool = {
47
47
  try {
48
48
  const { client, address } = await resolveClient();
49
49
  const chainId = getChainId();
50
- const result = await client.account.getTradeFlow({ chainId, limit: args.limit || 20 }, address);
50
+ const query = { chainId, page: args.page ?? 1, limit: args.limit ?? 20 };
51
+ const result = await client.account.getTradeFlow(query, address);
51
52
  const enhancedData = (result?.data || []).map((flow) => ({
52
53
  ...flow,
53
54
  typeDesc: getTradeFlowTypeDesc(flow.type)
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient, getChainId, getQuoteToken } from "../auth/resolveClient.js";
3
+ import { normalizeAddress } from "../utils/address.js";
3
4
  export const accountDepositTool = {
4
5
  name: "account_deposit",
5
6
  description: "Deposit funds from wallet into the MYX trading account.",
@@ -11,7 +12,7 @@ export const accountDepositTool = {
11
12
  try {
12
13
  const { client } = await resolveClient();
13
14
  const chainId = getChainId();
14
- const tokenAddress = args.tokenAddress || getQuoteToken();
15
+ const tokenAddress = normalizeAddress(args.tokenAddress || getQuoteToken(), "tokenAddress");
15
16
  const result = await client.account.deposit({
16
17
  amount: args.amount,
17
18
  tokenAddress,
@@ -1,26 +1,30 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient, getChainId, getQuoteToken } from "../auth/resolveClient.js";
3
+ import { normalizeAddress } from "../utils/address.js";
4
+ const MAX_UINT256 = "115792089237316195423570985008687907853269984665640564039457584007913129639935";
3
5
  export const checkApprovalTool = {
4
6
  name: "check_approval",
5
- description: "Check if token spending approval is needed. Optionally auto-approve max amount. Must be called before first trade.",
7
+ description: "Check if token spending approval is needed. Supports auto-approve exact amount (default) or optional unlimited approval.",
6
8
  schema: {
7
9
  amount: z.string().describe("Amount to check approval for (in token decimals)"),
8
10
  quoteToken: z.string().optional().describe("Token address to check. Uses default if omitted."),
9
- autoApprove: z.boolean().optional().describe("If true, automatically approve max amount when needed"),
11
+ autoApprove: z.boolean().optional().describe("If true, automatically approve when needed."),
12
+ approveMax: z.boolean().optional().describe("If true with autoApprove, approve unlimited MaxUint256 (default false: approve exact amount only)."),
10
13
  },
11
14
  handler: async (args) => {
12
15
  try {
13
16
  const { client, address } = await resolveClient();
14
17
  const chainId = getChainId();
15
- const quoteToken = args.quoteToken || getQuoteToken();
18
+ const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
16
19
  const needApproval = await client.utils.needsApproval(address, chainId, quoteToken, args.amount);
17
20
  if (needApproval && args.autoApprove) {
21
+ const approveAmount = args.approveMax ? MAX_UINT256 : args.amount;
18
22
  await client.utils.approveAuthorization({
19
23
  chainId,
20
24
  quoteAddress: quoteToken,
21
- amount: "115792089237316195423570985008687907853269984665640564039457584007913129639935", // MaxUint256
25
+ amount: approveAmount,
22
26
  });
23
- return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: { needApproval: true, approved: true, quoteToken } }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
27
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: { needApproval: true, approved: true, quoteToken, approvedAmount: approveAmount, approveMax: !!args.approveMax } }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
24
28
  }
25
29
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: { needApproval, approved: false, quoteToken } }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
26
30
  }
@@ -24,7 +24,7 @@ export const closeAllPositionsTool = {
24
24
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: { message: "No open positions in this pool.", closed: 0 } }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
25
25
  }
26
26
  // 2) 为每个仓位构建平仓参数
27
- const slippagePct = args.slippagePct || "200";
27
+ const slippagePct = args.slippagePct ?? "200";
28
28
  // 3) We need actual oracle prices to avoid Revert 0x613970e0 (InvalidParameter)
29
29
  const oraclePriceReq = await getOraclePrice(client, args.poolId).catch(() => null);
30
30
  let fallbackPrice = "0";
@@ -7,7 +7,7 @@ export const closePositionTool = {
7
7
  schema: {
8
8
  poolId: z.string().describe("Pool ID"),
9
9
  positionId: z.string().describe("Position ID to close"),
10
- direction: z.number().describe("0 = LONG, 1 = SHORT (must match position direction)"),
10
+ direction: z.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT (must match position direction)"),
11
11
  leverage: z.number().describe("Position leverage"),
12
12
  size: z.string().describe("Size to close (18 decimals)"),
13
13
  price: z.string().describe("Price in human-readable units"),
@@ -6,7 +6,7 @@ export const executeTradeTool = {
6
6
  description: "Execute a new trade or add to an existing position.",
7
7
  schema: {
8
8
  poolId: z.string().describe("Pool ID to trade in"),
9
- direction: z.number().describe("0 = LONG, 1 = SHORT"),
9
+ direction: z.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT"),
10
10
  collateralAmount: z.string().describe("Collateral in human-readable units (e.g. '100' for 100 USDC)"),
11
11
  leverage: z.number().optional().describe("Leverage multiplier (e.g. 10, default 10)"),
12
12
  price: z.string().optional().describe("Limit price in human-readable units (e.g. '3000'). Required for LIMIT orders, omitted for MARKET orders."),
@@ -16,7 +16,7 @@ export const getKlineTool = {
16
16
  poolId: args.poolId,
17
17
  chainId,
18
18
  interval: args.interval,
19
- limit: args.limit || 100,
19
+ limit: args.limit ?? 100,
20
20
  endTime: Date.now(),
21
21
  });
22
22
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
@@ -1,13 +1,16 @@
1
1
  import { resolveClient } from "../auth/resolveClient.js";
2
2
  import { searchMarket } from "../services/marketService.js";
3
+ import { z } from "zod";
3
4
  export const getMarketListTool = {
4
5
  name: "get_market_list",
5
- description: "Get the complete list of all available tradable markets/pools. Automatically filters for active and tradable markets. The 'state' field indicates market status: 0=Created, 1=WaitOracle, 2=Active, 3=PreDelisting, 4=Delisted.",
6
- schema: {},
7
- handler: async () => {
6
+ description: "Get tradable markets/pools (state=2 Active). Supports configurable result limit; backend may still enforce its own cap.",
7
+ schema: {
8
+ limit: z.number().optional().describe("Max results to request (default 1000)."),
9
+ },
10
+ handler: async (args) => {
8
11
  try {
9
12
  const { client } = await resolveClient();
10
- const activeMarkets = await searchMarket(client, "");
13
+ const activeMarkets = await searchMarket(client, "", args.limit ?? 1000);
11
14
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: activeMarkets }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
12
15
  }
13
16
  catch (error) {
@@ -9,7 +9,8 @@ export const getPositionsTool = {
9
9
  try {
10
10
  const { client, address } = await resolveClient();
11
11
  const data = await getPositions(client, address);
12
- const enhancedData = (data || []).map((pos) => ({
12
+ const positions = Array.isArray(data) ? data : (Array.isArray(data?.data) ? data.data : []);
13
+ const enhancedData = positions.map((pos) => ({
13
14
  ...pos,
14
15
  directionDesc: getDirectionDesc(pos.direction)
15
16
  }));
@@ -34,7 +34,8 @@ export const getOrderHistoryTool = {
34
34
  try {
35
35
  const { client, address } = await resolveClient();
36
36
  const chainId = getChainId();
37
- const result = await client.order.getOrderHistory({ chainId, poolId: args.poolId, limit: args.limit || 20 }, address);
37
+ const query = { chainId, poolId: args.poolId, page: args.page ?? 1, limit: args.limit ?? 20 };
38
+ const result = await client.order.getOrderHistory(query, address);
38
39
  const enhancedData = (result?.data || []).map((order) => ({
39
40
  ...order,
40
41
  orderTypeDesc: getOrderTypeDesc(order.orderType),
@@ -13,7 +13,8 @@ export const getPositionHistoryTool = {
13
13
  try {
14
14
  const { client, address } = await resolveClient();
15
15
  const chainId = getChainId();
16
- const result = await client.position.getPositionHistory({ chainId, poolId: args.poolId, limit: args.limit || 20 }, address);
16
+ const query = { chainId, poolId: args.poolId, page: args.page ?? 1, limit: args.limit ?? 20 };
17
+ const result = await client.position.getPositionHistory(query, address);
17
18
  const enhancedData = (result?.data || []).map((pos) => ({
18
19
  ...pos,
19
20
  directionDesc: getDirectionDesc(pos.direction),
@@ -7,7 +7,7 @@ export const setTpSlTool = {
7
7
  schema: {
8
8
  poolId: z.string().describe("Pool ID"),
9
9
  positionId: z.string().describe("Position ID"),
10
- direction: z.number().describe("0 = LONG, 1 = SHORT (position direction)"),
10
+ direction: z.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT (position direction)"),
11
11
  leverage: z.number().describe("Position leverage"),
12
12
  tpPrice: z.string().optional().describe("Take-profit trigger price (human-readable)"),
13
13
  tpSize: z.string().optional().describe("TP size (18 decimals). Omit or '0' for full position."),
@@ -0,0 +1,17 @@
1
+ import { getAddress } from "ethers";
2
+ export function normalizeAddress(value, label = "address") {
3
+ const raw = String(value || "").trim();
4
+ if (!raw)
5
+ throw new Error(`${label} is required.`);
6
+ try {
7
+ return getAddress(raw);
8
+ }
9
+ catch {
10
+ try {
11
+ return getAddress(raw.toLowerCase());
12
+ }
13
+ catch {
14
+ throw new Error(`${label} is not a valid EVM address: ${raw}`);
15
+ }
16
+ }
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michaleffffff/mcp-trading-server",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "myx-mcp": "dist/server.js"
@@ -9,7 +9,7 @@
9
9
  "dist"
10
10
  ],
11
11
  "scripts": {
12
- "build": "tsc",
12
+ "build": "rm -rf dist && tsc",
13
13
  "start": "node dist/server.js",
14
14
  "dev": "tsx src/server.ts",
15
15
  "test": "tsx tests/test_sepolia.ts"
@@ -1,63 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- BigInt.prototype.toJSON = function () { return this.toString(); };
4
- require("dotenv/config");
5
- const resolveClient_1 = require("../auth/resolveClient");
6
- const ethers_1 = require("ethers");
7
- async function main() {
8
- console.log("═══════════════════════════════════════");
9
- console.log(" 查询当前用户的 LP 持仓");
10
- console.log("═══════════════════════════════════════\n");
11
- const { client, address } = await (0, resolveClient_1.resolveClient)();
12
- const chainId = 59141;
13
- console.log(` 用户地址: ${address}`);
14
- console.log(` 开始获取所有市场池子...`);
15
- try {
16
- const listRes = await client.api.getMarketList();
17
- if (!listRes.data || !Array.length) {
18
- console.log("未找到任何池子");
19
- return;
20
- }
21
- const pools = listRes.data;
22
- console.log(` 共查到 ${pools.length} 个池子,逐个检查您的 LP 余额...\n`);
23
- let foundLP = false;
24
- for (const p of pools) {
25
- try {
26
- const info = await client.account.getAccountInfo(chainId, address, p.poolId);
27
- // info.data details user LP data
28
- if (info && info.data) {
29
- const lps = info.data.lpBalances || info.data.poolShares || info.data;
30
- let hasBalance = false;
31
- let balanceFormatted = "0";
32
- // Depending on API response structure, there may be baseAmount / quoteAmount
33
- // But LP token itself balance should be checked
34
- // Let's print the entire raw structure if quoteAmount > 0
35
- if (info.data.quoteAmount && BigInt(info.data.quoteAmount) > 0n) {
36
- hasBalance = true;
37
- balanceFormatted = ethers_1.ethers.formatUnits(info.data.quoteAmount, 6) + " USDC";
38
- }
39
- if (info.data.baseAmount && BigInt(info.data.baseAmount) > 0n) {
40
- hasBalance = true;
41
- balanceFormatted += " / " + ethers_1.ethers.formatUnits(info.data.baseAmount, 18) + " BASE";
42
- }
43
- if (hasBalance || Object.keys(info.data).some(k => k.toLowerCase().includes("balance") && info.data[k] !== "0")) {
44
- foundLP = true;
45
- console.log(`✅ 您在 [${p.symbol || p.name || p.poolId}] (${p.poolId}) 有流动性份额!`);
46
- console.log(" 当前池子数据:", JSON.stringify(info.data, null, 2));
47
- console.log("-----------------------------------------");
48
- }
49
- }
50
- }
51
- catch (e) {
52
- // Skip pools with errors
53
- }
54
- }
55
- if (!foundLP) {
56
- console.log(" ⚠️ 您当前没有任何池子的 LP 持仓。");
57
- }
58
- }
59
- catch (err) {
60
- console.error("❌ 查询失败:", err.message);
61
- }
62
- }
63
- main().catch(err => console.error("Fatal:", err));