googleapis/mcp-toolbox · error

missing 'kind' field or it is not a string: %v

Error message

missing 'kind' field or it is not a string: %v

What it means

This is the single-document variant of the missing-'kind' error: when the YAML file contains exactly one document, UnmarshalPrimitiveConfig returns an error that includes the raw parsed resource map (%v) instead of a document-location prefix. It means the top-level map has no 'kind' key or the value is not a string.

Source

Thrown at internal/server/config.go:207

	for index, doc := range file.Docs {
		if doc == nil || doc.Body == nil {
			continue
		}
		docIndex := index + 1
		var resource map[string]any
		if err := decoder.DecodeFromNodeContext(ctx, doc.Body, &resource); err != nil {
			if len(file.Docs) > 1 {
				return nil, nil, nil, nil, nil, nil, fmt.Errorf("document %d: %s", docIndex, yaml.FormatError(err, false, false))
			}
			return nil, nil, nil, nil, nil, nil, fmt.Errorf("unable to decode YAML document: %s", yaml.FormatError(err, false, false))
		}
		var kind, name string
		var ok bool
		if kind, ok = resource["kind"].(string); !ok {
			if len(file.Docs) > 1 {
				return nil, nil, nil, nil, nil, nil, fmt.Errorf("%s missing 'kind' field or it is not a string", formatDocLocation(docIndex, keyToken(doc.Body, "kind"), doc.Body))
			}
			return nil, nil, nil, nil, nil, nil, fmt.Errorf("missing 'kind' field or it is not a string: %v", resource)
		}
		if name, ok = resource["name"].(string); !ok {
			// A `kind: group` may omit `name` to target the default nameless group;
			// every other resource requires a name.
			if kind == "group" {
				if rawName, present := resource["name"]; !present || rawName == nil {
					name, ok = "", true
				}
			}
		}
		if !ok {
			if len(file.Docs) > 1 {
				fallbackToken := keyToken(doc.Body, "name")
				if fallbackToken == nil {
					fallbackToken = keyToken(doc.Body, "kind")
				}
				return nil, nil, nil, nil, nil, nil, fmt.Errorf("%s missing 'name' field or it is not a string", formatDocLocation(docIndex, fallbackToken, doc.Body))
			}

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Add 'kind: <source|tool|authService|group|...>' as a top-level key in the YAML document.
  2. Inspect the map printed in the error to confirm what was actually parsed; ensure kind sits at the root, not nested.
  3. If kind exists but parses oddly (e.g. null from an anchor), inline its literal string value.
  4. Check indentation so the root is a flat mapping of keys including kind.

Example fix

// before (file tools.yaml)
name: my-source
kind: 123
// after
kind: source
name: my-source
Defensive patterns

Strategy: validation

Validate before calling

docs := strings.Split(yamlText, "\n---")
if len(docs) == 1 {
    var root map[string]any
    if err := yaml.Unmarshal([]byte(yamlText), &root); err != nil { panic(err) }
    if k, ok := root["kind"].(string); !ok || k == "" {
        return fmt.Errorf("single-doc config needs a string 'kind', parsed root: %v", root)
    }
}

Type guard

k, ok := root["kind"]
hasStringKind := ok && func(v any) bool { s, isStr := v.(string); return isStr && s != "" }(k)

Try / catch

if err := server.ParseConfig(ctx, ...); err != nil {
    if strings.Contains(err.Error(), "missing 'kind'") {
        log.Fatalf("config error: add a top-level 'kind' string to %s: %v", toolsFile, err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling ParseConfig on a single-document YAML file whose root map lacks a 'kind' key or has a non-string kind value; the offending map is printed in the error.

Common situations: New users writing their first tools.yaml without kind; editors stripping or reordering keys; pasting a partial config fragment; YAML anchors causing kind to resolve to null.

Related errors


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