TR-GAB DocsBack to TR-GAB
MCP Server

Operate TR-GAB from an AI agent.

TR-GAB ships a Model Context Protocol (MCP) server that exposes the product as tools: market data, mining, workflows, validation, library and exports. Everything the UI does has a tool behind it, and the tools talk to the same running application, so an agent and a human can work on the same data.

Same app, same data The server is a thin client of the TR-GAB app running on your machine. Nothing runs without the app.

Around 150 tools Read tools are safe to call freely; write tools mirror the UI buttons and are audited in AI Activity.

Contracts, not guesses Every tool description states its input shape. Unknown keys and invalid values fail with the valid list.

Setup

The MCP server is installed with TR-GAB. Point your MCP client (Claude Code, Codex, any MCP host) at it:

{
  "mcpServers": {
    "tr-gab-pro": {
      "command": "node",
      "args": ["C:/Users/<you>/AppData/Local/Programs/TR-GAB/resources/mcp-server/mcp-server/index.js"],
      "env": { "TRGAB_PRO_URL": "http://localhost:30011" }
    }
  }
}
  • TRGAB_PRO_URL is the address of the running TR-GAB app (the installed app listens on port 30011).
  • Use exactly one MCP configuration per agent. Mixing two servers that point at different builds gives two behaviors and no error that explains them.
  • After updating TR-GAB, restart the agent’s MCP process: a running server keeps the code it started with.
  • Every write tool is logged in the app under AI Activity (list_mcp_activity). Since 1.0.30 it is paginated and filterable: since / until (ISO date or epoch ms), tool, workflowId, limit (up to 500) and offset; the answer carries total and nextOffset so you can walk a long day without reading it whole.
  • The app can refuse connections for one or two seconds while it starts a long job (a miner, a materialization). The server retries network errors a couple of times by itself; if it still fails, the error says so and you can simply retry.

Tool map: what each screen is called

Market Vault - sources and brokerslist_market_sources (take id and broker_utc_offset_sec from here), get_default_market_source (use marketSourceId for chaining), set_default_market_source, list_broker_symbols, resolve_broker_spread, calibrate_broker_symbol, get_default_experts_path (which MT5 terminal exports go to)
Market Vault - datalist_market_vault_data (mode: "summary" = what is downloaded, per symbol and timeframe; "range" = first / last candle and count for one symbol + timeframe; "preview" = a few candles between from and to; the summary ignores symbol filters on purpose - ask for range when you want one symbol), list_symbols (what the broker offers; use search and limit; omit sourceId for the default source), sync_candles, get_sync_status, stop_sync
Rule Mining - Discovercreate_rule_mining_job, get_rule_mining_job, list_rule_mining_results (include: "summary" gives rule_label), rule_mining_feasibility_scan
Pattern and Event Miningcreate_pattern_mining_job, create_event_mining_job and their list_*_results
Workflows - buildcreate_workflow, move_workflow_to_list, add_workflow_process (with target_databank_id / output_databank_id), update_workflow_process, create_workflow_databank, list_workflow_databanks
Workflows - run and readrun_workflow, get_workflow_run (run.progress and processes[].status while it runs), cancel_workflow_run, list_workflow_run_items (filter with databankId), list_workflow_robustness, list_workflow_walkforward (include: "metrics", filter by processId)
KPI filterscreate_kpi_filter, list_kpi_filters, create_filter_group
Strategy Librarysave_strategy_from_databank, list_strategies, get_strategy, rename_strategy, run_strategy (backtest a builder without saving; accepts projectId), get_strategy_zones (stagnation / win-lose / drawdown / macro zones of a saved strategy), get_strategy_regime_zones (in which market regimes the strategy wins or fails), rerun_and_save_strategy (re-run a saved builder, optionally patched, and persist fresh results in the same id)
Regimes (slope of the market)compute_regimes (D1 regimes of a symbol by slope in degrees, plus current), create_regime_projects (one Project Days project per regime)
Project Days (Pattern Scanner)create_pattern_project, add_pattern_project_days, list_pattern_project_days, delete_pattern_project_day; then projectId in create_rule_mining_job / create_pattern_mining_job / create_event_mining_job, in the miner_* workflow processes and in run_strategy
Portfolioscreate_portfolio (name, strategyIds, chartMode), update_portfolio (action: "addStrategy" and the other actions), get_portfolio, calculate_portfolio, export_portfolio_to_mt5
Broker export and testexport_mining_results_to_mt5, backtest_mining_result, scan_tester_batch, run_tester_batch, get_tester_batch_run (poll, no trades), get_tester_batch_replay (real MT5 trades vs engine trades)
Trading History - real KPIs (since 1.0.30)kpi_real (strategyName = the bot name; how a strategy is doing in LIVE trading: trades, net, PF, win rate, avg win / loss, max drawdown, first / last trade, stagnation - the same numbers the Real card and the cockpit Real panel show), list_real_strategies (every bot with a live log and its real KPIs)
Trading History - real portfolios (since 1.0.30)list_real_portfolios, create_real_portfolio (name, labels = bot names), update_real_portfolio (action: "addStrategy" | "removeStrategy" | "rename"), delete_real_portfolio, calculate_real_portfolio (real KPIs of the group and of each strategy; include: "full" adds the equity points). Groups of REAL strategies, apart from the Library portfolios above

