{"record":{"id":"c608a9b0a3aaeed5","repo":"PrefectHQ/fastmcp","slug":"structured-content-must-be-a-dict-or-none-got-ty","errorCode":null,"errorMessage":"structured_content must be a dict or None. Got {type(structured_content).__name__}: {structured_content!r}. Tools should wrap non-dict values based on their output_schema.","messagePattern":"structured_content must be a dict or None\\. Got (.+?): (.+?)\\. Tools should wrap non-dict values based on their output_schema\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/tools/base.py","lineNumber":146,"sourceCode":"        if structured_content is not None:\n            # Convert Prefab types to their wire-format envelope before\n            # generic serialization, so the renderer gets the right shape.\n            if is_prefab_app(structured_content):\n                structured_content = _prefab_to_json(structured_content)\n            elif is_prefab_component(structured_content):\n                structured_content = _prefab_to_json(\n                    prefab_app_from_component(structured_content)\n                )\n\n            try:\n                structured_content = _serialize_to_jsonable(structured_content)\n            except pydantic_core.PydanticSerializationError as e:\n                logger.error(\n                    f\"Could not serialize structured content. If this is unexpected, set your tool's output_schema to None to disable automatic serialization: {e}\"\n                )\n                raise\n            if not isinstance(structured_content, dict):\n                raise ValueError(\n                    \"structured_content must be a dict or None. \"\n                    f\"Got {type(structured_content).__name__}: {structured_content!r}. \"\n                    \"Tools should wrap non-dict values based on their output_schema.\"\n                )\n\n        super().__init__(\n            content=converted_content,\n            structured_content=structured_content,\n            meta=meta,\n            is_error=is_error,\n        )\n\n    @classmethod\n    def from_mcp_result(cls, result: CallToolResult) -> ToolResult:\n        \"\"\"Wrap a protocol result while preserving its exact wire representation.\"\"\"\n        tool_result = cls(\n            content=result.content,\n            structured_content=result.structured_content,","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/tools/base.py#L128-L164","documentation":"ToolResult.structured_content must be a dict (matching MCP's object-shaped structured results) or None. Non-dict values (list, str, int, etc.) cannot be sent as structured content directly; tools must wrap such values per their output_schema (which produces a {\"result\": ...} dict). The constructor raises ValueError naming the offending type and value.","triggerScenarios":"Constructing `ToolResult(structured_content=[1,2,3])` or with a scalar/string; returning a bare non-dict from code that bypasses the output_schema wrapping path (e.g. manually building results instead of relying on FunctionTool serialization).","commonSituations":"Manually constructing ToolResult from raw tool return values without applying output_schema wrapping; tools returning lists or primitives while authors assume auto-wrapping happens in ToolResult itself; migration from older code paths where wrapping was done elsewhere.","solutions":["Wrap non-dict values yourself, e.g. `structured_content={\"result\": value}`.","Declare an `output_schema` on the tool so automatic serialization produces a valid dict.","Pass the raw value as `content` instead of `structured_content` if structured output isn't needed.","If serialization is intentionally disabled, set the tool's output_schema to None and rely on content only."],"exampleFix":"# before\nreturn ToolResult(structured_content=[1, 2, 3])\n# after\nreturn ToolResult(content=[1, 2, 3], structured_content={\"result\": [1, 2, 3]})","handlingStrategy":"type-guard","validationCode":"def wrap_structured(value):\n    if value is None:\n        return None\n    if not isinstance(value, dict):\n        return {\"result\": value}\n    return value\n\nresult = ToolResult(content=value, structured_content=wrap_structured(value))","typeGuard":"def is_valid_structured(v) -> bool:\n    return v is None or isinstance(v, dict)","tryCatchPattern":"try:\n    return ToolResult(structured_content=value)\nexcept ValueError as e:\n    if \"must be a dict\" in str(e):\n        return ToolResult(content=value, structured_content={\"result\": value})\n    raise","preventionTips":["Declare an output_schema on tools so non-dict returns are auto-wrapped.","Never hand-build structured_content from raw tool values without wrapping.","Add a test asserting every structured result your server emits is a dict."],"tags":["python","valueerror","tool-result","structured-content","schema-validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}