{"record":{"id":"c0e61264560d0aad","repo":"PrefectHQ/fastmcp","slug":"invalid-manifest-format-for-skill-skill-name","errorCode":null,"errorMessage":"Invalid manifest format for skill: {skill_name}","messagePattern":"Invalid manifest format for skill: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/utilities/skills.py","lineNumber":124,"sourceCode":"    content = result[0]\n    if isinstance(content, mcp_types.TextResourceContents):\n        try:\n            manifest_data = json.loads(content.text)\n        except json.JSONDecodeError as e:\n            raise ValueError(f\"Invalid manifest JSON for skill: {skill_name}\") from e\n    else:\n        raise ValueError(f\"Unexpected manifest format for skill: {skill_name}\")\n\n    try:\n        return SkillManifest(\n            name=manifest_data[\"skill\"],\n            files=[\n                SkillFile(path=f[\"path\"], size=f[\"size\"], hash=f[\"hash\"])\n                for f in manifest_data[\"files\"]\n            ],\n        )\n    except (KeyError, TypeError) as e:\n        raise ValueError(f\"Invalid manifest format for skill: {skill_name}\") from e\n\n\nasync def download_skill(\n    client: Client,\n    skill_name: str,\n    target_dir: str | Path,\n    *,\n    overwrite: bool = False,\n) -> Path:\n    \"\"\"Download a skill and all its files to a local directory.\n\n    Creates a subdirectory named after the skill containing all files.\n\n    Args:\n        client: Connected FastMCP client\n        skill_name: Name of the skill to download\n        target_dir: Directory where skill folder will be created\n        overwrite: If True, overwrite existing skill directory. If False","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/utilities/skills.py#L106-L142","documentation":"The manifest parsed as JSON but its structure didn't match `SkillManifest` — required keys (`skill`, `files` with `path`/`size`/`hash`) were missing or of the wrong type, so the KeyError/TypeError from dict/field access is re-raised as this ValueError. The JSON is valid but the manifest shape is wrong.","triggerScenarios":"A skill server emits manifest JSON that lacks the `skill` or `files` keys, whose `files` entries are missing `path`, `size`, or `hash`, or where `files` is not a list of objects (e.g. a dict or list of strings).","commonSituations":"Version drift between the skill server's manifest format and what FastMCP expects, hand-written or third-party manifests that don't follow the schema, partial manifests produced when file metadata generation failed.","solutions":["Dump the raw manifest JSON and compare against the expected shape: `{\"skill\": <name>, \"files\": [{\"path\", \"size\", \"hash\"}, ...]}`.","Update or fix the skill server to emit all required fields with correct types.","Align server and client FastMCP versions if the manifest schema changed between releases."],"exampleFix":"// before\n{\"skill\": \"my-skill\", \"files\": [\"a.py\", \"b.py\"]}\n// after\n{\"skill\": \"my-skill\", \"files\": [{\"path\": \"a.py\", \"size\": 100, \"hash\": \"sha256:...\"}]}","handlingStrategy":"validation","validationCode":"def manifest_shape_ok(manifest: dict) -> bool:\n    if not isinstance(manifest, dict) or \"skill\" not in manifest or \"files\" not in manifest:\n        return False\n    files = manifest[\"files\"]\n    if not isinstance(files, list):\n        return False\n    return all(\n        isinstance(f, dict)\n        and isinstance(f.get(\"path\"), str)\n        and isinstance(f.get(\"size\"), int)\n        and isinstance(f.get(\"hash\"), str)\n        for f in files\n    )","typeGuard":"import json\nimport mcp.types as mcp_types\n\ndef parse_valid_manifest(text: str) -> dict | None:\n    try:\n        data = json.loads(text)\n    except json.JSONDecodeError:\n        return None\n    if manifest_shape_ok(data):\n        return data\n    return None","tryCatchPattern":"try:\n    manifest = await get_skill_manifest(client, skill_name)\nexcept ValueError as e:\n    if \"Invalid manifest format\" in str(e):\n        raw = json.loads((await client.read_resource(f\"skill://{skill_name}/_manifest\"))[0].text)\n        logger.error(\"Manifest shape mismatch for %s: keys=%s\", skill_name, list(raw))\n    raise","preventionTips":["Keep skill-server manifest output aligned with FastMCP's SkillManifest schema (skill, files[path/size/hash]).","Pin compatible FastMCP versions on server and client if the manifest format changes.","Validate manifests with a JSON schema in the server's own test suite."],"tags":["skills","manifest","schema-validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}