@michaleffffff/mcp-trading-server 3.0.16 → 3.0.20

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 CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.20 - 2026-03-19
4
+
5
+ ### Fixed
6
+ - Hardened `get_pool_metadata` funding-rate formatting:
7
+ - `fundingInfo.nextFundingRate` now preserves readable `%/秒` and `%/天` output for negative rates too.
8
+ - Regression coverage now asserts both numeric funding-rate views and display strings.
9
+
10
+ ## 3.0.19 - 2026-03-19
11
+
12
+ ### Fixed
13
+ - Refined `fundingInfo.nextFundingRate` formatting in `get_pool_metadata`:
14
+ - Displays percent per second as `%/秒`
15
+ - Displays derived percent per day as `%/天`
16
+ - Keeps raw integer and comma-separated views for audit/debug use
17
+
18
+ ## 3.0.18 - 2026-03-19
19
+
20
+ ### Fixed
21
+ - Further improved `fundingInfo` / `ioTracker` readability in `get_pool_metadata`:
22
+ - `fundingInfo.nextEpochTime` now includes UTC timestamp and seconds-until-next-epoch.
23
+ - `fundingInfo.nextFundingRate` and `lastFundingFeeTracker` now include comma-separated raw views.
24
+ - `ioTracker` now includes derived notional-at-entry views based on `poolEntryPrice`.
25
+
26
+ ## 3.0.17 - 2026-03-19
27
+
28
+ ### Fixed
29
+ - Improved precision handling for liquidity and pool read tools:
30
+ - `get_pool_metadata` now adds `poolInfoFormatted` for exchange rate, LP token price, LP supply, debt, collateral, reserves, and open interest.
31
+ - `get_lp_price` now returns both raw and human-readable formatted values.
32
+ - Added regression coverage to ensure formatted precision fields are present.
33
+
3
34
  ## 3.0.16 - 2026-03-18
4
35
 
5
36
  ### Fixed
package/README.md CHANGED
@@ -6,7 +6,7 @@ A production-ready MCP (Model Context Protocol) server for deep integration with
6
6
 
7
7
  # Release Notes
8
8
 
9
- - **Current release: 3.0.16**
9
+ - **Current release: 3.0.20**
10
10
  - **SDK baseline**: `@myx-trade/sdk@^1.0.2` compatibility completed.
11
11
  - **Refinement**: Consolidated 40+ specialized tools into ~26 high-level unified tools.
12
12
  - **Improved UX**: Enhanced AI parameter parsing, automated unit conversion, and structured error reporting.
@@ -20,6 +20,7 @@ A production-ready MCP (Model Context Protocol) server for deep integration with
20
20
  * **AI-First Design**: Automated Pool ID resolution and flexible unit handling (`human:` vs `raw:`).
21
21
  * **Deep Liquidity Support**: Tools for both traders and liquidity providers.
22
22
  * **Production Ready**: Robust error handling with actionable hints for LLMs.
23
+ * **Precision-Aware Reads**: Pool and LP read tools expose human-readable formatted values alongside raw on-chain integers.
23
24
  * **Compliant**: Full Model Context Protocol (MCP) support.
24
25
 
25
26
  ---
package/TOOL_EXAMPLES.md CHANGED
@@ -1,4 +1,4 @@
1
- # MYX MCP Tool Examples Handbook (v3.0.15)
1
+ # MYX MCP Tool Examples Handbook (v3.0.20)
2
2
 
3
3
  This guide provides practical MCP payload examples for the current unified toolset.
4
4
  All examples use the MCP format:
@@ -233,6 +233,8 @@ Unified pool detail, config, and liquidity info.
233
233
  }
234
234
  ```
235
235
 
236
+ `get_pool_metadata` returns raw values in `poolInfo` and precision-safe human-readable values in `poolInfoFormatted`, including readable funding epoch timestamps, funding-rate `%/秒` and `%/天`, and IO notional-at-entry views.
237
+
236
238
  ### `get_kline`
237
239
  Read chart data. Use `limit: 1` for the latest bar.
238
240
 
@@ -295,6 +297,8 @@ Read LP NAV price for BASE or QUOTE side.
295
297
  }
296
298
  ```
