PrefectHQ/fastmcp · error · ValueError
Invalid manifest JSON for skill: {skill_name}
Error message
Invalid manifest JSON for skill: {skill_name} What it means
The manifest resource came back as text content, but `json.loads` failed to parse it, so `get_skill_manifest` re-raises as this ValueError with the JSONDecodeError chained. The server returned something that should be manifest JSON but isn't valid JSON.
Source
Thrown at fastmcp_slim/fastmcp/utilities/skills.py:111
Returns:
SkillManifest with file listing
Raises:
ValueError: If manifest cannot be read or parsed
"""
manifest_uri = f"skill://{skill_name}/_manifest"
result = await client.read_resource(manifest_uri)
if not result:
raise ValueError(f"Could not read manifest for skill: {skill_name}")
content = result[0]
if isinstance(content, mcp_types.TextResourceContents):
try:
manifest_data = json.loads(content.text)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid manifest JSON for skill: {skill_name}") from e
else:
raise ValueError(f"Unexpected manifest format for skill: {skill_name}")
try:
return SkillManifest(
name=manifest_data["skill"],
files=[
SkillFile(path=f["path"], size=f["size"], hash=f["hash"])
for f in manifest_data["files"]
],
)
except (KeyError, TypeError) as e:
raise ValueError(f"Invalid manifest format for skill: {skill_name}") from e
async def download_skill(
client: Client,
skill_name: str,View on GitHub (pinned to 1f02114297)
Solutions
- Fetch the raw `_manifest` resource yourself (e.g. `await client.read_resource(f"skill://{name}/_manifest")`) and inspect the text to see exactly what the server returned.
- Fix or update the skill server so the manifest resource emits valid JSON matching the SkillManifest shape.
- Check the server logs for errors occurring during manifest serialization.
Example fix
// before (server side)
return "name: my-skill" # YAML, not JSON
// after
return json.dumps({"skill": "my-skill", "files": []}) Defensive patterns
Strategy: try-catch
Validate before calling
import json
async def manifest_is_valid_json(client, skill_name: str) -> bool:
import mcp.types as mcp_types
result = await client.read_resource(f"skill://{skill_name}/_manifest")
if not result or not isinstance(result[0], mcp_types.TextResourceContents):
return False
try:
json.loads(result[0].text)
return True
except json.JSONDecodeError:
return False Type guard
import json
import mcp.types as mcp_types
def is_text_json_content(item: object) -> bool:
if not isinstance(item, mcp_types.TextResourceContents):
return False
try:
json.loads(item.text)
return True
except json.JSONDecodeError:
return False Try / catch
try:
manifest = await get_skill_manifest(client, skill_name)
except ValueError as e:
if "Invalid manifest JSON" in str(e):
raw = (await client.read_resource(f"skill://{skill_name}/_manifest"))[0].text
logger.error("Bad manifest payload from server: %.200s", raw)
raise Prevention
- Fix skill servers to emit valid JSON (never YAML or template output) for `_manifest`.
- Check server logs for serialization errors when the manifest is generated.
- Add a smoke test that reads the manifest and json.loads it in CI.
When it happens
Trigger: A skill server whose `_manifest` resource emits malformed JSON — truncated output, an error page/markup served as text, YAML instead of JSON, or debug/log text accidentally written into the manifest resource.
Common situations: Custom or third-party skill servers with broken manifest generation, server-side template errors rendering into the resource body, encoding issues corrupting the payload, or an outdated server whose manifest format changed.
Related errors
- Could not read manifest for skill: {skill_name}
- Invalid manifest format for skill: {skill_name}
- CLI state is invalid: {path.name}
- Elicitation responses must be serializable as a JSON object
- Invalid JSON in tool arguments for '{func.name}': {func.argu
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/2b6e0f14d264e910.
Report an issue: GitHub.