mt4ctl
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mt4ctlWhich demo terminals are down?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mt4ctl
An MCP server for operating headless MetaTrader terminals — over SSH, from your agent.
Manage MetaTrader 4 terminals running under Wine + systemd on remote hosts (native Linux or WSL2) entirely through the Model Context Protocol: check status, read logs, capture screenshots, control the systemd lifecycle, and perform the tricky headless first-login — all as clean, typed tools.
Why
Algo traders increasingly run MetaTrader 4 headless on Linux — Wine under
Xvfb, supervised by systemd, no GUI. That's great for uptime and terrible for
day-to-day operations: every "is it connected?", "restart that one", or "log this
new account in" turns into a fragile chain of
ssh → (Windows cmd → wsl) → bash → systemctl → wine, with quoting hazards at
every hop.
mt4ctl collapses that chain into a handful of MCP tools. Point it at a registry
of your hosts and terminals, wire it into Claude (or any MCP client), and operate
the whole farm conversationally:
"Which demo terminals are down?" · "Restart demo2." · "Log demo2 into account 1000002 on ExampleBroker-Demo." · "Screenshot the live terminal so I can see the AutoTrading state."
Related MCP server: acp-mcp
Quickstart (5 minutes)
The init → list → doctor commands let you set up and verify everything
before wiring an MCP client:
# 1. write a starter registry, then fill in your hosts + terminals
uvx mt4ctl init # creates ~/.config/mt4ctl/terminals.yaml
$EDITOR ~/.config/mt4ctl/terminals.yaml
# 2. verify — offline, then over SSH (no MCP client needed)
uvx mt4ctl list # confirms the registry parses
uvx mt4ctl doctor # checks SSH, remote tools, units, data dirs
# 3. wire into Claude Code
claude mcp add --scope user mt4ctl \
--env MT4CTL_CONFIG="$HOME/.config/mt4ctl/terminals.yaml" \
-- uvx mt4ctlThen ask Claude: "Use mt4_list to show my configured terminals," then "mt4_status," and "mt4_doctor" if anything looks off. Full setup and other clients are below.
Features
Per-terminal connection detection — attributes established broker sockets to each terminal's
systemdcgroup, so terminals sharing a host (and a Wine prefix) are reported independently — not guessed from a host-wide count.Headless first-login — automates the one-time bootstrap a migrated terminal needs (MetaTrader's saved password is machine-bound), then hands control back to
systemdfor automatic reconnection on every restart.Idempotent strategy deploy — kubectl-apply for one terminal: push a local bundle of charts + experts and reconcile a terminal to it, touching only what mt4ctl deployed (foreign files like a watchdog's chart stay untouched), with a backup-and-restore-on-failure apply and a polling, report-only health verify that waits out the broker reconnect instead of guessing from a single snapshot.
Native and WSL2 hosts — one registry, two execution models; commands are base64-shipped so nothing breaks in the
cmd.exe → wsl.exe → bashgauntlet.Live-trading guardrails — terminals tagged
env: livereject mutating operations unless you passconfirm=true.Concurrent status — hosts are polled in parallel via
asyncio.Secrets stay secret — passwords resolve from arg → env → secrets file, are never logged, and the transient remote login config is
shred-ed after use.
How it works
┌────────────┐ MCP/stdio ┌──────────────────┐
│ MCP client │ ────────────► │ mt4ctl │
│ (Claude…) │ │ FastMCP server │
└────────────┘ └────────┬─────────┘
│ asyncio SSH (base64-framed)
┌─────────────────────┼─────────────────────┐
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ native Linux │ │ Windows + WSL2 │
│ sudo systemctl │ │ wsl -u root -- │
├─────────────────┤ ├──────────────────┤
│ mt4-live-main… │ systemd units running │ mt4-demo1… │
│ wine terminal.exe (Xvfb display) │ wine terminal.exe│
└─────────────────┘ └──────────────────┘A thin, typed core (models → config → ssh → scripts → deploy →
operations/login) sits under the server adapter, so the logic is testable
without a network and the MCP layer stays a one-line-per-tool shell.
Install
The fastest path needs no clone and no global install — uv
runs mt4ctl straight from the repo and fetches a matching Python itself:
uvx mt4ctl # runs the stdio serverNo uv yet? curl -LsSf https://astral.sh/uv/install.sh | sh — or skip it and use
the pipx path below.
Prefer a persistent mt4ctl command? Install it with uv or pipx:
uv tool install mt4ctl
# or
pipx install mt4ctlFor development:
git clone https://github.com/ak40u/mt4ctl.git && cd mt4ctl
python -m venv venv && source venv/bin/activate
pip install -e ".[dev]"The server machine needs either uv or Python 3.11+, plus SSH access to your
hosts. The remote hosts need the usual tools mt4ctl shells out to: systemctl,
ss, getent, (for screenshots) imagemagick/scrot + xdotool, and (for
deploy/adopt) GNU tar + a sha256 tool.
Configure
Copy the example registry and fill in your real hosts and terminals:
mkdir -p ~/.config/mt4ctl
cp examples/terminals.example.yaml ~/.config/mt4ctl/terminals.yamlThe registry is resolved from MT4CTL_CONFIG, then
~/.config/mt4ctl/terminals.yaml, then ./terminals.yaml. See
examples/terminals.example.yaml for the full
schema and docs/configuration.md for details.
Keep your populated registry private. It maps your accounts and infrastructure. The default
.gitignoreexcludesterminals.yaml.
Setting up terminal hosts
mt4ctl manages terminals; it doesn't install them. To stand up a host that runs
MT4 headless (Wine + Xvfb + systemd) so mt4ctl has something to drive:
Ubuntu / Linux server — Wine, the Xvfb + window-manager display, fonts (incl. the real Wingdings the MT4 smiley needs),
systemdunits, and the one-time headless login.Windows via WSL2 — the same stack inside WSL2, plus enabling WSL +
systemd, copying fonts from the Windows C: drive, boot autostart, and the WSL-specific gotchas.
Connect to an MCP client
Claude Code — one command wires it up (user scope = available in every project):
claude mcp add --scope user mt4ctl \
--env MT4CTL_CONFIG="$HOME/.config/mt4ctl/terminals.yaml" \
-- uvx mt4ctlOr commit a project .mcp.json to share with a team (Claude Code expands ${HOME}):
{
"mcpServers": {
"mt4ctl": {
"command": "uvx",
"args": ["mt4ctl"],
"env": { "MT4CTL_CONFIG": "${HOME}/.config/mt4ctl/terminals.yaml" }
}
}
}Claude Desktop — Settings → Developer → Edit Config (claude_desktop_config.json),
same shape but use an absolute config path (Desktop does not expand ${HOME}),
and an absolute command path if uvx is not on the GUI app's PATH (which uvx):
{
"mcpServers": {
"mt4ctl": {
"command": "uvx",
"args": ["mt4ctl"],
"env": { "MT4CTL_CONFIG": "/Users/you/.config/mt4ctl/terminals.yaml" }
}
}
}Installed
mt4ctlpersistently (uv/pipx)? Replacecommand/argswith just"command": "mt4ctl".
Tools
Tool | Mutates | Description |
| – | List configured terminals (offline). |
| – | Per-terminal service state + broker connection + log age. |
| – | Tail / grep a terminal's newest log file. |
| – | Capture a terminal window as PNG. |
| ✓ |
|
| ✓ | One-time headless login for auto-reconnect (live needs |
| – | Diagnose registry, SSH, remote tools, units, and data dirs. |
| – | List the experts (strategies) attached per terminal. |
| – | AutoTrading master switch + per-EA live-trading status. |
| – | Terminal build, broker server, and last broker ping. |
| ✓ | Reconcile a terminal to a local strategy bundle (live needs |
| ✓ | Record an already-running bundle as managed — the brownfield first cutover. |
| – | Poll a terminal until it is healthy after a restart (or report the failure). |
Full reference: docs/tools.md.
CLI
The subcommands mirror the MCP tool surface, so you can operate — and script — the whole farm without an MCP client:
# setup
mt4ctl init [path] # write a starter terminals.yaml (default: XDG config path)
mt4ctl list # list configured terminals (offline)
mt4ctl doctor # check registry, SSH, remote tools, units, data dirs
# read / inspect
mt4ctl status [terminal] # service + broker per terminal (exit 1 if unhealthy)
mt4ctl logs <terminal> [--pattern RE] [--lines N]
mt4ctl ea-list [terminal] # experts attached per terminal
mt4ctl autotrading [terminal] # AutoTrading master + per-EA live status
mt4ctl info [terminal] # build / broker server / last ping
mt4ctl screenshot <terminal> [--out-dir DIR]
# control / lifecycle (env=live needs --confirm)
mt4ctl control <terminal> {start|stop|restart} [--confirm]
mt4ctl login <terminal> <server> [--account A] [--password P] [--confirm]
mt4ctl verify <terminal> [--timeout SECONDS] # poll until healthy after a restart
mt4ctl deploy <terminal> <bundle> [--dry-run] [--confirm] [--reset-market-watch]
mt4ctl adopt <terminal> <bundle> [--confirm] # adopt an already-running farm
mt4ctl serve # run the MCP stdio server (the default with no subcommand)Health-oriented commands (status, verify, doctor) exit non-zero when
something is unhealthy, so a shell health-check can rely on the exit code rather
than grepping the output.
Deploy
Push a local bundle of charts + experts onto a terminal and reconcile it to that desired set — idempotently, touching only what mt4ctl deployed. The bundle mirrors the MT4 layout:
<bundle>/
profiles/default/<name>.chr # ready charts (one expert each)
MQL4/Experts/<folder>/<ea>.ex4 # the experts those charts referencemt4ctl deploy demo3 ./bundle --dry-run # preview the add/update/remove/foreign plan
mt4ctl deploy demo3 ./bundle # apply (env=live terminals need --confirm)It is apply-only (no selection, lot sizing, chart generation, or compilation —
you build the bundle), idempotent (a re-run is a no-op that still verifies health),
and managed-subset (foreign files like a watchdog's chart are never touched). The
write order is stop → drain → backup → apply → start; after the restart verify
polls until the terminal is healthy (report-only — it never reverts), and there
is no rollback command — recovery is to re-deploy the previous bundle. Add
--reset-market-watch to rebuild the terminal's Market Watch in the stopped window
(deletes symbols.sel, backed up first) and cap unbounded symbol carry-over.
Already running strategies on the farm? Take it under management first with
mt4ctl adopt <terminal> <bundle> (records the current footprint, changes
nothing), then deploy as usual. Full model and caveats: docs/deploy.md.
Security
Mutations on
env: liveterminals require explicitconfirm=true.Credentials resolve from argument →
MT4CTL_PASSWORD_<account>→ secrets file; they are never written to logs and the transient remote login config is shredded after use.All remote execution goes through your existing SSH config and key-based auth;
mt4ctlstores no credentials of its own.During
mt4_loginthe password is embedded in the base64-framed script handed tossh, so it is briefly visible in the local process list to your own user. On the remote side it is written only to a freshmktempconfig (mode 600) that a cleanup trapshreds on any exit path. On POSIX, the local secrets file is rejected if it is readable by group/other.
Deep dive
The MT4 "32 terminals per Windows user" limit — reproducing the cap on a clean box, locating the exact kernel object that enforces it (a per-instance Mutant in the session-local
\Sessions\<id>\BaseNamedObjects), and why running headless under Wine on Linux — whatmt4ctldrives — sidesteps it entirely.
Development
ruff check src tests # lint
mypy # type-check (strict)
pytest # testsSee docs/architecture.md for the module boundaries.
License
MIT © Pavel Volkov. See LICENSE.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ak40u/mt4ctl'
If you have feedback or need assistance with the MCP directory API, please join our Discord server