Mining and testing only on chosen days (Project Days)

A Pattern Scanner project is a named bag of days of one symbol: the days you consider bullish, a breakout list, a regime. Build it from the agent with create_pattern_project({ project: { name } }) (a plain project: do not pass miningConfig) and add_pattern_project_days({ projectId, symbol, days: ["YYYY-MM-DD", { dayStart, dayEnd }], timeframe }); list_pattern_project_days returns the expanded list the engine will trade. Then pass that projectId wherever a backtest runs:

  • create_rule_mining_job, create_pattern_mining_job, create_event_mining_job and the miner_rule / miner_pattern / miner_event workflow processes mine only on those days. The date range becomes the first and last saved day (any dataMode or lookback is ignored), 600 bars before the first day are loaded so indicators warm up, and the response echoes projectDays: { projectId, name, days, from, to }. An empty or unknown project is a 400, never a full-range run.
  • Every mined strategy carries dataMode: "project" and its projectId, so the downstream workflow stages (backtest, simple_retester, add_condition, evolver, robustness_filter, walkforward) keep the same days automatically.
  • run_strategy({ builder, projectId }) is the Strategy Lab retest in “Project Days” mode: trades open only on the saved days; summaryOnly (default true) keeps KPIs and meta.projectDays without the trade list.

The exported MT5 expert does not filter by days: the Strategy Tester runs the whole range.

Reading a strategy in zones

get_strategy_zones({ strategyId }) returns, per type, the coloured bands the Strategy Lab “Zones” selector paints: macro (red from an equity peak until a new high, only with 5 sessions, 5 trades and a 2% dip), stagnation (every stretch without a new equity high lasting at least options.stagnationMinDays, default 5; green = making new highs), trend (win/lose zigzag by options.trendMinMovePct, default 1% of equity; green rising from a trough, amber falling from a peak, so a rebound is green before the old high is beaten) and drawdown_peak_to_trough (peak to deepest trough of at least options.drawdownMinDepth, default 2%). Each zone has kind, label, from, to, durationDays, tradesCount and depthMax; counts summarises them. Days are operable days when the strategy is a Project Days one (since 1.0.30): the server reads the saved project, stagnationMinDays and every durationDays count only the days the strategy could trade, and the answer says daysBasis: "operable" with projectId and operableDays; for a full-range strategy daysBasis is "calendar". So a funded rule like “60 operable days without a new equity high” is read straight from the zones, no calendar arithmetic on your side. You can also pass equity[] + trades[] inline. The badPeriods inside get_strategy are the older stagnation list with a 1-day threshold: they look “always red” by construction.

