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
- Change the role to exactly `user` or `assistant` (lowercase).
- Remove the role field entirely if user is intended — it defaults to user.
- Fold system-style instructions into the first `user` message content.
- 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
- Omit `role` for user messages; only specify 'assistant' when needed.
- Never copy 'system'/'tool' roles from other frameworks into prompt YAML.
- Keep roles lowercase; validate prompts in CI with a JSON/YAML schema.
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
- error parsing argument: %w
- error substituting params for message: %w
- unknown prompt type: %q
- description is required for tool %q
- description is required for tool %q
AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05).
Data as JSON: /api/errors/786e1fca7185d01e.
Report an issue: GitHub.