googleapis/mcp-toolbox · error

doc %d: unexpected non-string key in input: %v

Error message

doc %d: unexpected non-string key in input: %v

What it means

ConvertConfig returns this when a YAML document in the input config stream contains a mapping key that is not a string. Migration expects every key in each document to be a string (section names like 'sources', 'tools'); non-string keys cannot be mapped to the flat schema. The doc index and offending key value are included.

Source

Thrown at cmd/internal/config.go:247

	// convert configuration file to flat format
	var input yaml.MapSlice
	decoder := yaml.NewDecoder(bytes.NewReader(raw), yaml.UseOrderedMap())
	encoder := yaml.NewEncoder(&buf, yaml.UseLiteralStyleIfMultiline(true))

	nestedFormatKey := []string{"sources", "authServices", "embeddingModels", "tools", "toolsets", "prompts", "groups"}
	docIndex := 0
	for {
		if err := decoder.Decode(&input); err != nil {
			if err == io.EOF {
				break
			}
			return nil, err
		}
		docIndex++
		for _, item := range input {
			key, ok := item.Key.(string)
			if !ok {
				return nil, fmt.Errorf("doc %d: unexpected non-string key in input: %v", docIndex, item.Key)
			}
			if hasKindField(input) {
				// this doc is already in flat format, encode to buf
				if err := encoder.Encode(migrateToolsetKind(ctx, input)); err != nil {
					return nil, err
				}
				break
			}
			// check if value conversion to yaml.MapSlice successfully
			if slice, ok := item.Value.(yaml.MapSlice); slices.Contains(nestedFormatKey, key) && ok {
				// srcKey is kept for error messages, which should name the key the
				// user actually wrote rather than the flat kind it maps to.
				srcKey := key
				switch key {
				case "authServices":
					key = "authService"
				case "sources":
					key = "source"

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Quote the offending key so YAML parses it as a string (e.g. "123": or "on":).
  2. Rename numeric/boolean keys to descriptive string names matching the schema.
  3. Validate the YAML with a linter that flags non-string keys before loading.

Example fix

# before: key parsed as boolean (YAML 1.1)
on:
  tools: []
# after
"on":
  tools: []
# or better, rename:
enabled:
  tools: []
Defensive patterns

Strategy: validation

Validate before calling

var doc map[string]any
if err := yaml.Unmarshal(raw, &doc); err != nil { log.Fatal(err) }
for k := range doc {
    if _, ok := k.(string); !ok { log.Fatalf("non-string key: %v", k) }
}

Type guard

func allStringKeys(n yaml.Node) bool {
    for i := 0; i+1 < len(n.Content); i += 2 {
        if n.Content[i].Tag != "!!str" { return false }
    }
    return true
}

Try / catch

if strings.Contains(err.Error(), "unexpected non-string key in input") {
    log.Fatalf("quote or rename non-string YAML keys: %v", err)
}

Prevention

When it happens

Trigger: A config file whose YAML has a non-string mapping key, e.g. `123: value`, `true: ...`, or an unquoted key that YAML decodes as int/bool, encountered while iterating input items in ConvertConfig.

Common situations: Unquoted numeric or boolean-looking keys in YAML (e.g. `on:`, `no:`, version numbers as keys) — classic YAML Norway problem; machine-generated configs emitting JSON with non-string keys.

Related errors


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