Add PyInstaller build support for deployment
- Add PyInstaller spec files (onefile and onedir modes) - Add build scripts for executable creation - Add MCP hooks for PyInstaller compatibility - Add test script for MCP server validation - Add Windsurf integration setup guide - Update main.py with better error reporting - Successfully tested MCP server works with ONEDIR build (45MB) Features: - Single executable deployment without Python dependency - ONEDIR mode preserves stdio communication for MCP - Build scripts with deployment instructions - Windsurf config integration guide Files added: - mt5-quant.spec (PyInstaller spec) - mt5-quant-onedir.spec (directory mode) - scripts/build-executable.sh (onefile) - scripts/build-executable-onedir.sh (directory) - hooks/hook-mcp.py (PyInstaller hook) - test_mcp.py (validation script) - WINDSURF_SETUP.md (integration guide)
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
# Windsurf MCP Integration Setup
|
||||
|
||||
## Quick Setup
|
||||
|
||||
### 1. Build Executable
|
||||
```bash
|
||||
bash scripts/build-executable-onedir.sh
|
||||
```
|
||||
|
||||
### 2. Configure Windsurf
|
||||
|
||||
Edit `~/.windsurf/config.yaml`:
|
||||
|
||||
```yaml
|
||||
mcpServers:
|
||||
mt5-quant:
|
||||
command: /Users/masdevid/jobs/mt5-mcp/dist/mt5-quant/mt5-quant
|
||||
```
|
||||
|
||||
Or dengan environment variable:
|
||||
```yaml
|
||||
mcpServers:
|
||||
mt5-quant:
|
||||
command: /Users/masdevid/jobs/mt5-mcp/dist/mt5-quant/mt5-quant
|
||||
env:
|
||||
MT5_MCP_HOME: /Users/masdevid/jobs/mt5-mcp
|
||||
```
|
||||
|
||||
### 3. Restart Windsurf
|
||||
Close dan reopen Windsurf untuk load MCP server.
|
||||
|
||||
### 4. Verify
|
||||
Di Windsurf chat, test dengan:
|
||||
```
|
||||
Run verify_setup
|
||||
```
|
||||
|
||||
## Deployment ke Multiple Machines
|
||||
|
||||
### Build untuk Distribution
|
||||
```bash
|
||||
# Build
|
||||
bash scripts/build-executable-onedir.sh
|
||||
|
||||
# Create tarball
|
||||
tar -czf mt5-quant-macos-arm64.tar.gz -C dist mt5-quant
|
||||
|
||||
# Deploy ke remote server
|
||||
scp mt5-quant-macos-arm64.tar.gz user@server:~/
|
||||
ssh user@server "tar -xzf mt5-quant-macos-arm64.tar.gz -C /opt/"
|
||||
ssh user@server "ln -s /opt/mt5-quant/mt5-quant /usr/local/bin/"
|
||||
|
||||
# Copy config
|
||||
scp -r config/mt5-quant.yaml user@server:~/.config/mt5-quant/config/
|
||||
```
|
||||
|
||||
### Target Machine Requirements
|
||||
- MetaTrader 5 installed (via Wine/CrossOver)
|
||||
- Config file di `~/.config/mt5-quant/config/mt5-quant.yaml`
|
||||
- **NO Python required!**
|
||||
|
||||
### Windsurf Config di Target Machine
|
||||
```yaml
|
||||
mcpServers:
|
||||
mt5-quant:
|
||||
command: /usr/local/bin/mt5-quant
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### MCP server not appearing
|
||||
1. Check Windsurf logs: `~/.windsurf/logs/`
|
||||
2. Verify executable path is absolute
|
||||
3. Test executable manually: `./dist/mt5-quant/mt5-quant --help`
|
||||
|
||||
### Config not found
|
||||
Set `MT5_MCP_HOME` environment variable atau pastikan config di default location:
|
||||
- macOS: `~/.config/mt5-quant/config/mt5-quant.yaml`
|
||||
|
||||
### Permission denied
|
||||
```bash
|
||||
chmod +x /path/to/mt5-quant
|
||||
```
|
||||
@@ -0,0 +1,18 @@
|
||||
# PyInstaller hook for mcp package
|
||||
# Ensures all mcp submodules are included
|
||||
|
||||
from PyInstaller.utils.hooks import collect_submodules, collect_data_files
|
||||
|
||||
hiddenimports = collect_submodules('mcp')
|
||||
datas = collect_data_files('mcp')
|
||||
|
||||
# Also ensure anyio dependencies are included
|
||||
hiddenimports += [
|
||||
'anyio',
|
||||
'anyio.streams',
|
||||
'anyio.streams.memory',
|
||||
'anyio.streams.text',
|
||||
'anyio._backends',
|
||||
'anyio._backends._asyncio',
|
||||
'anyio._backends._trio',
|
||||
]
|
||||
@@ -0,0 +1,131 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
"""
|
||||
PyInstaller spec file for MT5-Quant MCP Server (onedir mode)
|
||||
This version creates a directory with the executable + dependencies
|
||||
Better for MCP stdio communication than onefile mode
|
||||
|
||||
Build:
|
||||
pyinstaller mt5-quant-onedir.spec
|
||||
|
||||
Output:
|
||||
dist/mt5-quant/ directory with executable inside
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from PyInstaller.utils.hooks import collect_all
|
||||
|
||||
block_cipher = None
|
||||
|
||||
# Project root - the build script runs from project root, so use cwd
|
||||
root = Path(os.getcwd()).resolve()
|
||||
|
||||
# Collect entire mcp package (datas, binaries, hiddenimports)
|
||||
mcp_datas, mcp_binaries, mcp_hiddenimports = collect_all('mcp')
|
||||
|
||||
# Data files to include
|
||||
# Format: (source_path, dest_dir_in_bundle)
|
||||
datas = [
|
||||
# Include scripts directory (bash pipeline scripts)
|
||||
(str(root / 'scripts'), 'scripts'),
|
||||
# Include docs if needed
|
||||
(str(root / 'docs'), 'docs'),
|
||||
] + mcp_datas
|
||||
|
||||
# Hidden imports (modules that are imported dynamically or might be missed)
|
||||
hiddenimports = mcp_hiddenimports + [
|
||||
# MCP stdio - explicitly include for PyInstaller
|
||||
'mcp.server.stdio',
|
||||
'mcp.shared.memory',
|
||||
'mcp.shared.session',
|
||||
'mcp.server.models',
|
||||
# Analytics modules (local project modules)
|
||||
'analytics',
|
||||
'analytics.extract',
|
||||
'analytics.analyze',
|
||||
'analytics.optimize_parser',
|
||||
# Other dependencies
|
||||
'pydantic',
|
||||
'pydantic.v1',
|
||||
'pydantic.v1.fields',
|
||||
'pydantic.v1.main',
|
||||
'pydantic_core',
|
||||
'yaml',
|
||||
'_yaml',
|
||||
'yaml.constructor',
|
||||
'yaml.representer',
|
||||
'yaml.cyaml',
|
||||
'anyio',
|
||||
'anyio.streams',
|
||||
'anyio.streams.memory',
|
||||
'anyio.streams.text',
|
||||
# Other stdlib that might be dynamically imported
|
||||
'xml.etree.ElementTree',
|
||||
'html.parser',
|
||||
]
|
||||
|
||||
a = Analysis(
|
||||
['server/main.py'],
|
||||
pathex=[str(root), str(root / 'server'), str(root / 'analytics')],
|
||||
binaries=mcp_binaries,
|
||||
datas=datas,
|
||||
hiddenimports=hiddenimports,
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[
|
||||
# Exclude unnecessary packages to reduce binary size
|
||||
'matplotlib',
|
||||
'PIL',
|
||||
'numpy',
|
||||
'pandas',
|
||||
'scipy',
|
||||
'tkinter',
|
||||
'PyQt5',
|
||||
'PyQt6',
|
||||
'wx',
|
||||
'test',
|
||||
'unittest',
|
||||
'pydoc',
|
||||
'doctest',
|
||||
'email',
|
||||
'http.server',
|
||||
'ftplib',
|
||||
'telnetlib',
|
||||
'ssl',
|
||||
'sqlite3',
|
||||
],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='mt5-quant',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=True,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
|
||||
# For onedir mode, we need to use COLLECT
|
||||
# But actually, on macOS/Linux, just having the EXE without onefile=True creates a directory
|
||||
# Wait, actually we need to check PyInstaller version behavior
|
||||
# Modern PyInstaller creates onedir by default for EXE + COLLECT
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
"""
|
||||
PyInstaller spec file for MT5-Quant MCP Server
|
||||
|
||||
Build:
|
||||
pyinstaller mt5-quant.spec
|
||||
|
||||
Output:
|
||||
dist/mt5-quant (single executable)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from PyInstaller.utils.hooks import collect_all
|
||||
|
||||
block_cipher = None
|
||||
|
||||
# Project root - the build script runs from project root, so use cwd
|
||||
root = Path(os.getcwd()).resolve()
|
||||
|
||||
# Collect entire mcp package (datas, binaries, hiddenimports)
|
||||
mcp_datas, mcp_binaries, mcp_hiddenimports = collect_all('mcp')
|
||||
|
||||
# Data files to include
|
||||
# Format: (source_path, dest_dir_in_bundle)
|
||||
datas = [
|
||||
# Include scripts directory (bash pipeline scripts)
|
||||
(str(root / 'scripts'), 'scripts'),
|
||||
# Include docs if needed
|
||||
(str(root / 'docs'), 'docs'),
|
||||
] + mcp_datas
|
||||
|
||||
# Hidden imports (modules that are imported dynamically or might be missed)
|
||||
# Note: mcp_hiddenimports already includes all mcp submodules from collect_all()
|
||||
hiddenimports = mcp_hiddenimports + [
|
||||
# MCP package - all submodules
|
||||
'mcp',
|
||||
'mcp.types',
|
||||
'mcp.server',
|
||||
'mcp.server.stdio',
|
||||
'mcp.server.models',
|
||||
'mcp.server.session',
|
||||
'mcp.shared',
|
||||
'mcp.shared.memory',
|
||||
'mcp.shared.session',
|
||||
'mcp.shared.exceptions',
|
||||
'mcp.shared.context',
|
||||
'mcp.shared.progress',
|
||||
'mcp.shared.version',
|
||||
'mcp.client',
|
||||
'mcp.cli',
|
||||
# Analytics modules (local project modules)
|
||||
'analytics',
|
||||
'analytics.extract',
|
||||
'analytics.analyze',
|
||||
'analytics.optimize_parser',
|
||||
# Other dependencies
|
||||
'pydantic',
|
||||
'pydantic.v1',
|
||||
'pydantic.v1.fields',
|
||||
'pydantic.v1.main',
|
||||
'pydantic_core',
|
||||
'yaml',
|
||||
'_yaml',
|
||||
'yaml.constructor',
|
||||
'yaml.representer',
|
||||
'yaml.cyaml',
|
||||
'anyio',
|
||||
'anyio.streams',
|
||||
'anyio.streams.memory',
|
||||
'anyio.streams.text',
|
||||
'anyio._backends',
|
||||
'anyio._backends._asyncio',
|
||||
# Other stdlib that might be dynamically imported
|
||||
'xml.etree.ElementTree',
|
||||
'html.parser',
|
||||
'select',
|
||||
'ssl',
|
||||
'_ssl',
|
||||
'certifi',
|
||||
'email',
|
||||
'email.parser',
|
||||
'email.message',
|
||||
'importlib.metadata',
|
||||
]
|
||||
|
||||
a = Analysis(
|
||||
['server/main.py'],
|
||||
pathex=[str(root), str(root / 'server'), str(root / 'analytics')],
|
||||
binaries=mcp_binaries,
|
||||
datas=datas,
|
||||
hiddenimports=hiddenimports,
|
||||
hookspath=[str(root / 'hooks')],
|
||||
hooksconfig={
|
||||
'mcp': {
|
||||
'include_all': True,
|
||||
}
|
||||
},
|
||||
runtime_hooks=[],
|
||||
excludes=[
|
||||
# Exclude unnecessary packages to reduce binary size
|
||||
'matplotlib',
|
||||
'PIL',
|
||||
'numpy',
|
||||
'pandas',
|
||||
'scipy',
|
||||
'tkinter',
|
||||
'PyQt5',
|
||||
'PyQt6',
|
||||
'wx',
|
||||
'test',
|
||||
'unittest',
|
||||
'pydoc',
|
||||
'doctest',
|
||||
# 'email', # Required by pydantic -> mcp
|
||||
# 'http.server',
|
||||
# 'ftplib',
|
||||
# 'telnetlib',
|
||||
# 'ssl', # Required by anyio -> mcp
|
||||
'sqlite3',
|
||||
],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
# Create the EXE without including binaries/datas (onedir mode)
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='mt5-quant',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=False,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
|
||||
# Collect all files into directory (onedir mode)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=False,
|
||||
upx_exclude=[],
|
||||
name='mt5-quant'
|
||||
)
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/bin/bash
|
||||
# Build MT5-Quant as a directory bundle (onedir mode) using PyInstaller
|
||||
# This mode preserves stdin/stdout better for MCP communication
|
||||
# Output: dist/mt5-quant/ directory
|
||||
|
||||
set -e
|
||||
|
||||
# Get script directory and project root
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
echo "=== MT5-Quant PyInstaller Build (ONEDIR Mode) ==="
|
||||
echo "Project root: $PROJECT_ROOT"
|
||||
echo ""
|
||||
|
||||
# Determine Python interpreter
|
||||
if [ -f "$PROJECT_ROOT/.venv/bin/python3" ]; then
|
||||
PYTHON="$PROJECT_ROOT/.venv/bin/python3"
|
||||
elif [ -f "$PROJECT_ROOT/.venv/bin/python" ]; then
|
||||
PYTHON="$PROJECT_ROOT/.venv/bin/python"
|
||||
else
|
||||
PYTHON="python3"
|
||||
fi
|
||||
|
||||
echo "Using Python: $PYTHON"
|
||||
echo ""
|
||||
|
||||
# Ensure dependencies are installed
|
||||
echo "Installing dependencies..."
|
||||
$PYTHON -m pip install pyinstaller mcp pyyaml typer --quiet 2>/dev/null || true
|
||||
|
||||
# Clean previous builds
|
||||
echo "Cleaning previous builds..."
|
||||
rm -rf "$PROJECT_ROOT/build" "$PROJECT_ROOT/dist"
|
||||
|
||||
# Build the executable in onedir mode
|
||||
echo "Building executable in ONEDIR mode (this may take 1-2 minutes)..."
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Use onedir mode (no --onefile flag)
|
||||
# Note: mode is controlled by the .spec file
|
||||
$PYTHON -m PyInstaller \
|
||||
--clean \
|
||||
--noconfirm \
|
||||
--distpath "$PROJECT_ROOT/dist" \
|
||||
--workpath "$PROJECT_ROOT/build" \
|
||||
"$PROJECT_ROOT/mt5-quant.spec"
|
||||
|
||||
# Report results
|
||||
echo ""
|
||||
echo "=== Build Complete ==="
|
||||
echo ""
|
||||
echo "Executable location:"
|
||||
ls -la "$PROJECT_ROOT/dist/mt5-quant/" | head -10
|
||||
|
||||
echo ""
|
||||
echo "Main executable:"
|
||||
ls -lh "$PROJECT_ROOT/dist/mt5-quant/mt5-quant"
|
||||
|
||||
echo ""
|
||||
echo "Total size:"
|
||||
du -sh "$PROJECT_ROOT/dist/mt5-quant/"
|
||||
|
||||
echo ""
|
||||
echo "To test:"
|
||||
echo " ./dist/mt5-quant/mt5-quant"
|
||||
echo ""
|
||||
echo "To register with Claude Code:"
|
||||
echo " claude mcp add MT5-Quant -- $(pwd)/dist/mt5-quant/mt5-quant"
|
||||
echo ""
|
||||
echo "=== Deployment to Multiple Machines ==="
|
||||
echo ""
|
||||
echo "1. Copy entire directory to target machines:"
|
||||
echo " scp -r dist/mt5-quant/ user@server:/opt/"
|
||||
echo " ssh user@server ln -s /opt/mt5-quant/mt5-quant /usr/local/bin/"
|
||||
echo ""
|
||||
echo "2. Or create tarball for distribution:"
|
||||
echo " tar -czf mt5-quant-macos-arm64.tar.gz -C dist mt5-quant"
|
||||
echo ""
|
||||
echo "Requirements on target machine:"
|
||||
echo " - MetaTrader 5 installed (via Wine on macOS/Linux)"
|
||||
echo " - Config file at ~/.config/mt5-quant/config/mt5-quant.yaml"
|
||||
echo " - NO Python installation required!"
|
||||
@@ -0,0 +1,83 @@
|
||||
#!/bin/bash
|
||||
# Build MT5-Quant as a single executable using PyInstaller
|
||||
# Output: dist/mt5-quant
|
||||
|
||||
set -e
|
||||
|
||||
# Get script directory and project root
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
echo "=== MT5-Quant PyInstaller Build ==="
|
||||
echo "Project root: $PROJECT_ROOT"
|
||||
echo ""
|
||||
|
||||
# Determine Python interpreter
|
||||
if [ -f "$PROJECT_ROOT/.venv/bin/python3" ]; then
|
||||
PYTHON="$PROJECT_ROOT/.venv/bin/python3"
|
||||
elif [ -f "$PROJECT_ROOT/.venv/bin/python" ]; then
|
||||
PYTHON="$PROJECT_ROOT/.venv/bin/python"
|
||||
else
|
||||
PYTHON="python3"
|
||||
fi
|
||||
|
||||
echo "Using Python: $PYTHON"
|
||||
echo ""
|
||||
|
||||
# Ensure dependencies are installed
|
||||
echo "Installing dependencies..."
|
||||
$PYTHON -m pip install pyinstaller mcp pyyaml --quiet
|
||||
|
||||
# Clean previous builds
|
||||
echo "Cleaning previous builds..."
|
||||
rm -rf "$PROJECT_ROOT/build" "$PROJECT_ROOT/dist"
|
||||
|
||||
# Build the executable
|
||||
echo "Building executable (this may take 1-2 minutes)..."
|
||||
cd "$PROJECT_ROOT"
|
||||
$PYTHON -m PyInstaller \
|
||||
--clean \
|
||||
--noconfirm \
|
||||
--distpath "$PROJECT_ROOT/dist" \
|
||||
--workpath "$PROJECT_ROOT/build" \
|
||||
"$PROJECT_ROOT/mt5-quant.spec"
|
||||
|
||||
# Report results
|
||||
echo ""
|
||||
echo "=== Build Complete ==="
|
||||
echo ""
|
||||
echo "Executable location:"
|
||||
ls -lh dist/mt5-quant
|
||||
|
||||
echo ""
|
||||
echo "Size breakdown:"
|
||||
du -sh dist/ 2>/dev/null || echo " dist/: $(du -sh dist/mt5-quant 2>/dev/null | cut -f1)"
|
||||
|
||||
echo ""
|
||||
echo "To test:"
|
||||
echo " ./dist/mt5-quant --help"
|
||||
echo ""
|
||||
echo "To register with Claude Code:"
|
||||
echo " claude mcp add MT5-Quant -- $(pwd)/dist/mt5-quant"
|
||||
echo ""
|
||||
echo "=== Deployment to Multiple Machines ==="
|
||||
echo ""
|
||||
echo "1. Copy this single binary to target machines:"
|
||||
echo " scp dist/mt5-quant user@server:/usr/local/bin/"
|
||||
echo " ssh user@server chmod +x /usr/local/bin/mt5-quant"
|
||||
echo ""
|
||||
echo "2. Copy config directory (includes mt5-quant.yaml):"
|
||||
echo " scp -r config/ user@server:~/.config/mt5-quant/"
|
||||
echo ""
|
||||
echo "3. Or use bundled config (minimal):"
|
||||
echo " MT5_MCP_HOME=/path/to/config ./mt5-quant"
|
||||
echo ""
|
||||
echo "4. Register on target machine:"
|
||||
echo " claude mcp add MT5-Quant -- /usr/local/bin/mt5-quant"
|
||||
echo ""
|
||||
echo "Requirements on target machine:"
|
||||
echo " - MetaTrader 5 installed (via Wine on macOS/Linux)"
|
||||
echo " - Config file at ~/.config/mt5-quant/config/mt5-quant.yaml"
|
||||
echo " - NO Python installation required!"
|
||||
+4
-2
@@ -23,8 +23,10 @@ try:
|
||||
import mcp.server.stdio
|
||||
import mcp.types as types
|
||||
from mcp.server import Server
|
||||
except ImportError:
|
||||
print("ERROR: mcp package not installed. Run: pip install mcp", file=sys.stderr)
|
||||
except ImportError as e:
|
||||
print(f"ERROR: Failed to import MCP package: {e}", file=sys.stderr)
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# Config/ROOT_DIR resolution (priority order):
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test script for MCP server"""
|
||||
import subprocess
|
||||
import json
|
||||
import sys
|
||||
|
||||
def test_mcp_server(command):
|
||||
"""Test if an MCP server responds correctly"""
|
||||
print(f"Testing: {' '.join(command)}")
|
||||
print("-" * 50)
|
||||
|
||||
proc = subprocess.Popen(
|
||||
command,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=1 # Line buffered
|
||||
)
|
||||
|
||||
# Send initialize request
|
||||
init_request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "initialize",
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {"name": "test", "version": "1.0"}
|
||||
}
|
||||
}
|
||||
|
||||
print(f"Sending initialize request...")
|
||||
proc.stdin.write(json.dumps(init_request) + "\n")
|
||||
proc.stdin.flush()
|
||||
|
||||
# Read response with timeout
|
||||
import select
|
||||
import time
|
||||
|
||||
start = time.time()
|
||||
response_lines = []
|
||||
|
||||
while time.time() - start < 5: # 5 second timeout
|
||||
ready, _, _ = select.select([proc.stdout], [], [], 0.5)
|
||||
if ready:
|
||||
line = proc.stdout.readline()
|
||||
if line:
|
||||
response_lines.append(line.strip())
|
||||
print(f"Received: {line.strip()}")
|
||||
break
|
||||
|
||||
if not response_lines:
|
||||
print("ERROR: No response received within 5 seconds")
|
||||
proc.terminate()
|
||||
proc.wait()
|
||||
return False
|
||||
|
||||
# Parse response
|
||||
try:
|
||||
response = json.loads(response_lines[0])
|
||||
if response.get("id") == 1 and "result" in response:
|
||||
print("SUCCESS: MCP server responded correctly")
|
||||
|
||||
# Try to get tools list
|
||||
tools_request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "tools/list",
|
||||
"params": {}
|
||||
}
|
||||
proc.stdin.write(json.dumps(tools_request) + "\n")
|
||||
proc.stdin.flush()
|
||||
|
||||
start = time.time()
|
||||
while time.time() - start < 5:
|
||||
ready, _, _ = select.select([proc.stdout], [], [], 0.5)
|
||||
if ready:
|
||||
line = proc.stdout.readline()
|
||||
if line:
|
||||
print(f"Tools response: {line.strip()[:200]}...")
|
||||
break
|
||||
|
||||
proc.terminate()
|
||||
proc.wait()
|
||||
return True
|
||||
else:
|
||||
print(f"ERROR: Unexpected response: {response}")
|
||||
proc.terminate()
|
||||
proc.wait()
|
||||
return False
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"ERROR: Invalid JSON response: {e}")
|
||||
proc.terminate()
|
||||
proc.wait()
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test Python source
|
||||
print("\n=== Testing Python Source ===")
|
||||
python_ok = test_mcp_server([sys.executable, "server/main.py"])
|
||||
|
||||
# Test executable
|
||||
print("\n=== Testing PyInstaller Executable ===")
|
||||
exe_ok = test_mcp_server(["./dist/mt5-quant"])
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print(f"Python source: {'OK' if python_ok else 'FAILED'}")
|
||||
print(f"PyInstaller exe: {'OK' if exe_ok else 'FAILED'}")
|
||||
Reference in New Issue
Block a user