mcp-pystub

Detecta automaticamente pacotes stubáveis para builds de executáveis Python (PyInstaller/Nuitka) e gera código stub mínimo para reduzir o tamanho do executável

Documentação

mcp-pystub

Servidor MCP que detecta automaticamente pacotes substituíveis por stubs em builds de executáveis Python (PyInstaller / Nuitka / cx_Freeze) e gera código de stub mínimo para reduzir o tamanho do executável.


Um servidor MCP que detecta automaticamente pacotes stubáveis para builds de executáveis Python (PyInstaller / Nuitka / cx_Freeze) e gera código de stub mínimo para reduzir o tamanho do executável.

Contexto / Background

Ao transformar aplicativos Python em executáveis, bibliotecas pesadas importadas em nível de módulo por dependências são empacotadas por completo, inflando o tamanho do executável. Substituir pacotes não utilizados no caminho real do código por stubs mínimos (dummies) pode reduzir significativamente o tamanho.

Ao compilar aplicativos Python em executáveis, pacotes pesados importados em nível de módulo por dependências são empacotados integralmente, inflando o tamanho do executável. Substituir pacotes não utilizados por stubs mínimos pode reduzir significativamente o tamanho.

Resultados Verificados / Verified Results

Projeto asammdf — Build de exe com PyInstaller (teste E2E concluído):

Tamanho do exeFuncionamentoPySide6
Sem stub431 MBOKCarregado
Com stub asammdf.gui259 MBOKEliminado
Redução-40% (172 MB)Sem impacto

Ferramenta de conversão asammdf (resultado da análise):

stubbable:          pandas (59.7 MB), canmatrix (4.0 MB) — 合計 63.7 MB
submodule hints:    asammdf.gui → PySide6 (523 MB) 排除可能 — 50 hints 検出

Os 3 pacotes que eram stubados manualmente (pandas, canmatrix, asammdf.gui) foram todos detectados automaticamente

Funcionalidades / Features

Ferramenta / ToolDescrição / Description
analyzeAnalisa o grafo de imports e detecta automaticamente candidatos a stub. Também emite dicas de eliminação indireta de pacotes com extensão C / Auto-detect stubbable packages + submodule stub hints for C-extension elimination
graphVisualiza o grafo de imports como nós e arestas / Visualize import graph as nodes and edges
checkAnálise detalhada do uso de um pacote específico / Deep analysis of a specific package's usage
generateGera o código mínimo de stub do pacote + instruções de build / Generate minimal stub code + build instructions
generate_submoduleGera stubs de submódulos para eliminar indiretamente pacotes com extensão C / Generate submodule stubs to indirectly eliminate C-extension packages

Vereditos / Verdicts

Veredito / VerdictSignificado / Meaning
stubbablePode ser stubado. Não é usado no caminho de execução do projeto / Safe to stub. Not used in project's runtime path
nofollowImport protegido por try/except. Recomenda-se excluir com --nofollow-import-to / Protected import. Use --nofollow-import-to
requiredNão pode ser stubado. É realmente usado em tempo de execução / Cannot stub. Actually used at runtime

O que pode fazer / What It Can Do

  • Detecção genérica sem lista fixa de bibliotecas: Julgamento baseado apenas na estrutura AST. Sem hardcode
  • Rastreamento de uso em nível de função: Usa mdf.get() mas não usa mdf.to_dataframe() → pandas é stubbable
  • Distinção entre chamada e referência: isinstance(x, pd.DataFrame) é stub-safe, pd.DataFrame(data) é uso real
  • Detecção de herança de classes: class User(BaseModel) → pydantic é required
  • Detecção de chamadas em nível de módulo: Rastreia código executado no momento do import
  • Detecção automática de proteção try/except: Imports protegidos são classificados como nofollow
  • Geração automática de código de stub: Stub mínimo apenas com símbolos referenciados + instruções de build (backup, restauração, verificação)
  • Detecção automática de extensões C: Pacotes contendo .pyd / .so não podem ser stubados diretamente
  • Eliminação indireta de extensões C (novo na v0.2): Se um submódulo que importa um pacote com extensão C não for usado, é possível stubar esse submódulo para eliminar a extensão C. Comprovado: PySide6 (523 MB) → stub asammdf.gui reduziu 40% do exe
  • Design seguro para restauração (novo na v0.2): Tripla camada de segurança com backup + pip com versão fixada + comandos de verificação
  • Informações de hooks do PyInstaller: Notifica arquivos de hook que precisam ser desativados

