googleapis/mcp-toolbox · error

invalid role %q: must be 'user' or 'assistant'

Error message

invalid role %q: must be 'user' or 'assistant'

What it means

The custom UnmarshalYAML for prompt Messages validates that each message's `role` is either "user" or "assistant" (empty defaults to "user"). It throws this error when a prompt YAML specifies any other role value.

Source

Thrown at internal/prompts/messages.go:48

	userRole      = "user"
	assistantRole = "assistant"
)

func (m *Message) UnmarshalYAML(unmarshal func(interface{}) error) error {
	// Use a type alias to prevent an infinite recursion loop. The alias
	// has the same fields but lacks the UnmarshalYAML method.
	type messageAlias Message
	var alias messageAlias
	if err := unmarshal(&alias); err != nil {
		return err
	}

	*m = Message(alias)
	if m.Role == "" {
		m.Role = userRole
	}
	if m.Role != userRole && m.Role != assistantRole {
		return fmt.Errorf("invalid role %q: must be 'user' or 'assistant'", m.Role)
	}
	return nil
}

// SubstituteMessages takes a slice of Messages and a set of parameter values,
// and returns a new slice with all template variables resolved.
func SubstituteMessages(messages []Message, arguments Arguments, argValues parameters.ParamValues) ([]Message, error) {
	substitutedMessages := make([]Message, 0, len(messages))
	argsMap := argValues.AsMap()

	var params parameters.Parameters
	for _, arg := range arguments {
		params = append(params, arg.Parameter)
	}

	for _, msg := range messages {
		substitutedContent, err := parameters.ResolveTemplateParams(params, msg.Content, argsMap)
		if err != nil {

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Change the role to exactly `user` or `assistant` (lowercase).
  2. Remove the role field entirely if user is intended — it defaults to user.
  3. Fold system-style instructions into the first `user` message content.
  4. Check casing: "User" is invalid; only lowercase works.

Example fix

// before
messages:
  - role: system
    content: You are helpful.
// after
messages:
  - role: user
    content: You are helpful. (fold system text into user message)
Defensive patterns

Strategy: validation

Validate before calling

validRoles := map[string]bool{"user": true, "assistant": true}
for _, m := range cfg.Messages {
	if m.Role != "" && !validRoles[m.Role] {
		return fmt.Errorf("prompt message role %q invalid; use user|assistant", m.Role)
	}
}

Type guard

func isValidPromptRole(r string) bool {
	return r == "" || r == "user" || r == "assistant"
}

Try / catch

if err := yaml.Unmarshal(data, &cfg); err != nil {
	if strings.Contains(err.Error(), "invalid role") {
		return fmt.Errorf("prompt messages only support role 'user' or 'assistant' (omitted defaults to user)")
	}
	return err
}

Prevention

When it happens

Trigger: A prompt definition includes a message with role like "system", "tool", "function", a typo ("usre"), or different casing ("User"), which fails the strict equality check.

Common situations: Porting prompts from other LLM frameworks that allow "system" roles; copying ChatML/other schemas; capitalization inconsistencies in hand-written YAML.

Related errors


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/786e1fca7185d01e. Report an issue: GitHub.