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 exeFuncionamientoPySide6
Sin stubs431 MBOKCargado
Stub de asammdf.gui aplicado259 MBOKEliminado
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 / HerramientaDescripción / Descripción
analyzeAnaliza 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
graphVisualiza el grafo de imports como nodos y aristas / Visualiza el grafo de imports como nodos y aristas
checkAnálisis detallado del uso de un paquete específico / Análisis profundo del uso de un paquete específico
generateGenera el código mínimo del stub del paquete + instrucciones de build / Genera código stub mínimo + instrucciones de build
generate_submoduleGenera 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 / VeredictoSignificado / Significado
stubbableSe 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
nofollowImport protegido con try/except. Se recomienda excluir con --nofollow-import-to / Import protegido. Usa --nofollow-import-to
requiredNo 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 no mdf.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_submodule se 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 → dateutil no 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

  1. 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)
  2. Construcción del grafo de imports: Resuelve recursivamente las dependencias desde el punto de entrada con BFS (clasifica automáticamente stdlib / third_party / local)
  3. 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
  4. Detección a nivel de módulo: Si se llama a una función del paquete al importar, se eleva a required
  5. 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 proyectoTiempo de análisisNúmero de nodos
Ligero (click)335ms73
Mediano (flask)1,743ms230
Pesado (pandas)4,820ms491
Muy pesado (sympy)6,919ms681

テスト / Pruebas

python -m pytest tests/ -v

テスト実績 / Resultados de Pruebas

Tipo de pruebaCantidadResultado
Pruebas unitarias (9 módulos)91Todas pasan
Pruebas a gran escala con bibliotecas PyPI (requests, flask, pandas, etc.)840 fallos
Build de exe con PyInstaller + pruebas de funcionamiento22Todas PASS
E2E de asammdf (crear MDF → leer → remuestrear)1Reducción del exe en 40% + funcionamiento correcto
Pruebas de restauración (copia de seguridad → restauración → verificación)4Todas exitosas

ライセンス / Licencia

MIT