PrefectHQ/fastmcp · error · ImportError
Cannot load spec for {file_path}
Error message
Cannot load spec for {file_path} What it means
`importlib.util.spec_from_file_location` returned None or a spec without a loader, so Python could not determine how to load the file. FastMCP raises this immediately rather than failing later with a confusing AttributeError.
Source
Thrown at fastmcp_slim/fastmcp/server/providers/filesystem_discovery.py:250
existing = sys.modules.get(stem)
if existing is not None and getattr(existing, "__file__", None) != str(
file_path
):
module_name = f"_fastmcp_{stem}_{hashlib.sha1(str(file_path).encode()).hexdigest()[:12]}"
else:
module_name = stem
# Temporarily add parent to sys.path so module-level sibling imports resolve.
# Safe to remove after exec_module: all top-level imports are resolved by then,
# and sibling files imported as side effects are already in sys.modules.
path_added = parent_dir not in sys.path
if path_added:
sys.path.insert(0, parent_dir)
try:
spec = importlib.util.spec_from_file_location(module_name, file_path)
if spec is None or spec.loader is None:
raise ImportError(f"Cannot load spec for {file_path}")
existing = sys.modules.get(module_name)
if existing is not None:
# Re-exec in place rather than importlib.reload: reload() re-finds
# the module by name via sys.path, which fails for private keys
# (the file is tool.py, not _fastmcp_tool_xxx.py).
existing.__spec__ = spec
existing.__loader__ = spec.loader
existing.__file__ = str(file_path)
try:
spec.loader.exec_module(existing)
except Exception as e:
raise ImportError(
f"Failed to reload module {file_path}: {e}"
) from e
return existing
module = importlib.util.module_from_spec(spec)View on GitHub (pinned to 1f02114297)
Solutions
- Confirm the path points to a real `.py` file (`path.is_file()` and suffix == '.py') before discovery/import
- Exclude non-Python files from the discovery glob/pattern
- Re-check the file exists and is readable — it may have been deleted or moved after discovery
- If importing extensionless files, register a loader or rename with a `.py` extension
Example fix
// before
import_module_from_file(Path('plugins/tool')) # no extension
// after
import_module_from_file(Path('plugins/tool.py')) Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
assert (p := Path(fp)).is_file() and p.suffix == '.py', f'not a loadable .py file: {p}' Type guard
def loadable_module_file(p) -> bool:
from pathlib import Path
p = Path(p)
return p.is_file() and p.suffix == '.py' Try / catch
try:
mod = provider.import_module_from_file(path)
except ImportError as e:
if 'Cannot load spec' in str(e): skip_file(path) Prevention
- Filter discovery globs to '*.py' files only
- Confirm files exist before import (discovery and import can be separated in time)
- Never point the loader at directories or bytecode without source
When it happens
Trigger: Passing a path whose extension has no registered loader (e.g. `.txt`, extension-less file, `.pyc` without source context), a directory instead of a file, or a corrupted/unreadable file.
Common situations: Discovery glob picking up non-Python files; a file deleted or truncated between discovery and import; pointing the loader at a directory instead of a `.py` file.
Related errors
- module {__name__!r} has no attribute {name!r}
- Failed to import {module_name} from {file_path}: {e}
- Failed to execute module {file_path}: {e}
- module {__name__!r} has no attribute {name!r}
- Cannot resolve tool reference: {fn!r}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/a348faece85f4b4e.
Report an issue: GitHub.