AgentStack
MCP unreviewed MIT Self-run

Mt4ctl

mcp-ak40u-mt4ctl · by ak40u

MCP server for managing headless MetaTrader 4 terminals over SSH (Wine + systemd) — status, logs, screenshots, lifecycle control, headless login

No reviews yet
0 installs
1 views
0.0% view→install

Install

$ agentstack add mcp-ak40u-mt4ctl

Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Pipes remote content directly into a shell (remote code execution).

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • 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.

Are you the author of Mt4ctl? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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.

[](https://github.com/ak40u/mt4ctl/actions/workflows/ci.yml) [](https://pypi.org/project/mt4ctl/) [](https://www.python.org/) [](https://modelcontextprotocol.io) [](LICENSE)


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."

Quickstart (5 minutes)

The initlistdoctor 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 mt4ctl

Then ask Claude: "Use mt4list to show my configured terminals," then "mt4status," 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 systemd cgroup, 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 systemd for automatic reconnection on every restart.

  • Idempotent strategy deploykubectl-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 → bash gauntlet.

  • Live-trading guardrails — terminals tagged env: live reject mutating

operations unless you pass confirm=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 (modelsconfigsshscriptsdeployoperations/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 server

No 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 mt4ctl

For 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.yaml

The registry is resolved from MT4CTL_CONFIG, then ~/.config/mt4ctl/terminals.yaml, then ./terminals.yaml. See [examples/terminals.example.yaml](examples/terminals.example.yaml) for the full schema and [docs/configuration.md](docs/configuration.md) for details.

> Keep your populated registry private. It maps your accounts and > infrastructure. The default .gitignore excludes terminals.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](docs/install-linux-ubuntu.md) — Wine, the Xvfb +

window-manager display, fonts (incl. the real Wingdings the MT4 smiley needs), systemd units, and the one-time headless login.

  • [Windows via WSL2](docs/install-windows-wsl.md) — 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 mt4ctl

Or 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 mt4ctl persistently (uv/pipx)? Replace command/args with just > "command": "mt4ctl".

Tools

| Tool | Mutates | Description | | --- | :---: | --- | | mt4_list | – | List configured terminals (offline). | | mt4_status | – | Per-terminal service state + broker connection + log age. | | mt4_logs | – | Tail / grep a terminal's newest log file. | | mt4_screenshot | – | Capture a terminal window as PNG. | | mt4_control | ✓ | start / stop / restart a unit (live needs confirm). | | mt4_login | ✓ | One-time headless login for auto-reconnect (live needs confirm). | | mt4_doctor | – | Diagnose registry, SSH, remote tools, units, and data dirs. | | mt4_ea_list | – | List the experts (strategies) attached per terminal. | | mt4_autotrading | – | AutoTrading master switch + per-EA live-trading status. | | mt4_info | – | Terminal build, broker server, and last broker ping. | | mt4_deploy | ✓ | Reconcile a terminal to a local strategy bundle (live needs confirm). | | mt4_adopt | ✓ | Record an already-running bundle as managed — the brownfield first cutover. | | mt4_verify | – | Poll a terminal until it is healthy after a restart (or report the failure). |

Full reference: [docs/tools.md](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  [--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  [--out-dir DIR]

# control / lifecycle (env=live needs --confirm)
mt4ctl control  {start|stop|restart} [--confirm]
mt4ctl login   [--account A] [--password P] [--confirm]
mt4ctl verify  [--timeout SECONDS]                # poll until healthy after a restart
mt4ctl deploy   [--dry-run] [--confirm] [--reset-market-watch]
mt4ctl adopt   [--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:

/
  profiles/default/.chr        # ready charts (one expert each)
  MQL4/Experts//.ex4     # the experts those charts reference
mt4ctl 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 (records the current footprint, changes nothing), then deploy as usual. Full model and caveats: [docs/deploy.md](docs/deploy.md).

Security

  • Mutations on env: live terminals require explicit confirm=true.
  • Credentials resolve from argument → MT4CTL_PASSWORD_

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;

mt4ctl stores no credentials of its own.

  • During mt4_login the password is embedded in the base64-framed script handed

to ssh, so it is briefly visible in the local process list to your own user. On the remote side it is written only to a fresh mktemp config (mode 600) that a cleanup trap shreds 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](docs/the-32-terminal-limit.md)

reproducing the cap on a clean box, locating the exact kernel object that enforces it (a per-instance Mutant in the session-local \Sessions\\BaseNamedObjects), and why running headless under Wine on Linux — what mt4ctl drives — sidesteps it entirely.

Development

ruff check src tests      # lint
mypy                      # type-check (strict)
pytest                    # tests

See [docs/architecture.md](docs/architecture.md) for the module boundaries.

License

MIT © Pavel Volkov. See [LICENSE](LICENSE).

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.