@michaleffffff/mcp-trading-server 3.0.31 → 3.1.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/CHANGELOG.md +6 -0
- package/README.md +1 -4
- package/dist/auth/resolveClient.js +1 -14
- package/dist/prompts/tradingGuide.js +1 -3
- package/dist/server.js +2 -2
- package/dist/services/marketService.js +14 -7
- package/dist/services/poolService.js +3 -36
- package/dist/tools/getAllTickers.js +1 -1
- package/dist/tools/listPools.js +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.1.0 - 2026-03-20
|
|
4
|
+
### Changed
|
|
5
|
+
- 发布 `3.1.0`,统一版本元数据到新的主次版本号。
|
|
6
|
+
- 运行环境现在只区分测试环境与正式环境,不再走 beta host / beta 地址分支。
|
|
7
|
+
- 修复测试环境下的市场发现链路,`list_pools` 恢复返回当前链池列表,`get_all_tickers` 在主接口不可用时继续走 fallback。
|
|
8
|
+
|
|
3
9
|
## 3.0.31 - 2026-03-20
|
|
4
10
|
### Fixed
|
|
5
11
|
- 修复 `normalizeSlippageRatio` 在处理 1% 到 100% 之间数值时的歧义问题。
|
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
|
|
9
|
+
- **Current release: 3.1.0**
|
|
10
10
|
- **SDK baseline**: `@myx-trade/sdk@^1.0.4-beta.4` 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.
|
|
@@ -37,7 +37,6 @@ CHAIN_ID=...
|
|
|
37
37
|
BROKER_ADDRESS=0x...
|
|
38
38
|
QUOTE_TOKEN_ADDRESS=0x...
|
|
39
39
|
QUOTE_TOKEN_DECIMALS=...
|
|
40
|
-
IS_BETA_MODE=true
|
|
41
40
|
```
|
|
42
41
|
|
|
43
42
|
## Testnet `MYXBroker` Reference
|
|
@@ -46,7 +45,6 @@ IS_BETA_MODE=true
|
|
|
46
45
|
- Linea test: `0x634EfDC9dC76D7AbF6E49279875a31B02E9891e2`
|
|
47
46
|
|
|
48
47
|
Use the broker that matches your active RPC and chain configuration.
|
|
49
|
-
If `IS_BETA_MODE` is omitted, MCP now auto-detects beta mode for the two testnet brokers above.
|
|
50
48
|
|
|
51
49
|
---
|
|
52
50
|
|
|
@@ -91,7 +89,6 @@ If `IS_BETA_MODE` is omitted, MCP now auto-detects beta mode for the two testnet
|
|
|
91
89
|
|
|
92
90
|
- **Explicit execution price**: `open_position_simple` no longer auto-fills a fresh Oracle price for `MARKET` orders; provide `price` explicitly when opening a position.
|
|
93
91
|
- **SDK-delegated funding delta**: MCP no longer performs its own increase-order margin/deposit reconciliation. New increase orders delegate collateral shortfall handling to SDK `createIncreaseOrder`.
|
|
94
|
-
- **Beta broker compatibility**: current Arbitrum Sepolia / Linea Sepolia test brokers auto-enable beta mode when `IS_BETA_MODE` is unset.
|
|
95
92
|
- **Exact approvals by default**: local fallback flows now prefer exact approvals instead of implicit unlimited approval.
|
|
96
93
|
- **Size semantics**: `size` always means base-asset quantity, not USD notional.
|
|
97
94
|
- **Pre-check semantics**: `check_account_ready` now uses SDK `getAvailableMarginBalance`. When that read fails, the response marks `summary.degraded=true` and includes diagnostics instead of silently trusting stale `freeMargin`.
|
|
@@ -2,10 +2,6 @@ import { MyxClient } from "@myx-trade/sdk";
|
|
|
2
2
|
import { JsonRpcProvider, Wallet } from "ethers";
|
|
3
3
|
import { normalizeAddress } from "../utils/address.js";
|
|
4
4
|
let cached = null;
|
|
5
|
-
const BETA_BROKERS_BY_CHAIN = {
|
|
6
|
-
421614: ["0x895c4ae2a22bb26851011d733a9355f663a1f939"],
|
|
7
|
-
59141: ["0x634efdc9dc76d7abf6e49279875a31b02e9891e2"],
|
|
8
|
-
};
|
|
9
5
|
function getDefaultBrokerByChainId(chainId) {
|
|
10
6
|
// Testnet mappings
|
|
11
7
|
if (chainId === 421614)
|
|
@@ -22,14 +18,6 @@ function getDefaultQuoteTokenByChainId(chainId) {
|
|
|
22
18
|
return "0xD984fd34f91F92DA0586e1bE82E262fF27DC431b"; // Linea Sepolia
|
|
23
19
|
return "0xD984fd34f91F92DA0586e1bE82E262fF27DC431b";
|
|
24
20
|
}
|
|
25
|
-
function resolveIsBetaMode(chainId, brokerAddressRaw) {
|
|
26
|
-
const explicit = String(process.env.IS_BETA_MODE ?? "").trim().toLowerCase();
|
|
27
|
-
if (explicit === "true")
|
|
28
|
-
return true;
|
|
29
|
-
if (explicit === "false")
|
|
30
|
-
return false;
|
|
31
|
-
return Boolean(BETA_BROKERS_BY_CHAIN[chainId]?.includes(String(brokerAddressRaw ?? "").trim().toLowerCase()));
|
|
32
|
-
}
|
|
33
21
|
export async function resolveClient() {
|
|
34
22
|
if (cached)
|
|
35
23
|
return cached;
|
|
@@ -50,7 +38,6 @@ export async function resolveClient() {
|
|
|
50
38
|
throw new Error("QUOTE_TOKEN_ADDRESS env var is required.");
|
|
51
39
|
const brokerAddress = normalizeAddress(brokerAddressRaw, "BROKER_ADDRESS");
|
|
52
40
|
const quoteToken = normalizeAddress(quoteTokenRaw, "QUOTE_TOKEN_ADDRESS");
|
|
53
|
-
const isBetaMode = resolveIsBetaMode(chainId, brokerAddress);
|
|
54
41
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
55
42
|
const signer = new Wallet(privateKey, provider);
|
|
56
43
|
// Inject the EIP-1193 mock so SDK can sign transactions seamlessly
|
|
@@ -75,7 +62,7 @@ export async function resolveClient() {
|
|
|
75
62
|
signer: signer,
|
|
76
63
|
brokerAddress,
|
|
77
64
|
isTestnet,
|
|
78
|
-
isBetaMode,
|
|
65
|
+
isBetaMode: false,
|
|
79
66
|
walletClient: walletClient
|
|
80
67
|
});
|
|
81
68
|
cached = { client, address: signer.address, signer, chainId, quoteToken, quoteDecimals };
|
|
@@ -12,7 +12,7 @@ export const tradingGuidePrompt = {
|
|
|
12
12
|
content: {
|
|
13
13
|
type: "text",
|
|
14
14
|
text: `
|
|
15
|
-
# MYX Trading MCP Best Practices (v3.0
|
|
15
|
+
# MYX Trading MCP Best Practices (v3.1.0)
|
|
16
16
|
|
|
17
17
|
You are an expert crypto trader using the MYX Protocol. To ensure successful execution and safe handling of user funds, follow these patterns:
|
|
18
18
|
|
|
@@ -36,7 +36,6 @@ You are an expert crypto trader using the MYX Protocol. To ensure successful exe
|
|
|
36
36
|
- **Execution Price**: \`open_position_simple\` no longer auto-fills a fresh Oracle price for \`MARKET\`; provide \`price\` explicitly when you want MCP to compute size / fee previews.
|
|
37
37
|
- **Funding Delta Ownership**: MCP no longer performs its own increase-order margin/deposit reconciliation. SDK \`createIncreaseOrder\` owns deposit-delta handling for new increase orders.
|
|
38
38
|
- **Pre-check Diagnostics**: \`check_account_ready\` now reports SDK \`availableMarginBalance\` first. If that read degrades, inspect \`summary.degraded\` and \`diagnostics.availableMarginError\` before trusting fallback account fields.
|
|
39
|
-
- **Beta Broker Mode**: Current Arbitrum Sepolia / Linea Sepolia test brokers auto-enable beta mode when \`IS_BETA_MODE\` is omitted.
|
|
40
39
|
- **Approval Safety**: Local fallback flows prefer exact approval sizing. Do not assume unlimited approvals are necessary.
|
|
41
40
|
- **Position Semantics**: \`size\` is BASE quantity, not USD notional. If a \`positionId\` is supplied, \`direction\` must match the live position.
|
|
42
41
|
- **TP/SL Semantics**: LONG should use \`tpPrice > entryPrice\` and \`slPrice < entryPrice\`; SHORT uses the inverse. Plain integer strings like \`"65000"\` are treated as human prices, not implicit raw 30-decimal values.
|
|
@@ -49,7 +48,6 @@ You are an expert crypto trader using the MYX Protocol. To ensure successful exe
|
|
|
49
48
|
- Arbitrum test: \`0x895C4ae2A22bB26851011d733A9355f663a1F939\`
|
|
50
49
|
- Linea test: \`0x634EfDC9dC76D7AbF6E49279875a31B02E9891e2\`
|
|
51
50
|
- Always keep \`RPC_URL\`, \`CHAIN_ID\`, and \`BROKER_ADDRESS\` on the same network.
|
|
52
|
-
- If \`IS_BETA_MODE\` is unset, MCP auto-detects beta mode for those two testnet brokers.
|
|
53
51
|
|
|
54
52
|
Current Session:
|
|
55
53
|
- Wallet: ${address}
|
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
|
|
464
|
+
const server = new Server({ name: "myx-mcp-trading-server", version: "3.1.0" }, { 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
|
|
585
|
+
logger.info("🚀 MYX Trading MCP Server v3.1.0 running (stdio, pure on-chain, prod ready)");
|
|
586
586
|
}
|
|
587
587
|
main().catch((err) => {
|
|
588
588
|
logger.error("Fatal Server Startup Error", err);
|
|
@@ -46,6 +46,12 @@ function normalizePoolId(row) {
|
|
|
46
46
|
const poolId = row?.poolId ?? row?.pool_id ?? "";
|
|
47
47
|
return String(poolId);
|
|
48
48
|
}
|
|
49
|
+
function matchesChainId(row, chainId) {
|
|
50
|
+
const raw = row?.chainId ?? row?.chain_id;
|
|
51
|
+
if (raw === undefined || raw === null || raw === "")
|
|
52
|
+
return true;
|
|
53
|
+
return Number(raw) === chainId;
|
|
54
|
+
}
|
|
49
55
|
function matchesKeyword(row, keywordUpper) {
|
|
50
56
|
const poolId = normalizePoolId(row);
|
|
51
57
|
const haystack = [
|
|
@@ -67,7 +73,7 @@ async function fetchApiMarketRows(client, chainId) {
|
|
|
67
73
|
// Preferred path: documented markets.searchMarket
|
|
68
74
|
try {
|
|
69
75
|
const searchRes = await client.markets.searchMarket({ chainId, keyword: "", limit: 2000 });
|
|
70
|
-
const searchRows = extractMarketRows(searchRes, chainId).filter((row) => normalizePoolId(row));
|
|
76
|
+
const searchRows = extractMarketRows(searchRes, chainId).filter((row) => normalizePoolId(row) && matchesChainId(row, chainId));
|
|
71
77
|
if (searchRows.length > 0)
|
|
72
78
|
return searchRows;
|
|
73
79
|
}
|
|
@@ -76,7 +82,7 @@ async function fetchApiMarketRows(client, chainId) {
|
|
|
76
82
|
// Secondary path: documented markets.getPoolSymbolAll
|
|
77
83
|
try {
|
|
78
84
|
const symbolsRes = await client.markets.getPoolSymbolAll();
|
|
79
|
-
const symbolRows = collectRows(symbolsRes?.data ?? symbolsRes).filter((row) => normalizePoolId(row));
|
|
85
|
+
const symbolRows = collectRows(symbolsRes?.data ?? symbolsRes).filter((row) => normalizePoolId(row) && matchesChainId(row, chainId));
|
|
80
86
|
if (symbolRows.length > 0)
|
|
81
87
|
return symbolRows;
|
|
82
88
|
}
|
|
@@ -84,12 +90,12 @@ async function fetchApiMarketRows(client, chainId) {
|
|
|
84
90
|
}
|
|
85
91
|
// Legacy fallback: internal api namespace (for backward compatibility)
|
|
86
92
|
const marketListRes = await client.api?.getMarketList?.().catch(() => null);
|
|
87
|
-
const marketRows = extractMarketRows(marketListRes, chainId);
|
|
93
|
+
const marketRows = extractMarketRows(marketListRes, chainId).filter((row) => matchesChainId(row, chainId));
|
|
88
94
|
const marketRowsWithPoolId = marketRows.filter((row) => normalizePoolId(row));
|
|
89
95
|
if (marketRowsWithPoolId.length > 0)
|
|
90
96
|
return marketRowsWithPoolId;
|
|
91
97
|
const poolListRes = await client.api?.getPoolList?.().catch(() => null);
|
|
92
|
-
return collectRows(poolListRes?.data ?? poolListRes).filter((row) => normalizePoolId(row));
|
|
98
|
+
return collectRows(poolListRes?.data ?? poolListRes).filter((row) => normalizePoolId(row) && matchesChainId(row, chainId));
|
|
93
99
|
}
|
|
94
100
|
export async function getMarketPrice(client, poolId, chainIdOverride) {
|
|
95
101
|
const chainId = chainIdOverride ?? getChainId();
|
|
@@ -230,7 +236,7 @@ export async function getPoolList(client, chainIdOverride) {
|
|
|
230
236
|
const chainId = chainIdOverride ?? getChainId();
|
|
231
237
|
try {
|
|
232
238
|
const searchRes = await client.markets.searchMarket({ chainId, keyword: "", limit: 2000 });
|
|
233
|
-
const rows = extractMarketRows(searchRes, chainId).filter((row) => normalizePoolId(row));
|
|
239
|
+
const rows = extractMarketRows(searchRes, chainId).filter((row) => normalizePoolId(row) && matchesChainId(row, chainId));
|
|
234
240
|
if (rows.length > 0)
|
|
235
241
|
return rows;
|
|
236
242
|
}
|
|
@@ -238,13 +244,14 @@ export async function getPoolList(client, chainIdOverride) {
|
|
|
238
244
|
}
|
|
239
245
|
try {
|
|
240
246
|
const symbolsRes = await client.markets.getPoolSymbolAll();
|
|
241
|
-
const rows = collectRows(symbolsRes?.data ?? symbolsRes).filter((row) => normalizePoolId(row));
|
|
247
|
+
const rows = collectRows(symbolsRes?.data ?? symbolsRes).filter((row) => normalizePoolId(row) && matchesChainId(row, chainId));
|
|
242
248
|
if (rows.length > 0)
|
|
243
249
|
return rows;
|
|
244
250
|
}
|
|
245
251
|
catch {
|
|
246
252
|
}
|
|
247
|
-
|
|
253
|
+
const poolListRes = await client.api?.getPoolList?.();
|
|
254
|
+
return collectRows(poolListRes?.data ?? poolListRes).filter((row) => normalizePoolId(row) && matchesChainId(row, chainId));
|
|
248
255
|
}
|
|
249
256
|
/**
|
|
250
257
|
* 获取池子分级配置
|
|
@@ -13,12 +13,6 @@ const POOL_MANAGER_BY_CHAIN = {
|
|
|
13
13
|
97: "0x4F917ef137b573D9790b87e3cF6dfb698cF00c9c",
|
|
14
14
|
56: "0x13F2130c2F3bfd612BBCBF35FB9E467dd32bAF3A",
|
|
15
15
|
};
|
|
16
|
-
const POOL_MANAGER_BY_CHAIN_BETA = {
|
|
17
|
-
421614: "0x05314a21Fc97B74f168730153b2B63A870D25dE5",
|
|
18
|
-
59141: "0xcf51a6895864c6D8E507fC31EF16b9011287c5f4",
|
|
19
|
-
97: "0x9E84a999e15CCdb2F64a5AF10939c25769dF6b07",
|
|
20
|
-
56: "0x9E84a999e15CCdb2F64a5AF10939c25769dF6b07",
|
|
21
|
-
};
|
|
22
16
|
const POOL_MANAGER_ABI = [
|
|
23
17
|
"function deployPool((bytes32 marketId,address baseToken))",
|
|
24
18
|
"function getMarketPool(bytes32 marketId,address asset) view returns ((bytes32 marketId,bytes32 poolId,address baseToken,address quoteToken,uint8 riskTier,uint8 state,address basePoolToken,address quotePoolToken,uint16 maxPriceDeviation,bool compoundEnabled,uint64 windowCapUsd,address poolVault,address tradingVault))",
|
|
@@ -45,28 +39,6 @@ const LP_ROUTER_BY_CHAIN = {
|
|
|
45
39
|
quotePool: "0xB1E6df749A602892FafB27bb39Fd4F044527121E",
|
|
46
40
|
},
|
|
47
41
|
};
|
|
48
|
-
const LP_ROUTER_BY_CHAIN_BETA = {
|
|
49
|
-
421614: {
|
|
50
|
-
router: "0xfb790ECE13Cd9e296b4a06ABF38D10431360c236",
|
|
51
|
-
basePool: "0x1F767BEa83EDe5E0C18904f439C579f52c2c0F1b",
|
|
52
|
-
quotePool: "0x50ad7da312c58c6689bEF028954a366cb5b8ee13",
|
|
53
|
-
},
|
|
54
|
-
59141: {
|
|
55
|
-
router: "0x0AE31989318565620c03d35889a0B2536b8fba5C",
|
|
56
|
-
basePool: "0xc9e3826c42183207B418116A16827C1605132c51",
|
|
57
|
-
quotePool: "0x9A9934D3b22103dE822d94A22A3177d728FE0e5a",
|
|
58
|
-
},
|
|
59
|
-
97: {
|
|
60
|
-
router: "0x0F4C6f18Fb136DD1eBd6Da3C5d86a86597CF79a3",
|
|
61
|
-
basePool: "0x51B62554a76197d5DF2D5dC4D57FF54d40775938",
|
|
62
|
-
quotePool: "0x783Ed065a12e1C1D33c2a8d6408385C1843D3084",
|
|
63
|
-
},
|
|
64
|
-
56: {
|
|
65
|
-
router: "0x0F4C6f18Fb136DD1eBd6Da3C5d86a86597CF79a3",
|
|
66
|
-
basePool: "0x51B62554a76197d5DF2D5dC4D57FF54d40775938",
|
|
67
|
-
quotePool: "0x783Ed065a12e1C1D33c2a8d6408385C1843D3084",
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
42
|
const PREVIEW_POOL_ABI = [
|
|
71
43
|
"function previewLpAmountOut(bytes32,uint256,uint256) view returns (uint256)",
|
|
72
44
|
"function previewQuoteAmountOut(bytes32,uint256,uint256) view returns (uint256)",
|
|
@@ -165,24 +137,19 @@ async function withMutedSdkAbiMismatchLogs(runner) {
|
|
|
165
137
|
console.error = original;
|
|
166
138
|
}
|
|
167
139
|
}
|
|
168
|
-
function isBetaModeEnabled() {
|
|
169
|
-
return String(process.env.IS_BETA_MODE ?? "").trim().toLowerCase() === "true";
|
|
170
|
-
}
|
|
171
140
|
function getPoolManagerAddress(chainId) {
|
|
172
141
|
const envAddress = String(process.env.POOL_MANAGER_ADDRESS ?? "").trim();
|
|
173
142
|
if (envAddress) {
|
|
174
143
|
return normalizeAddress(envAddress, "POOL_MANAGER_ADDRESS");
|
|
175
144
|
}
|
|
176
|
-
const
|
|
177
|
-
const mapped = source[chainId];
|
|
145
|
+
const mapped = POOL_MANAGER_BY_CHAIN[chainId];
|
|
178
146
|
if (!mapped) {
|
|
179
|
-
throw new Error(`Pool manager address is not configured for chainId=${chainId}
|
|
147
|
+
throw new Error(`Pool manager address is not configured for chainId=${chainId}. Set POOL_MANAGER_ADDRESS env var.`);
|
|
180
148
|
}
|
|
181
149
|
return mapped;
|
|
182
150
|
}
|
|
183
151
|
function getLpAddresses(chainId) {
|
|
184
|
-
const
|
|
185
|
-
const matched = source[chainId];
|
|
152
|
+
const matched = LP_ROUTER_BY_CHAIN[chainId];
|
|
186
153
|
if (!matched) {
|
|
187
154
|
throw new Error(`Liquidity router config not found for chainId=${chainId}.`);
|
|
188
155
|
}
|
|
@@ -15,7 +15,7 @@ export const getAllTickersTool = {
|
|
|
15
15
|
// Fallback for networks/environments where getAllTickers endpoint is unavailable.
|
|
16
16
|
const chainId = getChainId();
|
|
17
17
|
const poolList = await getPoolList(client, chainId);
|
|
18
|
-
const pools = Array.isArray(poolList
|
|
18
|
+
const pools = Array.isArray(poolList) ? poolList : (Array.isArray(poolList?.data) ? poolList.data : []);
|
|
19
19
|
const poolIds = pools.map((p) => p?.poolId ?? p?.pool_id).filter((id) => !!id);
|
|
20
20
|
if (poolIds.length === 0) {
|
|
21
21
|
throw new Error("Failed to fetch all tickers and no pools were available for fallback query.");
|
package/dist/tools/listPools.js
CHANGED
|
@@ -21,7 +21,8 @@ export const listPoolsTool = {
|
|
|
21
21
|
getPoolList(client),
|
|
22
22
|
client.markets.getPoolSymbolAll().catch(() => ({ data: [] }))
|
|
23
23
|
]);
|
|
24
|
-
const
|
|
24
|
+
const poolsSource = Array.isArray(poolListRes) ? poolListRes : poolListRes?.data ?? poolListRes;
|
|
25
|
+
const poolsRaw = collectRows(poolsSource);
|
|
25
26
|
const symbolsRaw = collectRows(symbolsRes?.data ?? symbolsRes);
|
|
26
27
|
const symbolMap = new Map(symbolsRaw
|
|
27
28
|
.filter((row) => row?.poolId || row?.pool_id)
|