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 exe | Funcionamento | PySide6 | |
|---|---|---|---|
| Sem stub | 431 MB | OK | Carregado |
| Com stub asammdf.gui | 259 MB | OK | Eliminado |
| 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 / Tool | Descrição / Description |
|---|---|
analyze | Analisa 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 |
graph | Visualiza o grafo de imports como nós e arestas / Visualize import graph as nodes and edges |
check | Análise detalhada do uso de um pacote específico / Deep analysis of a specific package's usage |
generate | Gera o código mínimo de stub do pacote + instruções de build / Generate minimal stub code + build instructions |
generate_submodule | Gera stubs de submódulos para eliminar indiretamente pacotes com extensão C / Generate submodule stubs to indirectly eliminate C-extension packages |
Vereditos / Verdicts
| Veredito / Verdict | Significado / Meaning |
|---|---|
| stubbable | Pode ser stubado. Não é usado no caminho de execução do projeto / Safe to stub. Not used in project's runtime path |
| nofollow | Import protegido por try/except. Recomenda-se excluir com --nofollow-import-to / Protected import. Use --nofollow-import-to |
| required | Nã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 usamdf.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→dateutilnã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
- 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) - 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)
- 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
- Detecção em nível de módulo: Se funções do pacote são chamadas no momento do import, promove para required
- 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 projeto | Tempo de análise | Número de nós |
|---|---|---|
| Leve (click) | 335ms | 73 |
| Médio (flask) | 1.743ms | 230 |
| Pesado (pandas) | 4.820ms | 491 |
| Muito pesado (sympy) | 6.919ms | 681 |
Testes / Testing
python -m pytest tests/ -v
Resultados de Testes
| Tipo de teste | Quantidade | Resultado |
|---|---|---|
| Testes unitários (9 módulos) | 91 | Todos aprovados |
| Testes em larga escala com bibliotecas PyPI (requests, flask, pandas etc.) | 84 | 0 crashes |
| Build de exe PyInstaller + teste de funcionamento | 22 | Todos PASS |
| E2E asammdf (criação de MDF → leitura → reamostragem) | 1 | Redução de 40% no exe + funcionamento normal |
| Teste de restauração (backup → restauração → verificação) | 4 | Todos bem-sucedidos |
Licença / License
MIT