Files
mt5-quant/mt5-quant.spec
T
Devid HW 3cfd374160 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)
2026-04-18 13:00:28 +07:00

162 lines
3.6 KiB
RPMSpec

# -*- 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'
)