Market regimes by slope, and where a strategy wins or fails

A regime is the state of the market on each day: the slope of the price, in degrees. For every D1 bar the platform fits a regression of the log price over the last window bars (default 40); the slope is a percent per bar and the angle is atan(pctPerBar / refPctPerBar), so with refPctPerBar: 1 (default) 45 degrees means the price gains 1% per bar. It is the inclination you see on the chart, the same for gold at 4,000 and EURUSD at 1.10, and it does not depend on volatility. Days are bucketed into strong_up (angle at least strongUpDeg, 20), up (at least upDeg, 8), flat, down (at most downDeg, -8) and strong_down (at most strongDownDeg, -20); hysteresisDeg (2) and minRunBars (3) remove flicker. The answer carries metric: { id: "slope-deg", version, unit: "degrees", since, formula, defaults }: the measure changed in 1.0.27 (from a percentage channel to the slope in degrees) and Project Days created before that were bucketed with the old measure. Compare metric before comparing regimes between two runs or two versions, and recreate old regime projects (create_regime_projects with replace: true) when it differs. These eight knobs are the sliders of the Pattern Scanner “Regimes” tab and of the strategy viewer: pass the values the user tuned in regimeParams, otherwise the answer will not match what they see on screen.

  • compute_regimes({ symbol, timeframe: "D1", from, to, params }) returns days[{ date, angleDeg, regime }], runs[] (same-regime stretches, far apart in time still belong to the same regime), summary[], anglePercentiles (to place the thresholds) and current = the regime of the last closed bar (regime, angleDeg of the run, lastBarAngleDeg, since, days). Read-only.
  • create_regime_projects({ ...same, regimes?, replace? }) creates one Project Days project per regime whose items are the runs, then mine or retest only inside that regime with projectId (previous section).
  • get_strategy_regime_zones({ strategyId, zoneType?, zoneOptions?, regimeParams?, minTrades? }) crosses, by date, the D1 regimes of the strategy symbol with the strategy zones and its trades. It is stateless: recomputed on every call, nothing stored, and the response echoes every parameter used. zoneType picks one zone family (trend = win / lose is the default; stagnation, drawdown_peak_to_trough, macro) and zoneOptions its thresholds, exactly as in get_strategy_zones. The answer carries cross.pnlByRegime[] (each trade attributed to the regime of the D1 bar before its entry day, so there is no lookahead: days, trades, net, profitFactor, winRate, expectancy, maxDrawdown, verdict good | bad | neutral | insufficient with fewer than minTrades = 20 trades), cross.zoneShareByRegime[] (share of each regime’s days spent in each zone, badShare), cross.badZonesByRegime[] (the regime that dominated each bad stretch), goodRegimes, badRegimes and regimes.current.

Typical loop for an agent: compute_regimes to read current, get_strategy_regime_zones to see whether the strategy is good or bad in that regime, then create_regime_projects + run_strategy / the miners to re-mine or retest only in the good regimes. The verdict depends on the zone thresholds and the regime knobs you pass: keep them in the conversation (or in the created projects, which store their params) so results are comparable.

