Install
$ agentstack add mcp-ek-labs-pp-mt5 ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ● Shell / process execution Used
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
pp-mt5 · MetaTrader 5 — Printing Press CLI
> One CLI that turns MetaTrader 5 into a forensic record of every decision you ever made in markets — plus a tick-accurate replay engine — and makes both queryable from the shell.
$ pp-mt5 close all --filter "profit 0.05)"
Behind that one line: snapshot live positions to the local mirror, SELECT the candidates by SQL predicate, print them with projected P&L, hash the intent (tickets + filter — not price), and gate execution on --confirm . One round trip, one audit row per ticket. Every write command goes through the same path.
What works today: every command in the reference below. Foundation, local mirror, live reads, algo analytics, the safety pipeline, writes, the hero flow, the quant stack (bars/ticks export, features, replay, sma-cross backtest), and pp-mt5-mcp exposing 18 MCP tools over stdio. The full phased build log lives in [STATUS.md](./STATUS.md).
Limitations (read first)
- Windows-only host for live MT5. The
MetaTrader5Python package only runs on Windows. Mac/Linux users can still runpp-mt5 sql,replay,stats, andbacktestagainst a synced mirror — they just can't pull live data or send orders without a Windows MT5 host. - Python subprocess overhead. ~50ms per round trip to the bridge. The local SQLite mirror exists precisely to keep this off the hot path —
sql,stats,replay, andbacktestanswer in microseconds, not 50ms. - Helper EA required for trade event streaming (
pp-mt5 watch trades --tail). Phase 2. v1 workaround: pollpositions liston an interval. See [helper/TODO.md](./helper/TODO.md). - Sibling CLI:
pp-mt5-backtester. This tool is for live + algo + quant workflows. For headlessterminal64.exe + .inistrategy-tester runs, use the sibling repo. Different binary, different transport, no overlap.
Install
go install github.com/ek-labs/pp-mt5/cmd/pp-mt5@latest
go install github.com/ek-labs/pp-mt5/cmd/pp-mt5-mcp@latest
py -3 -m pip install MetaTrader5 # Windows only
Or build from source with a version stamp:
git clone https://github.com/ek-labs/pp-mt5
cd pp-mt5
.\scripts\build.ps1 -Version v0.1.0
.\bin\pp-mt5.exe --version
.\bin\pp-mt5-mcp.exe --version
The script stamps cli.Version via -ldflags; both binaries pick it up from the same variable.
Verify:
pp-mt5 doctor
doctor checks Python, the MetaTrader5 package, terminal state, login, store writability, and current safety mode — and prints the exact remediation for any failure.
Auth
The CLI never reads or stores passwords. Pass an env var name with --password-env:
$env:MT5_PASSWORD = "..."
pp-mt5 connect login --account 12345678 --server "Broker-Live" --password-env MT5_PASSWORD
For headless / CI use, save a profile to ~/.config/pp-mt5/config.toml:
[profiles.demo]
account = 12345678
server = "Broker-Demo"
password_env = "MT5_DEMO_PASSWORD"
[profiles.live]
account = 87654321
server = "Broker-Live"
password_env = "MT5_LIVE_PASSWORD"
Then pp-mt5 --profile demo account info.
Multi-account mirror
The local SQLite mirror keys every row on account_login, so it can hold data for as many broker accounts as you've synced. Read commands (stats, sql, history, bars/ticks/features etc.) scope to one account at a time. Resolution order:
--accountflag (explicit override)- Otherwise, the most recently synced account (
accounts.last_synced DESC LIMIT 1) - If neither yields a value →
exit 10with a hint to runpp-mt5 sync allfirst
So pp-mt5 stats summary --since 30d reads from whichever account you last synced. Switch with pp-mt5 --account 12345678 stats summary. Sync commands tag rows with the current logged-in account regardless of the flag — they get the account from the bridge, not from --account.
Quick start
pp-mt5 doctor
pp-mt5 connect login --account 12345678 --server Broker-Demo --password-env MT5_PASSWORD
pp-mt5 sync all --since 2024-01-01 # one-time mirror; subsequent runs are incremental
pp-mt5 stats summary --since 30d # 50µs from the local mirror, no bridge call
pp-mt5 sql "select symbol, sum(profit) p from deals group by symbol order by p"
Unique features
Hero: compound writes from a SQL predicate
$ pp-mt5 close all --filter "profit
$ pp-mt5 close all --filter "profit strftime('%s','now','-30 days')*1000"
Safety layer (non-negotiable)
Defense in depth — every write goes through:
- Live-mode gate. Both
MT5_LIVE=1env AND--i-understand-this-is-liveflag required for any live write. Either missing → exit 6. - Hash-confirm. First invocation prints
SHA-256of the canonical request and exits 6. Re-run with--confirmwithin the validity window. The window is bucketed: the hash is valid in its own bucket and the previous one, so depending on when you got the hash, you have anywhere from 60s to ~120s before it expires. - Per-command guardrails from
~/.config/pp-mt5/config.toml:max_volume_per_order,max_open_positions,max_daily_loss,kill_switch_file(a single touched file refuses all writes). - Audit log appended to
~/.local/share/pp-mt5/audit.jsonl— every write, hash, response. Never deleted by the CLI.
Tick-accurate replay
pp-mt5 replay --symbol EURUSD --from 2024-06-01 --to 2024-06-02 --speed 100x | your-strategy.py
Streams from the local mirror. Works offline. --granularity tick|bar:M1|bar:M5....
Quant export
pp-mt5 bars export --tf M1 --symbols "EUR*,XAU*" --since 2y --format csv --out-dir ./exports
CSV or JSONL (--format); parquet is on the roadmap. Reads the mirror; never touches the bridge.
Command reference
See pp-mt5 --help. Categories:
Foundation doctor, connect login|logout|status, account info, terminal info, sync all|symbols|bars|ticks|deals|orders|positions, sql
Live symbols list|info, quote, book, positions list, orders list, order check|send, position close|modify, close all --filter ..., risk preview
Algo history deals|orders, stats summary|by-symbol|by-hour|by-day-of-week|by-magic|streaks|drawdown, r-multiples, correlation, magic audit
Quant bars copy|export, ticks copy, features build, calendar sync|near, replay, backtest run|list
Phase 2 helper install, watch trades
Output formats
Default: human-friendly table in a TTY; auto-switches to JSON when piped. Force with:
| Flag | Behaviour | |---------------------|----------------------------------------------------------------------| | --json | JSON regardless of TTY | | --agent | --json --compact --no-color --no-input --yes | | --select a,b.c | Cherry-pick fields from JSON output | | --dry-run | Preview without executing; for writes implies the safety hash flow | | --human-friendly | Force tables even when piped |
Errors go to stderr, data to stdout.
Exit codes
| Code | Meaning | |------|--------------------------------------| | 0 | OK | | 2 | Usage | | 3 | Not found (symbol, ticket, deal, …) | | 4 | Auth | | 5 | Broker rejected | | 6 | Safety-layer rejected | | 7 | Rate limited | | 10 | Config | | 11 | MT5 terminal unreachable |
Agents need 5 vs 6 distinct: 5 is "broker said no" (re-try might help), 6 is "you didn't pass the safety gate" (you must change the command, not retry).
Agent usage
Pass --agent to any command. Designed for tool-using LLMs:
pp-mt5 positions list --agent
pp-mt5 stats summary --since 30d --agent --select win_rate,profit_factor,max_dd_pct
For writes, an agent should:
- Compose the command with
--dry-run. - Capture the printed hash from stdout.
- Re-invoke with
--confirm --i-understand-this-is-liveonly after surfacing the dry-run summary to the human and getting explicit approval. - Never set
MT5_LIVE=1from inside the agent process — that's a user-only action.
Use with an AI assistant
The whole point of pp-mt5 is that you can stop clicking through MT5 windows and just ask. The CLI ships a sibling MCP server (pp-mt5-mcp) that exposes 18 tools to any MCP-capable client. What that gives you, in practice:
You: "what's my MT5 balance and any open positions?"
Claude: "Balance 13,377.45 ZAR. No open positions, free margin matches
balance. Account is in PAPER mode."
You: "close every position losing more than 50 ZAR"
Claude: "Found 3: EURUSD -78, GBPJPY -112, XAUUSD -203. Total -393.
Hash: a3f7… — confirm?"
You: "yes"
Claude: ✅ closed all 3.
The safety pipeline (kill-switch / live-mode gate / hash-confirm / audit) applies identically through the MCP path — the agent cannot send a real-money order without your explicit yes on the dry-run hash.
Which Claude surface works?
| Surface | Works? | How | |---|---|---| | Claude Code (terminal) | ✅ today | Install the skill (next section) — /pp-mt5 becomes a slash-command with full safety/exit-code awareness | | Claude Desktop (Mac/Windows app) | ✅ with one config edit | JSON snippet below | | claude.ai web | ❌ | Web Claude only supports hosted MCP connectors; it can't reach a local binary on your machine | | Claude mobile | ❌ | Same as web |
Don't try to "fix" the web/mobile limitation by hosting pp-mt5-mcp on a public URL — exposing a tool that can send broker orders over the open internet is a bad idea even with auth.
Setup — Claude Code
Install:
go install github.com/ek-labs/pp-mt5/cmd/pp-mt5@latest
go install github.com/ek-labs/pp-mt5/cmd/pp-mt5-mcp@latest
A SKILL.md ships at [SKILL.md](./SKILL.md). Copy this directory into ~/.claude/skills/pp-mt5/, and /pp-mt5 will be auto-registered in your next Claude Code session. Or register the MCP server directly:
claude mcp add pp-mt5-mcp -- pp-mt5-mcp
Setup — Claude Desktop
Edit your Claude Desktop config file:
- Windows:
%APPDATA%\Claude\claude_desktop_config.json - macOS:
~/Library/Application Support/Claude/claude_desktop_config.json
Add (or merge with existing mcpServers):
{
"mcpServers": {
"pp-mt5": {
"command": "pp-mt5-mcp"
}
}
}
If pp-mt5-mcp isn't on your PATH, point at the absolute path instead:
{
"mcpServers": {
"pp-mt5": {
"command": "C:\\Users\\you\\go\\bin\\pp-mt5-mcp.exe"
}
}
}
Restart Claude Desktop. The 18 MT5 tools (mt5_account_info, mt5_positions_list, mt5_close_all, …) appear in the 🔌 connectors panel and any chat can use them.
The 18 MCP tools
- Foundation —
mt5_doctor,mt5_account_info,mt5_terminal_info - Live reads —
mt5_symbols_list,mt5_quote,mt5_positions_list,mt5_orders_list,mt5_history_deals,mt5_risk_preview - Algo —
mt5_stats_summary,mt5_sql(read-only) - Sync —
mt5_sync_all - Writes —
mt5_order_check(preview),mt5_order_send,mt5_close_all(both dry-run +confirmflow) - Quant —
mt5_backtest_run,mt5_backtest_list - Audit —
mt5_audit_tail
List tools without booting the server:
pp-mt5-mcp --list-tools
Tools advertise readOnlyHint and destructiveHint so the host can colour calls appropriately. The agent should always surface the dry-run summary before passing confirm: — if it doesn't, the safety pipeline still won't execute, but a well-behaved host walks you through it.
Troubleshooting
| Symptom | Likely cause + fix | |-----------------------------------------------|------------------------------------------------------------------------------| | doctor says Python not found | Install Python 3.10+; ensure py -3 works on Windows | | MetaTrader5 package missing | py -3 -m pip install MetaTrader5 — Windows only | | terminal not running | Start MetaTrader 5 and log in once before invoking the CLI | | exit 4 — auth | Wrong account/server/password; verify in the terminal first | | exit 6 — safety-layer rejected | Missing MT5_LIVE=1 env, missing --i-understand-this-is-live, expired hash, or kill switch file present | | exit 11 — terminal unreachable | mt5.initialize() returned False; restart the terminal | | Hash mismatch | The validity window (60–120s, bucketed) expired or your command flags changed — re-run dry-run |
Testing
go test ./... # pure-helper unit tests
go test -tags=integration -v ./test/integration # demo-account smoke tests
Integration tests opt-in three ways: the integration build tag, MT5_PAPER=1 in the environment, plus an in-test AccountInfo().IsLive() check that fatals if trade_mode == 2. Without any one of those, the suite skips; with all three, it exercises doctor / account info / sync / read commands / dry-run writes / sql / audit against a live demo terminal.
$env:MT5_PAPER = "1"
$env:MT5_ACCOUNT = "12345678"
$env:MT5_SERVER = "JustMarkets-Demo"
$env:MT5_PASSWORD = "..."
go test -tags=integration -v -timeout 120s ./test/integration
The dry-run write tests never pass --confirm so no order ever reaches the broker even on a demo account — they verify that the safety gate returns exit 6 and produces a hash.
Sources & inspiration
- MQL5 Python integration docs — the API surface this CLI wraps
- printingpress.dev — design philosophy
- [
library/media-and-entertainment/youtube/](../../media-and-entertainment/youtube/) — structural template (read-only reference) - Peter Steinberger's
discrawlandgogcli— the local-mirror playbook the press is built on github.com/ek-labs/pp-mt5-backtester/— sibling CLI for headless Strategy Tester runs
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: ek-labs
- Source: ek-labs/pp-mt5
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.