PrefectHQ/fastmcp · error · ValueError
ImageContent is only supported in user messages for OpenAI
Error message
ImageContent is only supported in user messages for OpenAI
What it means
The OpenAI sampling handler converts MCP sampling messages to OpenAI chat format. OpenAI's chat API only accepts image parts in messages with role 'user'; the handler therefore rejects any ImageContent found in a message whose role is not 'user' (e.g. 'assistant'). It throws a plain ValueError before making any API call.
Source
Thrown at fastmcp_slim/fastmcp/client/sampling/handlers/openai.py:358
openai_messages.append(
ChatCompletionUserMessageParam(
role="user",
content=content.text,
)
)
else:
openai_messages.append(
ChatCompletionAssistantMessageParam(
role="assistant",
content=content.text,
)
)
continue
# Handle ImageContent
if isinstance(content, ImageContent):
if message.role != "user":
raise ValueError(
"ImageContent is only supported in user messages for OpenAI"
)
openai_messages.append(
ChatCompletionUserMessageParam(
role="user",
content=[_image_content_to_openai_part(content)],
)
)
continue
# Handle AudioContent
if isinstance(content, AudioContent):
if message.role != "user":
raise ValueError(
"AudioContent is only supported in user messages for OpenAI"
)
openai_messages.append(
ChatCompletionUserMessageParam(View on GitHub (pinned to 1f02114297)
Solutions
- Move the ImageContent into a SamplingMessage with role='user' before passing the request to the OpenAI sampling handler.
- Drop or convert non-user ImageContent items to TextContent (e.g. a placeholder or caption) before invoking the handler.
- If images must appear in assistant turns, use a handler/provider that supports them; OpenAI chat completions does not.
- Wrap sampling calls in try/except ValueError and rebuild the message list with only user-role images on failure.
Example fix
// before SamplingMessage(role='assistant', content=[ImageContent(data=..., mimeType='image/png')]) // after SamplingMessage(role='user', content=[ImageContent(data=..., mimeType='image/png')])
Defensive patterns
Strategy: validation
Validate before calling
def validate_openai_sampling_messages(messages):
for m in messages:
if m.role != 'user' and any(isinstance(c, ImageContent) for c in m.content):
raise ValueError('ImageContent found in non-user message; move it to a user message')
return messages Type guard
def is_user_image_message(m) -> bool:
return m.role == 'user' and any(isinstance(c, ImageContent) for c in getattr(m, 'content', [])) Try / catch
try:
result = await openai_handler(messages, params)
except ValueError as e:
if 'ImageContent is only supported in user messages' in str(e):
messages = [m for m in messages if is_user_image_message(m) or not any(isinstance(c, ImageContent) for c in m.content)]
result = await openai_handler(messages, params)
else:
raise Prevention
- Only attach ImageContent to SamplingMessage with role='user'
- Add a pre-flight validator over the sampling message list
- Keep client/server MCP SDK versions aligned on content types
- Test sampling handlers with multimodal histories
When it happens
Trigger: Calling OpenAI sampling handler with a SamplingMessage whose role is 'assistant' (or any non-'user' role) and whose content list contains an ImageContent item during _convert_to_openai_messages.
Common situations: Custom server sampling handlers that echo images back in assistant messages; middleware or proxies that preserve the original message roles but attach screenshots or generated images to assistant turns; hand-built sampling request histories where role assignment is wrong.
Related errors
- AudioContent is only supported in user messages for OpenAI
- Unsupported tool_choice mode: {tool_choice.mode!r}
- Unsupported image MIME type for OpenAI: {content.mime_type!r
- Unsupported audio MIME type for OpenAI: {content.mime_type!r
- Unsupported content type for OpenAI: {type(item).__name__}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/f0bd157c2370df5d.
Report an issue: GitHub.