PrefectHQ/fastmcp · error · TypeError

messages must be str or list[Message], got {type(messages)._

Error message

messages must be str or list[Message], got {type(messages).__name__}

What it means

Prompt._normalize_messages accepts only str or list[Message] for messages. Any other top-level type (dict, tuple, None, list-like objects) raises TypeError naming the actual type received.

Source

Thrown at fastmcp_slim/fastmcp/prompts/base.py:178

        super().__init__(messages=normalized, description=description, meta=meta)

    @staticmethod
    def _normalize_messages(
        messages: str | list[Message],
    ) -> list[Message]:
        """Normalize input to list[Message]."""
        if isinstance(messages, str):
            return [Message(messages)]
        if isinstance(messages, list):
            # Validate all items are Message
            for i, item in enumerate(messages):
                if not isinstance(item, Message):
                    raise TypeError(
                        f"messages[{i}] must be Message, got {type(item).__name__}. "
                        f"Use Message({item!r}) to wrap the value."
                    )
            return messages
        raise TypeError(
            f"messages must be str or list[Message], got {type(messages).__name__}"
        )

    def to_mcp_prompt_result(self) -> GetPromptResult:
        """Convert to MCP GetPromptResult."""
        mcp_messages = [m.to_mcp_prompt_message() for m in self.messages]
        return GetPromptResult(
            description=self.description,
            messages=mcp_messages,
            _meta=self.meta,  # type: ignore[call-arg]  # _meta is Pydantic alias for meta field
        )


class InputRequiredPromptResult(PromptResult):
    """The full result of a single multi-round-trip prompt leg (SEP-2322).

    `InputRequiredResult` is a result type, not a `tools/call` feature: any
    request may resolve to one. When a prompt returns an `InputRequiredResult`

View on GitHub (pinned to 1f02114297)

Solutions

  1. Convert dicts to Message objects or use the documented str/list[Message] shapes
  2. Wrap sequence in a list: list(messages)
  3. Build Message objects from deserialized data before constructing Prompt

Example fix

// before
Prompt(name="p", messages={"role": "user", "content": "hi"})
// after
Prompt(name="p", messages=[Message("hi")])
Defensive patterns

Strategy: type-guard

Validate before calling

def normalize_top_level(msgs) -> list[Message]:
    if isinstance(msgs, str):
        return [Message(msgs)]
    if not isinstance(msgs, list):
        raise TypeError("messages must be str or list[Message]")
    return [m if isinstance(m, Message) else Message(m) for m in msgs]

Type guard

def is_str_or_message_list(v: object) -> bool:
    return isinstance(v, (str, list))

Try / catch

try:
    prompt = Prompt(name=name, messages=msgs)
except TypeError:
    prompt = Prompt(name=name, messages=[Message(json.dumps(msgs))])  # or correct the type

Prevention

When it happens

Trigger: Prompt(..., messages={"role": "user", "content": "hi"}) (dict), messages=("a","b") (tuple), messages=None, or a non-list sequence.

Common situations: Passing chat-completion-style dicts; passing a generator or tuple of Messages; JSON deserialization producing dicts instead of Message objects.

Understand the failure class

Background: "Wrong argument type", "must be a string", "expected Array or Prism::Scope": TypeError and ArgumentError when a library receives a value of the wrong type — this error's family across 28 libraries.

Related errors


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