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

  1. Confirm the path points to a real `.py` file (`path.is_file()` and suffix == '.py') before discovery/import
  2. Exclude non-Python files from the discovery glob/pattern
  3. Re-check the file exists and is readable — it may have been deleted or moved after discovery
  4. 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

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


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/a348faece85f4b4e. Report an issue: GitHub.