siyuan-note/siyuan · error

property %q: x-mcp-header can only be applied to primitive t

Error message

property %q: x-mcp-header can only be applied to primitive types (integer, string, boolean), got %q

What it means

Thrown by validateParamHeaderAnnotations when a property carries x-mcp-header but its declared type is not one of the allowed primitive types ("string", "integer", "boolean"). Header binding only makes sense for scalar values that can be serialized into a single header line; objects and arrays are rejected.

Source

Thrown at kernel/mcp/tools/validation.go:245

	var walk func(map[string]any, string) error
	walk = func(props map[string]any, prefix string) error {
		for propertyName, rawProperty := range props {
			property, ok := rawProperty.(map[string]any)
			if !ok {
				continue
			}
			path := propertyName
			if prefix != "" {
				path = prefix + "." + propertyName
			}
			if rawHeader, exists := property["x-mcp-header"]; exists {
				header, ok := rawHeader.(string)
				if !ok || header == "" {
					return fmt.Errorf(`property %q: x-mcp-header must be a non-empty string`, path)
				}
				propertyType, _ := property["type"].(string)
				if propertyType != "string" && propertyType != "integer" && propertyType != "boolean" {
					return fmt.Errorf(
						`property %q: x-mcp-header can only be applied to primitive types (integer, string, boolean), got %q`,
						path, propertyType)
				}
				if !validHTTPFieldName(header) {
					return fmt.Errorf(`property %q: x-mcp-header value %q is not a valid HTTP field name`, path, header)
				}
				normalized := strings.ToLower(header)
				if seen[normalized] {
					return fmt.Errorf(`property %q: duplicate x-mcp-header value %q`, path, header)
				}
				seen[normalized] = true
			}
			nested, _ := property["properties"].(map[string]any)
			if err := walk(nested, path); err != nil {
				return err
			}
		}
		return nil

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Change the property type to string, integer, or boolean if header binding is intended.
  2. If the property must remain an object/array, remove the x-mcp-header annotation and bind it to the request body instead.
  3. If you need multiple header values, model them as separate primitive properties each with their own x-mcp-header.

Example fix

// before
{"tags": {"type": "array", "items": {"type": "string"}, "x-mcp-header": "X-Tags"}}
// after: bind each tag to its own header
{"tag1": {"type": "string", "x-mcp-header": "X-Tag-1"}}
Defensive patterns

Strategy: validation

Validate before calling

if _, hasHeader := prop["x-mcp-header"]; hasHeader {
    t, _ := prop["type"].(string)
    if t != "string" && t != "integer" && t != "boolean" {
        return fmt.Errorf("x-mcp-header on non-primitive type %q", t)
    }
}

Type guard

func headerTypeAllowed(prop map[string]any) bool {
    if _, ok := prop["x-mcp-header"]; !ok { return true }
    t, _ := prop["type"].(string)
    return t == "string" || t == "integer" || t == "boolean"
}

Prevention

When it happens

Trigger: Registering a tool whose input schema has a property such as {"type": "array", "items": {...}, "x-mcp-header": "X-Tags"} or {"type": "object", "x-mcp-header": "X-Meta"}.

Common situations: A developer adds x-mcp-header to a property intended to carry structured data; a schema is reused and the header annotation is left on a now-array-typed property; missing type defaults to "" which also fails this check.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/98a11e94e82acc10. Report an issue: GitHub.