siyuan-note/siyuan · error

property : 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

During MCP tool schema validation, a property that declares x-mcp-header must carry a string value that is not empty. This extension keyword maps a schema property to an HTTP header, so a non-string or empty value is meaningless and rejected with a path-qualified error. Validation walks nested properties and prefixes the error with the dotted property path.

Solutions

  1. Set x-mcp-header to a non-empty string header name, e.g. "x-mcp-header": "X-Trace-Id"
  2. Remove the x-mcp-header key if the property is not meant to map to a header
  3. Check the generator/template that emits the schema for empty placeholder values

Example fix

// before
{"type":"string","x-mcp-header":""}
// after
{"type":"string","x-mcp-header":"X-Request-Id"}
Defensive patterns

Strategy: validation

Validate before calling

function validateHeaderAnnotations(schema) {
  for (const [name, prop] of Object.entries(schema.properties || {})) {
    if ('x-mcp-header' in prop && (typeof prop['x-mcp-header'] !== 'string' || prop['x-mcp-header'] === '')) {
      throw new Error(`property ${name}: x-mcp-header must be a non-empty string`);
    }
  }
}

Type guard

const hasValidHeader = (p) => typeof p['x-mcp-header'] === 'string' && p['x-mcp-header'] !== '';

Try / catch

try { registerTool(schema) } catch (e) { if (String(e).includes('x-mcp-header')) fixSchemaAndRetry(schema); else throw e; }

Prevention

When it happens

Trigger: Defining a tool input schema where a property sets "x-mcp-header": "" or a non-string value (e.g. true, 42, null), then the schema is validated by the kernel's MCP tool registration path.

Common situations: Hand-edited or generated tool schemas, template placeholders left empty, JSON tooling writing booleans where strings are expected.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/0e9e9fa2edefcab0. Report an issue: GitHub.

Appendix: 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 9f775e8a12)