@michaleffffff/mcp-trading-server 2.3.6 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -81,7 +81,7 @@ function zodSchemaToJsonSchema(zodSchema) {
81
81
  };
82
82
  }
83
83
  // ─── MCP Server ───
84
- const server = new Server({ name: "myx-mcp-trading-server", version: "2.3.6" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
84
+ const server = new Server({ name: "myx-mcp-trading-server", version: "2.4.0" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
85
85
  // List tools
86
86
  server.setRequestHandler(ListToolsRequestSchema, async () => {
87
87
  return {
@@ -181,7 +181,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
181
181
  async function main() {
182
182
  const transport = new StdioServerTransport();
183
183
  await server.connect(transport);
184
- logger.info("🚀 MYX Trading MCP Server v2.3.6 running (stdio, pure on-chain, prod ready)");
184
+ logger.info("🚀 MYX Trading MCP Server v2.4.0 running (stdio, pure on-chain, prod ready)");
185
185
  }
186
186
  main().catch((err) => {
187
187
  logger.error("Fatal Server Startup Error", err);
@@ -1,15 +1,15 @@
1
1
  import { getChainId } from "../auth/resolveClient.js";
2
2
  import { getMarketStateDesc } from "../utils/mappings.js";
3
- export async function getMarketPrice(client, poolId) {
4
- const chainId = getChainId();
3
+ export async function getMarketPrice(client, poolId, chainIdOverride) {
4
+ const chainId = chainIdOverride ?? getChainId();
5
5
  const ticker = await client.markets.getTickerList({
6
6
  chainId,
7
7
  poolIds: [poolId],
8
8
  });
9
9
  return ticker?.data?.[0] ?? null;
10
10
  }
11
- export async function getOraclePrice(client, poolId) {
12
- const chainId = getChainId();
11
+ export async function getOraclePrice(client, poolId, chainIdOverride) {
12
+ const chainId = chainIdOverride ?? getChainId();
13
13
  return client.utils.getOraclePrice(poolId, chainId);
14
14
  }
15
15
  export async function searchMarket(client, keyword, limit = 1000) {
@@ -57,8 +57,8 @@ export async function searchMarket(client, keyword, limit = 1000) {
57
57
  };
58
58
  });
59
59
  }
60
- export async function getMarketDetail(client, poolId) {
61
- const chainId = getChainId();
60
+ export async function getMarketDetail(client, poolId, chainIdOverride) {
61
+ const chainId = chainIdOverride ?? getChainId();
62
62
  const res = await client.markets.getMarketDetail({ chainId, poolId });
63
63
  // Ensure it's returned as { data: ... } for consistency with other services if needed,
64
64
  // but looking at existing tools, they often stringify the whole result.
@@ -74,7 +74,7 @@ export async function getPoolList(client) {
74
74
  /**
75
75
  * 获取池子分级配置
76
76
  */
77
- export async function getPoolLevelConfig(client, poolId) {
78
- const chainId = getChainId();
77
+ export async function getPoolLevelConfig(client, poolId, chainIdOverride) {
78
+ const chainId = chainIdOverride ?? getChainId();
79
79
  return client.api.getPoolLevelConfig({ poolId, chainId });
80
80
  }
@@ -12,58 +12,58 @@ export async function createPool(baseToken, marketId) {
12
12
  /**
13
13
  * 获取池子信息
14
14
  */
15
- export async function getPoolInfo(poolId) {
16
- const chainId = getChainId();
15
+ export async function getPoolInfo(poolId, chainIdOverride) {
16
+ const chainId = chainIdOverride ?? getChainId();
17
17
  return pool.getPoolInfo(chainId, poolId);
18
18
  }
19
19
  /**
20
20
  * 获取池子详情
21
21
  */
22
- export async function getPoolDetail(poolId) {
23
- const chainId = getChainId();
22
+ export async function getPoolDetail(poolId, chainIdOverride) {
23
+ const chainId = chainIdOverride ?? getChainId();
24
24
  return pool.getPoolDetail(chainId, poolId);
25
25
  }
26
26
  /**
27
27
  * 获取流动性信息
28
28
  */
29
- export async function getLiquidityInfo(client, poolId, marketPrice) {
30
- const chainId = getChainId();
29
+ export async function getLiquidityInfo(client, poolId, marketPrice, chainIdOverride) {
30
+ const chainId = chainIdOverride ?? getChainId();
31
31
  const price30 = ensureUnits(marketPrice, 30, "marketPrice");
32
32
  return client.utils.getLiquidityInfo({ chainId, poolId, marketPrice: price30 });
33
33
  }
34
34
  /**
35
35
  * Quote 池 deposit
36
36
  */
37
- export async function quoteDeposit(poolId, amount, slippage) {
38
- const chainId = getChainId();
37
+ export async function quoteDeposit(poolId, amount, slippage, chainIdOverride) {
38
+ const chainId = chainIdOverride ?? getChainId();
39
39
  return quote.deposit({ chainId, poolId, amount, slippage });
40
40
  }
41
41
  /**
42
42
  * Quote 池 withdraw
43
43
  */
44
- export async function quoteWithdraw(poolId, amount, slippage) {
45
- const chainId = getChainId();
44
+ export async function quoteWithdraw(poolId, amount, slippage, chainIdOverride) {
45
+ const chainId = chainIdOverride ?? getChainId();
46
46
  return quote.withdraw({ chainId, poolId, amount, slippage });
47
47
  }
48
48
  /**
49
49
  * Base 池 deposit
50
50
  */
51
- export async function baseDeposit(poolId, amount, slippage) {
52
- const chainId = getChainId();
51
+ export async function baseDeposit(poolId, amount, slippage, chainIdOverride) {
52
+ const chainId = chainIdOverride ?? getChainId();
53
53
  return base.deposit({ chainId, poolId, amount, slippage });
54
54
  }
55
55
  /**
56
56
  * Base 池 withdraw
57
57
  */
58
- export async function baseWithdraw(poolId, amount, slippage) {
59
- const chainId = getChainId();
58
+ export async function baseWithdraw(poolId, amount, slippage, chainIdOverride) {
59
+ const chainId = chainIdOverride ?? getChainId();
60
60
  return base.withdraw({ chainId, poolId, amount, slippage });
61
61
  }
62
62
  /**
63
63
  * 获取 LP 价格
64
64
  */
65
- export async function getLpPrice(poolType, poolId) {
66
- const chainId = getChainId();
65
+ export async function getLpPrice(poolType, poolId, chainIdOverride) {
66
+ const chainId = chainIdOverride ?? getChainId();
67
67
  if (poolType === "BASE") {
68
68
  return base.getLpPrice(chainId, poolId);
69
69
  }
@@ -1,7 +1,7 @@
1
1
  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
- import { ensureUnits, parseHumanUnits } from "../utils/units.js";
4
+ import { ensureUnits } from "../utils/units.js";
5
5
  import { normalizeAddress } from "../utils/address.js";
6
6
  /**
7
7
  * 将人类可读数值转为指定精度的整数字串
@@ -35,7 +35,7 @@ export async function openPosition(client, address, args) {
35
35
  let calcPrice30;
36
36
  let orderPrice30;
37
37
  if (args.price) {
38
- const limitPrice30 = parseHumanUnits(args.price, 30, "price");
38
+ const limitPrice30 = ensureUnits(args.price, 30, "price");
39
39
  calcPrice30 = limitPrice30;
40
40
  orderPrice30 = limitPrice30;
41
41
  }
@@ -46,7 +46,7 @@ export async function openPosition(client, address, args) {
46
46
  console.error(`[Market Order] Fetched oracle price for size math: ${calcPrice30}`);
47
47
  }
48
48
  // 保证金转为 quote token 精度
49
- const collateralWei = parseHumanUnits(args.collateralAmount, quoteDecimals, "collateralAmount");
49
+ const collateralWei = ensureUnits(args.collateralAmount, quoteDecimals, "collateralAmount");
50
50
  // 仓位大小 (size): 在 MYX 这一版中,size 表示 Base Token(WETH)的数量,精度通常为 18
51
51
  // 计算公式:size = (collateral * leverage) / price
52
52
  // 为了防止浮点溢出,我们可以全部转为 BigInt 计算
@@ -85,11 +85,11 @@ export async function openPosition(client, address, args) {
85
85
  };
86
86
  // 可选止盈止损
87
87
  if (args.tpPrice) {
88
- orderParams.tpPrice = parseHumanUnits(args.tpPrice, 30, "tpPrice");
88
+ orderParams.tpPrice = ensureUnits(args.tpPrice, 30, "tpPrice");
89
89
  orderParams.tpSize = size;
90
90
  }
91
91
  if (args.slPrice) {
92
- orderParams.slPrice = parseHumanUnits(args.slPrice, 30, "slPrice");
92
+ orderParams.slPrice = ensureUnits(args.slPrice, 30, "slPrice");
93
93
  orderParams.slSize = size;
94
94
  }
95
95
  // tradingFee: SDK 第二个参数
@@ -111,7 +111,7 @@ export async function closePosition(client, address, args) {
111
111
  if (args.direction === undefined || args.direction === null) {
112
112
  throw new Error("direction is required (0=LONG, 1=SHORT), must match position direction.");
113
113
  }
114
- const price30 = parseHumanUnits(args.price, 30, "price");
114
+ const price30 = ensureUnits(args.price, 30, "price");
115
115
  const sizeWei = ensureUnits(args.size, 18, "size");
116
116
  const dir = resolveDirection(args.direction);
117
117
  return client.order.createDecreaseOrder({
@@ -125,6 +125,7 @@ export async function closePosition(client, address, args) {
125
125
  collateralAmount: "0",
126
126
  size: sizeWei,
127
127
  price: price30,
128
+ timeInForce: 0,
128
129
  postOnly: false,
129
130
  slippagePct: args.slippagePct ?? "100",
130
131
  executionFeeToken: quoteToken,
@@ -151,14 +152,14 @@ export async function setPositionTpSl(client, address, args) {
151
152
  executionFeeToken: quoteToken,
152
153
  tpTriggerType: args.direction === 0 ? TriggerType.GTE : TriggerType.LTE,
153
154
  slTriggerType: args.direction === 0 ? TriggerType.LTE : TriggerType.GTE,
154
- slippagePct: "100", // 默认 100 BPS = 1%
155
+ slippagePct: args.slippagePct ?? "100",
155
156
  };
156
157
  if (args.tpPrice) {
157
- params.tpPrice = parseHumanUnits(args.tpPrice, 30, "tpPrice");
158
+ params.tpPrice = ensureUnits(args.tpPrice, 30, "tpPrice");
158
159
  params.tpSize = args.tpSize || "0";
159
160
  }
160
161
  if (args.slPrice) {
161
- params.slPrice = parseHumanUnits(args.slPrice, 30, "slPrice");
162
+ params.slPrice = ensureUnits(args.slPrice, 30, "slPrice");
162
163
  params.slSize = args.slSize || "0";
163
164
  }
164
165
  return client.order.createPositionTpSlOrder(params);
@@ -173,7 +174,7 @@ export async function adjustMargin(client, address, args) {
173
174
  return client.position.adjustCollateral({
174
175
  poolId: args.poolId,
175
176
  positionId: args.positionId,
176
- adjustAmount: parseHumanUnits(args.adjustAmount, quoteDecimals, "adjustAmount"),
177
+ adjustAmount: ensureUnits(args.adjustAmount, quoteDecimals, "adjustAmount"),
177
178
  quoteToken,
178
179
  chainId,
179
180
  address,
@@ -234,14 +235,14 @@ export async function updateOrderTpSl(client, address, args) {
234
235
  const quoteToken = normalizeAddress(args.quoteToken || getQuoteToken(), "quoteToken");
235
236
  const params = {
236
237
  orderId: args.orderId,
237
- tpSize: args.tpSize || "0",
238
- tpPrice: args.tpPrice ? parseHumanUnits(args.tpPrice, 30, "tpPrice") : "0",
239
- slSize: args.slSize || "0",
240
- slPrice: args.slPrice ? parseHumanUnits(args.slPrice, 30, "slPrice") : "0",
238
+ tpSize: args.tpSize ? ensureUnits(args.tpSize, 18, "tpSize") : "0",
239
+ tpPrice: args.tpPrice ? ensureUnits(args.tpPrice, 30, "tpPrice") : "0",
240
+ slSize: args.slSize ? ensureUnits(args.slSize, 18, "slSize") : "0",
241
+ slPrice: args.slPrice ? ensureUnits(args.slPrice, 30, "slPrice") : "0",
241
242
  useOrderCollateral: args.useOrderCollateral ?? true,
242
243
  executionFeeToken: quoteToken,
243
- size: args.size || "0",
244
- price: args.price || "0",
244
+ size: args.size ? ensureUnits(args.size, 18, "size") : "0",
245
+ price: args.price ? ensureUnits(args.price, 30, "price") : "0",
245
246
  };
246
247
  return client.order.updateOrderTpSl(params, quoteToken, chainId, address, args.marketId, args.isTpSlOrder ?? true);
247
248
  }
@@ -40,6 +40,7 @@ export const getTradeFlowTool = {
40
40
  name: "get_trade_flow",
41
41
  description: "Get account trade flow / transaction history.",
42
42
  schema: {
43
+ poolId: z.string().optional().describe("Optional pool ID filter."),
43
44
  page: z.coerce.number().optional().describe("Page number (default 1)"),
44
45
  limit: z.coerce.number().optional().describe("Results per page (default 20)"),
45
46
  },
@@ -47,7 +48,7 @@ export const getTradeFlowTool = {
47
48
  try {
48
49
  const { client, address } = await resolveClient();
49
50
  const chainId = getChainId();
50
- const query = { chainId, page: args.page ?? 1, limit: args.limit ?? 20 };
51
+ const query = { chainId, poolId: args.poolId, page: args.page ?? 1, limit: args.limit ?? 20 };
51
52
  const result = await client.account.getTradeFlow(query, address);
52
53
  const enhancedData = (result?.data || []).map((flow) => ({
53
54
  ...flow,
@@ -7,7 +7,7 @@ export const adjustMarginTool = {
7
7
  schema: {
8
8
  poolId: z.string().describe("Pool ID"),
9
9
  positionId: z.string().describe("Position ID"),
10
- adjustAmount: z.string().describe("Amount in token decimals. Positive = add, negative = remove."),
10
+ adjustAmount: z.string().describe("SDK standard: quote token raw units. Positive = add, negative = remove. Compatibility: human-readable decimals are also accepted."),
11
11
  quoteToken: z.string().optional().describe("Quote token address"),
12
12
  },
13
13
  handler: async (args) => {
@@ -4,15 +4,16 @@ export const cancelOrderTool = {
4
4
  name: "cancel_order",
5
5
  description: "Cancel an open order by its order ID.",
6
6
  schema: {
7
- orderIds: z.array(z.string()).describe("Array of order IDs to cancel"),
7
+ orderId: z.string().optional().describe("Single order ID to cancel"),
8
+ orderIds: z.array(z.string()).optional().describe("Array of order IDs to cancel"),
8
9
  },
9
10
  handler: async (args) => {
10
11
  try {
11
12
  const { client } = await resolveClient();
12
13
  const chainId = getChainId();
13
- const ids = args.orderIds;
14
+ const ids = args.orderId ? [args.orderId] : (Array.isArray(args.orderIds) ? args.orderIds : []);
14
15
  if (!ids || ids.length === 0)
15
- throw new Error("orderIds array is required.");
16
+ throw new Error("orderId or orderIds is required.");
16
17
  let result;
17
18
  if (ids.length === 1) {
18
19
  result = await client.order.cancelOrder(ids[0], chainId);
@@ -2,12 +2,13 @@ import { z } from "zod";
2
2
  import { resolveClient, getChainId, getQuoteToken } from "../auth/resolveClient.js";
3
3
  import { getOraclePrice } from "../services/marketService.js";
4
4
  import { ensureUnits } from "../utils/units.js";
5
+ import { bpsLikeString } from "../utils/schema.js";
5
6
  export const closeAllPositionsTool = {
6
7
  name: "close_all_positions",
7
8
  description: "Emergency: close ALL open positions in a pool at once. Use for risk management.",
8
9
  schema: {
9
10
  poolId: z.string().describe("Pool ID to close all positions in"),
10
- slippagePct: z.string().optional().describe("Slippage in bps (default '200' = 2% for emergency)"),
11
+ slippagePct: bpsLikeString.optional().describe("Slippage in bps (4-digit precision): 1 = 0.01%, default 200 = 2% for emergency"),
11
12
  },
12
13
  handler: async (args) => {
13
14
  try {
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient } from "../auth/resolveClient.js";
3
3
  import { closePosition as closePos } from "../services/tradeService.js";
4
+ import { bpsLikeString } from "../utils/schema.js";
4
5
  export const closePositionTool = {
5
6
  name: "close_position",
6
7
  description: "Close an open position.",
@@ -11,9 +12,9 @@ export const closePositionTool = {
11
12
  message: "direction must be 0 (LONG) or 1 (SHORT)",
12
13
  }).describe("0 = LONG, 1 = SHORT (must match position direction)"),
13
14
  leverage: z.coerce.number().describe("Position leverage"),
14
- size: z.string().describe("Size to close (human-readable or 18-decimal raw units)"),
15
- price: z.string().describe("Price in human-readable units"),
16
- slippagePct: z.string().optional().describe("Slippage in bps, default '100'"),
15
+ size: z.string().describe("SDK standard: 18-decimal raw size. Compatibility: human-readable decimals are also accepted."),
16
+ price: z.string().describe("SDK standard: 30-decimal raw price. Compatibility: human-readable decimals are also accepted."),
17
+ slippagePct: bpsLikeString.optional().describe("Slippage in bps (4-digit precision): 1 = 0.01%, default 100 = 1%"),
17
18
  quoteToken: z.string().optional().describe("Quote token address"),
18
19
  },
19
20
  handler: async (args) => {
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient } from "../auth/resolveClient.js";
3
3
  import { openPosition } from "../services/tradeService.js";
4
+ import { bpsLikeString } from "../utils/schema.js";
4
5
  export const executeTradeTool = {
5
6
  name: "execute_trade",
6
7
  description: "Execute a new trade or add to an existing position.",
@@ -9,15 +10,15 @@ export const executeTradeTool = {
9
10
  direction: z.coerce.number().int().refine((value) => value === 0 || value === 1, {
10
11
  message: "direction must be 0 (LONG) or 1 (SHORT)",
11
12
  }).describe("0 = LONG, 1 = SHORT"),
12
- collateralAmount: z.string().describe("Collateral in human-readable units (e.g. '100' for 100 USDC)"),
13
+ collateralAmount: z.string().describe("SDK standard: quote token raw units. Compatibility: human-readable decimals are also accepted."),
13
14
  leverage: z.coerce.number().optional().describe("Leverage multiplier (e.g. 10, default 10)"),
14
- price: z.string().optional().describe("Limit price in human-readable units (e.g. '3000'). Required for LIMIT orders, omitted for MARKET orders."),
15
+ price: z.string().optional().describe("SDK standard: 30-decimal raw price for LIMIT orders. Compatibility: human-readable decimals are also accepted."),
15
16
  quoteToken: z.string().optional().describe("Quote token address. Uses env default if omitted."),
16
17
  quoteDecimals: z.coerce.number().optional().describe("Quote token decimals (default 6)."),
17
- slippagePct: z.string().optional().describe("Slippage in bps, default '100' = 1%"),
18
+ slippagePct: bpsLikeString.optional().describe("Slippage in bps (4-digit precision): 1 = 0.01%, default 100 = 1%"),
18
19
  positionId: z.string().optional().describe("Existing position ID to add to. Default '0' = new position."),
19
- tpPrice: z.string().optional().describe("Take-profit price (human-readable)"),
20
- slPrice: z.string().optional().describe("Stop-loss price (human-readable)"),
20
+ tpPrice: z.string().optional().describe("SDK standard: 30-decimal raw TP price. Compatibility: human-readable decimals are also accepted."),
21
+ slPrice: z.string().optional().describe("SDK standard: 30-decimal raw SL price. Compatibility: human-readable decimals are also accepted."),
21
22
  },
22
23
  handler: async (args) => {
23
24
  try {
@@ -1,17 +1,71 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
+ function normalizeLower(value) {
4
+ return String(value ?? "").trim().toLowerCase();
5
+ }
6
+ function collectRows(input) {
7
+ if (Array.isArray(input)) {
8
+ return input.flatMap((item) => collectRows(item));
9
+ }
10
+ if (!input || typeof input !== "object") {
11
+ return [];
12
+ }
13
+ if (input.poolId || input.pool_id) {
14
+ return [input];
15
+ }
16
+ return Object.values(input).flatMap((value) => collectRows(value));
17
+ }
18
+ function findPoolIdByBaseAddress(rows, baseAddress) {
19
+ const target = normalizeLower(baseAddress);
20
+ if (!target)
21
+ return null;
22
+ for (const row of rows) {
23
+ const candidates = [
24
+ row.baseToken,
25
+ row.base_token,
26
+ row.baseAddress,
27
+ row.base_address,
28
+ row.base,
29
+ row.tokenAddress,
30
+ row.token_address,
31
+ ];
32
+ const hit = candidates.some((candidate) => normalizeLower(candidate) === target);
33
+ if (!hit)
34
+ continue;
35
+ const poolId = row.poolId || row.pool_id;
36
+ if (poolId) {
37
+ return String(poolId);
38
+ }
39
+ }
40
+ return null;
41
+ }
3
42
  export const getBaseDetailTool = {
4
43
  name: "get_base_detail",
5
- description: "Get base asset detail metrics for a pool.",
44
+ description: "Get base asset detail metrics. SDK doc uses baseAddress; MCP also accepts poolId for compatibility.",
6
45
  schema: {
7
- poolId: z.string().describe("Pool ID"),
46
+ baseAddress: z.string().optional().describe("SDK doc parameter: base token address."),
47
+ poolId: z.string().optional().describe("Compatibility parameter: pool ID."),
8
48
  chainId: z.coerce.number().optional().describe("Optional chainId override"),
9
49
  },
10
50
  handler: async (args) => {
11
51
  try {
12
52
  const { client } = await resolveClient();
13
53
  const chainId = args.chainId ?? getChainId();
14
- const result = await client.markets.getBaseDetail({ chainId, poolId: args.poolId });
54
+ let poolId = args.poolId;
55
+ if (!poolId && args.baseAddress) {
56
+ const sources = await Promise.allSettled([
57
+ client.api.getPoolList(),
58
+ client.api.getMarketList(),
59
+ ]);
60
+ const rows = sources
61
+ .filter((result) => result.status === "fulfilled")
62
+ .flatMap((result) => collectRows(result.value));
63
+ poolId = findPoolIdByBaseAddress(rows, args.baseAddress) || undefined;
64
+ }
65
+ if (!poolId) {
66
+ throw new Error("poolId is required. If using baseAddress, no matching pool was found.");
67
+ }
68
+ const result = await client.markets.getBaseDetail({ chainId, poolId });
15
69
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }] };
16
70
  }
17
71
  catch (error) {
@@ -1,16 +1,18 @@
1
1
  import { z } from "zod";
2
- import { resolveClient } from "../auth/resolveClient.js";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  import { getMarketPrice } from "../services/marketService.js";
4
4
  export const getMarketPriceTool = {
5
5
  name: "get_market_price",
6
6
  description: "Get the current market price for a specific pool.",
7
7
  schema: {
8
8
  poolId: z.string().describe("Pool ID to get price for"),
9
+ chainId: z.coerce.number().optional().describe("Optional chainId override"),
9
10
  },
10
11
  handler: async (args) => {
11
12
  try {
12
13
  const { client } = await resolveClient();
13
- const data = await getMarketPrice(client, args.poolId);
14
+ const chainId = args.chainId ?? getChainId();
15
+ const data = await getMarketPrice(client, args.poolId, chainId);
14
16
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
15
17
  }
16
18
  catch (error) {
@@ -1,16 +1,18 @@
1
1
  import { z } from "zod";
2
- import { resolveClient } from "../auth/resolveClient.js";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  import { getOraclePrice } from "../services/marketService.js";
4
4
  export const getOraclePriceTool = {
5
5
  name: "get_oracle_price",
6
6
  description: "Get the current oracle price for a specific pool.",
7
7
  schema: {
8
8
  poolId: z.string().describe("Pool ID to get oracle price for"),
9
+ chainId: z.coerce.number().optional().describe("Optional chainId override"),
9
10
  },
10
11
  handler: async (args) => {
11
12
  try {
12
13
  const { client } = await resolveClient();
13
- const data = await getOraclePrice(client, args.poolId);
14
+ const chainId = args.chainId ?? getChainId();
15
+ const data = await getOraclePrice(client, args.poolId, chainId);
14
16
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
15
17
  }
16
18
  catch (error) {
@@ -1,9 +1,10 @@
1
1
  import { z } from "zod";
2
2
  import { quoteDeposit, quoteWithdraw, baseDeposit, baseWithdraw, getLpPrice, } from "../services/poolService.js";
3
3
  import { parseSafeNumber } from "../utils/units.js";
4
+ import { lpSlippageLike } from "../utils/schema.js";
4
5
  export const manageLiquidityTool = {
5
6
  name: "manage_liquidity",
6
- description: "Add or withdraw liquidity from a BASE or QUOTE pool. Amount is in human-readable units (e.g. 2000 for USDC, 0.01 for ETH).",
7
+ description: "Add or withdraw liquidity from a BASE or QUOTE pool. Amount is in human-readable units. SDK-standard slippage is ratio (0.01 = 1%), and BPS input is auto-compatible.",
7
8
  schema: {
8
9
  action: z.string().trim().toLowerCase().refine((value) => value === "deposit" || value === "withdraw", {
9
10
  message: "action must be 'deposit' or 'withdraw'.",
@@ -13,13 +14,14 @@ export const manageLiquidityTool = {
13
14
  }).describe("'BASE' or 'QUOTE'"),
14
15
  poolId: z.string().describe("Pool ID"),
15
16
  amount: z.union([z.number(), z.string()]).describe("Amount in human-readable units (e.g. 2000 for USDC, 0.01 for ETH)"),
16
- slippage: z.union([z.number(), z.string()]).describe("Slippage tolerance (e.g. 0.01 = 1%)"),
17
+ slippage: lpSlippageLike.describe("SDK-standard LP slippage ratio (0.01 = 1%). BPS input is also accepted for compatibility (100 => 1%)."),
18
+ chainId: z.coerce.number().optional().describe("Optional chainId override"),
17
19
  },
18
20
  handler: async (args) => {
19
21
  try {
20
22
  const { action, poolType, poolId } = args;
21
23
  const amount = parseSafeNumber(args.amount, "amount");
22
- const slippage = parseSafeNumber(args.slippage, "slippage");
24
+ const slippage = args.slippage;
23
25
  if (amount <= 0)
24
26
  throw new Error("amount must be a positive number.");
25
27
  if (slippage < 0)
@@ -27,13 +29,13 @@ export const manageLiquidityTool = {
27
29
  let result;
28
30
  if (poolType === "QUOTE") {
29
31
  result = action === "deposit"
30
- ? await quoteDeposit(poolId, amount, slippage)
31
- : await quoteWithdraw(poolId, amount, slippage);
32
+ ? await quoteDeposit(poolId, amount, slippage, args.chainId)
33
+ : await quoteWithdraw(poolId, amount, slippage, args.chainId);
32
34
  }
33
35
  else {
34
36
  result = action === "deposit"
35
- ? await baseDeposit(poolId, amount, slippage)
36
- : await baseWithdraw(poolId, amount, slippage);
37
+ ? await baseDeposit(poolId, amount, slippage, args.chainId)
38
+ : await baseWithdraw(poolId, amount, slippage, args.chainId);
37
39
  }
38
40
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
39
41
  }
@@ -50,10 +52,11 @@ export const getLpPriceTool = {
50
52
  message: "poolType must be 'BASE' or 'QUOTE'.",
51
53
  }).describe("'BASE' or 'QUOTE'"),
52
54
  poolId: z.string().describe("Pool ID"),
55
+ chainId: z.coerce.number().optional().describe("Optional chainId override"),
53
56
  },
54
57
  handler: async (args) => {
55
58
  try {
56
- const data = await getLpPrice(args.poolType, args.poolId);
59
+ const data = await getLpPrice(args.poolType, args.poolId, args.chainId);
57
60
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
58
61
  }
59
62
  catch (error) {
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { resolveClient } from "../auth/resolveClient.js";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  import { getMarketDetail } from "../services/marketService.js";
4
4
  import { getPoolInfo, getLiquidityInfo } from "../services/poolService.js";
5
5
  export const getMarketDetailTool = {
@@ -7,11 +7,13 @@ export const getMarketDetailTool = {
7
7
  description: "Get detailed information for a specific trading pool (fee rates, open interest, etc.).",
8
8
  schema: {
9
9
  poolId: z.string().describe("Pool ID"),
10
+ chainId: z.coerce.number().optional().describe("Optional chainId override"),
10
11
  },
11
12
  handler: async (args) => {
12
13
  try {
13
14
  const { client } = await resolveClient();
14
- const data = await getMarketDetail(client, args.poolId);
15
+ const chainId = args.chainId ?? getChainId();
16
+ const data = await getMarketDetail(client, args.poolId, chainId);
15
17
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
16
18
  }
17
19
  catch (error) {
@@ -24,10 +26,12 @@ export const getPoolInfoTool = {
24
26
  description: "Get pool on-chain information (reserves, utilization, etc.).",
25
27
  schema: {
26
28
  poolId: z.string().describe("Pool ID"),
29
+ chainId: z.coerce.number().optional().describe("Optional chainId override"),
27
30
  },
28
31
  handler: async (args) => {
29
32
  try {
30
- const data = await getPoolInfo(args.poolId);
33
+ const chainId = args.chainId ?? getChainId();
34
+ const data = await getPoolInfo(args.poolId, chainId);
31
35
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
32
36
  }
33
37
  catch (error) {
@@ -41,11 +45,13 @@ export const getLiquidityInfoTool = {
41
45
  schema: {
42
46
  poolId: z.string().describe("Pool ID"),
43
47
  marketPrice: z.string().describe("Current market price (can be human-readable e.g. '0.69' or 30-decimal raw units)"),
48
+ chainId: z.coerce.number().optional().describe("Optional chainId override"),
44
49
  },
45
50
  handler: async (args) => {
46
51
  try {
47
52
  const { client } = await resolveClient();
48
- const data = await getLiquidityInfo(client, args.poolId, args.marketPrice);
53
+ const chainId = args.chainId ?? getChainId();
54
+ const data = await getLiquidityInfo(client, args.poolId, args.marketPrice, chainId);
49
55
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
50
56
  }
51
57
  catch (error) {
@@ -1,16 +1,18 @@
1
1
  import { z } from "zod";
2
- import { resolveClient } from "../auth/resolveClient.js";
2
+ import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
3
  import { getPoolLevelConfig } from "../services/marketService.js";
4
4
  export const getPoolLevelConfigTool = {
5
5
  name: "get_pool_level_config",
6
6
  description: "Get the level configuration and trading limits for a specific pool.",
7
7
  schema: {
8
8
  poolId: z.string().describe("Pool ID"),
9
+ chainId: z.coerce.number().optional().describe("Optional chainId override"),
9
10
  },
10
11
  handler: async (args) => {
11
12
  try {
12
13
  const { client } = await resolveClient();
13
- const result = await getPoolLevelConfig(client, args.poolId);
14
+ const chainId = args.chainId ?? getChainId();
15
+ const result = await getPoolLevelConfig(client, args.poolId, chainId);
14
16
  return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: result }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
15
17
  }
16
18
  catch (error) {
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient } from "../auth/resolveClient.js";
3
3
  import { setPositionTpSl } from "../services/tradeService.js";
4
+ import { bpsLikeString } from "../utils/schema.js";
4
5
  export const setTpSlTool = {
5
6
  name: "set_tp_sl",
6
7
  description: "Set take profit and stop loss prices for an open position.",
@@ -11,10 +12,11 @@ export const setTpSlTool = {
11
12
  message: "direction must be 0 (LONG) or 1 (SHORT)",
12
13
  }).describe("0 = LONG, 1 = SHORT (position direction)"),
13
14
  leverage: z.coerce.number().describe("Position leverage"),
14
- tpPrice: z.string().optional().describe("Take-profit trigger price (human-readable)"),
15
- tpSize: z.string().optional().describe("TP size (18 decimals). Omit or '0' for full position."),
16
- slPrice: z.string().optional().describe("Stop-loss trigger price (human-readable)"),
17
- slSize: z.string().optional().describe("SL size (18 decimals). Omit or '0' for full position."),
15
+ tpPrice: z.string().optional().describe("SDK standard: 30-decimal raw TP price. Compatibility: human-readable decimals are also accepted."),
16
+ tpSize: z.string().optional().describe("SDK standard: 18-decimal raw TP size. Omit or '0' for full position."),
17
+ slPrice: z.string().optional().describe("SDK standard: 30-decimal raw SL price. Compatibility: human-readable decimals are also accepted."),
18
+ slSize: z.string().optional().describe("SDK standard: 18-decimal raw SL size. Omit or '0' for full position."),
19
+ slippagePct: bpsLikeString.optional().describe("Slippage in bps (4-digit precision): 1 = 0.01%, default 100 = 1%"),
18
20
  quoteToken: z.string().optional().describe("Quote token address"),
19
21
  },
20
22
  handler: async (args) => {
@@ -8,10 +8,13 @@ export const updateOrderTpSlTool = {
8
8
  schema: {
9
9
  orderId: z.string().describe("The ID of the order to update"),
10
10
  marketId: z.string().describe("The market ID (config hash) for the order"),
11
- tpPrice: z.string().optional().describe("New take-profit trigger price (human-readable)"),
12
- tpSize: z.string().optional().describe("New TP size (18 decimals). Omit or '0' for full position."),
13
- slPrice: z.string().optional().describe("New stop-loss trigger price (human-readable)"),
14
- slSize: z.string().optional().describe("New SL size (18 decimals). Omit or '0' for full position."),
11
+ size: z.string().optional().describe("SDK standard: 18-decimal raw order size."),
12
+ price: z.string().optional().describe("SDK standard: 30-decimal raw order price."),
13
+ tpPrice: z.string().optional().describe("SDK standard: 30-decimal raw TP price. Compatibility: human-readable decimals are also accepted."),
14
+ tpSize: z.string().optional().describe("SDK standard: 18-decimal raw TP size. Omit or '0' for full position."),
15
+ slPrice: z.string().optional().describe("SDK standard: 30-decimal raw SL price. Compatibility: human-readable decimals are also accepted."),
16
+ slSize: z.string().optional().describe("SDK standard: 18-decimal raw SL size. Omit or '0' for full position."),
17
+ useOrderCollateral: booleanLike.optional().describe("Whether to use the order collateral (SDK default true)."),
15
18
  isTpSlOrder: booleanLike.optional().describe("Whether this is a TP/SL order (default true)"),
16
19
  quoteToken: z.string().optional().describe("Quote token address"),
17
20
  },
@@ -1,6 +1,8 @@
1
1
  import { z } from "zod";
2
2
  const trueValues = new Set(["true", "1", "yes", "y", "on"]);
3
3
  const falseValues = new Set(["false", "0", "no", "n", "off"]);
4
+ const DECIMAL_RE = /^-?\d+(\.\d+)?$/;
5
+ const EPSILON = 1e-8;
4
6
  /**
5
7
  * Safe boolean coercion for MCP inputs:
6
8
  * - boolean: true/false
@@ -16,3 +18,67 @@ export const booleanLike = z.union([
16
18
  message: "Expected boolean-like value: true/false/1/0/yes/no/on/off.",
17
19
  }).transform((value) => trueValues.has(value)),
18
20
  ]);
21
+ function isNearlyInteger(value) {
22
+ return Math.abs(value - Math.round(value)) < EPSILON;
23
+ }
24
+ function parseBpsLike(value) {
25
+ const raw = String(value).trim();
26
+ if (!raw)
27
+ throw new Error("slippage is required.");
28
+ if (!DECIMAL_RE.test(raw))
29
+ throw new Error("slippage must be numeric.");
30
+ const parsed = Number(raw);
31
+ if (!Number.isFinite(parsed))
32
+ throw new Error("slippage must be finite.");
33
+ if (parsed < 0)
34
+ throw new Error("slippage must be >= 0.");
35
+ if (isNearlyInteger(parsed)) {
36
+ return Math.round(parsed);
37
+ }
38
+ // Backward compatibility: legacy ratio input (e.g. 0.01 = 1%) -> bps
39
+ if (parsed > 0 && parsed < 1) {
40
+ const bps = parsed * 10000;
41
+ if (!isNearlyInteger(bps)) {
42
+ throw new Error("legacy slippage ratio is too precise; use integer bps.");
43
+ }
44
+ return Math.round(bps);
45
+ }
46
+ throw new Error("slippage must be integer bps (1 = 0.01%).");
47
+ }
48
+ function parseLpSlippageLike(value) {
49
+ const raw = String(value).trim();
50
+ if (!raw)
51
+ throw new Error("slippage is required.");
52
+ if (!DECIMAL_RE.test(raw))
53
+ throw new Error("slippage must be numeric.");
54
+ const parsed = Number(raw);
55
+ if (!Number.isFinite(parsed))
56
+ throw new Error("slippage must be finite.");
57
+ if (parsed < 0)
58
+ throw new Error("slippage must be >= 0.");
59
+ // SDK doc convention for LP: ratio (e.g. 0.01 = 1%)
60
+ if (parsed <= 1) {
61
+ return parsed;
62
+ }
63
+ // Backward compatibility for MCP callers that send BPS (e.g. 100 = 1%)
64
+ return parsed / 10000;
65
+ }
66
+ export const bpsLikeNumber = z.union([z.number(), z.string()]).transform((value, ctx) => {
67
+ try {
68
+ return parseBpsLike(value);
69
+ }
70
+ catch (error) {
71
+ ctx.addIssue({ code: "custom", message: error.message || "Invalid slippage." });
72
+ return z.NEVER;
73
+ }
74
+ });
75
+ export const bpsLikeString = bpsLikeNumber.transform((value) => value.toString());
76
+ export const lpSlippageLike = z.union([z.number(), z.string()]).transform((value, ctx) => {
77
+ try {
78
+ return parseLpSlippageLike(value);
79
+ }
80
+ catch (error) {
81
+ ctx.addIssue({ code: "custom", message: error.message || "Invalid LP slippage." });
82
+ return z.NEVER;
83
+ }
84
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michaleffffff/mcp-trading-server",
3
- "version": "2.3.6",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "myx-mcp": "dist/server.js"