Three details that make the daily loop close (since 1.0.28):

  • The open bar does not count. sync_candles writes the bar of the current day while it is still forming; compute_regimes, create_regime_projects and get_strategy_regime_zones drop it, so current is always the last closed bar and the answer says openBarExcluded: "YYYY-MM-DD". Pass includeOpenBar: true to keep it.
  • Change a saved strategy without losing its backtest. save_strategy with an id and no results now keeps the stored equity, trades, KPIs and bad periods (resultsKept: true), and flags resultsStale: true when the builder changed; send results: null to drop them on purpose. To change the builder and get fresh results in the same id without moving the blob through the agent, call rerun_and_save_strategy({ id, builderPatch }), e.g. builderPatch: { moneyManagement: { riskAmount: 25 } }: the server re-runs the saved builder (Project Days mode included) and persists the new results under the same id. With toToday: true (since 1.0.30) the range is extended to the last candle in the vault for the strategy symbol and timeframe (the builder becomes dataMode: "range", start resolved from the saved mode, endDate = last vault day; the answer carries dateRange), so the engine backtest covers the same period as the live account and both curves can be compared side by side. It does not apply to a Project Days strategy: add the new days to its project.
  • The zones read one equity point per trade. get_strategy_zones and get_strategy_regime_zones work on the per-trade equity (deposit + one point per closed trade). When a strategy was saved with the per-bar series (Strategy Lab, rerun_and_save_strategy before 1.0.29) the server rebuilds it from the trades and says equityBasis: "synthesized"; "trades" means the stored series was already per trade. Note that the profit factor, and with it the verdict per regime, can change with riskAmount even with the same trades: lots are rounded to the broker minimum, so cross at the risk you will trade.
  • run_strategy reproduces the miner exactly when the builder is unchanged. Take the builder from list_workflow_run_items (include: "builder") and run it as is: same trades, profit factor and net (verified on project and range items). Two edits break parity on purpose: moneyManagement.riskAmount changes the lots (rounded to the broker step, so a trade can appear or vanish at a limit), and a builder still in dataMode: "vault_relative" resolves its window against the vault of today, not the day it was mined - pin dataMode: "range" with the item’s startDate / endDate to reproduce. provenance.swapMode is what the broker reports; swap is only charged when provenance.swapApplied is true (builder.swap.enabled).
  • Decide at one risk, ship at that risk. export_portfolio_to_mt5({ portfolioId, moneyManagement }) overrides the money management of every exported EA (riskAmount, riskPerTrade, fixedLots, method…) without touching the Library; the answer echoes moneyManagementApplied.

Comparing the engine with the real MetaTrader tester

The internal engine and the MetaTrader Strategy Tester are two independent measurements of the same strategy, and the platform keeps both:

  1. Export the candidates (export_mining_results_to_mt5, or an export_mt5 process in a workflow), then run_tester_batch on that folder; poll get_tester_batch_run - each item returns the MT5 KPIs. The poll response does not carry per-trade arrays (pass includeEngineTrades: true if you need them). Since 1.0.30 the run first checks that the MetaTrader terminal of the source answers: if it does not, the call fails at once with 409 TERMINAL_UNREACHABLE (and errorKind on the run) instead of a batch that stays pending forever; open the terminal (the app launches it on /connect when it is closed) and retry, or pass skipTerminalCheck: true when you know it is up.
  2. get_tester_batch_replay(runId, itemId) returns mt5Trades and mt5Equity (real tester output) next to engineTrades and engineEquity for the same strategy: align them by entry time and compare fills, PnL per trade and where the curves diverge.
  3. Inside workflows, list_workflow_run_items exposes kpis (engine) and mt5Results (broker) per candidate after a tester process has run.

Compare trade counts before KPIs. The replay carries tradesMismatch when the engine and MT5 executed a different number of trades (more than 10% apart). A better profit factor on the broker side usually means the tester skipped trades - lot cap, margin, volume step - not a better strategy. Treat any disagreement as a finding about costs, sessions, fills, lot sizing or pending-order handling, not as noise.

The minimal end-to-end cycle

  1. list_market_sources - take the real sourceId from id (see the note on masking below), or get_default_market_source and use default.marketSourceId.
  2. list_market_vault_data (mode: "summary") - what symbols and timeframes are already downloaded. If the symbol is missing, sync_candles for the timeframe you will mine (H1, H4…) and poll get_sync_status until status is done. Sync the history before mining: three months of M15 can look like a strategy that five years prove to be noise.
  3. create_rule_mining_job - see the minimal example below. Poll get_rule_mining_job; completed_combinations equals total_combinations when it finished.
  4. create_workflow, then add_workflow_process with a miner_rule as the first stage (same config as the job) followed by filter, robustness_filter, walkforward and export_mt5, each with its target_databank_id (input) and output_databank_id (output) set in the same call. Then run_workflow and poll get_workflow_run. There is no tool that moves the results of a standalone job into a workflow databank: the job is for exploring, the workflow miner is the pipeline.
  5. list_workflow_run_items with databankId to read one stage; save_strategy_from_databank for the survivors.
  6. export_mining_results_to_mt5 / backtest_mining_result against the default broker.

