googleapis/mcp-toolbox · error

header param %s got value of type %t, not string

Error message

header param %s got value of type %t, not string

What it means

HTTP header parameters must be supplied as JSON/YAML strings. getHeaders iterates the declared header parameters and errors when a provided value exists but is not a string (e.g. a number, boolean, object, or array), since HTTP header values must be text.

Source

Thrown at internal/tools/http/http.go:264

		}
		query.Add(p.GetName(), fmt.Sprintf("%v", v))
	}
	parsedURL.RawQuery = query.Encode()
	return parsedURL.String(), nil
}

// Helper function to generate the HTTP headers upon Tool invocation.
func getHeaders(headerParams parameters.Parameters, defaultHeaders map[string]string, paramsMap map[string]any) (map[string]string, error) {
	// Populate header params
	allHeaders := make(map[string]string)
	maps.Copy(allHeaders, defaultHeaders)
	for _, p := range headerParams {
		headerValue, ok := paramsMap[p.GetName()]
		if ok {
			if strValue, ok := headerValue.(string); ok {
				allHeaders[p.GetName()] = strValue
			} else {
				return nil, fmt.Errorf("header param %s got value of type %t, not string", p.GetName(), headerValue)
			}
		}
	}
	return allHeaders, nil
}

func (t Tool) Invoke(ctx context.Context, s sources.Source, params parameters.ParamValues, accessToken tools.AccessToken) (any, util.ToolboxError) {
	source, ok := s.(compatibleSource)
	if !ok {
		return nil, util.NewClientServerError("source used is not compatible with the tool", http.StatusInternalServerError, nil)
	}
	// Combine Source and Tool headers.
	// In case of conflict, Tool header overrides Source header
	combinedHeaders := make(map[string]string)
	maps.Copy(combinedHeaders, source.HttpDefaultHeaders())
	maps.Copy(combinedHeaders, t.Cfg.Headers)

	paramsMap := params.AsMap()

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Convert the value to a string before invoking (e.g. "5", "true", "12345")
  2. Quote the value in YAML tool configs so it parses as a string
  3. If the value comes from an LLM/model call, add instructions or schema constraints so header params are always emitted as strings

Example fix

// before
{"headers": {"X-Count": 5}}
// after
{"headers": {"X-Count": "5"}}
Defensive patterns

Strategy: type-guard

Validate before calling

for k, v := range headerArgs {
    if _, ok := v.(string); !ok {
        return fmt.Errorf("header %s must be a string, got %T", k, v)
    }
}

Type guard

func allStrings(m map[string]any) bool {
    for _, v := range m {
        if _, ok := v.(string); !ok { return false }
    }
    return true
}

Try / catch

headers, err := tool.Invoke(ctx, params)
if err != nil && strings.Contains(err.Error(), "got value of type") {
    // coerce values to strings and retry once
}
var typeErr *json.UnmarshalTypeError // if parsing client-side, check this

Prevention

When it happens

Trigger: Invoke is called with a header parameter set to a non-string JSON value, e.g. {"X-Count": 5} or {"X-Flag": true} or a nested object, for a parameter declared in headerParams.

Common situations: LLM tool callers emitting unquoted numbers/booleans for headers; clients passing integers for numeric-looking headers like X-Request-Id: 12345; YAML configs with unquoted values that parse as bool/int.

Related errors


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