From 52b024b50d8da04a68f42b6a0ed8bd177404359b Mon Sep 17 00:00:00 2001 From: Chang Phui Hock Date: Wed, 29 Oct 2025 15:59:22 +0800 Subject: [PATCH 1/6] Add upper bound to MetaTrader5 dependency (<=5.0.5370) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 83e7a8c..8b287ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", From b0e62d2b9d2699a939c4d096dc6d63c07f4002d4 Mon Sep 17 00:00:00 2001 From: Chang Phui Hock Date: Thu, 30 Oct 2025 15:41:57 +0800 Subject: [PATCH 2/6] refactor(mcp): use MT5_* environment variables in lifespan context - Update app_lifespan to read from MT5_LOGIN, MT5_PASSWORD, MT5_SERVER, MT5_PATH - Consolidate on single source of truth for environment variables - Remove unused imports (argparse, logging, load_dotenv) - Remove __main__ block (replaced by cli.py) --- src/metatrader_mcp/server.py | 36 ++++++------------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/src/metatrader_mcp/server.py b/src/metatrader_mcp/server.py index 12783ab..951a54f 100644 --- a/src/metatrader_mcp/server.py +++ b/src/metatrader_mcp/server.py @@ -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 # ──────────────────────────────────────────────────────────────────────────────── @@ -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: @@ -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" - ) From 3c60bd763431ce0934e50a671c73292255eefd34 Mon Sep 17 00:00:00 2001 From: Chang Phui Hock Date: Thu, 30 Oct 2025 15:42:04 +0800 Subject: [PATCH 3/6] refactor(cli): simplify MCP CLI and remove redundant env var assignments - Remove redundant os.environ assignments (already handled by Click's envvar) - Remove unused os import - Click's envvar parameter reads from environment, no need to rewrite - Environment variables available during server lifespan execution --- src/metatrader_mcp/cli.py | 40 ++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/metatrader_mcp/cli.py b/src/metatrader_mcp/cli.py index 8c99973..e6d3d88 100644 --- a/src/metatrader_mcp/cli.py +++ b/src/metatrader_mcp/cli.py @@ -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() \ No newline at end of file From c4516a92735904c3788e8ed3514eb0faa5d498c1 Mon Sep 17 00:00:00 2001 From: Chang Phui Hock Date: Thu, 30 Oct 2025 15:42:13 +0800 Subject: [PATCH 4/6] refactor(openapi): consolidate MT5_* environment variables and improve CLI - Update lifespan to read from MT5_LOGIN, MT5_PASSWORD, MT5_SERVER, MT5_PATH - Fix argparse usage (remove invalid envvar parameter) - Add CLI arg precedence: CLI args override environment variables - Fall back to environment variables when CLI args not provided - Set resolved values to environment for lifespan handler access --- src/metatrader_openapi/main.py | 48 +++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/metatrader_openapi/main.py b/src/metatrader_openapi/main.py index 407b46a..769ae4e 100644 --- a/src/metatrader_openapi/main.py +++ b/src/metatrader_openapi/main.py @@ -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 @@ -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__": From 50a10c4898e6c9d5b4fb8c0c53d5e3755c39ccc2 Mon Sep 17 00:00:00 2001 From: Chang Phui Hock Date: Thu, 30 Oct 2025 15:42:21 +0800 Subject: [PATCH 5/6] docs: update README for HTTP transport and remove SSE references - Change 'Option B: HTTP/SSE Transport' to 'Option B: HTTP Transport' - Update command from --transport sse to --transport http - Update endpoint from /sse to /mcp (Streamable HTTP standard) - Remove deprecated SSE transport references - Update security guidelines to focus on reverse proxy approach --- README.md | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b9d574..6f6a441 100644 --- a/README.md +++ b/README.md @@ -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://:/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: @@ -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 @@ -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: From 83b8a40c39daaf7ff76f3111c5585f4c73cc40f1 Mon Sep 17 00:00:00 2001 From: Chang Phui Hock Date: Thu, 30 Oct 2025 15:42:28 +0800 Subject: [PATCH 6/6] chore: clean up .env.example with consolidated environment variables - Remove duplicate/conflicting variable definitions - Use MT5_LOGIN, MT5_PASSWORD, MT5_SERVER, MT5_PATH as canonical variables - Remove MCP_TRANSPORT and other unrelated variables - Add clear comments for optional vs required settings --- .env.example | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index cfd487e..0456edd 100644 --- a/.env.example +++ b/.env.example @@ -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 \ No newline at end of file