diff --git a/src/fastmcp/client/sampling/handlers/anthropic.py b/src/fastmcp/client/sampling/handlers/anthropic.py index b7a6ce090..0939121d1 100644 --- a/src/fastmcp/client/sampling/handlers/anthropic.py +++ b/src/fastmcp/client/sampling/handlers/anthropic.py @@ -235,6 +235,10 @@ class AnthropicSamplingHandler: is_error=item.isError if item.isError else False, ) ) + else: + raise ValueError( + f"Unsupported content type for Anthropic: {type(item).__name__}" + ) if content_blocks: anthropic_messages.append( diff --git a/src/fastmcp/client/sampling/handlers/openai.py b/src/fastmcp/client/sampling/handlers/openai.py index ffc40f158..bbd6a0ae5 100644 --- a/src/fastmcp/client/sampling/handlers/openai.py +++ b/src/fastmcp/client/sampling/handlers/openai.py @@ -243,6 +243,10 @@ class OpenAISamplingHandler: content=content_text, ) ) + else: + raise ValueError( + f"Unsupported content type for OpenAI: {type(item).__name__}" + ) # Add assistant message with tool calls if present # OpenAI requires: assistant (with tool_calls) -> tool messages diff --git a/tests/client/sampling/handlers/test_anthropic_handler.py b/tests/client/sampling/handlers/test_anthropic_handler.py index 5910eb92b..757ec2eeb 100644 --- a/tests/client/sampling/handlers/test_anthropic_handler.py +++ b/tests/client/sampling/handlers/test_anthropic_handler.py @@ -8,14 +8,17 @@ from mcp.types import ( AudioContent, CreateMessageResult, CreateMessageResultWithTools, + EmbeddedResource, ImageContent, ModelHint, ModelPreferences, SamplingMessage, TextContent, + TextResourceContents, ToolResultContent, ToolUseContent, ) +from pydantic import AnyUrl from fastmcp.client.sampling.handlers.anthropic import ( AnthropicSamplingHandler, @@ -372,3 +375,28 @@ def test_convert_messages_with_tool_result_content(): "is_error": False, } ] + + +def test_convert_messages_raises_on_unsupported_content_type(): + """Unsupported content types should raise ValueError. + + SamplingMessage validates content against a union of known types, so + we use model_construct to bypass validation and simulate a future + SDK content type that the handler doesn't know about yet. + """ + embedded = EmbeddedResource( + type="resource", + resource=TextResourceContents( + uri=AnyUrl("file:///test.txt"), text="hello", mimeType="text/plain" + ), + ) + # Must be inside a list content — single-content messages hit a + # different check. Use model_construct to bypass Pydantic's + # union validation (EmbeddedResource is not in the content union). + msg = SamplingMessage.model_construct( + role="user", + content=[TextContent(type="text", text="prefix"), embedded], + ) + + with pytest.raises(ValueError, match="Unsupported content type for Anthropic"): + AnthropicSamplingHandler._convert_to_anthropic_messages([msg]) diff --git a/tests/client/sampling/handlers/test_openai_handler.py b/tests/client/sampling/handlers/test_openai_handler.py index e80ba3292..4cfb9d606 100644 --- a/tests/client/sampling/handlers/test_openai_handler.py +++ b/tests/client/sampling/handlers/test_openai_handler.py @@ -6,11 +6,13 @@ from mcp.types import ( AudioContent, CreateMessageRequestParams, CreateMessageResult, + EmbeddedResource, ImageContent, ModelHint, ModelPreferences, SamplingMessage, TextContent, + TextResourceContents, ToolUseContent, ) from openai import AsyncOpenAI @@ -25,6 +27,7 @@ from openai.types.chat import ( ChatCompletionUserMessageParam, ) from openai.types.chat.chat_completion import Choice +from pydantic import AnyUrl from fastmcp.client.sampling.handlers.openai import ( OpenAISamplingHandler, @@ -316,3 +319,25 @@ async def test_chat_completion_to_create_message_result(): role="assistant", model="gpt-4o-mini", ) + + +def test_convert_messages_raises_on_unsupported_content_type(): + """Unsupported content types should raise ValueError. + + SamplingMessage validates content against a union of known types, so + we use model_construct to bypass validation and simulate a future + SDK content type that the handler doesn't know about yet. + """ + embedded = EmbeddedResource( + type="resource", + resource=TextResourceContents( + uri=AnyUrl("file:///test.txt"), text="hello", mimeType="text/plain" + ), + ) + msg = SamplingMessage.model_construct( + role="user", + content=[TextContent(type="text", text="prefix"), embedded], + ) + + with pytest.raises(ValueError, match="Unsupported content type for OpenAI"): + OpenAISamplingHandler._convert_to_openai_messages(None, [msg])