siyuan-note/siyuan · error
property %q: x-mcp-header must be a non-empty string
Error message
property %q: x-mcp-header must be a non-empty string
What it means
Thrown by validateParamHeaderAnnotations while walking input-schema properties: a property declares the x-mcp-header annotation but its value is not a string or is the empty string. The annotation marks a property whose value should be bound to an HTTP request header when the tool is backed by an HTTP request, so it must name a concrete header.
Source
Thrown at kernel/mcp/tools/validation.go:241
return nil
}
properties, _ := root["properties"].(map[string]any)
seen := map[string]bool{}
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 {View on GitHub (pinned to 251596fc0d)
Solutions
- Set x-mcp-header to a non-empty header name string, e.g. "X-Request-Id".
- Remove the x-mcp-header key entirely if the property is not meant to be bound to a header.
- Lint the schema before registration: every x-mcp-header must be a non-empty string.
Example fix
// before
{"name": {"type": "string", "x-mcp-header": ""}}
// after
{"name": {"type": "string", "x-mcp-header": "X-Custom-Header"}} Defensive patterns
Strategy: validation
Validate before calling
if h, ok := prop["x-mcp-header"].(string); !ok || h == "" {
return fmt.Errorf("property x-mcp-header must be a non-empty string")
} Type guard
func isNonEmptyStringHeader(prop map[string]any) bool {
h, ok := prop["x-mcp-header"].(string)
return ok && h != ""
} Prevention
- Lint tool schemas for x-mcp-header type correctness before registration.
- Generate header annotations from a single source of truth rather than hand-editing.
- Drop the x-mcp-header key when a property is not header-bound.
When it happens
Trigger: Registering a tool whose input schema has a property like {"x-mcp-header": 123} or {"x-mcp-header": ""}. The walker checks every property under the top-level properties map, including nested objects.
Common situations: A typo sets x-mcp-header to a non-string (number/boolean/object); a templating step leaves an empty placeholder; copy-paste of an annotation block with the header name stripped.
Related errors
- property %q: x-mcp-header can only be applied to primitive t
- property %q: x-mcp-header value %q is not a valid HTTP field
- property %q: duplicate x-mcp-header value %q
- schema exceeds %d bytes
- root type must be "object"
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/0e9e9fa2edefcab0.
Report an issue: GitHub.