PrefectHQ/fastmcp · error · FileNotFoundError

File not found: {file_path}

Error message

File not found: {file_path}

What it means

SkillProvider.read() resolves the requested file inside the skill directory and raises FileNotFoundError if the resolved path does not exist on disk. The path has already passed the safe_join security check, so this is purely a missing-file condition reported with the caller-supplied file_path.

Source

Thrown at fastmcp_slim/fastmcp/server/providers/skills/skill_provider.py:89

class SkillFileTemplate(ResourceTemplate):
    """A template for accessing files within a skill."""

    skill_info: SkillInfo

    async def read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult:
        """Read a file from the skill directory."""
        file_path = arguments.get("path", "")

        # Security: reject traversal, absolute-path injection, null bytes, and
        # symlink escapes before touching the filesystem.
        try:
            full_path = safe_join(self.skill_info.path, file_path)
        except PathEscapeError as e:
            raise ValueError(f"Invalid path: {e}") from e

        if not full_path.exists():
            raise FileNotFoundError(f"File not found: {file_path}")

        if not full_path.is_file():
            raise ValueError(f"Not a file: {file_path}")

        # Determine if binary or text based on mime type
        mime_type, _ = mimetypes.guess_type(str(full_path))
        if mime_type and mime_type.startswith("text/"):
            return full_path.read_text(encoding="utf-8")
        else:
            return full_path.read_bytes()

    async def _read(
        self,
        uri: str,
        params: dict[str, Any],
    ) -> ResourceResult:
        """Server entry point - read file directly without creating ephemeral resource."""
        # Call read() directly and convert to ResourceResult

View on GitHub (pinned to 1f02114297)

Solutions

  1. List the skill's files (e.g. via its resources) and read an existing filename with correct casing
  2. Fix the path in the caller/URI to match the actual file on disk
  3. Ship or restore the missing file into the skill directory

Example fix

// before
await provider.read('skill://my-skill/SKIL.MD')  # wrong case
// after
await provider.read('skill://my-skill/SKILL.md')
Defensive patterns

Strategy: fallback

Validate before calling

from pathlib import Path
target = Path(skill_dir) / file_path
if not target.is_file():
    file_path = suggest_closest_file(skill_dir, file_path)  # fuzzy-match or list files

Try / catch

try:
    content = await provider.read(uri)
except FileNotFoundError as e:
    logger.warning('Missing skill file: %s', e)
    content = await provider.read(default_uri_for(skill))

Prevention

When it happens

Trigger: read() (via _read) with a file_path that survives path validation but doesn't exist under the skill directory — wrong filename, wrong case, deleted/renamed file, or file not shipped with the skill.

Common situations: Typo'd filenames, case-sensitive filesystems (Linux) vs case-insensitive expectations, skill folders missing optional assets, stale URIs after skill reorganization.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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