mcp-pystub
Detección automática de paquetes stubificables para compilaciones de ejecutables Python (PyInstaller/Nuitka) y generación de código stub mínimo para reducir el tamaño del ejecutable
Documentación
mcp-pystub
Servidor MCP que detecta automáticamente paquetes sustituibles por stubs en builds de ejecutables Python (PyInstaller / Nuitka / cx_Freeze) y genera código stub mínimo para reducir el tamaño del ejecutable.
Un servidor MCP que detecta automáticamente paquetes sustituibles por stubs para builds de ejecutables Python (PyInstaller / Nuitka / cx_Freeze) y genera código stub mínimo para reducir el tamaño del ejecutable.
背景 / Antecedentes
Al convertir aplicaciones Python en ejecutables, las bibliotecas pesadas importadas a nivel de módulo por las dependencias se incluyen por completo, inflando el tamaño del ejecutable. Reemplazar los paquetes no utilizados en la ruta de código real con stubs mínimos (dummies) puede reducir significativamente el tamaño.
Al compilar aplicaciones Python en ejecutables, los paquetes pesados importados a nivel de módulo por las dependencias se incluyen por completo, inflando el tamaño del ejecutable. Reemplazar los paquetes no utilizados con stubs mínimos puede reducir significativamente el tamaño.
実証データ / Resultados Verificados
Proyecto asammdf — Build de exe con PyInstaller (verificado con pruebas E2E):
| Tamaño del exe | Funcionamiento | PySide6 | |
|---|---|---|---|
| Sin stubs | 431 MB | OK | Cargado |
| Stub de asammdf.gui aplicado | 259 MB | OK | Eliminado |
| Reducción | -40% (172 MB) | Sin impacto |
Herramienta de conversión asammdf (resultado de analyze):
stubbable: pandas (59.7 MB), canmatrix (4.0 MB) — 合計 63.7 MB
submodule hints: asammdf.gui → PySide6 (523 MB) 排除可能 — 50 hints 検出
Los 3 paquetes que se stubban manualmente (pandas, canmatrix, asammdf.gui) fueron todos detectados automáticamente
機能 / Características
| Herramienta / Herramienta | Descripción / Descripción |
|---|---|
analyze | Analiza el grafo de imports y detecta automáticamente candidatos a stub. También genera sugerencias de eliminación indirecta de paquetes con extensión C / Detecta automáticamente paquetes sustituibles + sugerencias de stubs de submódulos para eliminar extensiones C |
graph | Visualiza el grafo de imports como nodos y aristas / Visualiza el grafo de imports como nodos y aristas |
check | Análisis detallado del uso de un paquete específico / Análisis profundo del uso de un paquete específico |
generate | Genera el código mínimo del stub del paquete + instrucciones de build / Genera código stub mínimo + instrucciones de build |
generate_submodule | Genera stubs de submódulos para eliminar indirectamente paquetes con extensión C / Genera stubs de submódulos para eliminar indirectamente paquetes con extensión C |
判定結果 / Veredictos
| Veredicto / Veredicto | Significado / Significado |
|---|---|
| stubbable | Se puede sustituir por stub. No se usa en la ruta de ejecución del proyecto / Seguro de sustituir. No se usa en la ruta de ejecución del proyecto |
| nofollow | Import protegido con try/except. Se recomienda excluir con --nofollow-import-to / Import protegido. Usa --nofollow-import-to |
| required | No se puede sustituir por stub. Se usa realmente / No se puede sustituir. Se usa realmente en tiempo de ejecución |
できること / Qué Puede Hacer
- Detección genérica sin fijar bibliotecas: Solo usa la estructura AST. Sin código hardcodeado
- Seguimiento de uso a nivel de función: Usa
mdf.get()pero nomdf.to_dataframe()→ pandas es stubbable - Distinción entre llamadas y referencias:
isinstance(x, pd.DataFrame)es seguro para stub,pd.DataFrame(data)es uso real - Detección de herencia de clases:
class User(BaseModel)→ pydantic es required - Detección de llamadas a nivel de módulo: Rastrea código que se ejecuta al importar
- Detección automática de protección try/except: Los imports protegidos se clasifican como
nofollow - Generación automática de código stub: Stub mínimo solo con símbolos referenciados + instrucciones de build (copia de seguridad, restauración, verificación)
- Detección automática de extensiones C: Los paquetes con .pyd / .so no se pueden sustituir directamente
- Eliminación indirecta de extensiones C (nuevo en v0.2): Si un submódulo que importa un paquete con extensión C no se usa, se puede sustituir ese submódulo por un stub para eliminar la extensión C. Verificado con PySide6 (523 MB) → stub de asammdf.gui reduce el exe en 40%
- Diseño seguro de restauración (nuevo en v0.2): Triple seguridad con copia de seguridad + pip con versión fija + comandos de verificación
- Información de hooks de PyInstaller: Notifica los archivos de hook que deben desactivarse
できないこと・制限事項 / Limitaciones
- Paquetes con extensión C (.pyd / .so): Sustituirlos directamente provocaría la falta de la extensión C, por lo que se clasifican como required. Sin embargo, con
generate_submodulese puede hacer eliminación indirecta (soportado en v0.2) - Imports dinámicos (
importlib.import_module(変数)): No se pueden rastrear con análisis estático (se notifica con warnings) - Hooks personalizados de PyInstaller: Pueden entrar en conflicto con los stubs y requieren desactivación manual
- Patrones de inicialización diferida: Paquetes que no se llaman directamente en
__init__sino en métodos posteriores reducen la precisión de detección (se clasifican como required por seguridad) - Discrepancia entre nombre de pip y nombre de import: Mapeos como
python-dateutil→dateutilno están soportados - Condicionales en tiempo de ejecución: Los imports dentro de
if sys.version < (3,11)no se pueden determinar con análisis estático
安全性の設計方針 / Política de Seguridad
Diseño que nunca permite que "se clasificó como stubbable pero en realidad era necesario". Si hay duda en la clasificación, se inclina hacia required (lado seguro). Se tolera el error inverso (clasificar como required algo que realmente es stubbable).
解析アルゴリズム / Algoritmo de Análisis
- Extracción de imports: Analiza las sentencias de import de cada archivo con
ast.parse()(distingue entre nivel de módulo, nivel de función y protección try-except) - Construcción del grafo de imports: Resuelve recursivamente las dependencias desde el punto de entrada con BFS (clasifica automáticamente stdlib / third_party / local)
- Análisis de uso: Identifica funciones gateway (funciones que llaman al paquete dentro de bibliotecas dependientes) y rastrea si el código del proyecto las llama. Las referencias de nombre solamente (isinstance, anotaciones de tipo) se excluyen como seguras para stub
- Detección a nivel de módulo: Si se llama a una función del paquete al importar, se eleva a required
- Eliminación indirecta de submódulos (v0.2): Identifica submódulos que importan paquetes con extensión C y, si el proyecto no los importa directamente ni llama a símbolos re-exportados, genera sugerencias de stub
インストール / Instalación
pip install mcp-pystub
依存パッケージ / Dependencias
mcp>=1.0.0- SDK de Model Context Protocol- El motor de análisis usa solo la biblioteca estándar de Python (ast, importlib, pathlib)
使い方 / Uso
MCP サーバーとして起動 / Ejecutar como servidor MCP
mcp-pystub
Claude Desktop / Configuración de Claude Code
{
"mcpServers": {
"pystub": {
"command": "mcp-pystub"
}
}
}
ツール使用例 / Ejemplos de Uso de Herramientas
analyze
入力 / Input:
entry_point: "C:/project/converter.py"
python_path: "C:/project/.venv/Lib/site-packages"
出力 / Output:
{
"stubbable": [
{"package_name": "pandas", "estimated_size_mb": 59.7, "reason": "依存ライブラリ経由でのみ import..."}
],
"required": [
{"package_name": "numpy", "reason": "プロジェクトコードが直接 import し使用"},
{"package_name": "PySide6", "estimated_size_mb": 523.2,
"reason": "C 拡張...ただし asammdf.gui をスタブ化することで間接排除が可能",
"submodule_stubs": [{"submodule": "asammdf.gui", "target_package": "PySide6"}]}
],
"nofollow": [
{"package_name": "mpmath"}
],
"submodule_stub_hints": [
{"submodule": "asammdf.gui", "parent_package": "asammdf",
"target_package": "PySide6", "imported_symbols": ["plot"]}
],
"analysis_time_ms": 6478
}
generate
入力 / Input:
entry_point: "C:/project/converter.py"
package_name: "pandas"
出力 / Output:
{
"files": {
"pandas/__init__.py": "...",
"pandas/core/api.py": "class DataFrame: pass\nclass Series: pass\n..."
},
"original_file_count": 2980,
"stub_file_count": 266,
"stub_total_bytes": 209530,
"build_instructions": {
"install_commands": ["pip uninstall -y pandas", "# cp stubs to site-packages"],
"uninstall_commands": ["pip install pandas"],
"hook_disable": ["# hook-pandas*.py → .disabled"]
}
}
generate_submodule (nuevo en v0.2)
入力 / Input:
entry_point: "C:/project/converter.py"
parent_package: "asammdf"
submodule: "asammdf.gui"
出力 / Output:
{
"parent_package": "asammdf",
"submodule": "asammdf.gui",
"files": {
"asammdf/gui/__init__.py": "\"\"\"Auto-generated stub...\"\"\"\ndef plot(*args, **kwargs): ..."
},
"eliminated_packages": ["PySide6", "scipy", "lxml"],
"original_size_bytes": 5907331,
"stub_size_bytes": 144,
"build_instructions": {
"backup_commands": ["cp -r .../asammdf/gui .../asammdf/gui.bak"],
"install_commands": ["rm -rf .../asammdf/gui", "cp -r _stubs/asammdf/gui/ .../asammdf/gui/"],
"uninstall_commands": ["mv .../asammdf/gui.bak .../asammdf/gui"],
"verify_commands": ["python -c \"import asammdf; print('OK')\""]
}
}
パフォーマンス / Rendimiento
| Tamaño del proyecto | Tiempo de análisis | Número de nodos |
|---|---|---|
| Ligero (click) | 335ms | 73 |
| Mediano (flask) | 1,743ms | 230 |
| Pesado (pandas) | 4,820ms | 491 |
| Muy pesado (sympy) | 6,919ms | 681 |
テスト / Pruebas
python -m pytest tests/ -v
テスト実績 / Resultados de Pruebas
| Tipo de prueba | Cantidad | Resultado |
|---|---|---|
| Pruebas unitarias (9 módulos) | 91 | Todas pasan |
| Pruebas a gran escala con bibliotecas PyPI (requests, flask, pandas, etc.) | 84 | 0 fallos |
| Build de exe con PyInstaller + pruebas de funcionamiento | 22 | Todas PASS |
| E2E de asammdf (crear MDF → leer → remuestrear) | 1 | Reducción del exe en 40% + funcionamiento correcto |
| Pruebas de restauración (copia de seguridad → restauración → verificación) | 4 | Todas exitosas |
ライセンス / Licencia
MIT