MCP server for the Pionex exchange, built on pionex_py, with strict anti-hallucination guardrails
Strong tool definitions with comprehensive descriptions (avg 180 chars, well above baseline 34 chars minimum). All 14 tools have clear verb-noun naming and detailed parameter documentation. Input schemas present for all tools with proper type declarations. However, output schemas are documented only in descriptions, not as formal JSON Schema objects. Error handling guidance is minimal, tools lack recovery hints for common failure modes. Tool composition is excellent (single responsibility, clear chaining via IDs), and security posture is strong (no credentials in params, server-side injection). Missing: explicit enum constraints for interval/symbol validation, formal output schema definitions, and error classification patterns.
Detect Fair Value Gaps (3-candle imbalances) on live candles and report whether each gap is still open, partially filled or filled. Use when the user asks about imbalances, unfilled gaps or "FVG" zones as potential reaction areas. For supply/demand candles use detect_order_blocks; for swing trend use get_market_structure. This is a HEURISTIC computed by the server, not exchange data. Definition: bullish FVG when low[i+1] > high[i-1] (zone [high[i-1], low[i+1]]); bearish is symmetric. Args: symbol: exact spot symbol, e.g. 'BTC_USDT'. interval: exactly one of 1M, 5M, 15M, 30M, 60M, 4H, 8H, 12H, 1D. lookback: candles to scan, 30-500 (default 200). min_gap_pct: ignore gaps smaller than this % of price, 0-10 (default 0 = keep all). only_open: true (default) omits gaps already fully filled. Returns `data.fvgs`: list of {direction, top, bottom, formed_at (epoch ms), status: open|partially_filled|filled}, plus total_detected, returned, last_close and candles_used. computed=true. Public — no credentials.
Detect order blocks — the last opposing candle before an impulsive move — on live candles, and classify each as fresh, mitigated or broken. Use when the user asks for supply/demand zones or "order blocks" as potential reaction areas. For 3-candle imbalances use detect_fvg; for swing-based trend use get_market_structure. This is a HEURISTIC computed by the server; other order-block definitions will yield different zones. Definition: a candle whose body |close-open| exceeds displacement_factor × ATR(14) marks an impulse; the order block is the last opposing-colour candle among the 3 before it.
Return the account's spot balances per coin (free, frozen, total). Use for "how much X do I have". For balances valued in a quote currency with weights use get_portfolio. Balances are live account facts — never estimate them. Args: include_zero: include coins with zero balance (default false). Returns `data` as a mapping coin → {free, frozen, total} (floats). Requires API credentials with read permission; works in read-only mode.
Output schemas documented only in prose descriptions, not as formal JSON Schema. LLMs cannot parse unstructured text to infer response structure reliably. Formal schemas enable downstream tool chaining and reduce hallucination.
Interval parameter (1M, 5M, 15M, etc.) lacks enum constraint in schema. Described as 'exactly one of' in text, but LLMs cannot parse prose constraints reliably. Should be declared as enum in JSON Schema.
Error handling lacks recovery guidance. Tools document API errors pass through verbatim, but no guidance on retryability, user-fixable vs fatal, or next steps. E.g., 'symbol not found' should suggest list_symbols or get_symbol_info.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 64 | 2026-07-28+ | v2 |
Compute the current value of one or more EMAs (exponential moving averages) from live candle closes, and where the price sits relative to each. Use for trend-following context ("is price above the 200 EMA?"). For a full indicator panel (RSI, MACD, ATR, Bollinger) use get_indicators instead; this tool exists to pick custom periods. Args: symbol: exact spot symbol, e.g. 'BTC_USDT'. interval: exactly one of 1M, 5M, 15M, 30M, 60M, 4H, 8H, 12H, 1D. periods: comma-separated integers 2-400 (default "20,50,200"). limit: candles to fetch, 30-500 (default 500). A period needs at least that many candles or its value is null. Returns `data.emas` keyed "EMA<n>" → {value, price_above, distance_pct} plus last_close, last_candle_time (epoch ms) and candles_used. computed=true: standard EMA (SMA seed, k=2/(n+1)). Public — no credentials.
Return the account's recent trade fills (executions) for a symbol — latest 100. Use for realised prices, fees and maker/taker role. For the fills of one specific order use get_fills_by_order. Args: symbol: exact spot symbol, e.g. 'BTC_USDT'. start_time_ms / end_time_ms: optional epoch-ms range (0 = unset). Returns `data.fills`: list of {id, orderId, symbol, side, role, price, size, fee, feeCoin, timestamp ms}, verbatim. Requires API credentials (read).
Return every fill belonging to one spot order. Use to compute the real average entry price and total fee of a filled order from exchange data instead of estimating. Args: order_id: numeric exchange orderId (from get_order, get_open_orders or a confirm_action result). Returns `data.fills`: list of fill objects verbatim. Requires API credentials (read).
Compute a panel of classic indicators from live candles: RSI(14), MACD(12,26,9), ATR(14), Bollinger Bands(20,2) and SMA/EMA 20/50/200 — latest values only. Use as the default one-call market snapshot before discussing momentum, volatility or trend. For custom EMA periods use get_emas; for price-action structures use detect_fvg, detect_order_blocks or get_market_structure. Args: symbol: exact spot symbol, e.g. 'BTC_USDT'. interval: exactly one of 1M, 5M, 15M, 30M, 60M, 4H, 8H, 12H, 1D. limit: candles to fetch, 30-500 (default 500). SMA/EMA 200 need ≥200 candles or they are null. Returns `data` with rsi_14, macd_12_26_9 {line, signal, histogram}, atr_14, bollinger_20_2 {upper, middle, lower}, sma{}, ema{}, last_close, last_candle_time (epoch ms), candles_used. computed=true with standard formulas (Wilder RSI/ATR). Public — no credentials.
Return the account's currently open (unfilled) spot orders for a symbol. Use to find orderIds before cancel_order / prepare_cancel_orders, or to check what is resting on the book. For closed orders use get_order_history. Args: symbol: exact spot symbol, e.g. 'BTC_USDT'. Returns `data.orders`: list of order objects (orderId, clientOrderId, side, type, price, size, filledSize, status, createTime ms), verbatim. Requires API credentials (read).
Return the details of one spot order by its numeric orderId. Use to check status (OPEN/CLOSED), filled size, average price and fee of a known order. If you only have the client id use get_order_by_client_id. Args: order_id: numeric exchange orderId from get_open_orders, get_order_history or a confirm_action result — never from memory. Returns the exchange's order object verbatim. Requires API credentials (read).
Return one spot order by the clientOrderId it was created with. Use to RECONCILE an order whose confirm_action response was lost or timed out: every prepare_order returns a `client_order_id`; look it up here before ever re-preparing the same order. Args: client_order_id: the id shown by prepare_order (e.g. 'mcp-1a2b3c4d5e6f') or one supplied by the user. Returns the exchange's order object verbatim, or a verbatim API error if no order with that id exists (meaning it was never placed). Requires API credentials (read).
Return the account's historical (open and closed) spot orders for a symbol. Use for past activity, P&L reconstruction or audit. For only the resting orders use get_open_orders; for executions use get_fills. Args: symbol: exact spot symbol, e.g. 'BTC_USDT'. limit: orders to return, 1-200 (default 50). start_time_ms / end_time_ms: optional epoch-ms range (0 = unset). Returns `data.orders`: list of order objects verbatim, newest first. Requires API credentials (read).
Return a whole-portfolio view valued in a quote currency: per-coin amount, live price, value and weight, plus the total. Use for allocation questions ("what % is BTC?") and before compute_rebalance_plan. For raw balances use get_balances. Args: quote: valuation currency (default 'USDT'). Returns `data` with total_value and `positions` (sorted by value: coin, amount, price, value, weight). computed=true: value = amount × live price, weight = value / total_value. Requires API credentials (read).
List every anti-hallucination and safety rule this server enforces. Use it to explain to the user why an action was blocked or why a value is unavailable. Purely local, no credentials needed. Returns `data.rules`: a list of human-readable rule statements including the currently configured numeric limits.
Report the server's version, capability gates and active operator limits. Use this first in a session, or whenever another tool reports that it is disabled, to learn which features are on and which environment variable controls each limit. Purely local: no exchange request, no credentials needed. Returns `data` with: version, credentials_configured, trading_enabled, bots_enabled, futures_enabled, earn_enabled, max_order_notional, max_price_deviation_pct, max_leverage, symbol_whitelist, confirmation_ttl_seconds, pending_confirmations, audit_log.
Symbol parameter accepts free-form strings without validation hint in schema. Description says 'exact spot symbol, e.g. BTC_USDT' but no enum or pattern constraint. LLMs may hallucinate invalid symbols.
Pagination parameters (limit, start_time_ms, end_time_ms) lack explicit min/max bounds in schema. Descriptions mention ranges (e.g., '1-200') but JSON Schema should enforce via minimum/maximum properties.