OpenBB-finance/OpenBB · error · ValueError
The 'files' dict must include a 'SKILL.md' entry as the main
Error message
The 'files' dict must include a 'SKILL.md' entry as the main skill file.
What it means
Raised by the MCP server's install_skill tool when the provided files dict lacks a 'SKILL.md' key. A skill is defined by its SKILL.md manifest; supporting files alone are meaningless, so the server refuses the install before touching the filesystem. It is a strict contract check on the tool arguments.
Source
Thrown at openbb_platform/extensions/mcp_server/openbb_mcp_server/app/app.py:783
Field(
description=(
"Target skills provider to install into. "
"Use 'bundled' for the server's built-in skills directory, "
"or a vendor name: "
+ ", ".join(f"'{k}'" for k in _VENDOR_SKILLS_PROVIDERS)
+ "."
),
),
] = "bundled",
) -> dict:
"""Install a skill (SKILL.md + supporting files) into a SkillsDirectoryProvider.
Creates the skill directory if needed, writes all files,
and registers the new skill with the target provider so it becomes
immediately available via list_resources / read_resource.
"""
if "SKILL.md" not in files:
raise ValueError(
"The 'files' dict must include a 'SKILL.md' entry as the main skill file."
)
# Find the target SkillsDirectoryProvider
target_key = target.lower().strip()
target_provider: SkillsDirectoryProvider | None = None
for provider in mcp.providers:
if not isinstance(provider, SkillsDirectoryProvider):
continue
if target_key == "bundled":
if settings.default_skills_dir:
bundled_root = Path(settings.default_skills_dir).resolve()
if bundled_root in provider._roots: # noqa: SLF001
target_provider = provider
break
else:View on GitHub (pinned to 3e071fcc2c)
Solutions
- Add a 'SKILL.md' entry (exact key) to the files dict with the skill's frontmatter and instructions.
- Verify the key's casing is exactly 'SKILL.md' before calling the tool.
- When uploading from a directory, fail fast locally if SKILL.md is absent rather than sending partial files.
Example fix
# before
await install_skill(skill_name="my-skill", target="bundled", files={"helper.py": "..."})
# after
await install_skill(
skill_name="my-skill",
target="bundled",
files={"SKILL.md": "---\nname: my-skill\ndescription: ...\n---\nDo X", "helper.py": "..."},
) Defensive patterns
Strategy: validation
Validate before calling
def validate_skill_files(files: dict[str, str]) -> None:
if "SKILL.md" not in files:
raise ValueError("files must contain exactly 'SKILL.md' as the manifest key")
if not files["SKILL.md"].strip():
raise ValueError("SKILL.md content is empty") Type guard
def is_installable_skill(files: dict) -> bool:
return isinstance(files, dict) and "SKILL.md" in files and bool(str(files["SKILL.md"]).strip()) Try / catch
try:
await install_skill(skill_name=name, target=target, files=files)
except ValueError as e:
if "SKILL.md" in str(e):
files = {**files, "SKILL.md": default_manifest(name)}
await install_skill(skill_name=name, target=target, files=files)
else:
raise Prevention
- Always build the files dict starting from SKILL.md and append supporting files.
- Match the key case exactly — 'SKILL.md', not 'skill.md'.
- When zipping a directory for upload, fail fast if SKILL.md is missing.
When it happens
Trigger: Calling install_skill(files={'README.md': '...'}, ...) with no SKILL.md entry; keying the manifest as 'skill.md' (lowercase) or 'SKILL.MD'; passing an empty or malformed files dict.
Common situations: Automated skill-upload scripts that iterate a directory but skip or rename SKILL.md; case-mismatched filenames on case-sensitive checks; clients copying example payloads that omit the manifest.
Related errors
- Category '{category}' not found. Available categories: {', '
- Subcategory '{subcategory}' not found in category '{category
- Target provider '{target}' not found or not loaded. Availabl
- Target provider '{target}' has no configured root directorie
- At least one extension type must be selected.
AI-assisted analysis of OpenBB-finance/OpenBB@3e071fcc2c (2026-08-14).
Data as JSON: /api/errors/a5a56053667fbf92.
Report an issue: GitHub.