PrefectHQ/fastmcp · error · FileNotFoundError

File not found: {self.file_path}

Error message

File not found: {self.file_path}

What it means

SkillFileResource.read() raises FileNotFoundError when the resolved file inside a skill folder does not exist on disk. The path is first validated by safe_join (rejecting traversal/absolute injection), then checked with full_path.exists() before reading. This means the URI resolved to a real resource entry but the underlying file was deleted, renamed, or never created.

Source

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

    def get_meta(self) -> dict[str, Any]:
        meta = super().get_meta()
        fastmcp = cast(dict[str, Any], meta["fastmcp"])
        fastmcp["skill"] = {
            "name": self.skill_info.name,
        }
        return meta

    async def read(self) -> str | bytes | ResourceResult:
        """Read the file content."""
        # Security: reject traversal, absolute-path injection, null bytes, and
        # symlink escapes before touching the filesystem.
        try:
            full_path = safe_join(self.skill_info.path, self.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: {self.file_path}")

        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()


# -----------------------------------------------------------------------------
# SkillProvider - handles a SINGLE skill folder
# -----------------------------------------------------------------------------


class SkillProvider(Provider):
    """Provider that exposes a single skill folder as MCP resources.

    Each skill folder must contain a main file (default: SKILL.md) and may
    contain additional supporting files.

View on GitHub (pinned to 1f02114297)

Solutions

  1. List the current files in the skill folder (skill://{name}/_manifest resource) and use an existing file_path in the URI
  2. Re-add or restore the missing file in the skill directory on disk
  3. Refresh the server's resource listing (restart or re-enumerate) if the cache is stale after files changed
  4. Verify the skill_path configured on SkillProvider points at the directory you think it does

Example fix

// before: guessing a supporting file name
await client.read_resource('skill://my-skill/examples/demo.py')
// after: discover real file names from the manifest first
manifest = await client.read_resource('skill://my-skill/_manifest')
# pick a file_path actually present in the manifest, then read it
await client.read_resource('skill://my-skill/SKILL.md')
Defensive patterns

Strategy: try-catch

Validate before calling

import os
p = os.path.join(skill_dir, 'examples/demo.py')
assert not os.path.isabs('examples/demo.py') and os.path.exists(p), 'file missing from skill folder'

Type guard

from pathlib import Path
def file_exists_in_skill(skill_dir: Path, rel: str) -> bool:
    candidate = (skill_dir / rel).resolve()
    return candidate.is_file() and candidate.is_relative_to(skill_dir.resolve())

Try / catch

try:
    content = await client.read_resource('skill://my-skill/examples/demo.py')
except FileNotFoundError:
    manifest = await client.read_resource('skill://my-skill/_manifest')
    content = await pick_existing_file(manifest)

Prevention

When it happens

Trigger: Reading a skill:// resource whose file_path points to a file that no longer exists — e.g. the skill folder was modified after the provider enumerated it, the client requests a supporting file that isn't in the skill directory, or a stale ResourceTemplate match references a removed file.

Common situations: Skill directories regenerated or cleaned by a build/deploy step while a server holds cached resource listings; typo'd file names in client URIs; skills synced from git where files were moved; container images that stripped non-main files from the skill folder.

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/86b3e7cc48fb2583. Report an issue: GitHub.