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

  1. 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.
  2. Fix or update the skill server so the manifest resource emits valid JSON matching the SkillManifest shape.
  3. 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

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


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