PrefectHQ/fastmcp · error · ValueError

Unexpected manifest format for skill: {skill_name}

Error message

Unexpected manifest format for skill: {skill_name}

What it means

`get_skill_manifest` expects the manifest resource's first content item to be `TextResourceContents`; when the server returns a different content type (e.g. BlobResourceContents/binary), this ValueError is raised because the manifest cannot be interpreted as JSON text.

Source

Thrown at fastmcp_slim/fastmcp/utilities/skills.py:113

        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,
    target_dir: str | Path,
    *,

View on GitHub (pinned to 1f02114297)

Solutions

  1. Fix the skill server so `_manifest` is registered as a text resource (application/json TextResourceContents).
  2. Inspect what content type the server returns for `skill://{name}/_manifest` and correct the resource registration/mime type.
  3. If a proxy is involved, ensure it isn't rewriting resource contents into blobs.

Example fix

// before (server side)
yield BlobResourceContents(uri=uri, blob=base64.b64encode(data))
// after
yield TextResourceContents(uri=uri, mimeType="application/json", text=json.dumps(manifest))
Defensive patterns

Strategy: type-guard

Validate before calling

import mcp.types as mcp_types
async def manifest_content_type(client, skill_name: str) -> str | None:
    result = await client.read_resource(f"skill://{skill_name}/_manifest")
    if result and isinstance(result[0], mcp_types.TextResourceContents):
        return result[0].mimeType
    return None

Type guard

import mcp.types as mcp_types

def is_text_resource_content(item: object) -> bool:
    return isinstance(item, mcp_types.TextResourceContents)

Try / catch

try:
    manifest = await get_skill_manifest(client, skill_name)
except ValueError as e:
    if "Unexpected manifest format" in str(e):
        raise SkillServerMisconfigured(
            f"{skill_name}: server returns non-text manifest; register it as TextResourceContents"
        ) from e
    raise

Prevention

When it happens

Trigger: Calling `get_skill_manifest` against a skill server that serves the `_manifest` resource as a binary blob or otherwise non-text content rather than `TextResourceContents`.

Common situations: Custom skill-server implementations that base64-encode or otherwise blob-encode the manifest, middleware that converts text resources to blobs, or a server bug registering the manifest under a mime type that triggers blob handling.

Related errors


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