Limitações / Limitations

  • Pacotes com extensão C (.pyd / .so): stubar diretamente causaria falta da extensão C, portanto são classificados como required. Porém, com generate_submodule é possível a eliminação indireta (suportado na v0.2)
  • Imports dinâmicos (importlib.import_module(変数)): não podem ser rastreados por análise estática (notificados via warnings)
  • Hooks personalizados do PyInstaller: podem conflitar com stubs, exigindo desativação manual do hook
  • Padrões de inicialização tardia: pacotes não chamados diretamente em __init__ mas usados em métodos posteriores têm precisão de detecção reduzida (classificados como required por segurança)
  • Incompatibilidade entre nome pip e nome de import: mapeamentos como python-dateutil → dateutil não são suportados
  • Ramificações condicionais em tempo de execução: imports dentro de if sys.version < (3,11) não podem ser avaliados por análise estática

Política de Segurança / Safety Policy

Design que nunca permite que "classificado como stubbable mas na verdade era necessário" aconteça. Em caso de dúvida, classifica como required (lado seguro). O erro inverso (era stubbable mas classificado como required) é aceitável.

Algoritmo de Análise / Analysis Algorithm

  1. Extração de Imports: Analisa declarações de import de cada arquivo com ast.parse() (distinguindo nível de módulo / nível de função / proteção try-except)
  2. Construção do grafo de imports: Resolve dependências recursivamente a partir do ponto de entrada usando BFS (classificação automática em stdlib / third_party / local)
  3. Análise de uso: Identifica funções gateway (funções dentro de bibliotecas dependentes que chamam o pacote) e rastreia se o código do projeto as chama. Referências apenas por nome (isinstance, anotações de tipo) são excluídas como stub-safe
  4. Detecção em nível de módulo: Se funções do pacote são chamadas no momento do import, promove para required
  5. Eliminação indireta de submódulos (v0.2): Identifica submódulos que importam pacotes com extensão C e, se o projeto não os importa diretamente nem chama símbolos re-exportados, emite dica de stub

Instalação / Installation

pip install mcp-pystub

Dependências / Dependencies

  • mcp>=1.0.0 - SDK do Model Context Protocol
  • O motor de análise usa apenas a biblioteca padrão do Python (ast, importlib, pathlib)

Uso / Usage

Iniciar como servidor MCP / Run as MCP server

mcp-pystub

Configuração do Claude Desktop / Claude Code

{
  "mcpServers": {
    "pystub": {
      "command": "mcp-pystub"
    }
  }
}

Exemplos de uso das ferramentas / Tool Examples

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 (novo na 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')\""]
    }
  }

Desempenho / Performance

Tamanho do projetoTempo de análiseNúmero de nós
Leve (click)335ms73
Médio (flask)1.743ms230
Pesado (pandas)4.820ms491
Muito pesado (sympy)6.919ms681

Testes / Testing

python -m pytest tests/ -v

Resultados de Testes

Tipo de testeQuantidadeResultado
Testes unitários (9 módulos)91Todos aprovados
Testes em larga escala com bibliotecas PyPI (requests, flask, pandas etc.)840 crashes
Build de exe PyInstaller + teste de funcionamento22Todos PASS
E2E asammdf (criação de MDF → leitura → reamostragem)1Redução de 40% no exe + funcionamento normal
Teste de restauração (backup → restauração → verificação)4Todos bem-sucedidos

Licença / License

MIT