PrefectHQ/fastmcp · error · NotADirectoryError

Not a directory: {self.path}

Error message

Not a directory: {self.path}

What it means

DirectoryResource.list_files() raises NotADirectoryError when the configured `path` exists but is a file (or other non-directory), so a directory listing cannot be produced. This separates 'path missing' (210) from 'path exists but wrong kind of node'.

Source

Thrown at fastmcp_slim/fastmcp/resources/types.py:165

    @property
    def _async_path(self) -> AsyncPath:
        return AsyncPath(self.path)

    @pydantic.field_validator("path")
    @classmethod
    def validate_absolute_path(cls, path: Path) -> Path:
        """Ensure path is absolute."""
        if not path.is_absolute():
            raise ValueError("Path must be absolute")
        return path

    async def list_files(self) -> list[Path]:
        """List files in the directory."""
        if not await self._async_path.exists():
            raise FileNotFoundError(f"Directory not found: {self.path}")
        if not await self._async_path.is_dir():
            raise NotADirectoryError(f"Not a directory: {self.path}")

        pattern = self.pattern or "*"

        glob_fn = self._async_path.rglob if self.recursive else self._async_path.glob
        try:
            return [Path(p) async for p in glob_fn(pattern) if await p.is_file()]
        except Exception as e:
            raise ResourceError(f"Error listing directory {self.path}") from e

    @override
    async def read(self) -> ResourceResult:
        """Read the directory listing."""
        try:
            files: list[Path] = await self.list_files()

            file_list = [str(f.relative_to(self.path)) for f in files]

            content = json.dumps({"files": file_list}, indent=2)

View on GitHub (pinned to 1f02114297)

Solutions

  1. Point DirectoryResource.path at a directory instead of a file.
  2. If you meant to expose a single file, use FileResource (or the appropriate resource type) rather than DirectoryResource.
  3. Add a startup assertion: path.is_dir() before registering the resource.
  4. If a symlink is involved, confirm it resolves to a directory (Path.resolve().is_dir()).

Example fix

// before
DirectoryResource(path=Path('/data/config.json'), name='config')  # it's a file

// after
from fastmcp.resources import FileResource
FileResource(path=Path('/data/config.json'), name='config')
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path
path = Path('/data/config.json')
if path.exists() and not path.is_dir():
    raise NotADirectoryError(f'Expected a directory: {path}')

Try / catch

try:
    result = await resource.read()
except NotADirectoryError as e:
    logger.error('path is not a directory: %s', e)

Prevention

When it happens

Trigger: Construct DirectoryResource with `path` set to an existing regular file (or symlink to a file, socket, etc.); the is_dir() check at fastmcp_slim/fastmcp/resources/types.py:165 fails when read()/list_files() is called.

Common situations: Config points at a single file (e.g. /etc/passwd or a config.json) instead of its parent directory; path is a symlink whose target changed to a file; copy/paste error swapping a file path for the directory that contains it.

Related errors


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