This project demonstrates and fixes a module import error when using fastmcp (which depends on py-key-value-aio that uses beartype) with PyInstaller.
When packaging a FastMCP server with PyInstaller, the bundled executable fails at runtime with ModuleNotFoundError for modules imported after beartype is initialized.
Root Cause: When py-key-value-aio imports, it calls beartype_this_package() which installs beartype's import hook via add_beartype_pathhook(). This hook:
- Prepends a custom
FileFindertosys.path_hooks - Clears
sys.path_importer_cache - Uses
SourceFileLoaderwhich expects.pysource files
In PyInstaller's frozen environment, there are no source files - only compiled bytecode in a PYZ archive. When beartype's hook takes precedence over PyInstaller's PyiFrozenFinder, all subsequent imports fail.
A PyInstaller runtime hook patches beartype's add_beartype_pathhook to skip installation in frozen environments. See hooks/pyi_rth_beartype.py.
The key insight: we must patch two locations because clawpkgmain.py does from ... import add_beartype_pathhook, creating a local binding that survives module-level patches.
beartype_pyinstaller_repro/
├── src/
│ └── beartype_pyinstaller_repro/
│ ├── __init__.py
│ └── server.py # FastMCP server with Fibonacci tool
├── tests/
│ └── test_integration.py # Integration tests
├── server.py # Entry point for PyInstaller
├── server.spec # PyInstaller configuration
├── pyproject.toml # Project configuration
└── Makefile # Build commands
- Python 3.12+
- uv package manager
# Install dependencies
uv syncBuild the PyInstaller executable:
make buildOr manually:
uv run pyinstaller server.specThe executable will be created at dist/server (or dist/server.exe on Windows).
This test verifies that the server works correctly when run directly:
uv run pytest tests/test_integration.py::test_direct_import -vThis should pass, demonstrating that the code works fine outside of PyInstaller.
This test runs the bundled executable and checks for import errors:
# First build the executable
make build
# Then run the integration test
uv run pytest tests/test_integration.py::test_pyinstaller_executable_imports -vThis test is expected to fail (or detect the import error), demonstrating the bug.
uv run pytest tests/ -v-
Install dependencies:
uv sync
-
Build the executable:
make build
-
Run the executable directly:
./dist/server
You should see a
ModuleNotFoundErrororImportError. The error may mentiondiskcacheor other modules, but the root cause is beartype initialization breaking subsequent imports in the PyInstaller bundle. -
Compare with direct execution:
uv run python server.py
This should work fine, demonstrating that the issue is specific to the PyInstaller bundle.
GitHub Actions CI is configured to:
- Run
rufffor code quality checks - Run pytest tests
- Build the PyInstaller executable
- Run integration tests
See .github/workflows/ci.yml for details.
fastmcp- FastMCP framework for building MCP serverspyinstaller- Python application bundlerpytest- Testing frameworkruff- Fast Python linter
The issue stems from fastmcp's dependency on py-key-value-aio, which initializes beartype when imported. In PyInstaller bundles, after beartype is initialized, the Python import system breaks and all subsequent imports fail with ModuleNotFoundError, even for modules that are actually included in the bundle.
- ✅ Direct execution: Works correctly
- ✅ Direct import test: Passes
- ✅ PyInstaller bundle: Works with runtime hook fix
To use this fix in your own PyInstaller project:
-
Copy the runtime hook (
hooks/pyi_rth_beartype.py) to your project -
Add it to your spec file:
runtime_hooks=['path/to/pyi_rth_beartype.py'],
-
Or via command line:
pyinstaller --runtime-hook=path/to/pyi_rth_beartype.py your_app.py
See docs/INVESTIGATION.md for a detailed analysis of the root cause and various fix approaches considered.