@michaleffffff/mcp-trading-server 3.0.10 → 3.0.15
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 +50 -0
- package/README.md +92 -1
- package/TOOL_EXAMPLES.md +291 -54
- package/dist/prompts/tradingGuide.js +4 -2
- package/dist/server.js +95 -4
- package/dist/services/tradeService.js +62 -2
- package/dist/tools/closePosition.js +1 -1
- package/dist/tools/executeTrade.js +6 -1
- package/dist/tools/getBaseDetail.js +86 -0
- package/dist/tools/openPositionSimple.js +5 -0
- package/dist/tools/searchTools.js +237 -12
- package/dist/utils/mappings.js +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.15 - 2026-03-18
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Hardened trading parameter compatibility with SDK `v1.0.2`:
|
|
7
|
+
- `execute_trade` / `close_position` now enforce `timeInForce=IOC` only (`0` / `IOC`).
|
|
8
|
+
- Added `poolId` ↔ `marketId` consistency validation on `execute_trade`.
|
|
9
|
+
- Added preflight size/notional validation so `size` is treated strictly as BASE quantity instead of mistaken USD order value.
|
|
10
|
+
- Hardened `get_base_detail`:
|
|
11
|
+
- If SDK returns empty data for a valid active base token, MCP now falls back to market search + market detail to synthesize base metadata instead of returning false `NOT_FOUND`.
|
|
12
|
+
|
|
13
|
+
## 3.0.14 - 2026-03-18
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Improved `open_position_simple` compatibility for MCP/LLM callers:
|
|
17
|
+
- Accepts optional `marketId` input without triggering schema rejection.
|
|
18
|
+
- Validates supplied `marketId` against the market resolved from `poolId` / `keyword`.
|
|
19
|
+
- Added regression coverage for `dryRun` calls that include `marketId`.
|
|
20
|
+
|
|
21
|
+
## 3.0.13 - 2026-03-18
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Updated docs to better reflect the consolidated toolset:
|
|
25
|
+
- Added a `Legacy Mapping` section to `README.md`
|
|
26
|
+
- Added a `Tool Discovery` workflow using `search_tools`
|
|
27
|
+
- Added a dedicated `Parameter Format Guide` with canonical examples
|
|
28
|
+
- Rebuilt `TOOL_EXAMPLES.md` into a current, MCP-first example handbook with clearer payload formats for trading, liquidity, account, and discovery workflows.
|
|
29
|
+
- Improved `search_tools`:
|
|
30
|
+
- Searches legacy tool names, aliases, categories, and intent phrases
|
|
31
|
+
- Returns categories, aliases, required args, and common parameter hints
|
|
32
|
+
- Returns fallback suggestions when no exact match is found
|
|
33
|
+
- Updated `trading_best_practices` prompt to reference `search_tools`, consolidated tools, and tolerant enum handling.
|
|
34
|
+
|
|
35
|
+
## 3.0.12 - 2026-03-18
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
- Expanded MCP server-side alias normalization beyond case-insensitive enums:
|
|
39
|
+
- `direction`: supports `buy/sell`, `long/short`, `bull/bear`
|
|
40
|
+
- `action`: supports `add/remove/increase/decrease` and normalizes to `deposit/withdraw`
|
|
41
|
+
- `orderType`: supports case-insensitive `market/limit/stop/conditional`
|
|
42
|
+
- `triggerType`: supports case-insensitive `none/gte/lte`
|
|
43
|
+
- This keeps tool schemas strict while making AI/tool callers more tolerant of natural-language style inputs.
|
|
44
|
+
|
|
45
|
+
## 3.0.11 - 2026-03-18
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
- Improved MCP argument normalization for string enums:
|
|
49
|
+
- `z.enum(...)` inputs are now matched case-insensitively at the server layer before Zod validation.
|
|
50
|
+
- Lowercase/mixed-case inputs like `open`, `history`, `all`, `base`, `quote`, `long`, `short` now normalize to canonical enum values automatically.
|
|
51
|
+
- This reduces avoidable `INVALID_PARAM` errors for AI callers while preserving the same canonical tool schemas.
|
|
52
|
+
|
|
3
53
|
## 3.0.10 - 2026-03-18
|
|
4
54
|
|
|
5
55
|
### 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.
|
|
9
|
+
- **Current release: 3.0.15**
|
|
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.
|
|
@@ -44,6 +44,7 @@ QUOTE_TOKEN_DECIMALS=18
|
|
|
44
44
|
### 📈 Market Analysis
|
|
45
45
|
* **`find_pool`**: Discover active markets by keyword/symbol (e.g., "BTC", "ETH").
|
|
46
46
|
* **`list_pools`**: List all tradable assets on the current chain.
|
|
47
|
+
* **`search_tools`**: Discover the right MCP tool by keyword, legacy tool name, or intent phrase.
|
|
47
48
|
* **`get_price`**: Fetch real-time prices (Impact Market Price or Oracle Price).
|
|
48
49
|
* **`get_pool_metadata`**: Comprehensive metrics (Fees, Open Interest, Liquidity Depth).
|
|
49
50
|
* **`get_kline`**: Fetch candlestick data for technical analysis.
|
|
@@ -75,6 +76,96 @@ QUOTE_TOKEN_DECIMALS=18
|
|
|
75
76
|
|
|
76
77
|
---
|
|
77
78
|
|
|
79
|
+
# Tool Discovery
|
|
80
|
+
|
|
81
|
+
When a client or LLM is unsure which tool to call:
|
|
82
|
+
|
|
83
|
+
1. Use `search_tools(keyword="open order")` or `search_tools(keyword="get_market_price")`
|
|
84
|
+
2. Read the returned `aliases`, `category`, and `commonArgs`
|
|
85
|
+
3. Confirm market context with `find_pool`
|
|
86
|
+
4. Call the recommended high-level tool instead of a removed legacy tool
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{ "name": "search_tools", "arguments": { "keyword": "get_open_orders" } }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{ "name": "search_tools", "arguments": { "keyword": "add base lp" } }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
# Parameter Format Guide
|
|
101
|
+
|
|
102
|
+
Use these conventions when generating tool arguments:
|
|
103
|
+
|
|
104
|
+
- `poolId`: Prefer a real hex pool id from `find_pool` or `list_pools`
|
|
105
|
+
- `keyword`: Use a market symbol like `"BTC"`, `"ETH"`, `"ARB"`
|
|
106
|
+
- `direction`: `LONG` / `SHORT` are canonical; lowercase and aliases like `buy` / `sell` are tolerated
|
|
107
|
+
- `status`: `OPEN` / `HISTORY` / `ALL` are canonical; lowercase is tolerated
|
|
108
|
+
- `poolType`: `BASE` / `QUOTE` are canonical; lowercase is tolerated
|
|
109
|
+
- `orderType`: `MARKET` / `LIMIT` / `STOP` / `CONDITIONAL`
|
|
110
|
+
- `timeInForce`: SDK `v1.0.2` currently supports `IOC` only, so use `0` or `"IOC"`
|
|
111
|
+
- `size`: base token quantity, not USD notional; expected order value is usually `collateralAmount * leverage`
|
|
112
|
+
- Human units: `"100"` means 100 USDC or 100 token units depending on field
|
|
113
|
+
- Raw units: `"raw:1000000"` means exact on-chain integer units
|
|
114
|
+
|
|
115
|
+
Examples:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"name": "get_orders",
|
|
120
|
+
"arguments": { "status": "OPEN", "limit": 20 }
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"name": "manage_liquidity",
|
|
127
|
+
"arguments": {
|
|
128
|
+
"poolId": "0x...",
|
|
129
|
+
"poolType": "BASE",
|
|
130
|
+
"action": "deposit",
|
|
131
|
+
"amount": 1000,
|
|
132
|
+
"slippage": 0.01
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"name": "open_position_simple",
|
|
140
|
+
"arguments": {
|
|
141
|
+
"keyword": "ARB",
|
|
142
|
+
"direction": "LONG",
|
|
143
|
+
"collateralAmount": "100",
|
|
144
|
+
"leverage": 5,
|
|
145
|
+
"orderType": "LIMIT",
|
|
146
|
+
"price": "2.5"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
# Legacy Mapping
|
|
154
|
+
|
|
155
|
+
Common old-to-new tool mappings:
|
|
156
|
+
|
|
157
|
+
- `get_market_price` / `get_oracle_price` -> `get_price`
|
|
158
|
+
- `get_market_detail` / `get_pool_info` / `get_liquidity_info` / `get_pool_level_config` -> `get_pool_metadata`
|
|
159
|
+
- `get_pool_list` / `get_pool_symbol_all` -> `list_pools`
|
|
160
|
+
- `search_market` / `get_pool_by_symbol` -> `find_pool`
|
|
161
|
+
- `get_open_orders` / `get_order_history` -> `get_orders`
|
|
162
|
+
- `get_positions` / `get_position_history` -> `get_positions_all`
|
|
163
|
+
- `cancel_order` / `cancel_all_orders` -> `cancel_orders`
|
|
164
|
+
- `set_tp_sl` / `update_order_tp_sl` -> `manage_tp_sl`
|
|
165
|
+
- `get_account` / `get_account_info` / `get_balances` / `get_margin_balance` -> `get_account_snapshot`
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
78
169
|
# Documentation
|
|
79
170
|
|
|
80
171
|
For detailed implementation examples and parameter guides, see:
|
package/TOOL_EXAMPLES.md
CHANGED
|
@@ -1,150 +1,387 @@
|
|
|
1
|
-
# MYX MCP Tool Examples Handbook (v3.0.
|
|
1
|
+
# MYX MCP Tool Examples Handbook (v3.0.15)
|
|
2
2
|
|
|
3
|
-
This guide provides practical payload examples for the
|
|
4
|
-
All examples
|
|
3
|
+
This guide provides practical MCP payload examples for the current unified toolset.
|
|
4
|
+
All examples use the MCP format:
|
|
5
|
+
|
|
6
|
+
```json
|
|
7
|
+
{ "name": "tool_name", "arguments": { "...": "..." } }
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Discovery First
|
|
13
|
+
|
|
14
|
+
### `search_tools`
|
|
15
|
+
Find the right tool by intent phrase, old tool name, or keyword.
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"name": "search_tools",
|
|
20
|
+
"arguments": { "keyword": "get_open_orders" }
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"name": "search_tools",
|
|
27
|
+
"arguments": { "keyword": "add base lp" }
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### `find_pool`
|
|
32
|
+
Resolve a market keyword to a tradable `poolId`.
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"name": "find_pool",
|
|
37
|
+
"arguments": { "keyword": "ETH", "limit": 5 }
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `list_pools`
|
|
42
|
+
Browse all current markets on the active chain.
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"name": "list_pools",
|
|
47
|
+
"arguments": {}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
5
50
|
|
|
6
51
|
---
|
|
7
52
|
|
|
8
|
-
##
|
|
53
|
+
## Trading
|
|
54
|
+
|
|
55
|
+
### `open_position_simple`
|
|
56
|
+
Recommended high-level entry tool.
|
|
9
57
|
|
|
10
|
-
### `open_position_simple` (Recommended)
|
|
11
|
-
High-level tool that computes size and fees automatically.
|
|
12
58
|
```json
|
|
13
59
|
{
|
|
14
60
|
"name": "open_position_simple",
|
|
15
61
|
"arguments": {
|
|
16
|
-
"keyword": "
|
|
62
|
+
"keyword": "ARB",
|
|
63
|
+
"marketId": "0x...",
|
|
17
64
|
"direction": "LONG",
|
|
18
65
|
"collateralAmount": "100",
|
|
19
|
-
"leverage":
|
|
20
|
-
"
|
|
21
|
-
"
|
|
66
|
+
"leverage": 5,
|
|
67
|
+
"orderType": "LIMIT",
|
|
68
|
+
"price": "2.5",
|
|
69
|
+
"tpPrice": "2.9",
|
|
70
|
+
"slPrice": "2.2"
|
|
22
71
|
}
|
|
23
72
|
}
|
|
24
73
|
```
|
|
25
74
|
|
|
26
|
-
|
|
27
|
-
|
|
75
|
+
`marketId` is optional on `open_position_simple`. If supplied, it is validated against the market resolved from `poolId` or `keyword`.
|
|
76
|
+
`size` is always the base-asset quantity, not the USD notional. For example, a 500 USD order at price 1200 implies `size ≈ 0.416666...`.
|
|
77
|
+
|
|
78
|
+
Raw-units example:
|
|
79
|
+
|
|
28
80
|
```json
|
|
29
|
-
// Mode 1: Specific IDs
|
|
30
81
|
{
|
|
31
|
-
"name": "
|
|
32
|
-
"arguments": {
|
|
82
|
+
"name": "open_position_simple",
|
|
83
|
+
"arguments": {
|
|
84
|
+
"poolId": "0x...",
|
|
85
|
+
"direction": "SHORT",
|
|
86
|
+
"collateralAmount": "raw:100000000",
|
|
87
|
+
"leverage": 10,
|
|
88
|
+
"orderType": "MARKET"
|
|
89
|
+
}
|
|
33
90
|
}
|
|
91
|
+
```
|
|
34
92
|
|
|
35
|
-
|
|
93
|
+
### `execute_trade`
|
|
94
|
+
Low-level increase-order tool when you want full control.
|
|
95
|
+
|
|
96
|
+
```json
|
|
36
97
|
{
|
|
37
|
-
"name": "
|
|
38
|
-
"arguments": {
|
|
98
|
+
"name": "execute_trade",
|
|
99
|
+
"arguments": {
|
|
100
|
+
"poolId": "0x...",
|
|
101
|
+
"marketId": "0x...",
|
|
102
|
+
"direction": "LONG",
|
|
103
|
+
"orderType": "LIMIT",
|
|
104
|
+
"price": "2500",
|
|
105
|
+
"size": "0.2",
|
|
106
|
+
"collateralAmount": "100",
|
|
107
|
+
"timeInForce": 0,
|
|
108
|
+
"leverage": 5
|
|
109
|
+
}
|
|
39
110
|
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`timeInForce` should be `0` (or `"IOC"` in string form) for SDK `v1.0.2`.
|
|
40
114
|
|
|
41
|
-
|
|
115
|
+
### `close_position`
|
|
116
|
+
Close or reduce a position. Use `ALL` for a full close.
|
|
117
|
+
|
|
118
|
+
```json
|
|
42
119
|
{
|
|
43
|
-
"name": "
|
|
44
|
-
"arguments": {
|
|
120
|
+
"name": "close_position",
|
|
121
|
+
"arguments": {
|
|
122
|
+
"poolId": "0x...",
|
|
123
|
+
"positionId": "0x...",
|
|
124
|
+
"direction": "LONG",
|
|
125
|
+
"orderType": "MARKET",
|
|
126
|
+
"collateralAmount": "ALL",
|
|
127
|
+
"size": "ALL",
|
|
128
|
+
"price": "2200",
|
|
129
|
+
"timeInForce": 0,
|
|
130
|
+
"postOnly": false,
|
|
131
|
+
"slippagePct": "50",
|
|
132
|
+
"executionFeeToken": "0x...",
|
|
133
|
+
"leverage": 5
|
|
134
|
+
}
|
|
45
135
|
}
|
|
46
136
|
```
|
|
47
137
|
|
|
48
138
|
### `manage_tp_sl`
|
|
49
|
-
|
|
139
|
+
Create or update TP/SL on an open position.
|
|
140
|
+
|
|
50
141
|
```json
|
|
51
142
|
{
|
|
52
143
|
"name": "manage_tp_sl",
|
|
53
144
|
"arguments": {
|
|
54
|
-
"poolId": "
|
|
55
|
-
"positionId": "
|
|
145
|
+
"poolId": "0x...",
|
|
146
|
+
"positionId": "0x...",
|
|
147
|
+
"direction": "LONG",
|
|
56
148
|
"leverage": 5,
|
|
57
|
-
"tpPrice": "
|
|
58
|
-
"slPrice": "
|
|
149
|
+
"tpPrice": "2800",
|
|
150
|
+
"slPrice": "2300"
|
|
59
151
|
}
|
|
60
152
|
}
|
|
61
153
|
```
|
|
62
154
|
|
|
63
|
-
Delete both TP/SL
|
|
155
|
+
Delete both TP/SL orders:
|
|
156
|
+
|
|
64
157
|
```json
|
|
65
158
|
{
|
|
66
159
|
"name": "manage_tp_sl",
|
|
67
160
|
"arguments": {
|
|
68
|
-
"poolId": "
|
|
69
|
-
"positionId": "
|
|
161
|
+
"poolId": "0x...",
|
|
162
|
+
"positionId": "0x...",
|
|
70
163
|
"tpPrice": "0",
|
|
71
164
|
"slPrice": "0"
|
|
72
165
|
}
|
|
73
166
|
}
|
|
74
167
|
```
|
|
75
168
|
|
|
76
|
-
|
|
169
|
+
### `cancel_orders`
|
|
170
|
+
Supports single-order, pool-wide, or account-wide cancellation.
|
|
77
171
|
|
|
78
|
-
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"name": "cancel_orders",
|
|
175
|
+
"arguments": { "orderIds": ["123", "124"] }
|
|
176
|
+
}
|
|
177
|
+
```
|
|
79
178
|
|
|
80
|
-
### `find_pool`
|
|
81
|
-
Keyword or symbol based discovery.
|
|
82
179
|
```json
|
|
83
180
|
{
|
|
84
|
-
"name": "
|
|
85
|
-
"arguments": { "
|
|
181
|
+
"name": "cancel_orders",
|
|
182
|
+
"arguments": { "poolId": "0x..." }
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"name": "cancel_orders",
|
|
189
|
+
"arguments": { "cancelAll": true }
|
|
86
190
|
}
|
|
87
191
|
```
|
|
88
192
|
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Market Data
|
|
196
|
+
|
|
89
197
|
### `get_price`
|
|
90
|
-
|
|
198
|
+
Read either market or oracle price.
|
|
199
|
+
|
|
91
200
|
```json
|
|
92
201
|
{
|
|
93
202
|
"name": "get_price",
|
|
94
|
-
"arguments": {
|
|
95
|
-
"poolId": "0x...",
|
|
96
|
-
"priceType": "market"
|
|
203
|
+
"arguments": {
|
|
204
|
+
"poolId": "0x...",
|
|
205
|
+
"priceType": "market"
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"name": "get_price",
|
|
213
|
+
"arguments": {
|
|
214
|
+
"poolId": "0x...",
|
|
215
|
+
"priceType": "oracle"
|
|
97
216
|
}
|
|
98
217
|
}
|
|
99
218
|
```
|
|
100
219
|
|
|
101
220
|
### `get_pool_metadata`
|
|
102
|
-
|
|
221
|
+
Unified pool detail, config, and liquidity info.
|
|
222
|
+
|
|
103
223
|
```json
|
|
104
224
|
{
|
|
105
225
|
"name": "get_pool_metadata",
|
|
106
|
-
"arguments": {
|
|
226
|
+
"arguments": {
|
|
227
|
+
"poolId": "0x...",
|
|
228
|
+
"includeConfig": true,
|
|
229
|
+
"includeLiquidity": true
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### `get_kline`
|
|
235
|
+
Read chart data. Use `limit: 1` for the latest bar.
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"name": "get_kline",
|
|
240
|
+
"arguments": {
|
|
241
|
+
"poolId": "0x...",
|
|
242
|
+
"interval": "1m",
|
|
243
|
+
"limit": 50
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Liquidity
|
|
251
|
+
|
|
252
|
+
### `manage_liquidity`
|
|
253
|
+
Add or remove BASE/QUOTE LP.
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"name": "manage_liquidity",
|
|
258
|
+
"arguments": {
|
|
259
|
+
"poolId": "0x...",
|
|
260
|
+
"poolType": "BASE",
|
|
261
|
+
"action": "deposit",
|
|
262
|
+
"amount": 1000,
|
|
263
|
+
"slippage": 0.01
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Alias-friendly form also works:
|
|
269
|
+
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"name": "manage_liquidity",
|
|
273
|
+
"arguments": {
|
|
274
|
+
"poolId": "0x...",
|
|
275
|
+
"poolType": "base",
|
|
276
|
+
"action": "add",
|
|
277
|
+
"amount": 1000,
|
|
278
|
+
"slippage": 0.01
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### `get_lp_price`
|
|
284
|
+
Read LP NAV price for BASE or QUOTE side.
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
{
|
|
288
|
+
"name": "get_lp_price",
|
|
289
|
+
"arguments": {
|
|
290
|
+
"poolId": "0x...",
|
|
291
|
+
"poolType": "QUOTE"
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### `get_my_lp_holdings`
|
|
297
|
+
Read current LP balances across pools.
|
|
298
|
+
|
|
299
|
+
```json
|
|
300
|
+
{
|
|
301
|
+
"name": "get_my_lp_holdings",
|
|
302
|
+
"arguments": {
|
|
303
|
+
"includeZero": false,
|
|
304
|
+
"maxPools": 20
|
|
305
|
+
}
|
|
107
306
|
}
|
|
108
307
|
```
|
|
109
308
|
|
|
110
309
|
---
|
|
111
310
|
|
|
112
|
-
##
|
|
311
|
+
## Account And Monitoring
|
|
113
312
|
|
|
114
313
|
### `get_account_snapshot`
|
|
115
|
-
|
|
314
|
+
Read wallet balance, trading account info, and VIP snapshot.
|
|
315
|
+
|
|
116
316
|
```json
|
|
117
317
|
{
|
|
118
318
|
"name": "get_account_snapshot",
|
|
119
|
-
"arguments": {
|
|
319
|
+
"arguments": {
|
|
320
|
+
"poolId": "0x..."
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### `check_account_ready`
|
|
326
|
+
Pre-check whether collateral is available before trading.
|
|
327
|
+
|
|
328
|
+
```json
|
|
329
|
+
{
|
|
330
|
+
"name": "check_account_ready",
|
|
331
|
+
"arguments": {
|
|
332
|
+
"poolId": "0x...",
|
|
333
|
+
"collateralAmount": "100"
|
|
334
|
+
}
|
|
120
335
|
}
|
|
121
336
|
```
|
|
122
337
|
|
|
123
338
|
### `get_orders`
|
|
124
|
-
|
|
339
|
+
Read open orders, history, or both.
|
|
340
|
+
|
|
125
341
|
```json
|
|
126
342
|
{
|
|
127
343
|
"name": "get_orders",
|
|
128
|
-
"arguments": {
|
|
129
|
-
"status": "
|
|
130
|
-
"
|
|
344
|
+
"arguments": {
|
|
345
|
+
"status": "OPEN",
|
|
346
|
+
"poolId": "0x...",
|
|
347
|
+
"limit": 20
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Lowercase is also accepted:
|
|
353
|
+
|
|
354
|
+
```json
|
|
355
|
+
{
|
|
356
|
+
"name": "get_orders",
|
|
357
|
+
"arguments": {
|
|
358
|
+
"status": "open",
|
|
359
|
+
"limit": 20
|
|
131
360
|
}
|
|
132
361
|
}
|
|
133
362
|
```
|
|
134
363
|
|
|
135
364
|
### `get_positions_all`
|
|
136
|
-
|
|
365
|
+
Read open positions, history, or both.
|
|
366
|
+
|
|
137
367
|
```json
|
|
138
368
|
{
|
|
139
369
|
"name": "get_positions_all",
|
|
140
|
-
"arguments": {
|
|
370
|
+
"arguments": {
|
|
371
|
+
"status": "ALL",
|
|
372
|
+
"poolId": "0x...",
|
|
373
|
+
"limit": 20
|
|
374
|
+
}
|
|
141
375
|
}
|
|
142
376
|
```
|
|
143
377
|
|
|
144
378
|
---
|
|
145
379
|
|
|
146
|
-
##
|
|
380
|
+
## Parameter Conventions
|
|
147
381
|
|
|
148
|
-
1.
|
|
149
|
-
2.
|
|
150
|
-
3.
|
|
382
|
+
1. Use `find_pool` before trading if you do not already have a `poolId`.
|
|
383
|
+
2. Human-readable units are allowed on high-level tools, such as `"100"` or `"0.5"`.
|
|
384
|
+
3. Exact on-chain values can be passed with the `raw:` prefix.
|
|
385
|
+
4. Canonical enums are still preferred:
|
|
386
|
+
`OPEN|HISTORY|ALL`, `BASE|QUOTE`, `LONG|SHORT`, `MARKET|LIMIT|STOP`.
|
|
387
|
+
5. The server tolerates common lowercase and alias forms for better AI compatibility.
|
|
@@ -12,12 +12,12 @@ 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.0.12)
|
|
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
|
|
|
19
19
|
## 1. The Standard Workflow
|
|
20
|
-
1. **Discovery**: Use \`find_pool\` with a keyword (e.g. "BTC") to get the \`poolId\`.
|
|
20
|
+
1. **Discovery**: Use \`search_tools\` if the intent is unclear, then use \`find_pool\` with a keyword (e.g. "BTC") to get the \`poolId\`.
|
|
21
21
|
2. **Context**: Use \`get_account_snapshot\` (with \`poolId\`) to check balances, trading metrics, and VIP tier. Use \`get_price\` for real-time market/oracle prices.
|
|
22
22
|
3. **Pre-check**: Use \`check_account_ready\` to ensure the trading account has enough collateral.
|
|
23
23
|
4. **Execution**: Prefer \`open_position_simple\` for entry. It supports Stop-Loss (\`slPrice\`) and Take-Profit (\`tpPrice\`) in one call.
|
|
@@ -26,10 +26,12 @@ You are an expert crypto trader using the MYX Protocol. To ensure successful exe
|
|
|
26
26
|
|
|
27
27
|
## 2. Parameter Tips
|
|
28
28
|
- **Consolidated Tools**: Many legacy tools have been merged. Always use the high-level versions (e.g., \`get_price\` instead of \`get_market_price\`).
|
|
29
|
+
- **Discovery**: \`search_tools\` understands legacy names like \`get_open_orders\` and intent phrases like \`add base lp\`.
|
|
29
30
|
- **Unit Prefixes**: Prefer \`human:\` for readable amounts (e.g., "100" USDC) and \`raw:\` for exact on-chain units.
|
|
30
31
|
- **Slippage**: Default is 100 (1%). For volatile tokens, consider 200-300 (2-3%).
|
|
31
32
|
- **Fees**: Use \`get_pool_metadata\` to view current fee tiers and pool configuration.
|
|
32
33
|
- **LP Strategy**: Use \`get_my_lp_holdings\` to monitor liquidity positions. Naming follows \`mBASE.QUOTE\` (e.g., \`mBTC.USDC\`).
|
|
34
|
+
- **Enum Tolerance**: The server tolerates common lowercase or alias inputs such as \`open\`, \`base\`, \`buy\`, and \`add\`, but canonical forms are still preferred in documentation.
|
|
33
35
|
|
|
34
36
|
Current Session:
|
|
35
37
|
- Wallet: ${address}
|
package/dist/server.js
CHANGED
|
@@ -168,9 +168,88 @@ function coerceStringToStringArray(input) {
|
|
|
168
168
|
}
|
|
169
169
|
return input;
|
|
170
170
|
}
|
|
171
|
+
function coerceEnumString(input, values) {
|
|
172
|
+
if (typeof input !== "string")
|
|
173
|
+
return input;
|
|
174
|
+
const normalizedInput = input.trim().toLowerCase();
|
|
175
|
+
if (!normalizedInput)
|
|
176
|
+
return input;
|
|
177
|
+
const stringValues = values.filter((value) => typeof value === "string");
|
|
178
|
+
const matched = stringValues.find((value) => value.toLowerCase() === normalizedInput);
|
|
179
|
+
return matched ?? input;
|
|
180
|
+
}
|
|
181
|
+
function normalizeCommonStringAlias(key, input) {
|
|
182
|
+
if (typeof input !== "string")
|
|
183
|
+
return input;
|
|
184
|
+
const normalizedInput = input.trim().toLowerCase();
|
|
185
|
+
if (!normalizedInput)
|
|
186
|
+
return input;
|
|
187
|
+
const aliasMap = {
|
|
188
|
+
status: {
|
|
189
|
+
open: "OPEN",
|
|
190
|
+
history: "HISTORY",
|
|
191
|
+
all: "ALL",
|
|
192
|
+
},
|
|
193
|
+
poolType: {
|
|
194
|
+
base: "BASE",
|
|
195
|
+
quote: "QUOTE",
|
|
196
|
+
},
|
|
197
|
+
direction: {
|
|
198
|
+
buy: "LONG",
|
|
199
|
+
long: "LONG",
|
|
200
|
+
bull: "LONG",
|
|
201
|
+
sell: "SHORT",
|
|
202
|
+
short: "SHORT",
|
|
203
|
+
bear: "SHORT",
|
|
204
|
+
},
|
|
205
|
+
action: {
|
|
206
|
+
add: "deposit",
|
|
207
|
+
increase: "deposit",
|
|
208
|
+
deposit: "deposit",
|
|
209
|
+
remove: "withdraw",
|
|
210
|
+
decrease: "withdraw",
|
|
211
|
+
withdraw: "withdraw",
|
|
212
|
+
},
|
|
213
|
+
orderType: {
|
|
214
|
+
market: "MARKET",
|
|
215
|
+
limit: "LIMIT",
|
|
216
|
+
stop: "STOP",
|
|
217
|
+
conditional: "CONDITIONAL",
|
|
218
|
+
},
|
|
219
|
+
triggerType: {
|
|
220
|
+
none: "NONE",
|
|
221
|
+
gte: "GTE",
|
|
222
|
+
lte: "LTE",
|
|
223
|
+
},
|
|
224
|
+
priceType: {
|
|
225
|
+
market: "market",
|
|
226
|
+
oracle: "oracle",
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
return aliasMap[key]?.[normalizedInput] ?? input;
|
|
230
|
+
}
|
|
231
|
+
function readEnumValues(schema) {
|
|
232
|
+
if (!schema?._def)
|
|
233
|
+
return [];
|
|
234
|
+
const entries = schema._def.entries;
|
|
235
|
+
if (entries && typeof entries === "object") {
|
|
236
|
+
return Object.values(entries);
|
|
237
|
+
}
|
|
238
|
+
const values = schema._def.values;
|
|
239
|
+
if (Array.isArray(values)) {
|
|
240
|
+
return values;
|
|
241
|
+
}
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
171
244
|
function coerceBySchema(value, schema) {
|
|
172
245
|
const unwrapped = unwrapSchema(schema);
|
|
173
246
|
const type = unwrapped?._def?.type;
|
|
247
|
+
if (type === "enum") {
|
|
248
|
+
return coerceEnumString(value, readEnumValues(unwrapped));
|
|
249
|
+
}
|
|
250
|
+
if (type === "nativeEnum") {
|
|
251
|
+
return coerceEnumString(value, Object.values(unwrapped?._def?.values ?? {}));
|
|
252
|
+
}
|
|
174
253
|
if (type === "boolean") {
|
|
175
254
|
return coerceBooleanString(value);
|
|
176
255
|
}
|
|
@@ -183,7 +262,18 @@ function coerceBySchema(value, schema) {
|
|
|
183
262
|
if (type === "union") {
|
|
184
263
|
const options = Array.isArray(unwrapped?._def?.options) ? unwrapped._def.options : [];
|
|
185
264
|
for (const option of options) {
|
|
186
|
-
const
|
|
265
|
+
const unwrappedOption = unwrapSchema(option);
|
|
266
|
+
const optionType = unwrappedOption?._def?.type;
|
|
267
|
+
if (optionType === "enum") {
|
|
268
|
+
const coerced = coerceEnumString(value, readEnumValues(unwrappedOption));
|
|
269
|
+
if (typeof coerced === "string" && coerced !== value)
|
|
270
|
+
return coerced;
|
|
271
|
+
}
|
|
272
|
+
if (optionType === "nativeEnum") {
|
|
273
|
+
const coerced = coerceEnumString(value, Object.values(unwrappedOption?._def?.values ?? {}));
|
|
274
|
+
if (typeof coerced === "string" && coerced !== value)
|
|
275
|
+
return coerced;
|
|
276
|
+
}
|
|
187
277
|
if (optionType === "boolean") {
|
|
188
278
|
const coerced = coerceBooleanString(value);
|
|
189
279
|
if (typeof coerced === "boolean")
|
|
@@ -207,7 +297,8 @@ function normalizeToolArgsBySchema(rawArgs, schema) {
|
|
|
207
297
|
for (const [key, fieldSchema] of Object.entries(schema)) {
|
|
208
298
|
if (!Object.prototype.hasOwnProperty.call(source, key))
|
|
209
299
|
continue;
|
|
210
|
-
|
|
300
|
+
const aliasedValue = normalizeCommonStringAlias(key, source[key]);
|
|
301
|
+
normalized[key] = coerceBySchema(aliasedValue, fieldSchema);
|
|
211
302
|
}
|
|
212
303
|
return normalized;
|
|
213
304
|
}
|
|
@@ -370,7 +461,7 @@ function zodSchemaToJsonSchema(zodSchema) {
|
|
|
370
461
|
};
|
|
371
462
|
}
|
|
372
463
|
// ─── MCP Server ───
|
|
373
|
-
const server = new Server({ name: "myx-mcp-trading-server", version: "3.0.
|
|
464
|
+
const server = new Server({ name: "myx-mcp-trading-server", version: "3.0.15" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
|
|
374
465
|
// List tools
|
|
375
466
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
376
467
|
return {
|
|
@@ -491,7 +582,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
491
582
|
async function main() {
|
|
492
583
|
const transport = new StdioServerTransport();
|
|
493
584
|
await server.connect(transport);
|
|
494
|
-
logger.info("🚀 MYX Trading MCP Server v3.0.
|
|
585
|
+
logger.info("🚀 MYX Trading MCP Server v3.0.15 running (stdio, pure on-chain, prod ready)");
|
|
495
586
|
}
|
|
496
587
|
main().catch((err) => {
|
|
497
588
|
logger.error("Fatal Server Startup Error", err);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Direction, OrderType, TriggerType } from "@myx-trade/sdk";
|
|
2
|
+
import { formatUnits } from "ethers";
|
|
2
3
|
import { getChainId, getQuoteToken, getQuoteDecimals } from "../auth/resolveClient.js";
|
|
3
4
|
import { ensureUnits } from "../utils/units.js";
|
|
4
5
|
import { normalizeAddress } from "../utils/address.js";
|
|
5
6
|
import { normalizeSlippagePct4dp } from "../utils/slippage.js";
|
|
6
7
|
import { finalizeMutationResult } from "../utils/mutationResult.js";
|
|
7
8
|
import { extractErrorMessage } from "../utils/errorMessage.js";
|
|
9
|
+
import { mapTimeInForce } from "../utils/mappings.js";
|
|
8
10
|
function resolveDirection(direction) {
|
|
9
11
|
if (typeof direction === "string") {
|
|
10
12
|
const text = direction.trim().toUpperCase();
|
|
@@ -104,6 +106,54 @@ function toBigIntOrZero(value) {
|
|
|
104
106
|
return 0n;
|
|
105
107
|
}
|
|
106
108
|
}
|
|
109
|
+
const ORDER_VALUE_SCALE = 1000000n;
|
|
110
|
+
const DECIMAL_INPUT_RE = /^\d+(\.\d+)?$/;
|
|
111
|
+
function parseScaledDecimal(value, scale, label) {
|
|
112
|
+
const text = String(value ?? "").trim();
|
|
113
|
+
if (!text)
|
|
114
|
+
throw new Error(`${label} is required.`);
|
|
115
|
+
if (!DECIMAL_INPUT_RE.test(text)) {
|
|
116
|
+
throw new Error(`${label} must be numeric.`);
|
|
117
|
+
}
|
|
118
|
+
const [integerPart, fractionPart = ""] = text.split(".");
|
|
119
|
+
const normalizedFraction = fractionPart.slice(0, scale).padEnd(scale, "0");
|
|
120
|
+
return BigInt(`${integerPart}${normalizedFraction}`);
|
|
121
|
+
}
|
|
122
|
+
function absBigInt(value) {
|
|
123
|
+
return value < 0n ? -value : value;
|
|
124
|
+
}
|
|
125
|
+
function computeQuoteNotionalRaw(sizeRaw, priceRaw30, baseDecimals, quoteDecimals) {
|
|
126
|
+
const numerator = sizeRaw * priceRaw30 * (10n ** BigInt(quoteDecimals));
|
|
127
|
+
const denominator = 10n ** BigInt(baseDecimals + 30);
|
|
128
|
+
return numerator / denominator;
|
|
129
|
+
}
|
|
130
|
+
function computeRecommendedSizeRaw(targetQuoteRaw, priceRaw30, baseDecimals, quoteDecimals) {
|
|
131
|
+
const numerator = targetQuoteRaw * (10n ** BigInt(baseDecimals + 30));
|
|
132
|
+
const denominator = priceRaw30 * (10n ** BigInt(quoteDecimals));
|
|
133
|
+
return numerator / denominator;
|
|
134
|
+
}
|
|
135
|
+
function validateIncreaseOrderEconomics(args) {
|
|
136
|
+
const collateralRawBig = BigInt(args.collateralRaw);
|
|
137
|
+
const sizeRawBig = BigInt(args.sizeRaw);
|
|
138
|
+
const priceRawBig = BigInt(args.priceRaw30);
|
|
139
|
+
if (collateralRawBig <= 0n || sizeRawBig <= 0n || priceRawBig <= 0n)
|
|
140
|
+
return;
|
|
141
|
+
const leverageScaled = parseScaledDecimal(args.leverage, 6, "leverage");
|
|
142
|
+
const targetQuoteRaw = (collateralRawBig * leverageScaled) / ORDER_VALUE_SCALE;
|
|
143
|
+
const actualQuoteRaw = computeQuoteNotionalRaw(sizeRawBig, priceRawBig, args.baseDecimals, args.quoteDecimals);
|
|
144
|
+
const deltaRaw = absBigInt(actualQuoteRaw - targetQuoteRaw);
|
|
145
|
+
const minToleranceRaw = args.quoteDecimals >= 2 ? 10n ** BigInt(args.quoteDecimals - 2) : 1n;
|
|
146
|
+
const pctToleranceRaw = targetQuoteRaw / 100n;
|
|
147
|
+
const toleranceRaw = pctToleranceRaw > minToleranceRaw ? pctToleranceRaw : minToleranceRaw;
|
|
148
|
+
if (deltaRaw <= toleranceRaw)
|
|
149
|
+
return;
|
|
150
|
+
const recommendedSizeRaw = computeRecommendedSizeRaw(targetQuoteRaw, priceRawBig, args.baseDecimals, args.quoteDecimals);
|
|
151
|
+
const targetHuman = formatUnits(targetQuoteRaw, args.quoteDecimals);
|
|
152
|
+
const actualHuman = formatUnits(actualQuoteRaw, args.quoteDecimals);
|
|
153
|
+
const recommendedSizeHuman = formatUnits(recommendedSizeRaw, args.baseDecimals);
|
|
154
|
+
const priceHuman = formatUnits(priceRawBig, 30);
|
|
155
|
+
throw new Error(`Invalid size semantics: size is BASE quantity, not USD notional. collateralAmount*leverage implies ≈${targetHuman} quote, but size*price implies ≈${actualHuman} quote. At price ${priceHuman}, recommended size is ≈${recommendedSizeHuman}.`);
|
|
156
|
+
}
|
|
107
157
|
async function resolveDecimalsForUpdateOrder(client, chainId, marketId, poolIdHint) {
|
|
108
158
|
let baseDecimals = 18;
|
|
109
159
|
let quoteDecimals = getQuoteDecimals();
|
|
@@ -199,6 +249,15 @@ export async function openPosition(client, address, args) {
|
|
|
199
249
|
const sizeRaw = ensureUnits(args.size, baseDecimals, "size", { allowImplicitRaw: false });
|
|
200
250
|
const priceRaw = ensureUnits(args.price, 30, "price", { allowImplicitRaw: false });
|
|
201
251
|
const tradingFeeRaw = ensureUnits(args.tradingFee, quoteDecimals, "tradingFee", { allowImplicitRaw: false });
|
|
252
|
+
validateIncreaseOrderEconomics({
|
|
253
|
+
collateralRaw,
|
|
254
|
+
sizeRaw,
|
|
255
|
+
priceRaw30: priceRaw,
|
|
256
|
+
leverage: args.leverage,
|
|
257
|
+
baseDecimals,
|
|
258
|
+
quoteDecimals,
|
|
259
|
+
});
|
|
260
|
+
const timeInForce = mapTimeInForce(args.timeInForce);
|
|
202
261
|
// --- Auto-Deposit Logic (Strict) ---
|
|
203
262
|
const allowAutoDeposit = args.autoDeposit !== false;
|
|
204
263
|
console.log(`[tradeService] Checking marginBalance for ${address} in pool ${args.poolId}...`);
|
|
@@ -247,7 +306,7 @@ export async function openPosition(client, address, args) {
|
|
|
247
306
|
collateralAmount: collateralRaw,
|
|
248
307
|
size: sizeRaw,
|
|
249
308
|
price: priceRaw,
|
|
250
|
-
timeInForce
|
|
309
|
+
timeInForce,
|
|
251
310
|
postOnly: args.postOnly,
|
|
252
311
|
slippagePct: normalizeSlippagePct4dp(args.slippagePct),
|
|
253
312
|
executionFeeToken,
|
|
@@ -282,6 +341,7 @@ export async function closePosition(client, address, args) {
|
|
|
282
341
|
}
|
|
283
342
|
const baseDecimals = poolData.baseDecimals || 18;
|
|
284
343
|
const quoteDecimals = poolData.quoteDecimals || 6;
|
|
344
|
+
const timeInForce = mapTimeInForce(args.timeInForce);
|
|
285
345
|
return client.order.createDecreaseOrder({
|
|
286
346
|
chainId,
|
|
287
347
|
address,
|
|
@@ -293,7 +353,7 @@ export async function closePosition(client, address, args) {
|
|
|
293
353
|
collateralAmount: ensureUnits(args.collateralAmount, quoteDecimals, "collateralAmount", { allowImplicitRaw: false }),
|
|
294
354
|
size: ensureUnits(args.size, baseDecimals, "size", { allowImplicitRaw: false }),
|
|
295
355
|
price: ensureUnits(args.price, 30, "price", { allowImplicitRaw: false }),
|
|
296
|
-
timeInForce
|
|
356
|
+
timeInForce,
|
|
297
357
|
postOnly: args.postOnly,
|
|
298
358
|
slippagePct: normalizeSlippagePct4dp(args.slippagePct),
|
|
299
359
|
executionFeeToken,
|
|
@@ -41,7 +41,7 @@ export const closePositionTool = {
|
|
|
41
41
|
collateralAmount: z.union([z.string(), z.number()]).describe("Collateral amount (human/raw). Also supports ALL/FULL/MAX to use live position collateral raw."),
|
|
42
42
|
size: z.union([z.string(), z.number()]).describe("Position size (human/raw). Also supports ALL/FULL/MAX for exact full-close raw size."),
|
|
43
43
|
price: z.union([z.string(), z.number()]).describe("Price (human or 30-dec raw units)"),
|
|
44
|
-
timeInForce: z.
|
|
44
|
+
timeInForce: z.union([z.number(), z.string()]).describe("SDK v1.0.2 supports IOC only. Use 0 or 'IOC'."),
|
|
45
45
|
postOnly: z.coerce.boolean().describe("Post-only flag"),
|
|
46
46
|
slippagePct: z.coerce.string().default("50").describe(SLIPPAGE_PCT_4DP_DESC),
|
|
47
47
|
executionFeeToken: z.string().describe("Execution fee token address"),
|
|
@@ -23,7 +23,7 @@ export const executeTradeTool = {
|
|
|
23
23
|
collateralAmount: z.union([z.string(), z.number()]).describe("Collateral. e.g. '100' or 'raw:100000000' (6 decimals for USDC)."),
|
|
24
24
|
size: z.union([z.string(), z.number()]).describe("Notional size in base tokens. e.g. '0.5' BTC or 'raw:50000000'."),
|
|
25
25
|
price: z.union([z.string(), z.number()]).describe("Execution or Limit price. e.g. '65000' or 'raw:...'"),
|
|
26
|
-
timeInForce: z.
|
|
26
|
+
timeInForce: z.union([z.number(), z.string()]).describe("SDK v1.0.2 supports IOC only. Use 0 or 'IOC'."),
|
|
27
27
|
postOnly: z.coerce.boolean().describe("If true, order only executes as Maker."),
|
|
28
28
|
slippagePct: z.coerce.string().default("50").describe(`${SLIPPAGE_PCT_4DP_DESC}. Default is 50 (0.5%).`),
|
|
29
29
|
executionFeeToken: z.string().optional().describe("Address of token to pay gas/execution fees (typically USDC). Default is pool quoteToken."),
|
|
@@ -50,6 +50,11 @@ export const executeTradeTool = {
|
|
|
50
50
|
const poolData = poolResponse?.data || (poolResponse?.marketId ? poolResponse : null);
|
|
51
51
|
if (!poolData)
|
|
52
52
|
throw new Error(`Could not find pool metadata for ID: ${poolId}`);
|
|
53
|
+
const resolvedMarketId = String(poolData.marketId ?? "").trim();
|
|
54
|
+
const requestedMarketId = String(args.marketId ?? "").trim();
|
|
55
|
+
if (resolvedMarketId && requestedMarketId && resolvedMarketId.toLowerCase() !== requestedMarketId.toLowerCase()) {
|
|
56
|
+
throw new Error(`Invalid marketId: marketId mismatch for poolId=${poolId}. Provided=${requestedMarketId}, resolved=${resolvedMarketId}.`);
|
|
57
|
+
}
|
|
53
58
|
const baseDecimals = Number(poolData.baseDecimals ?? 18);
|
|
54
59
|
const quoteDecimals = Number(poolData.quoteDecimals ?? 6);
|
|
55
60
|
const mappedDirection = mapDirection(args.direction);
|
|
@@ -2,9 +2,87 @@ import { z } from "zod";
|
|
|
2
2
|
import { resolveClient, getChainId } from "../auth/resolveClient.js";
|
|
3
3
|
import { normalizeAddress } from "../utils/address.js";
|
|
4
4
|
import { extractErrorMessage } from "../utils/errorMessage.js";
|
|
5
|
+
function collectRows(input) {
|
|
6
|
+
if (Array.isArray(input))
|
|
7
|
+
return input.flatMap(collectRows);
|
|
8
|
+
if (!input || typeof input !== "object")
|
|
9
|
+
return [];
|
|
10
|
+
if (input.poolId || input.pool_id || input.marketId || input.market_id)
|
|
11
|
+
return [input];
|
|
12
|
+
return Object.values(input).flatMap(collectRows);
|
|
13
|
+
}
|
|
5
14
|
function isNonEmptyObject(value) {
|
|
6
15
|
return !!value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0;
|
|
7
16
|
}
|
|
17
|
+
async function findBaseDetailFromMarkets(client, chainId, baseAddress) {
|
|
18
|
+
let rows = [];
|
|
19
|
+
try {
|
|
20
|
+
const searchRes = await client.markets.searchMarket({ chainId, keyword: "", limit: 2000 });
|
|
21
|
+
rows = collectRows(searchRes?.data ?? searchRes);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
rows = [];
|
|
25
|
+
}
|
|
26
|
+
if (rows.length === 0) {
|
|
27
|
+
try {
|
|
28
|
+
const poolListRes = await client.api?.getPoolList?.();
|
|
29
|
+
rows = collectRows(poolListRes?.data ?? poolListRes);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
rows = [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const normalized = baseAddress.toLowerCase();
|
|
36
|
+
const matched = rows.find((row) => String(row?.baseToken ?? row?.baseAddress ?? "").trim().toLowerCase() === normalized);
|
|
37
|
+
if (!matched)
|
|
38
|
+
return null;
|
|
39
|
+
const poolId = String(matched?.poolId ?? matched?.pool_id ?? "").trim();
|
|
40
|
+
if (!poolId) {
|
|
41
|
+
return {
|
|
42
|
+
chainId,
|
|
43
|
+
baseAddress,
|
|
44
|
+
baseToken: baseAddress,
|
|
45
|
+
baseSymbol: matched?.baseSymbol ?? null,
|
|
46
|
+
baseDecimals: matched?.baseDecimals ?? matched?.base_decimals ?? null,
|
|
47
|
+
baseTokenIcon: matched?.baseTokenIcon ?? matched?.base_token_icon ?? null,
|
|
48
|
+
marketId: matched?.marketId ?? matched?.market_id ?? null,
|
|
49
|
+
poolId: null,
|
|
50
|
+
source: "market_search_fallback",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const detailRes = await client.markets.getMarketDetail({ chainId, poolId });
|
|
55
|
+
const detail = detailRes?.data || (detailRes?.marketId ? detailRes : null);
|
|
56
|
+
if (detail) {
|
|
57
|
+
return {
|
|
58
|
+
chainId,
|
|
59
|
+
baseAddress,
|
|
60
|
+
baseToken: detail.baseToken ?? baseAddress,
|
|
61
|
+
baseSymbol: detail.baseSymbol ?? matched?.baseSymbol ?? null,
|
|
62
|
+
baseDecimals: detail.baseDecimals ?? matched?.baseDecimals ?? matched?.base_decimals ?? null,
|
|
63
|
+
baseTokenIcon: detail.baseTokenIcon ?? matched?.baseTokenIcon ?? null,
|
|
64
|
+
quoteSymbol: detail.quoteSymbol ?? matched?.quoteSymbol ?? null,
|
|
65
|
+
marketId: detail.marketId ?? matched?.marketId ?? null,
|
|
66
|
+
poolId: detail.poolId ?? poolId,
|
|
67
|
+
source: "market_detail_fallback",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
chainId,
|
|
75
|
+
baseAddress,
|
|
76
|
+
baseToken: baseAddress,
|
|
77
|
+
baseSymbol: matched?.baseSymbol ?? null,
|
|
78
|
+
baseDecimals: matched?.baseDecimals ?? matched?.base_decimals ?? null,
|
|
79
|
+
baseTokenIcon: matched?.baseTokenIcon ?? matched?.base_token_icon ?? null,
|
|
80
|
+
quoteSymbol: matched?.quoteSymbol ?? null,
|
|
81
|
+
marketId: matched?.marketId ?? matched?.market_id ?? null,
|
|
82
|
+
poolId,
|
|
83
|
+
source: "market_search_fallback",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
8
86
|
function buildReadErrorPayload(args, messageLike, code = "SDK_READ_ERROR") {
|
|
9
87
|
const chainId = args.chainId ?? getChainId();
|
|
10
88
|
const message = extractErrorMessage(messageLike, "Failed to read base token detail.");
|
|
@@ -44,10 +122,18 @@ export const getBaseDetailTool = {
|
|
|
44
122
|
return { content: [{ type: "text", text: JSON.stringify(body, (_, v) => typeof v === "bigint" ? v.toString() : v, 2) }], isError: true };
|
|
45
123
|
}
|
|
46
124
|
if (payload === null || payload === undefined) {
|
|
125
|
+
const fallback = await findBaseDetailFromMarkets(client, chainId, baseAddress);
|
|
126
|
+
if (fallback) {
|
|
127
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: fallback }, null, 2) }] };
|
|
128
|
+
}
|
|
47
129
|
const body = buildReadErrorPayload({ ...args, baseAddress }, "get_base_detail returned empty data.", "NOT_FOUND");
|
|
48
130
|
return { content: [{ type: "text", text: JSON.stringify(body, null, 2) }], isError: true };
|
|
49
131
|
}
|
|
50
132
|
if (typeof payload === "object" && !Array.isArray(payload) && !isNonEmptyObject(payload)) {
|
|
133
|
+
const fallback = await findBaseDetailFromMarkets(client, chainId, baseAddress);
|
|
134
|
+
if (fallback) {
|
|
135
|
+
return { content: [{ type: "text", text: JSON.stringify({ status: "success", data: fallback }, null, 2) }] };
|
|
136
|
+
}
|
|
51
137
|
const body = buildReadErrorPayload({ ...args, baseAddress }, "get_base_detail returned an empty object.", "NOT_FOUND");
|
|
52
138
|
return { content: [{ type: "text", text: JSON.stringify(body, null, 2) }], isError: true };
|
|
53
139
|
}
|
|
@@ -39,6 +39,7 @@ export const openPositionSimpleTool = {
|
|
|
39
39
|
schema: {
|
|
40
40
|
poolId: z.string().optional().describe("Hex Pool ID. Provide either poolId or keyword."),
|
|
41
41
|
keyword: z.string().optional().describe('Recommended: Market keyword, e.g. "BTC", "ETH", "XRP".'),
|
|
42
|
+
marketId: z.string().optional().describe("Optional market config hash. If provided, it must match the market resolved from poolId/keyword."),
|
|
42
43
|
direction: z.any().describe("0=LONG, 1=SHORT, or strings like 'BUY'/'SELL'/'LONG'/'SHORT'."),
|
|
43
44
|
collateralAmount: z.coerce
|
|
44
45
|
.string()
|
|
@@ -92,6 +93,10 @@ export const openPositionSimpleTool = {
|
|
|
92
93
|
const marketId = String(detail.marketId ?? "").trim();
|
|
93
94
|
if (!marketId)
|
|
94
95
|
throw new Error(`marketId missing from market detail for poolId=${poolId}`);
|
|
96
|
+
const requestedMarketId = String(args.marketId ?? "").trim();
|
|
97
|
+
if (requestedMarketId && requestedMarketId.toLowerCase() !== marketId.toLowerCase()) {
|
|
98
|
+
throw new Error(`marketId mismatch for poolId=${poolId}. Provided=${requestedMarketId}, resolved=${marketId}.`);
|
|
99
|
+
}
|
|
95
100
|
const baseDecimals = Number(detail.baseDecimals ?? 18);
|
|
96
101
|
const quoteDecimals = Number(detail.quoteDecimals ?? 6);
|
|
97
102
|
const quoteToken = String(detail.quoteToken ?? "").trim();
|
|
@@ -1,30 +1,255 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import * as allTools from "./index.js";
|
|
3
|
+
const TOOL_DISCOVERY_META = {
|
|
4
|
+
find_pool: {
|
|
5
|
+
category: "market-discovery",
|
|
6
|
+
aliases: ["search_market", "get_pool_by_symbol", "find market", "find symbol"],
|
|
7
|
+
intents: ["discover market", "lookup pool", "resolve pool id"],
|
|
8
|
+
commonArgs: ["keyword"],
|
|
9
|
+
},
|
|
10
|
+
list_pools: {
|
|
11
|
+
category: "market-discovery",
|
|
12
|
+
aliases: ["get_pool_list", "get_pool_symbol_all", "all pools"],
|
|
13
|
+
intents: ["browse markets", "list tradable pools"],
|
|
14
|
+
},
|
|
15
|
+
get_price: {
|
|
16
|
+
category: "market-data",
|
|
17
|
+
aliases: ["get_market_price", "get_oracle_price", "price"],
|
|
18
|
+
intents: ["read market price", "read oracle price"],
|
|
19
|
+
commonArgs: ["poolId", "priceType"],
|
|
20
|
+
},
|
|
21
|
+
get_pool_metadata: {
|
|
22
|
+
category: "market-data",
|
|
23
|
+
aliases: ["get_market_detail", "get_pool_info", "get_liquidity_info", "get_pool_level_config"],
|
|
24
|
+
intents: ["inspect pool", "read liquidity", "read config"],
|
|
25
|
+
commonArgs: ["poolId", "includeLiquidity", "includeConfig"],
|
|
26
|
+
},
|
|
27
|
+
get_kline: {
|
|
28
|
+
category: "market-data",
|
|
29
|
+
aliases: ["candles", "ohlcv", "chart"],
|
|
30
|
+
intents: ["read kline", "read candlestick"],
|
|
31
|
+
commonArgs: ["poolId", "interval", "limit"],
|
|
32
|
+
},
|
|
33
|
+
open_position_simple: {
|
|
34
|
+
category: "trading",
|
|
35
|
+
aliases: ["open trade", "open position", "quick trade"],
|
|
36
|
+
intents: ["open long", "open short", "trade with tp sl"],
|
|
37
|
+
commonArgs: ["poolId|keyword", "direction", "collateralAmount", "leverage", "orderType"],
|
|
38
|
+
},
|
|
39
|
+
execute_trade: {
|
|
40
|
+
category: "trading",
|
|
41
|
+
aliases: ["create increase order", "advanced trade"],
|
|
42
|
+
intents: ["low-level open order", "custom increase order"],
|
|
43
|
+
commonArgs: ["poolId", "marketId", "direction", "orderType", "size", "price"],
|
|
44
|
+
},
|
|
45
|
+
close_position: {
|
|
46
|
+
category: "trading",
|
|
47
|
+
aliases: ["reduce position", "close trade"],
|
|
48
|
+
intents: ["close long", "close short"],
|
|
49
|
+
commonArgs: ["poolId", "positionId", "direction", "orderType", "size"],
|
|
50
|
+
},
|
|
51
|
+
close_all_positions: {
|
|
52
|
+
category: "trading",
|
|
53
|
+
aliases: ["panic close", "emergency close"],
|
|
54
|
+
intents: ["close everything", "flatten exposure"],
|
|
55
|
+
commonArgs: ["poolId|keyword"],
|
|
56
|
+
},
|
|
57
|
+
manage_tp_sl: {
|
|
58
|
+
category: "trading",
|
|
59
|
+
aliases: ["set tp sl", "update tp sl", "delete tp sl"],
|
|
60
|
+
intents: ["set stop loss", "set take profit", "cancel protection"],
|
|
61
|
+
commonArgs: ["poolId", "positionId|orderId", "tpPrice", "slPrice"],
|
|
62
|
+
},
|
|
63
|
+
cancel_orders: {
|
|
64
|
+
category: "trading",
|
|
65
|
+
aliases: ["cancel_all_orders", "cancel order", "revoke order"],
|
|
66
|
+
intents: ["cancel pending orders", "clear orders"],
|
|
67
|
+
commonArgs: ["orderIds|poolId|cancelAll"],
|
|
68
|
+
},
|
|
69
|
+
manage_liquidity: {
|
|
70
|
+
category: "liquidity",
|
|
71
|
+
aliases: ["add lp", "remove lp", "deposit liquidity", "withdraw liquidity"],
|
|
72
|
+
intents: ["add base lp", "add quote lp", "withdraw lp"],
|
|
73
|
+
commonArgs: ["poolId", "poolType", "action", "amount", "slippage"],
|
|
74
|
+
},
|
|
75
|
+
get_lp_price: {
|
|
76
|
+
category: "liquidity",
|
|
77
|
+
aliases: ["lp nav", "pool token price"],
|
|
78
|
+
intents: ["read lp nav", "read lp token price"],
|
|
79
|
+
commonArgs: ["poolId", "poolType"],
|
|
80
|
+
},
|
|
81
|
+
get_my_lp_holdings: {
|
|
82
|
+
category: "liquidity",
|
|
83
|
+
aliases: ["my lp", "lp balances"],
|
|
84
|
+
intents: ["list my liquidity", "portfolio lp"],
|
|
85
|
+
},
|
|
86
|
+
get_account_snapshot: {
|
|
87
|
+
category: "account",
|
|
88
|
+
aliases: ["get_account", "get_account_info", "balances overview"],
|
|
89
|
+
intents: ["check balances", "account overview"],
|
|
90
|
+
commonArgs: ["poolId"],
|
|
91
|
+
},
|
|
92
|
+
get_orders: {
|
|
93
|
+
category: "account",
|
|
94
|
+
aliases: ["get_open_orders", "order history"],
|
|
95
|
+
intents: ["list orders", "pending orders", "filled orders"],
|
|
96
|
+
commonArgs: ["status", "poolId", "limit"],
|
|
97
|
+
},
|
|
98
|
+
get_positions_all: {
|
|
99
|
+
category: "account",
|
|
100
|
+
aliases: ["get_positions", "position history"],
|
|
101
|
+
intents: ["list positions", "open positions", "closed positions"],
|
|
102
|
+
commonArgs: ["status", "poolId", "limit"],
|
|
103
|
+
},
|
|
104
|
+
get_trade_flow: {
|
|
105
|
+
category: "account",
|
|
106
|
+
aliases: ["account flow", "tx history"],
|
|
107
|
+
intents: ["read trade history", "activity feed"],
|
|
108
|
+
},
|
|
109
|
+
account_deposit: {
|
|
110
|
+
category: "account",
|
|
111
|
+
aliases: ["deposit margin", "transfer to trading"],
|
|
112
|
+
intents: ["fund trading account"],
|
|
113
|
+
commonArgs: ["amount", "tokenAddress"],
|
|
114
|
+
},
|
|
115
|
+
account_withdraw: {
|
|
116
|
+
category: "account",
|
|
117
|
+
aliases: ["withdraw margin", "transfer to wallet"],
|
|
118
|
+
intents: ["withdraw trading funds"],
|
|
119
|
+
commonArgs: ["poolId", "amount", "isQuoteToken"],
|
|
120
|
+
},
|
|
121
|
+
check_account_ready: {
|
|
122
|
+
category: "account",
|
|
123
|
+
aliases: ["precheck balance", "ready to trade"],
|
|
124
|
+
intents: ["validate collateral", "pre-trade check"],
|
|
125
|
+
commonArgs: ["poolId|keyword", "collateralAmount"],
|
|
126
|
+
},
|
|
127
|
+
search_tools: {
|
|
128
|
+
category: "utils",
|
|
129
|
+
aliases: ["find tool", "discover tool"],
|
|
130
|
+
intents: ["which tool should i use", "tool discovery"],
|
|
131
|
+
commonArgs: ["keyword"],
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
function normalizeText(value) {
|
|
135
|
+
return String(value ?? "").trim().toLowerCase();
|
|
136
|
+
}
|
|
137
|
+
function uniqueStrings(values) {
|
|
138
|
+
return [...new Set(values.filter(Boolean))];
|
|
139
|
+
}
|
|
140
|
+
function scoreTextMatch(query, candidate, weight) {
|
|
141
|
+
if (!candidate)
|
|
142
|
+
return 0;
|
|
143
|
+
if (candidate === query)
|
|
144
|
+
return weight + 6;
|
|
145
|
+
if (candidate.startsWith(query))
|
|
146
|
+
return weight + 3;
|
|
147
|
+
if (candidate.includes(query))
|
|
148
|
+
return weight;
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
function isOptionalField(field) {
|
|
152
|
+
return Boolean(field?.isOptional?.()) || field?._def?.type === "optional" || field?._def?.type === "default";
|
|
153
|
+
}
|
|
154
|
+
function getFieldDescription(field) {
|
|
155
|
+
return String(field?._def?.description ?? "").trim();
|
|
156
|
+
}
|
|
157
|
+
function summarizeSchema(schema) {
|
|
158
|
+
if (!schema)
|
|
159
|
+
return { required: [], optional: [] };
|
|
160
|
+
const entries = Object.entries(schema);
|
|
161
|
+
const required = entries.filter(([, field]) => !isOptionalField(field)).map(([name]) => name);
|
|
162
|
+
const optional = entries.filter(([, field]) => isOptionalField(field)).map(([name]) => name);
|
|
163
|
+
return { required, optional };
|
|
164
|
+
}
|
|
3
165
|
export const searchToolsTool = {
|
|
4
166
|
name: "search_tools",
|
|
5
|
-
description: "[UTILS] Search for available tools by keyword
|
|
167
|
+
description: "[UTILS] Search for available tools by keyword, legacy tool name, or intent. Returns categories, aliases, and common parameters.",
|
|
6
168
|
schema: {
|
|
7
|
-
keyword: z.string().describe("Keyword
|
|
169
|
+
keyword: z.string().describe("Keyword, old tool name, or intent phrase to search for."),
|
|
8
170
|
},
|
|
9
171
|
handler: async (args) => {
|
|
10
172
|
try {
|
|
11
|
-
const keyword = args.keyword
|
|
173
|
+
const keyword = normalizeText(args.keyword);
|
|
12
174
|
const tools = Object.values(allTools);
|
|
13
175
|
const matches = tools
|
|
14
|
-
.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
176
|
+
.map((tool) => {
|
|
177
|
+
const meta = TOOL_DISCOVERY_META[tool.name] ?? { category: "general" };
|
|
178
|
+
const schemaSummary = summarizeSchema(tool.schema);
|
|
179
|
+
const aliases = uniqueStrings(meta.aliases ?? []);
|
|
180
|
+
const intents = uniqueStrings(meta.intents ?? []);
|
|
181
|
+
const score = scoreTextMatch(keyword, normalizeText(tool.name), 18) +
|
|
182
|
+
scoreTextMatch(keyword, normalizeText(tool.description), 10) +
|
|
183
|
+
aliases.reduce((sum, alias) => sum + scoreTextMatch(keyword, normalizeText(alias), 12), 0) +
|
|
184
|
+
intents.reduce((sum, intent) => sum + scoreTextMatch(keyword, normalizeText(intent), 9), 0) +
|
|
185
|
+
scoreTextMatch(keyword, normalizeText(meta.category), 4);
|
|
186
|
+
return {
|
|
187
|
+
tool,
|
|
188
|
+
meta,
|
|
189
|
+
schemaSummary,
|
|
190
|
+
aliases,
|
|
191
|
+
intents,
|
|
192
|
+
score,
|
|
193
|
+
};
|
|
194
|
+
})
|
|
195
|
+
.filter((entry) => entry.score > 0)
|
|
196
|
+
.sort((a, b) => b.score - a.score || a.tool.name.localeCompare(b.tool.name))
|
|
197
|
+
.map((entry) => {
|
|
198
|
+
const commonArgs = uniqueStrings([
|
|
199
|
+
...(entry.meta.commonArgs ?? []),
|
|
200
|
+
...entry.schemaSummary.required.slice(0, 4),
|
|
201
|
+
]);
|
|
202
|
+
const commonArgsWithHints = commonArgs.map((name) => {
|
|
203
|
+
const baseName = name.split("|")[0];
|
|
204
|
+
const field = entry.tool.schema?.[baseName];
|
|
205
|
+
const description = getFieldDescription(field);
|
|
206
|
+
return description ? `${name}: ${description}` : name;
|
|
207
|
+
});
|
|
208
|
+
return {
|
|
209
|
+
name: entry.tool.name,
|
|
210
|
+
category: entry.meta.category,
|
|
211
|
+
description: entry.tool.description,
|
|
212
|
+
aliases: entry.aliases,
|
|
213
|
+
intents: entry.intents,
|
|
214
|
+
requiredArgs: entry.schemaSummary.required,
|
|
215
|
+
commonArgs: commonArgsWithHints,
|
|
216
|
+
};
|
|
217
|
+
});
|
|
21
218
|
if (matches.length === 0) {
|
|
22
|
-
|
|
219
|
+
const suggestions = tools
|
|
220
|
+
.map((tool) => ({
|
|
221
|
+
name: tool.name,
|
|
222
|
+
description: tool.description,
|
|
223
|
+
category: TOOL_DISCOVERY_META[tool.name]?.category ?? "general",
|
|
224
|
+
}))
|
|
225
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
226
|
+
.slice(0, 8);
|
|
227
|
+
return {
|
|
228
|
+
content: [{
|
|
229
|
+
type: "text",
|
|
230
|
+
text: JSON.stringify({
|
|
231
|
+
status: "success",
|
|
232
|
+
data: {
|
|
233
|
+
count: 0,
|
|
234
|
+
keyword: args.keyword,
|
|
235
|
+
message: `No tools found matching: ${args.keyword}`,
|
|
236
|
+
suggestions,
|
|
237
|
+
},
|
|
238
|
+
}, null, 2),
|
|
239
|
+
}],
|
|
240
|
+
};
|
|
23
241
|
}
|
|
24
242
|
return {
|
|
25
243
|
content: [{
|
|
26
244
|
type: "text",
|
|
27
|
-
text: JSON.stringify({
|
|
245
|
+
text: JSON.stringify({
|
|
246
|
+
status: "success",
|
|
247
|
+
data: {
|
|
248
|
+
count: matches.length,
|
|
249
|
+
keyword: args.keyword,
|
|
250
|
+
tools: matches,
|
|
251
|
+
},
|
|
252
|
+
}, null, 2)
|
|
28
253
|
}]
|
|
29
254
|
};
|
|
30
255
|
}
|
package/dist/utils/mappings.js
CHANGED
|
@@ -170,3 +170,14 @@ export const mapTriggerType = (input) => {
|
|
|
170
170
|
return 2;
|
|
171
171
|
throw new Error(`Invalid triggerType: ${input}. Use 0/NONE, 1/GTE or 2/LTE.`);
|
|
172
172
|
};
|
|
173
|
+
/**
|
|
174
|
+
* SDK v1.0.2 currently exposes IOC only.
|
|
175
|
+
*/
|
|
176
|
+
export const mapTimeInForce = (input) => {
|
|
177
|
+
if (input === 0)
|
|
178
|
+
return 0;
|
|
179
|
+
const s = String(input ?? "").trim().toUpperCase();
|
|
180
|
+
if (s === "0" || s === "IOC")
|
|
181
|
+
return 0;
|
|
182
|
+
throw new Error(`Invalid timeInForce: ${input}. SDK v1.0.2 currently supports IOC only, use 0/IOC.`);
|
|
183
|
+
};
|