mt4ctl

mt4ctl

An MCP server for operating headless MetaTrader 4 terminals remotely over SSH, enabling status checks, logs, screenshots, systemd control, and automated first-login.

Category
Visit Server

README

<div align="center">

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.

CI PyPI Python MCP License: MIT

</div>


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 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 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 for the full schema and 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 — 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 — 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.

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 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 <terminal> <bundle> (records the current footprint, changes nothing), then deploy as usual. Full model and caveats: docs/deploy.md.

Security

  • Mutations on env: live terminals require explicit confirm=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; 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 — 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 — what mt4ctl drives — sidesteps it entirely.

Development

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

See docs/architecture.md for the module boundaries.

License

MIT © Pavel Volkov. See LICENSE.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured