@michaleffffff/mcp-trading-server 2.3.5 → 2.3.6

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.5" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
84
+ const server = new Server({ name: "myx-mcp-trading-server", version: "2.3.6" }, { 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.5 running (stdio, pure on-chain, prod ready)");
184
+ logger.info("🚀 MYX Trading MCP Server v2.3.6 running (stdio, pure on-chain, prod ready)");
185
185
  }
186
186
  main().catch((err) => {
187
187
  logger.error("Fatal Server Startup Error", err);
@@ -67,5 +67,8 @@ export async function getLpPrice(poolType, poolId) {
67
67
  if (poolType === "BASE") {
68
68
  return base.getLpPrice(chainId, poolId);
69
69
  }
70
- return quote.getLpPrice(chainId, poolId);
70
+ if (poolType === "QUOTE") {
71
+ return quote.getLpPrice(chainId, poolId);
72
+ }
73
+ throw new Error("poolType must be 'BASE' or 'QUOTE'.");
71
74
  }
@@ -40,8 +40,8 @@ export const getTradeFlowTool = {
40
40
  name: "get_trade_flow",
41
41
  description: "Get account trade flow / transaction history.",
42
42
  schema: {
43
- page: z.number().optional().describe("Page number (default 1)"),
44
- limit: z.number().optional().describe("Results per page (default 20)"),
43
+ page: z.coerce.number().optional().describe("Page number (default 1)"),
44
+ limit: z.coerce.number().optional().describe("Results per page (default 20)"),
45
45
  },
46
46
  handler: async (args) => {
47
47
  try {
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient, getChainId, getQuoteToken } from "../auth/resolveClient.js";
3
3
  import { normalizeAddress } from "../utils/address.js";
4
+ import { booleanLike } from "../utils/schema.js";
4
5
  export const accountDepositTool = {
5
6
  name: "account_deposit",
6
7
  description: "Deposit funds from wallet into the MYX trading account.",
@@ -31,7 +32,7 @@ export const accountWithdrawTool = {
31
32
  schema: {
32
33
  poolId: z.string().describe("Pool ID to withdraw from"),
33
34
  amount: z.string().describe("Amount to withdraw (in token decimals)"),
34
- isQuoteToken: z.boolean().optional().describe("Whether to withdraw as quote token (default true)"),
35
+ isQuoteToken: booleanLike.optional().describe("Whether to withdraw as quote token (default true)"),
35
36
  },
36
37
  handler: async (args) => {
37
38
  try {
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient, getChainId, getQuoteToken } from "../auth/resolveClient.js";
3
3
  import { normalizeAddress } from "../utils/address.js";
4
+ import { booleanLike } from "../utils/schema.js";
4
5
  const MAX_UINT256 = "115792089237316195423570985008687907853269984665640564039457584007913129639935";
5
6
  export const checkApprovalTool = {
6
7
  name: "check_approval",
@@ -8,8 +9,8 @@ export const checkApprovalTool = {
8
9
  schema: {
9
10
  amount: z.string().describe("Amount to check approval for (in token decimals)"),
10
11
  quoteToken: z.string().optional().describe("Token address to check. Uses default if omitted."),
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)."),
12
+ autoApprove: booleanLike.optional().describe("If true, automatically approve when needed."),
13
+ approveMax: booleanLike.optional().describe("If true with autoApprove, approve unlimited MaxUint256 (default false: approve exact amount only)."),
13
14
  },
14
15
  handler: async (args) => {
15
16
  try {
@@ -7,8 +7,10 @@ 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.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT (must match position direction)"),
11
- leverage: z.number().describe("Position leverage"),
10
+ direction: z.coerce.number().int().refine((value) => value === 0 || value === 1, {
11
+ message: "direction must be 0 (LONG) or 1 (SHORT)",
12
+ }).describe("0 = LONG, 1 = SHORT (must match position direction)"),
13
+ leverage: z.coerce.number().describe("Position leverage"),
12
14
  size: z.string().describe("Size to close (human-readable or 18-decimal raw units)"),
13
15
  price: z.string().describe("Price in human-readable units"),
14
16
  slippagePct: z.string().optional().describe("Slippage in bps, default '100'"),
@@ -6,12 +6,14 @@ 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.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT"),
9
+ direction: z.coerce.number().int().refine((value) => value === 0 || value === 1, {
10
+ message: "direction must be 0 (LONG) or 1 (SHORT)",
11
+ }).describe("0 = LONG, 1 = SHORT"),
10
12
  collateralAmount: z.string().describe("Collateral in human-readable units (e.g. '100' for 100 USDC)"),
11
- leverage: z.number().optional().describe("Leverage multiplier (e.g. 10, default 10)"),
13
+ leverage: z.coerce.number().optional().describe("Leverage multiplier (e.g. 10, default 10)"),
12
14
  price: z.string().optional().describe("Limit price in human-readable units (e.g. '3000'). Required for LIMIT orders, omitted for MARKET orders."),
13
15
  quoteToken: z.string().optional().describe("Quote token address. Uses env default if omitted."),
14
- quoteDecimals: z.number().optional().describe("Quote token decimals (default 6)."),
16
+ quoteDecimals: z.coerce.number().optional().describe("Quote token decimals (default 6)."),
15
17
  slippagePct: z.string().optional().describe("Slippage in bps, default '100' = 1%"),
16
18
  positionId: z.string().optional().describe("Existing position ID to add to. Default '0' = new position."),
17
19
  tpPrice: z.string().optional().describe("Take-profit price (human-readable)"),
@@ -4,7 +4,7 @@ export const getAccountVipInfoTool = {
4
4
  name: "get_account_vip_info",
5
5
  description: "Get account VIP/fee-tier information.",
6
6
  schema: {
7
- chainId: z.number().optional().describe("Optional chainId override"),
7
+ chainId: z.coerce.number().optional().describe("Optional chainId override"),
8
8
  },
9
9
  handler: async (args) => {
10
10
  try {
@@ -5,7 +5,7 @@ export const getBaseDetailTool = {
5
5
  description: "Get base asset detail metrics for a pool.",
6
6
  schema: {
7
7
  poolId: z.string().describe("Pool ID"),
8
- chainId: z.number().optional().describe("Optional chainId override"),
8
+ chainId: z.coerce.number().optional().describe("Optional chainId override"),
9
9
  },
10
10
  handler: async (args) => {
11
11
  try {
@@ -6,7 +6,7 @@ export const getKlineTool = {
6
6
  schema: {
7
7
  poolId: z.string().describe("Pool ID"),
8
8
  interval: z.string().describe("K-line interval: '1m','5m','15m','30m','1h','4h','1d','1w','1M'"),
9
- limit: z.number().optional().describe("Number of bars (default 100)"),
9
+ limit: z.coerce.number().optional().describe("Number of bars (default 100)"),
10
10
  },
11
11
  handler: async (args) => {
12
12
  try {
@@ -5,7 +5,7 @@ export const getMarketListTool = {
5
5
  name: "get_market_list",
6
6
  description: "Get tradable markets/pools (state=2 Active). Supports configurable result limit; backend may still enforce its own cap.",
7
7
  schema: {
8
- limit: z.number().optional().describe("Max results to request (default 1000)."),
8
+ limit: z.coerce.number().optional().describe("Max results to request (default 1000)."),
9
9
  },
10
10
  handler: async (args) => {
11
11
  try {
@@ -5,7 +5,7 @@ export const getNetworkFeeTool = {
5
5
  description: "Estimate network fee requirements for a market.",
6
6
  schema: {
7
7
  marketId: z.string().describe("Market ID"),
8
- chainId: z.number().optional().describe("Optional chainId override"),
8
+ chainId: z.coerce.number().optional().describe("Optional chainId override"),
9
9
  },
10
10
  handler: async (args) => {
11
11
  try {
@@ -4,9 +4,9 @@ export const getUserTradingFeeRateTool = {
4
4
  name: "get_user_trading_fee_rate",
5
5
  description: "Get maker/taker fee rates for a given assetClass and riskTier.",
6
6
  schema: {
7
- assetClass: z.number().describe("Asset class ID"),
8
- riskTier: z.number().describe("Risk tier"),
9
- chainId: z.number().optional().describe("Optional chainId override"),
7
+ assetClass: z.coerce.number().describe("Asset class ID"),
8
+ riskTier: z.coerce.number().describe("Risk tier"),
9
+ chainId: z.coerce.number().optional().describe("Optional chainId override"),
10
10
  },
11
11
  handler: async (args) => {
12
12
  try {
@@ -5,8 +5,12 @@ export const manageLiquidityTool = {
5
5
  name: "manage_liquidity",
6
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
7
  schema: {
8
- action: z.string().describe("'deposit' or 'withdraw'"),
9
- poolType: z.string().describe("'BASE' or 'QUOTE'"),
8
+ action: z.string().trim().toLowerCase().refine((value) => value === "deposit" || value === "withdraw", {
9
+ message: "action must be 'deposit' or 'withdraw'.",
10
+ }).describe("'deposit' or 'withdraw'"),
11
+ poolType: z.string().trim().toUpperCase().refine((value) => value === "BASE" || value === "QUOTE", {
12
+ message: "poolType must be 'BASE' or 'QUOTE'.",
13
+ }).describe("'BASE' or 'QUOTE'"),
10
14
  poolId: z.string().describe("Pool ID"),
11
15
  amount: z.union([z.number(), z.string()]).describe("Amount in human-readable units (e.g. 2000 for USDC, 0.01 for ETH)"),
12
16
  slippage: z.union([z.number(), z.string()]).describe("Slippage tolerance (e.g. 0.01 = 1%)"),
@@ -16,10 +20,6 @@ export const manageLiquidityTool = {
16
20
  const { action, poolType, poolId } = args;
17
21
  const amount = parseSafeNumber(args.amount, "amount");
18
22
  const slippage = parseSafeNumber(args.slippage, "slippage");
19
- if (!["deposit", "withdraw"].includes(action))
20
- throw new Error("action must be 'deposit' or 'withdraw'.");
21
- if (!["BASE", "QUOTE"].includes(poolType))
22
- throw new Error("poolType must be 'BASE' or 'QUOTE'.");
23
23
  if (amount <= 0)
24
24
  throw new Error("amount must be a positive number.");
25
25
  if (slippage < 0)
@@ -46,7 +46,9 @@ export const getLpPriceTool = {
46
46
  name: "get_lp_price",
47
47
  description: "Get the current internal net asset value (NAV) price of an LP token for a BASE or QUOTE pool. Note: This is NOT the underlying token's external Oracle market price (e.g. WETH's price), but rather the internal exchange rate / net worth of the LP token itself which fluctuates based on pool PnL and fees.",
48
48
  schema: {
49
- poolType: z.string().describe("'BASE' or 'QUOTE'"),
49
+ poolType: z.string().trim().toUpperCase().refine((value) => value === "BASE" || value === "QUOTE", {
50
+ message: "poolType must be 'BASE' or 'QUOTE'.",
51
+ }).describe("'BASE' or 'QUOTE'"),
50
52
  poolId: z.string().describe("Pool ID"),
51
53
  },
52
54
  handler: async (args) => {
@@ -27,8 +27,8 @@ export const getOrderHistoryTool = {
27
27
  description: "Get historical orders with optional pool filter and pagination.",
28
28
  schema: {
29
29
  poolId: z.string().optional().describe("Filter by pool ID"),
30
- page: z.number().optional().describe("Page number (default 1)"),
31
- limit: z.number().optional().describe("Results per page (default 20)"),
30
+ page: z.coerce.number().optional().describe("Page number (default 1)"),
31
+ limit: z.coerce.number().optional().describe("Results per page (default 20)"),
32
32
  },
33
33
  handler: async (args) => {
34
34
  try {
@@ -6,8 +6,8 @@ export const getPositionHistoryTool = {
6
6
  description: "Get historical closed positions with optional pool filter and pagination.",
7
7
  schema: {
8
8
  poolId: z.string().optional().describe("Filter by pool ID"),
9
- page: z.number().optional().describe("Page number (default 1)"),
10
- limit: z.number().optional().describe("Results per page (default 20)"),
9
+ page: z.coerce.number().optional().describe("Page number (default 1)"),
10
+ limit: z.coerce.number().optional().describe("Results per page (default 20)"),
11
11
  },
12
12
  handler: async (args) => {
13
13
  try {
@@ -6,7 +6,7 @@ export const searchMarketTool = {
6
6
  description: "Search for an active market by keyword.",
7
7
  schema: {
8
8
  keyword: z.string().describe('Search keyword, e.g. "BTC", "ETH"'),
9
- limit: z.number().optional().describe("Max results (default 100)"),
9
+ limit: z.coerce.number().optional().describe("Max results (default 100)"),
10
10
  },
11
11
  handler: async (args) => {
12
12
  try {
@@ -7,8 +7,10 @@ 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.union([z.literal(0), z.literal(1)]).describe("0 = LONG, 1 = SHORT (position direction)"),
11
- leverage: z.number().describe("Position leverage"),
10
+ direction: z.coerce.number().int().refine((value) => value === 0 || value === 1, {
11
+ message: "direction must be 0 (LONG) or 1 (SHORT)",
12
+ }).describe("0 = LONG, 1 = SHORT (position direction)"),
13
+ leverage: z.coerce.number().describe("Position leverage"),
12
14
  tpPrice: z.string().optional().describe("Take-profit trigger price (human-readable)"),
13
15
  tpSize: z.string().optional().describe("TP size (18 decimals). Omit or '0' for full position."),
14
16
  slPrice: z.string().optional().describe("Stop-loss trigger price (human-readable)"),
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { resolveClient } from "../auth/resolveClient.js";
3
3
  import { updateOrderTpSl } from "../services/tradeService.js";
4
+ import { booleanLike } from "../utils/schema.js";
4
5
  export const updateOrderTpSlTool = {
5
6
  name: "update_order_tp_sl",
6
7
  description: "Update an existing take profit or stop loss order.",
@@ -11,7 +12,7 @@ export const updateOrderTpSlTool = {
11
12
  tpSize: z.string().optional().describe("New TP size (18 decimals). Omit or '0' for full position."),
12
13
  slPrice: z.string().optional().describe("New stop-loss trigger price (human-readable)"),
13
14
  slSize: z.string().optional().describe("New SL size (18 decimals). Omit or '0' for full position."),
14
- isTpSlOrder: z.boolean().optional().describe("Whether this is a TP/SL order (default true)"),
15
+ isTpSlOrder: booleanLike.optional().describe("Whether this is a TP/SL order (default true)"),
15
16
  quoteToken: z.string().optional().describe("Quote token address"),
16
17
  },
17
18
  handler: async (args) => {
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+ const trueValues = new Set(["true", "1", "yes", "y", "on"]);
3
+ const falseValues = new Set(["false", "0", "no", "n", "off"]);
4
+ /**
5
+ * Safe boolean coercion for MCP inputs:
6
+ * - boolean: true/false
7
+ * - number: 1/0
8
+ * - string: true/false/1/0/yes/no/on/off (case-insensitive)
9
+ */
10
+ export const booleanLike = z.union([
11
+ z.boolean(),
12
+ z.number().refine((value) => value === 0 || value === 1, {
13
+ message: "Expected 0 or 1 for boolean value.",
14
+ }).transform((value) => value === 1),
15
+ z.string().trim().toLowerCase().refine((value) => trueValues.has(value) || falseValues.has(value), {
16
+ message: "Expected boolean-like value: true/false/1/0/yes/no/on/off.",
17
+ }).transform((value) => trueValues.has(value)),
18
+ ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michaleffffff/mcp-trading-server",
3
- "version": "2.3.5",
3
+ "version": "2.3.6",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "myx-mcp": "dist/server.js"