microsoft/autogen · error · ValueError

Unrecognized message role: {message.role}

Error message

Unrecognized message role: {message.role}

What it means

parse_sampling_message maps MCP sampling messages to AutoGen messages and accepts only role 'user' or 'assistant'; any other role string raises ValueError(f'Unrecognized message role: {message.role}'). The MCP sampling spec only defines these two roles, so this error indicates a malformed or protocol-violating message from the server.

Source

Thrown at python/packages/autogen-ext/src/autogen_ext/tools/mcp/_host/_sampling.py:85

    Raises:
        ValueError: If message role is not recognized
        AssertionError: If assistant message content is not text
    """
    content = parse_sampling_content(message.content, model_info=model_info)
    if message.role == "user":
        return UserMessage(
            source="user",
            content=[content],
        )
    elif message.role == "assistant":
        assert isinstance(content, str), "Assistant messages only support string content."
        return AssistantMessage(
            source="assistant",
            content=content,
        )
    else:
        raise ValueError(f"Unrecognized message role: {message.role}")


def finish_reason_to_stop_reason(finish_reason: FinishReasons) -> StopReason:
    """Convert AutoGen finish reasons to MCP stop reasons.

    Args:
        finish_reason: AutoGen completion finish reason

    Returns:
        Corresponding MCP stop reason
    """
    if finish_reason == "stop":
        return "endTurn"
    elif finish_reason == "length":
        return "maxTokens"
    else:
        return finish_reason

View on GitHub (pinned to 027ecf0a37)

Solutions

  1. Fix the server to send only role 'user' or 'assistant' in sampling messages (system prompts go in the separate systemPrompt field of the sampling request).
  2. If a system prompt is intended, move it into the sampling request's systemPrompt parameter instead of a message entry.
  3. Upgrade the server library (e.g. official typescript/python MCP SDK) which enforces the role union.
  4. As a host-side mitigation, catch the ValueError per message and reject that sampling request cleanly.

Example fix

# server side — before
messages = [types.SamplingMessage(role="system", content=text_block)]

# server side — after
result = await session.create_message(
    messages=[types.SamplingMessage(role="user", content=text_block)],
    system_prompt="You are a helpful assistant.
    max_tokens=1024,
)
Defensive patterns

Strategy: validation

Validate before calling

ALLOWED_ROLES = {"user", "assistant"}
if message.role not in ALLOWED_ROLES:
    raise ValueError(f"sampling message role must be one of {sorted(ALLOWED_ROLES)}, got {message.role!r}")

Type guard

from typing import Any, TypeGuard

def is_supported_sampling_role(role: Any) -> TypeGuard[str]:
    return role in {"user", "assistant"}

Try / catch

try:
    msg = parse_sampling_message(message, model_info)
except ValueError as e:
    if "Unrecognized message role" in str(e):
        return error_result("malformed sampling message")
    raise

Prevention

When it happens

Trigger: An MCP server sends a sampling message with role='system', 'tool', 'function', or a custom string; parse_sampling_message falls through user/assistant branches and raises.

Common situations: A hand-written or experimental MCP server reusing LLM chat roles (system/tool) in createMessage payloads; server code built against a different understanding of the sampling schema; typos like 'User'.

Related errors


AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15). Data as JSON: /api/errors/fd633890dc7ed2f0. Report an issue: GitHub.