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
- Fix the skill server so `_manifest` is registered as a text resource (application/json TextResourceContents).
- Inspect what content type the server returns for `skill://{name}/_manifest` and correct the resource registration/mime type.
- 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
- Register the `_manifest` resource as text with mimeType application/json.
- Ensure proxies/middleware don't convert text resources to blobs.
- Assert the content type in integration tests for custom skill servers.
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
- Could not read manifest for skill: {skill_name}
- Unsupported content type for Anthropic: {type(item).__name__
- Unsupported content type: {type(item)}
- File not found: {self.file_path}
- Invalid manifest JSON for skill: {skill_name}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/e24c5fa02658110c.
Report an issue: GitHub.