Minimal create_rule_mining_job

{
  "name": "GOLD H1 RSI probe",
  "config": {
    "sourceId": "<uuid from list_market_sources>",
    "symbol": "GOLD",
    "timeframe": "H1",
    "dateFrom": "2024-01-01",
    "dateTo": "2025-12-31",
    "direction": "both",
    "scans": [
      { "kind": "indicator_threshold", "enabled": true, "indicators": ["RSI"], "periods": [14], "operators": [">", "<"], "thresholds": [30, 70] }
    ],
    "slAtrRange": { "min": 1, "max": 3, "step": 1 },
    "tpAtrRange": { "min": 1.5, "max": 4, "step": 1 },
    "atrPeriod": 14,
    "minTrades": 30,
    "mmMethod": "fixed_risk_amount",
    "riskAmount": 100,
    "initialCapital": 10000,
    "entryOrder": "market"
  }
}

The three scans[] kinds (keys differ per kind)

[
  { "kind": "indicator_threshold",    "indicators": ["RSI"], "periods": [14], "operators": [">", "<"], "thresholds": [30, 70] },
  { "kind": "price_vs_indicator",     "priceField": "close", "indicators": ["EMA"], "periods": [20, 50], "operators": [">", "<"] },
  { "kind": "indicator_vs_indicator", "leftIndicators": ["EMA"], "leftPeriods": [10], "rightIndicators": ["EMA"], "rightPeriods": [50], "operators": [">", "<"] }
]
  • priceField is one string (close, open, high, low), not an array. periods and thresholds are arrays of numbers, not ranges. Indicators: RSI, CCI, ATR, SMA, EMA, VWAP for thresholds; SMA, EMA, VWAP against price; SMA, EMA against each other.
  • A scan without kind, with the keys of another kind, or a regimes[].kind outside trend | momentum | cci | chop | hurst | entropy is rejected with the valid list. regimes[] entries are { kind, enabled, indicator?, period?, mode?: "above" | "below" | "both", tf? }.
  • direction defaults to both in the job and in the feasibility scan.
  • entryOrder: market (default when omitted), stop (breakout), limit (pullback) or sweep (tries market, stop and limit per candidate and keeps the best - about three times the work). Pending orders take pendingOffsetRange (ATR multiples) and pendingExpirationBars.
  • sessionFilter is { enabled, timeFrom: "HH:MM", timeTo: "HH:MM" } in broker time; other keys are rejected. It applies while mining, in the miner screens and in workflow miners alike.
  • Costs: spread and slippage are in price units (the symbol quote: 0.49 on GOLD with point 0.01 is 49 points). spreadMode defaults to auto, which uses the broker spread and ignores spread when the broker has one; manual always uses your spread. The job and scan responses return spreadApplied with the value the engine actually used, its source and its units.
  • A finished job with zero results is not an error: check total_combinations, then lower minTrades or the KPI filter, or widen the date range or the scan.

Feasibility scan

rule_mining_feasibility_scan takes the same config plus timeframes[], maxVariantsPerTf, includeSlTpVariation and scanOrder (an array such as ["scans", "sltp"], required when more rows pass than maxVariantsPerTf). Each row carries ruleName, direction, scanCondition, slAtr, tpAtr, rawSignals and the KPIs. It persists nothing.