297
299
 
300
+ `get_lp_price` returns both `raw` and `formatted` NAV price values.
301
+
298
302
  ### `get_my_lp_holdings`
299
303
  Read current LP balances across pools.
300
304
 
package/dist/server.js CHANGED
@@ -461,7 +461,7 @@ function zodSchemaToJsonSchema(zodSchema) {
461
461
  };
462
462
  }
463
463
  // ─── MCP Server ───
464
- const server = new Server({ name: "myx-mcp-trading-server", version: "3.0.16" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
464
+ const server = new Server({ name: "myx-mcp-trading-server", version: "3.0.20" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
465
465
  // List tools
466
466
  server.setRequestHandler(ListToolsRequestSchema, async () => {
467
467
  return {
@@ -582,7 +582,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
582
582
  async function main() {
583
583
  const transport = new StdioServerTransport();
584
584
  await server.connect(transport);
585
- logger.info("🚀 MYX Trading MCP Server v3.0.16 running (stdio, pure on-chain, prod ready)");
585
+ logger.info("🚀 MYX Trading MCP Server v3.0.20 running (stdio, pure on-chain, prod ready)");
586
586
  }
587
587
  main().catch((err) => {
588
588
  logger.error("Fatal Server Startup Error", err);
@@ -1,10 +1,12 @@
1
1
  import { z } from "zod";
2
+ import { formatUnits } from "ethers";
2
3
  import { resolveClient, getChainId } from "../auth/resolveClient.js";
3
4
  import { getMarketDetail, resolvePool } from "../services/marketService.js";
4
5
  import { getPoolInfo, getLiquidityInfo } from "../services/poolService.js";
5
6
  import { extractErrorMessage } from "../utils/errorMessage.js";
6
7
  import { parseUserPrice30 } from "../utils/units.js";
7
8
  const INTEGER_RE = /^\d+$/;
9
+ const SIGNED_INTEGER_RE = /^-?\d+$/;
8
10
  const DECIMAL_RE = /^\d+(\.\d+)?$/;
9
11
  function normalizeMarketPrice30Input(value) {
10
12
  const text = String(value ?? "").trim();
@@ -29,6 +31,180 @@ function compactWarning(scope, err) {
29
31
  }
30
32
  return `${scope}: ${flat}`;
31
33
  }
34
+ function formatRawValue(value, decimals) {
35
+ const text = String(value ?? "").trim();
36
+ if (!INTEGER_RE.test(text))
37
+ return null;
38
+ try {
39
+ return formatUnits(text, decimals);
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ }
45
+ function buildFormattedAmount(value, decimals, symbol) {
46
+ const raw = String(value ?? "");
47
+ const formatted = formatRawValue(value, decimals);
48
+ return {
49
+ raw,
50
+ decimals,
51
+ formatted,
52
+ display: formatted && symbol ? `${formatted} ${symbol}` : formatted,
53
+ symbol: symbol ?? null,
54
+ };
55
+ }
56
+ function buildFormattedPrice30(value, quoteSymbol) {
57
+ const raw = String(value ?? "");
58
+ const formatted = formatRawValue(value, 30);
59
+ return {
60
+ raw,
61
+ decimals: 30,
62
+ formatted,
63
+ display: formatted && quoteSymbol ? `${formatted} ${quoteSymbol}` : formatted,
64
+ symbol: quoteSymbol ?? null,
65
+ };
66
+ }
67
+ function buildFormattedRatio18(value) {
68
+ const raw = String(value ?? "");
69
+ const formatted = formatRawValue(value, 18);
70
+ return {
71
+ raw,
72
+ decimals: 18,
73
+ formatted,
74
+ display: formatted,
75
+ };
76
+ }
77
+ function buildRawScalar(value) {
78
+ const raw = String(value ?? "").trim();
79
+ if (!SIGNED_INTEGER_RE.test(raw)) {
80
+ return { raw, withCommas: raw || null };
81
+ }
82
+ try {
83
+ return {
84
+ raw,
85
+ withCommas: BigInt(raw).toLocaleString("en-US"),
86
+ };
87
+ }
88
+ catch {
89
+ return { raw, withCommas: raw };
90
+ }
91
+ }
92
+ function formatScaledIntegerString(raw, decimals) {
93
+ if (!SIGNED_INTEGER_RE.test(raw))
94
+ return null;
95
+ const negative = raw.startsWith("-");
96
+ const digits = negative ? raw.slice(1) : raw;
97
+ const padded = digits.padStart(decimals + 1, "0");
98
+ const intPart = padded.slice(0, -decimals) || "0";
99
+ const fracPart = padded.slice(-decimals).replace(/0+$/, "");
100
+ const normalized = fracPart ? `${intPart}.${fracPart}` : intPart;
101
+ return negative ? `-${normalized}` : normalized;
102
+ }
103
+ function buildFundingRateInfo(value) {
104
+ const base = buildRawScalar(value);
105
+ const raw = String(value ?? "").trim();
106
+ if (!SIGNED_INTEGER_RE.test(raw))
107
+ return base;
108
+ try {
109
+ const rawBig = BigInt(raw);
110
+ const perDayRaw = rawBig * 86400n;
111
+ const percentPerSecond = formatScaledIntegerString(raw, 12);
112
+ const percentPerDay = formatScaledIntegerString(perDayRaw.toString(), 12);
113
+ return {
114
+ ...base,
115
+ scale: "1e12 => percent",
116
+ percentPerSecond,
117
+ displayPerSecond: percentPerSecond ? `${percentPerSecond}%/秒` : null,
118
+ percentPerDay,
119
+ displayPerDay: percentPerDay ? `${percentPerDay}%/天` : null,
120
+ };
121
+ }
122
+ catch {
123
+ return base;
124
+ }
125
+ }
126
+ function buildTimestampInfo(value) {
127
+ const raw = String(value ?? "").trim();
128
+ const base = buildRawScalar(value);
129
+ if (!INTEGER_RE.test(raw)) {
130
+ return base;
131
+ }
132
+ const seconds = Number(raw);
133
+ if (!Number.isFinite(seconds) || seconds <= 0) {
134
+ return base;
135
+ }
136
+ const isoUtc = new Date(seconds * 1000).toISOString();
137
+ const secondsUntil = seconds - Math.floor(Date.now() / 1000);
138
+ return {
139
+ ...base,
140
+ isoUtc,
141
+ secondsUntil,
142
+ };
143
+ }
144
+ function computeQuoteNotionalDisplay(baseRaw, price30Raw, baseDecimals, quoteDecimals, quoteSymbol) {
145
+ const baseText = String(baseRaw ?? "").trim();
146
+ const priceText = String(price30Raw ?? "").trim();
147
+ if (!INTEGER_RE.test(baseText) || !INTEGER_RE.test(priceText))
148
+ return null;
149
+ try {
150
+ const notionalRaw = (BigInt(baseText) * BigInt(priceText) * (10n ** BigInt(quoteDecimals))) /
151
+ (10n ** BigInt(baseDecimals + 30));
152
+ return buildFormattedAmount(notionalRaw.toString(), quoteDecimals, quoteSymbol);
153
+ }
154
+ catch {
155
+ return null;
156
+ }
157
+ }
158
+ function formatPoolInfoSnapshot(poolInfo, marketDetail) {
159
+ if (!poolInfo || typeof poolInfo !== "object")
160
+ return null;
161
+ const baseSymbol = String(marketDetail?.baseSymbol ?? "BASE");
162
+ const quoteSymbol = String(marketDetail?.quoteSymbol ?? "QUOTE");
163
+ const baseDecimals = Number(marketDetail?.baseDecimals ?? 18);
164
+ const quoteDecimals = Number(marketDetail?.quoteDecimals ?? 6);
165
+ return {
166
+ quotePool: poolInfo.quotePool ? {
167
+ poolToken: poolInfo.quotePool.poolToken ?? null,
168
+ exchangeRate: buildFormattedRatio18(poolInfo.quotePool.exchangeRate),
169
+ poolTokenPrice: buildFormattedPrice30(poolInfo.quotePool.poolTokenPrice, quoteSymbol),
170
+ poolTokenSupply: buildFormattedAmount(poolInfo.quotePool.poolTokenSupply, 18, `m${quoteSymbol}.${baseSymbol}`),
171
+ totalDebt: buildFormattedAmount(poolInfo.quotePool.totalDebt, quoteDecimals, quoteSymbol),
172
+ baseCollateral: buildFormattedAmount(poolInfo.quotePool.baseCollateral, baseDecimals, baseSymbol),
173
+ } : null,
174
+ basePool: poolInfo.basePool ? {
175
+ poolToken: poolInfo.basePool.poolToken ?? null,
176
+ exchangeRate: buildFormattedRatio18(poolInfo.basePool.exchangeRate),
177
+ poolTokenPrice: buildFormattedPrice30(poolInfo.basePool.poolTokenPrice, quoteSymbol),
178
+ poolTokenSupply: buildFormattedAmount(poolInfo.basePool.poolTokenSupply, 18, `m${baseSymbol}.${quoteSymbol}`),
179
+ totalDebt: buildFormattedAmount(poolInfo.basePool.totalDebt, quoteDecimals, quoteSymbol),
180
+ baseCollateral: buildFormattedAmount(poolInfo.basePool.baseCollateral, baseDecimals, baseSymbol),
181
+ } : null,
182
+ reserveInfo: poolInfo.reserveInfo ? {
183
+ baseTotalAmount: buildFormattedAmount(poolInfo.reserveInfo.baseTotalAmount, baseDecimals, baseSymbol),
184
+ baseReservedAmount: buildFormattedAmount(poolInfo.reserveInfo.baseReservedAmount, baseDecimals, baseSymbol),
185
+ quoteTotalAmount: buildFormattedAmount(poolInfo.reserveInfo.quoteTotalAmount, quoteDecimals, quoteSymbol),
186
+ quoteReservedAmount: buildFormattedAmount(poolInfo.reserveInfo.quoteReservedAmount, quoteDecimals, quoteSymbol),
187
+ } : null,
188
+ fundingInfo: poolInfo.fundingInfo ? {
189
+ nextFundingRate: buildFundingRateInfo(poolInfo.fundingInfo.nextFundingRate),
190
+ lastFundingFeeTracker: buildRawScalar(poolInfo.fundingInfo.lastFundingFeeTracker),
191
+ nextEpochTime: buildTimestampInfo(poolInfo.fundingInfo.nextEpochTime),
192
+ } : null,
193
+ ioTracker: poolInfo.ioTracker ? {
194
+ tracker: buildFormattedAmount(poolInfo.ioTracker.tracker, baseDecimals, baseSymbol),
195
+ longSize: buildFormattedAmount(poolInfo.ioTracker.longSize, baseDecimals, baseSymbol),
196
+ shortSize: buildFormattedAmount(poolInfo.ioTracker.shortSize, baseDecimals, baseSymbol),
197
+ poolEntryPrice: buildFormattedPrice30(poolInfo.ioTracker.poolEntryPrice, quoteSymbol),
198
+ trackerNotionalAtEntry: computeQuoteNotionalDisplay(poolInfo.ioTracker.tracker, poolInfo.ioTracker.poolEntryPrice, baseDecimals, quoteDecimals, quoteSymbol),
199
+ longNotionalAtEntry: computeQuoteNotionalDisplay(poolInfo.ioTracker.longSize, poolInfo.ioTracker.poolEntryPrice, baseDecimals, quoteDecimals, quoteSymbol),
200
+ shortNotionalAtEntry: computeQuoteNotionalDisplay(poolInfo.ioTracker.shortSize, poolInfo.ioTracker.poolEntryPrice, baseDecimals, quoteDecimals, quoteSymbol),
201
+ } : null,
202
+ liquidityInfo: poolInfo.liquidityInfo ? {
203
+ windowCaps: buildFormattedAmount(poolInfo.liquidityInfo.windowCaps, quoteDecimals, quoteSymbol),
204
+ openInterest: buildFormattedAmount(poolInfo.liquidityInfo.openInterest, 18, quoteSymbol),
205
+ } : null,
206
+ };
207
+ }
32
208
  export const getPoolMetadataTool = {
33
209
  name: "get_pool_metadata",
34
210
  description: "[MARKET] Get comprehensive metadata for a pool (market detail, on-chain info, liquidity, and limits).",
@@ -57,6 +233,11 @@ export const getPoolMetadataTool = {
57
233
  // 2. Pool Info (Reserves, Utilization)
58
234
  try {
59
235
  results.poolInfo = await getPoolInfo(poolId, chainId, client);
236
+ const rawMarketDetail = results.marketDetail?.data ?? results.marketDetail;
237
+ const formatted = formatPoolInfoSnapshot(results.poolInfo, rawMarketDetail);
238
+ if (formatted) {
239
+ results.poolInfoFormatted = formatted;
240
+ }
60
241
  }
61
242
  catch (err) {
62
243
  errors.push(compactWarning("poolInfo", err));
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { formatUnits } from "ethers";
2
3
  import { quoteDeposit, quoteWithdraw, baseDeposit, baseWithdraw, getLpPrice, } from "../services/poolService.js";
3
4
  import { resolveClient, getChainId } from "../auth/resolveClient.js";
4
5
  import { resolvePool } from "../services/marketService.js";
@@ -45,6 +46,20 @@ function resolveLpAssetNames(detail) {
45
46
  quoteLpAssetName,
46
47
  };
47
48
  }
49
+ function formatLpPricePayload(value, quoteSymbol) {
50
+ const raw = String(value ?? "").trim();
51
+ if (!/^\d+$/.test(raw)) {
52
+ return { raw, formatted: null, decimals: 30, symbol: quoteSymbol ?? null };
53
+ }
54
+ const formatted = formatUnits(raw, 30);
55
+ return {
56
+ raw,
57
+ formatted,
58
+ decimals: 30,
59
+ symbol: quoteSymbol ?? null,
60
+ display: quoteSymbol ? `${formatted} ${quoteSymbol}` : formatted,
61
+ };
62
+ }
48
63
  export const manageLiquidityTool = {
49
64
  name: "manage_liquidity",
50
65
  description: "[LIQUIDITY] Add or withdraw liquidity from a BASE or QUOTE pool. Success response includes LP naming metadata: base `mBASE.QUOTE`, quote `mQUOTE.BASE`, plus `operatedLpAssetName` based on poolType.",
@@ -135,8 +150,20 @@ export const getLpPriceTool = {
135
150
  },
136
151
  handler: async (args) => {
137
152
  try {
138
- const data = await getLpPrice(args.poolType, args.poolId, args.chainId);
139
- return { content: [{ type: "text", text: JSON.stringify({ status: "success", data }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
153
+ const { client } = await resolveClient();
154
+ const chainId = args.chainId ?? getChainId();
155
+ const poolId = await resolvePool(client, args.poolId, undefined, chainId);
156
+ const detailRes = await client.markets.getMarketDetail({ chainId, poolId }).catch(() => null);
157
+ const detail = detailRes?.data || (detailRes?.marketId ? detailRes : null);
158
+ const quoteSymbol = String(detail?.quoteSymbol ?? "").trim() || null;
159
+ const rawPrice = await getLpPrice(args.poolType, poolId, chainId);
160
+ const payload = {
161
+ raw: String(rawPrice ?? ""),
162
+ formatted: formatLpPricePayload(rawPrice, quoteSymbol ?? undefined),
163
+ poolType: args.poolType,
164
+ poolId,
165
+ };
166
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: payload }, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }] };
140
167
  }
141
168
  catch (error) {
142
169
  return { content: [{ type: "text", text: `Error: ${extractErrorMessage(error)}` }], isError: true };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michaleffffff/mcp-trading-server",
3
- "version": "3.0.16",
3
+ "version": "3.0.20",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "myx-mcp": "dist/server.js"