Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
# MetaTrader 5 MCP Server Credentials Example
LOGIN=your_login_here
PASSWORD=your_password_here
SERVER=your_server_here
# MetaTrader 5 Credentials
MT5_LOGIN=123456
MT5_PASSWORD=your_password_here
MT5_SERVER=MetaQuotes-Demo

# Optional: Path to MT5 terminal executable (auto-detected if not specified)
# PATH=C:\Program Files\MetaTrader 5\terminal64.exe
# MT5_PATH=C:\Program Files\MetaTrader 5\terminal64.exe

# MCP Transport Settings (optional)
# MCP_TRANSPORT=http
# MCP_HOST=127.0.0.1
# MCP_PORT=8000
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,33 @@ If your MT5 terminal is installed in a non-standard location, add the `--path` a

5. Start chatting! Try: *"What's my account balance?"*

#### Option B: Use with Open WebUI (For ChatGPT and other LLMs)
#### Option B: Use with HTTP Transport (For remote clients or web-based LLMs)

1. Start the MCP server with HTTP transport:

```bash
metatrader-mcp-server --transport http --login YOUR_LOGIN --password YOUR_PASSWORD --server YOUR_SERVER --host 0.0.0.0 --port 8080
```

**Optional: Specify Custom MT5 Terminal Path**

If your MT5 terminal is installed in a non-standard location, add the `--path` argument:

```bash
metatrader-mcp-server --transport http --login YOUR_LOGIN --password YOUR_PASSWORD --server YOUR_SERVER --path "C:\Program Files\MetaTrader 5\terminal64.exe" --host 0.0.0.0 --port 8080
```

2. The MCP server will start with Streamable HTTP endpoint available at `http://<host>:<port>/mcp`

3. Configure your MCP client to connect to the HTTP endpoint

**Transport Options:**
- `--transport stdio` (default): Use standard I/O for local MCP clients (like Claude Desktop)
- `--transport http`: Use HTTP with Streamable HTTP transport for remote clients
- `--host`: Host to bind to (default: 127.0.0.1)
- `--port`: Port to listen on (default: 8000)

#### Option C: Use with Open WebUI (For ChatGPT and other LLMs)

1. Start the HTTP server:

Expand All @@ -180,6 +206,8 @@ metatrader-http-server --login YOUR_LOGIN --password YOUR_PASSWORD --server YOUR

4. Now you can use trading tools in your Open WebUI chats!

**Note:** This uses the REST API interface, not the MCP protocol. For MCP over HTTP/SSE, use Option B.

---

