MCP server for managing headless MetaTrader terminals over SSH (Wine + systemd)
Project description
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."
Quickstart (5 minutes)
mt4_list works offline (no SSH/MT4 needed), so you can confirm the wiring
before anything else lines up:
# 1. create a minimal registry
mkdir -p ~/.config/mt4ctl
cat > ~/.config/mt4ctl/terminals.yaml <<'YAML'
hosts:
box: { ssh: my-ssh-alias, kind: native }
terminals:
t1: { host: box, service: mt4-t1, data_dir: /home/trader/mt4/t1, account: "1000001" }
YAML
# 2. add to Claude Code (uvx runs the server straight from git — no install)
claude mcp add --scope user mt4ctl \
--env MT4CTL_CONFIG="$HOME/.config/mt4ctl/terminals.yaml" \
-- uvx --from git+https://github.com/ak40u/mt4ctl mt4ctl
Then ask Claude: "Use mt4_list to show my configured terminals." You should
see your t1 row. Once the SSH alias and systemd unit line up, ask for
"mt4_status t1". 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. - 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 → 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 --from git+https://github.com/ak40u/mt4ctl 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 git+https://github.com/ak40u/mt4ctl
# or
pipx install git+https://github.com/ak40u/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, and (for screenshots) imagemagick/scrot + xdotool.
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
.gitignoreexcludesterminals.yaml.
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 --from git+https://github.com/ak40u/mt4ctl mt4ctl
Or commit a project .mcp.json to share with a team (Claude Code expands ${HOME}):
{
"mcpServers": {
"mt4ctl": {
"command": "uvx",
"args": ["--from", "git+https://github.com/ak40u/mt4ctl", "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": ["--from", "git+https://github.com/ak40u/mt4ctl", "mt4ctl"],
"env": { "MT4CTL_CONFIG": "/Users/you/.config/mt4ctl/terminals.yaml" }
}
}
}
Installed
mt4ctlpersistently (uv/pipx)? Replacecommand/argswith 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). |
Full reference: docs/tools.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.
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.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mt4ctl-0.1.0.tar.gz.
File metadata
- Download URL: mt4ctl-0.1.0.tar.gz
- Upload date:
- Size: 34.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ff526e3b79c5a2264e05436b42a9723483ca446672222f61a1b9e8bb9aeaf480
|
|
| MD5 |
9e6533406cd63ce7efebc9880b1ade98
|
|
| BLAKE2b-256 |
fea35e8750ea0cde20126c5cf8618484017052fc15b1f6ce16c56e4ec057b826
|
Provenance
The following attestation bundles were made for mt4ctl-0.1.0.tar.gz:
Publisher:
release.yml on ak40u/mt4ctl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mt4ctl-0.1.0.tar.gz -
Subject digest:
ff526e3b79c5a2264e05436b42a9723483ca446672222f61a1b9e8bb9aeaf480 - Sigstore transparency entry: 1614951084
- Sigstore integration time:
-
Permalink:
ak40u/mt4ctl@4e150f6eb6aa5ba80a094b7314dc5ea09f075cd6 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ak40u
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4e150f6eb6aa5ba80a094b7314dc5ea09f075cd6 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mt4ctl-0.1.0-py3-none-any.whl.
File metadata
- Download URL: mt4ctl-0.1.0-py3-none-any.whl
- Upload date:
- Size: 26.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6aba2209ab8b0236e523e950a2d342d72aa03e6b3571af35546af0d72e836c06
|
|
| MD5 |
f5c66384863ef2d12f9070f3a002a9a6
|
|
| BLAKE2b-256 |
a9658d64a6c43c0dde1fe97a708d284818b0712d86532d099921743d52182308
|
Provenance
The following attestation bundles were made for mt4ctl-0.1.0-py3-none-any.whl:
Publisher:
release.yml on ak40u/mt4ctl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mt4ctl-0.1.0-py3-none-any.whl -
Subject digest:
6aba2209ab8b0236e523e950a2d342d72aa03e6b3571af35546af0d72e836c06 - Sigstore transparency entry: 1614951220
- Sigstore integration time:
-
Permalink:
ak40u/mt4ctl@4e150f6eb6aa5ba80a094b7314dc5ea09f075cd6 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ak40u
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4e150f6eb6aa5ba80a094b7314dc5ea09f075cd6 -
Trigger Event:
push
-
Statement type: