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 ResourceResultView on GitHub (pinned to 1f02114297)
Solutions
- List the skill's files (e.g. via its resources) and read an existing filename with correct casing
- Fix the path in the caller/URI to match the actual file on disk
- 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
- List the skill's files before reading rather than hard-coding names
- Match filename casing exactly; assume case-sensitive filesystems
- Include all referenced assets when packaging skills; add existence checks to skill CI
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
- Not a file: {file_path}
- File not found: {self.file_path}
- File {name!r} not found. Available: {available}
- Could not restrict access to CLI state
- Could not create the CLI state directory
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/9c39c403bc709dc1.
Report an issue: GitHub.