mt5-mcp
MT5-MCP
MT5-MCP is an MCP (Model Context Protocol) server exposing MetaTrader 5 — market data, live streaming with a durable log, and full order/position lifecycle management — as tools for AI agents (Claude, Cursor, or any MCP-aware client). Runs locally against the MT5 terminal already installed on this machine; MetaTrader 5 is the only backend connector for now.
Current status: Bolts 1–5 shipped. Market data, live streaming, and order/position tools (with a mandatory dry-run-by-default safety layer) are all live. See docs/aidlc/BOLTS.md for what's next (history/audit tools, the auth checkpoint).
Docs
AI-DLC Inception charter — purpose, scope, stack, architecture, safety layer, risks
Tool & data model spec — the original full tool surface spec
Construction Bolt backlog — ordered, reviewable units of work and what each one actually shipped
Incident/investigation notes — the stdio-hang investigation; do not use
stdiotransport for any tool that touches MetaTrader5 (see below)
Install
python -m venv .venv
.venv\Scripts\pip install -e ".[dev]"
.venv\Scripts\pytest -qCopy .env.example to .env and set MT5_PATH to your terminal's terminal64.exe (auto-detection works if the terminal is already running, but an explicit path is more reliable — see diagnostics/FINDINGS.md for why relying on auto-detection is risky).
Run the server
.venv\Scripts\mt5-mcp
# or: .venv\Scripts\python.exe -m mt5_mcpDefault transport is streamable-http, bound to 127.0.0.1:3403 (loopback only, reserved in E:\MyAgent\workflow\ports\REGISTRY.md). This is deliberate — stdio was the original design but was found to hang indefinitely on any tool that calls into MetaTrader5 (root cause never identified despite extensive isolation; see diagnostics/FINDINGS.md). stdio is still available (MT5_MCP_TRANSPORT=stdio) but should not be trusted for anything beyond the ping tool.
Connect an MCP client
Claude Code:
claude mcp add --transport http mt5-mcp http://127.0.0.1:3403/mcpClaude Desktop / other JSON-config clients, add to the client's MCP server config:
{
"mcpServers": {
"mt5-mcp": {
"url": "http://127.0.0.1:3403/mcp"
}
}
}(Exact key names for HTTP-transport servers vary by client — check that client's docs if this doesn't work as-is.)
The server must already be running (mt5-mcp in a terminal) before the client connects — this project doesn't yet register itself as a background/managed process.
Public access (client on a different machine)
A DEV instance of this server is reverse-proxied at https://mt5-mcp-dev.delena.buzz (nginx + Cloudflare, port 3403 on this host) so a client on another machine can connect without a VPN/SSH tunnel:
claude mcp add --transport http mt5-mcp https://mt5-mcp-dev.delena.buzz/mcp⚠️ No authentication gate today — explicit, documented decision, not an oversight. Anyone with the URL can call every read-only tool and every execution tool. MT5_MCP_DRY_RUN is a server-side env var on this host, not something a remote caller can flip — but check its current value before relying on it as a safety net: it does not always default to dry-run-on in practice, only in the absence of an explicit override, and this host's .env is not committed (check E:\MyAgent\workflow\ports\REGISTRY.md's :3403 row for the live current status, since it changes and this file doesn't get re-edited on every toggle). When dry-run is on, execution tools always return simulated responses. When it's off, an unauthenticated caller can place/modify/cancel/close a real order on the connected account, bounded only by MT5_MCP_MAX_LOT_SIZE/MT5_MCP_MAX_OPEN_POSITIONS (always enforced regardless of dry-run) and the kill-switch (MT5_MCP_KILL_SWITCH_PATH — create that file to immediately block place_order). Either way, an unauthenticated caller can always read real market data and real open-position state (tickets/volumes/P&L). Full CSS integration is planned but not built — see docs/aidlc/INCEPTION.md's "Auth / security note" and E:\MyAgent\workflow\css\CLIENT-REGISTRY.md's mt5-mcp row (status waived-no-auth) for the reasoning and current status. Treat this URL accordingly until that changes.
If you run your own reverse proxy in front of this server, add its hostname to MT5_MCP_PUBLIC_HOSTNAMES (comma-separated) — FastMCP's built-in DNS-rebinding protection otherwise rejects any Host header besides 127.0.0.1/localhost/::1 with a 421.
Tools
All tools return the standard envelope: {success, error_code, error_message, retryable, request_id, data}.
Market data (read-only)
get_historical_ohlcv(symbol, timeframe, from_date?, to_date?, from_bar?, to_bar?, limit?, include_volume?, include_spread?, session_filter?, price_type?, only_completed_bars?)—timeframe: M1/M5/M15/M30/H1/H4/D1/W1/MN1.price_typeonly supports"bid"(or omitted) — ask/mid/last not implemented.get_symbol_info(symbol)— contract size, tick size/value, digits, swaps, margin, current bid/ask.
Live streaming (durable log, not true push — see below)
subscribe_live_data(symbol, data_types?)—data_types:["tick"]and/or["bar"](fixed M1). Returns asubscription_id.unsubscribe_live_data(subscription_id?, symbol?)— stop by id or all subscriptions for a symbol.get_stream_log(symbol, from_time?, to_time?, data_type?, limit?)— read back logged ticks/bars, including after unsubscribing.
Orders (execution — dry-run by default, see Safety below)
place_order(symbol, order_type, side, volume, price?, stop_loss?, take_profit?, comment?, magic_number?, deviation?, client_order_id?)—order_type:market/limit/stop.side:buy/sell.pricerequired for limit/stop.client_order_idis logged for traceability only — it does not deduplicate retries.modify_order(ticket, price?, stop_loss?, take_profit?, volume?, expiration?)— modifies a pending order.expirationis not implemented — passing it raisesunsupported_parameter.cancel_order(ticket)— cancels a pending order.
Positions
get_open_positions(symbol?, magic_number?, comment?)— read-only.modify_position(ticket, stop_loss?, take_profit?)— changes SL/TP on an open position.close_position(ticket, volume?)— full or partial close.close_all_positions(symbol?, side?, magic_number?)— bulk close.
Health
ping()— no MT5 connection required, works even if MT5_PATH is wrong.
Not implemented: stop_limit/trailing_stop order types, get_order_history/get_deal_history/get_position_history (Bolt 6), CSS auth (deferred, see INCEPTION.md).
Safety layer (order/position tools)
Dry-run is on by default. No real order ever gets placed unless you explicitly set
MT5_MCP_DRY_RUN=falsein the server's environment. It's an env var, not a tool parameter — an agent can't flip it mid-conversation. Dry-run responses use real current market prices, so they're shaped like a real fill would be.Kill-switch: create a file at
MT5_MCP_KILL_SWITCH_PATH(default:<repo root>\MT5_MCP_KILL_SWITCH) andplace_order(only — the sole action that creates new exposure) is blocked immediately, independent of the dry-run setting. Delete the file to re-enable.modify_order/cancel_order/modify_position/close_position/close_all_positionsare deliberately not blocked by the kill-switch — an emergency stop shouldn't also trap you inside an existing position.Limits:
MT5_MCP_MAX_LOT_SIZE(default0.10) andMT5_MCP_MAX_OPEN_POSITIONS(default3) — both enforced server-side onplace_order, not just documented.Audit log: every order/position attempt — dry-run or real, success or failure — is written to a local SQLite log (
MT5_MCP_AUDIT_LOG_DB, default<repo root>\mt5_mcp_audit_log.db), before the tool call returns.Nothing here authorizes a live (non-demo) account. The connector only ever points at whatever
MT5_PATH's terminal is logged into — currentlyOctaFX-Demo. Pointing this at a real account is a distinct, explicit decision this project has not made.
Environment variables
Variable | Default | Purpose |
| auto-detect | Path to |
|
|
|
|
| Bind host for http/sse transports |
|
| Bind port (reserved in the port registry) |
| (none) | Comma-separated extra Host headers to accept from a reverse proxy — required for any public hostname to work, see "Public access" above |
|
| Must be exactly |
|
| If this file exists, |
|
| Max volume per |
|
| Max concurrent open positions before |
|
| SQLite path for |
|
| SQLite path for the order/position audit trail |
All .db files are gitignored — they're local runtime state, not source.