## 💡 Usage Examples
Expand Down Expand Up @@ -352,6 +380,16 @@ The server will automatically load credentials from the `.env` file.
metatrader-http-server --host 127.0.0.1 --port 9000
```

### Securing HTTP Transport

When using the MCP server with HTTP transport for remote access, consider these security measures:

1. **Use HTTPS**: Configure a reverse proxy (nginx, Apache) with SSL/TLS to encrypt traffic
2. **IP Whitelisting**: Limit connections to known IP addresses using firewall rules
3. **Firewall**: Use firewall rules to restrict port access
4. **Network Isolation**: Keep the server on a private network or VPN
5. **Rate Limiting**: Use a reverse proxy to implement rate limiting and DDoS protection

### Connection Parameters

The MT5 client supports additional configuration:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ classifiers = [
]
dependencies = [
"python-dotenv>=1.1.0",
"MetaTrader5>=5.0.45",
"MetaTrader5>=5.0.45,<=5.0.5370",
"numpy>=2.2.4",
"pandas>=2.2.3",
"tabulate>=0.9.0",
Expand Down
40 changes: 23 additions & 17 deletions src/metatrader_mcp/cli.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import click
import os
from dotenv import load_dotenv
from metatrader_mcp.server import mcp

load_dotenv()

@click.command()
@click.option("--login", required=True, type=int, help="MT5 login ID")
@click.option("--password", required=True, help="MT5 password")
@click.option("--server", required=True, help="MT5 server name")
@click.option("--path", default=None, help="Path to MT5 terminal executable (optional, auto-detected if not provided)")
def main(login, password, server, path):
"""Launch the MetaTrader MCP STDIO server."""
load_dotenv()
# override env vars if provided via CLI
os.environ["login"] = str(login)
os.environ["password"] = password
os.environ["server"] = server
if path:
os.environ["path"] = path
# run STDIO transport
mcp.run(transport="stdio")
@click.option("--login", type=int, envvar="MT5_LOGIN", help="MT5 login ID")
@click.option("--password", envvar="MT5_PASSWORD", help="MT5 password")
@click.option("--server", envvar="MT5_SERVER", help="MT5 server name")
@click.option("--path", envvar="MT5_PATH", help="Path to MT5 terminal executable (optional, auto-detected if not provided)")
@click.option("--transport", type=click.Choice(["stdio", "http"]), default="stdio", envvar="MCP_TRANSPORT", help="Transport to use: 'stdio' for standard I/O (default), 'http' for Streamable HTTP transport")
@click.option("--host", default="127.0.0.1", envvar="MCP_HOST", help="Host for HTTP transport (ignored for stdio)")
@click.option("--port", default=8000, type=int, envvar="MCP_PORT", help="Port for HTTP transport (ignored for stdio)")
def main(login, password, server, path, transport, host, port):
"""Launch the MetaTrader MCP server."""

# run the MCP server with the chosen transport
if transport == "http":
# Update settings with custom host and port
mcp.settings.host = host
mcp.settings.port = port

# Run with Streamable HTTP transport
mcp.run(transport="streamable-http")
else:
mcp.run(transport="stdio")

if __name__ == "__main__":
# pylint: disable=no-value-for-parameter
main()
main()
36 changes: 6 additions & 30 deletions src/metatrader_mcp/server.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
#!/usr/bin/env python3
import os
import argparse
import logging
from dotenv import load_dotenv

from mcp.server.fastmcp import FastMCP, Context
from contextlib import asynccontextmanager
from collections.abc import AsyncIterator
from dataclasses import dataclass
from typing import Optional, Union

from mcp.server.fastmcp import FastMCP, Context

from metatrader_mcp.utils import init, get_client

# ────────────────────────────────────────────────────────────────────────────────
Expand All @@ -24,10 +21,10 @@ async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:

try:
client = init(
os.getenv("login"),
os.getenv("password"),
os.getenv("server"),
os.getenv("path")
os.getenv("MT5_LOGIN"),
os.getenv("MT5_PASSWORD"),
os.getenv("MT5_SERVER"),
os.getenv("MT5_PATH")
)
yield AppContext(client=client)
finally:
Expand Down Expand Up @@ -227,24 +224,3 @@ def cancel_pending_orders_by_symbol(ctx: Context, symbol: str) -> dict:
"""Cancel all pending orders for a specific symbol."""
client = get_client(ctx)
return client.order.cancel_pending_orders_by_symbol(symbol=symbol)

if __name__ == "__main__":
load_dotenv()
parser = argparse.ArgumentParser(description="MetaTrader MCP Server")
parser.add_argument("--login", type=str, help="MT5 login")
parser.add_argument("--password", type=str, help="MT5 password")
parser.add_argument("--server", type=str, help="MT5 server name")
parser.add_argument("--path", type=str, help="Path to MT5 terminal executable (optional)")

args = parser.parse_args()

# inject into lifespan via env vars
if args.login: os.environ["login"] = args.login
if args.password: os.environ["password"] = args.password
if args.server: os.environ["server"] = args.server
if args.path: os.environ["path"] = args.path

# run the MCP server (must call mcp.run)
mcp.run(
transport="stdio"
)
48 changes: 27 additions & 21 deletions src/metatrader_openapi/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
# Define a lifespan handler for MT5 client lifecycle
@asynccontextmanager
async def lifespan(app):
# Load environment and support uppercase or lowercase vars
# Load environment variables
load_dotenv()
login = os.getenv("LOGIN", os.getenv("login"))
password = os.getenv("PASSWORD", os.getenv("password"))
server = os.getenv("SERVER", os.getenv("server"))
path = os.getenv("PATH", os.getenv("path"))
# Use MT5_* variables as source of truth
login = os.getenv("MT5_LOGIN")
password = os.getenv("MT5_PASSWORD")
server = os.getenv("MT5_SERVER")
path = os.getenv("MT5_PATH")
client = init(login, password, server, path)
app.state.client = client
yield
Expand Down Expand Up @@ -60,30 +61,35 @@ def strip_prefix(route: APIRoute) -> str:
def main():
load_dotenv()
parser = argparse.ArgumentParser(description="MetaTrader OpenAPI server")
parser.add_argument("--login", required=True, help="MT5 login")
parser.add_argument("--password", required=True, help="MT5 password")
parser.add_argument("--server", required=True, help="MT5 server address")
parser.add_argument("--login", type=int, default=None, help="MT5 login (or set MT5_LOGIN env var)")
parser.add_argument("--password", default=None, help="MT5 password (or set MT5_PASSWORD env var)")
parser.add_argument("--server", default=None, help="MT5 server address (or set MT5_SERVER env var)")
parser.add_argument("--path", default=None, help="Path to MT5 terminal executable (optional, auto-detected if not provided)")
parser.add_argument("--host", default="127.0.0.1", help="Bind host")
parser.add_argument("--port", type=int, default=8000, help="Bind port")
args = parser.parse_args()

# set both uppercase and lowercase env vars for CLI
os.environ["LOGIN"] = args.login
os.environ["PASSWORD"] = args.password
os.environ["SERVER"] = args.server
os.environ["login"] = args.login
os.environ["password"] = args.password
os.environ["server"] = args.server
if args.path:
os.environ["PATH"] = args.path
os.environ["path"] = args.path


# Use CLI args if provided, otherwise fall back to environment variables
login = args.login or (int(os.getenv("MT5_LOGIN")) if os.getenv("MT5_LOGIN") else None) # type: ignore
password = args.password or os.getenv("MT5_PASSWORD")
server = args.server or os.getenv("MT5_SERVER")
path = args.path or os.getenv("MT5_PATH")

# Set environment variables for the lifespan handler to use
if login:
os.environ["MT5_LOGIN"] = str(login)
if password:
os.environ["MT5_PASSWORD"] = password
if server:
os.environ["MT5_SERVER"] = server
if path:
os.environ["MT5_PATH"] = path

uvicorn.run(
"metatrader_openapi.main:app",
host=args.host,
port=args.port,
reload=True,
reload=False,
)

if __name__ == "__main__":
Expand Down