{"record":{"id":"f7ea52fe318210b4","repo":"PrefectHQ/fastmcp","slug":"output-schemas-must-represent-object-types-due-to","errorCode":null,"errorMessage":"Output schemas must represent object types due to MCP spec limitations. Received: {final_output_schema!r}","messagePattern":"Output schemas must represent object types due to MCP spec limitations\\. Received: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/tools/function_tool.py","lineNumber":341,"sourceCode":"        # Normalize task to TaskConfig\n        task_value = metadata.task\n        if task_value is None:\n            task_config = TaskConfig(mode=\"forbidden\")\n        elif isinstance(task_value, bool):\n            task_config = TaskConfig.from_bool(task_value)\n        else:\n            task_config = task_value\n        task_config.validate_function(fn, func_name)\n\n        # Handle output_schema\n        if isinstance(metadata.output_schema, NotSetT):\n            final_output_schema = parsed_fn.output_schema\n        else:\n            final_output_schema = metadata.output_schema\n\n        if final_output_schema is not None and isinstance(final_output_schema, dict):\n            if not _is_object_schema(final_output_schema):\n                raise ValueError(\n                    f\"Output schemas must represent object types due to MCP spec limitations. \"\n                    f\"Received: {final_output_schema!r}\"\n                )\n\n        return cls(\n            fn=parsed_fn.fn,\n            return_type=parsed_fn.return_type,\n            name=metadata.name or parsed_fn.name,\n            version=str(metadata.version) if metadata.version is not None else None,\n            title=metadata.title,\n            description=metadata.description\n            if metadata.description is not None\n            else parsed_fn.description,\n            icons=metadata.icons,\n            parameters=parsed_fn.input_schema,\n            output_schema=final_output_schema,\n            annotations=metadata.annotations,\n            tags=metadata.tags or set(),","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/tools/function_tool.py#L323-L359","documentation":"The MCP spec only permits tool output schemas of type 'object', so from_function() validates any provided (or inferred) output schema and raises ValueError when the schema root is not an object. This keeps the tool's structured output compliant with the protocol.","triggerScenarios":"Passing output_schema with a non-object root (e.g. {'type': 'array'}, {'type': 'string'}, bool/int schemas) to from_function, or metadata.output_schema being non-object; a wrapped function whose inferred output schema is non-object (via fn schema wrapping it becomes object, so usually explicit user-supplied schemas).","commonSituations":"Returning lists or scalars from a tool and hand-writing a matching schema; copying JSON Schema fragments from non-MCP projects; wrapping functions returning collections and expecting array schemas.","solutions":["Wrap the return value in an object: return {'items': my_list} and use an object schema","Remove the explicit output_schema and let the library derive a compliant wrapped-object schema","If using a pydantic model, ensure the root model is an object model, not a list/scalar type"],"exampleFix":"// before\nFunctionTool.from_function(get_ids, output_schema={'type': 'array', 'items': {'type': 'integer'}})\n// after\nFunctionTool.from_function(get_ids, output_schema={'type': 'object', 'properties': {'ids': {'type': 'array', 'items': {'type': 'integer'}}}, 'required': ['ids']})","handlingStrategy":"validation","validationCode":"def ensure_object_schema(schema):\n    if schema is None:\n        return\n    if isinstance(schema, dict) and schema.get('type') != 'object' and 'properties' not in schema:\n        raise ValueError(f'Output schema must be object type, got: {schema.get(\"type\")}')","typeGuard":"def is_object_schema(schema) -> bool:\n    return (isinstance(schema, dict)\n            and (schema.get('type') == 'object' or 'properties' in schema))","tryCatchPattern":"try:\n    tool = FunctionTool.from_function(fn, output_schema=schema)\nexcept ValueError as e:\n    if 'object types' in str(e):\n        tool = FunctionTool.from_function(fn)  # let library derive wrapped schema","preventionTips":["Return dicts/pydantic models (objects) from tools with explicit output schemas","Never hand-write array/scalar root schemas for MCP tools","Validate schemas with a JSON-Schema linter before registering"],"tags":["python","json-schema","mcp-spec","output-schema"],"backgroundTag":"schema-validation-failed","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}