PrefectHQ/fastmcp · error · ValueError

Unsupported tool_choice mode: {tool_choice.mode!r}

Error message

Unsupported tool_choice mode: {tool_choice.mode!r}

What it means

_convert_tool_choice_to_openai maps MCP ToolChoice modes 'auto', 'required', and 'none' to OpenAI tool_choice values. Any other mode string has no OpenAI equivalent and raises this ValueError with the offending mode repr.

Source

Thrown at fastmcp_slim/fastmcp/client/sampling/handlers/openai.py:449

                        parameters=parameters,
                    ),
                )
            )
        return openai_tools

    @staticmethod
    def _convert_tool_choice_to_openai(
        tool_choice: ToolChoice,
    ) -> ChatCompletionToolChoiceOptionParam:
        """Convert MCP tool_choice to OpenAI format."""
        if tool_choice.mode == "auto":
            return "auto"
        elif tool_choice.mode == "required":
            return "required"
        elif tool_choice.mode == "none":
            return "none"
        else:
            raise ValueError(f"Unsupported tool_choice mode: {tool_choice.mode!r}")

    @staticmethod
    def _chat_completion_to_result_with_tools(
        chat_completion: ChatCompletion,
    ) -> CreateMessageResultWithTools:
        """Convert OpenAI response to CreateMessageResultWithTools."""
        if len(chat_completion.choices) == 0:
            raise ValueError("No response for completion")

        first_choice = chat_completion.choices[0]
        message = first_choice.message

        # Determine stop reason
        stop_reason: StopReason
        if first_choice.finish_reason == "tool_calls":
            stop_reason = "toolUse"
        elif first_choice.finish_reason == "stop":
            stop_reason = "endTurn"

View on GitHub (pinned to 1f02114297)

Solutions

  1. Set tool_choice.mode to 'auto', 'required', or 'none' before invoking the OpenAI handler.
  2. If you need 'force a specific function', pass the target tool name via sampling params the OpenAI path supports, or map it to 'required' plus prompt guidance.
  3. Translate Anthropic-style modes ('any' -> 'required') in an adapter before calling.
  4. Catch ValueError and default to 'auto' when strict forcing is not required.

Example fix

// before
ToolChoice(mode='any')  # anthropic-style
// after
ToolChoice(mode='required')
Defensive patterns

Strategy: validation

Validate before calling

VALID_MODES = {'auto', 'required', 'none'}
def assert_valid_tool_choice(tool_choice):
    if tool_choice is not None and tool_choice.mode not in VALID_MODES:
        raise ValueError(f'mode {tool_choice.mode!r} unsupported by OpenAI handler; use auto/required/none')

Type guard

def is_openai_compatible_tool_choice(tc) -> bool:
    return tc is None or getattr(tc, 'mode', None) in {'auto', 'required', 'none'}

Try / catch

try:
    result = await openai_handler(messages, params)
except ValueError as e:
    if str(e).startswith('Unsupported tool_choice mode'):
        params.toolChoice = ToolChoice(mode='auto')
        result = await openai_handler(messages, params)
    else:
        raise

Prevention

When it happens

Trigger: Calling the OpenAI sampling handler with a ToolChoice whose mode is not one of 'auto'/'required'/'none' — e.g. a provider-specific mode like 'any' or 'forced_function' carried over from another backend.

Common situations: Reusing tool_choice objects built for Anthropic-style handlers ('any' mode) with the OpenAI handler; config files with typo'd mode values; newer MCP SDK adding a mode enum member the handler hasn't mapped yet.

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/edbb31c8c5b25423. Report an issue: GitHub.