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
- Quote the offending key so YAML parses it as a string (e.g. "123": or "on":).
- Rename numeric/boolean keys to descriptive string names matching the schema.
- 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
- Quote any key that YAML might parse as bool/int (on, off, yes, no, 123).
- Run yamllint with key-duplicates/quoted-keys rules on config files.
- Generate configs from JSON tooling with string-only keys.
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
- doc %d: invalid config format at key %q: %w
- doc %d: invalid config format at key %q: expected nested for
- %s missing 'kind' field or it is not a string
- missing 'kind' field or it is not a string: %v
- %s missing 'name' field or it is not a string
AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05).
Data as JSON: /api/errors/b5838e33473e8991.
Report an issue: GitHub.