Contracts every tool follows

  • sourceId is masked in read tools. list_market_vault_data, list_workflow_run_items, get_workflow and any config_json return "__SANITIZED__" in place of source ids (privacy policy). Take the real id from list_market_sources (id) or get_default_market_source (marketSourceId) and put it back before re-sending a config. brokerId is masked everywhere because it embeds the account number: the broker tools (list_broker_symbols, calibrate_broker_symbol, symbol overrides) accept sourceId instead, or nothing for the default source.
  • Response size levels. list_workflow_robustness, list_workflow_walkforward and list_rule_mining_results take include: "summary" | "metrics" | "full". summary has no numbers; metrics is what an agent needs (percentiles, in-sample/out-of-sample bundles, rule_json and rule_label) in a few KB per row; full carries per-simulation curves, every walk-forward window with all its candidates, or every trade - hundreds of KB per row.
  • Time is broker time. Every candle and trade timestamp is the broker server’s time with a trailing Z that is only a serialization artifact. list_market_sources returns broker_utc_offset_sec (broker minus UTC) so you can translate sessions; sessionFilter windows are broker time too.
  • KPI ids are snake_case as in the KPI registry: net_profit, profit_factor, max_drawdown_pct, win_rate, rrr, expectancy, sharpe, sortino, total_trades… The camelCase names you see in engine results (profitFactor, maxDrawdownPct, totalNetProfit, percentProfitable) are accepted as aliases. An unknown id is rejected with the valid list; it never silently matches nothing.
  • KPI filter clauses are always combined with AND. op is one of > < >= <= = !=. mode is optional: omit it for observed KPIs, or use { "type": "robustness", "level": 25 } / { "type": "wfo", "period": "oos" } to evaluate Monte Carlo or walk-forward results.
  • Workflows are created in the default group and list. Use move_workflow_to_list afterwards.
  • One run alive per workflow. Calling run_workflow while a run is in progress returns 409 RUN_IN_PROGRESS and does not touch the live run. Poll get_workflow_run or cancel it explicitly with cancel_workflow_run.
  • Process configs. filter, robustness_filter and walkforward reject unknown keys with the valid list. add_condition, backtest, simple_retester, export_mt5, tester_batch_run and clear are documented in the add_workflow_process description (add_condition takes conditions[], not scans[]). Validation processes without a filterPayload evaluate nothing: their rows come back with evaluated: false and passed: null.
  • Process identity. processes[].process_id (from get_workflow / get_workflow_run) is stable; use it as processId when reading robustness or walk-forward rows. processIdx is the position in the run plan and changes when you run a subset with processIds.
  • Robustness needs perturbation to mean anything. probability is per parameter (default 0.10); with a one-condition rule most simulations come out identical to the original and the row says so in warnings_json and identical_iterations. Raise it to 0.5-1.0 for simple rules.
  • sync_candles is asynchronous. It returns a job id immediately; poll get_sync_status. It downloads what the broker serves: some brokers keep only a few months of M1, so sync H1/H4 directly when you need years of history. By default a sync also re-calibrates the broker spread override from the ticks it downloaded, which changes the spread of every later backtest; pass calibrateBroker: false to keep it, and read warnings[] in get_sync_status.
  • Feature gates. Mount-on-broker tools (mount_mt5_bot, get_mt5_mount_status and the NinjaTrader and cTrader equivalents) are off in the installed app and answer 404 with reason: "feature-disabled". Feasibility scans and Easy mode are available since 1.0.16.

Reading validation results

list_workflow_robustness and list_workflow_walkforward return one row per candidate with evaluated and passed. passed only means something when evaluated is true, that is, when the process had a filterPayload. A row with passed: null was measured but not judged. Ask for include: "metrics" to read by how much a candidate passed: Monte Carlo percentiles (5, 25, 50, 75, 95) of every KPI, and the in-sample / out-of-sample / average bundle of the walk-forward.

max_drawdown_pct is the largest peak-to-trough fall of the closed-trade equity curve, relative to the peak, not to the initial capital. A value above 100% means the equity went below zero from its peak (the account was blown), not a calculation error.

Treat the tool descriptions as the contract: they are generated from the same code that validates your input. When something is rejected, the error carries the valid values - correct once instead of retrying.