siyuan-note/siyuan · error

root type must be "object"

Error message

root type must be "object"

What it means

Thrown by resolveToolSchema when requireObject is true (input schemas only) and the parsed jsonschema.Schema's Type field is not the literal "object". The MCP specification mandates that a tool's inputSchema be a JSON Schema of type object whose properties describe the named arguments; anything else is rejected before Resolve runs.

Source

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

	}
	var raw any
	if err = json.Unmarshal(data, &raw); err != nil {
		return nil, err
	}
	if err = validateJSONComplexity(raw, maxToolSchemaDepth, maxToolSchemaNodes); err != nil {
		return nil, err
	}
	if requireObject {
		if err = validateParamHeaderAnnotations(raw); err != nil {
			return nil, err
		}
	}
	var parsed jsonschema.Schema
	if err = json.Unmarshal(data, &parsed); err != nil {
		return nil, err
	}
	if requireObject && parsed.Type != "object" {
		return nil, fmt.Errorf(`root type must be "object"`)
	}
	return parsed.Resolve(nil)
}

func (validator *ToolValidator) ValidateInput(arguments map[string]any) error {
	return validator.ValidateInputContext(context.Background(), arguments)
}

func (validator *ToolValidator) ValidateInputContext(ctx context.Context, arguments map[string]any) error {
	if validator == nil || validator.input == nil {
		return nil
	}
	value, err := prepareValidationValue(arguments)
	if err != nil {
		return err
	}
	return validateResolved(ctx, validator.validationSlots, validator.input, value)
}

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Wrap the input schema as {"type":"object","properties":{...}} with the actual parameters under properties.
  2. If the tool takes no arguments, use {"type":"object","properties":{}} (an empty object schema) rather than null or an empty map.
  3. Verify you are assigning to InputSchema, not OutputSchema — output schemas allow non-object root types.

Example fix

// before
InputSchema: ToolSchema{Raw: map[string]any{"type": "array"}}
// after
InputSchema: ToolSchema{Raw: map[string]any{"type": "object", "properties": map[string]any{}}}
Defensive patterns

Strategy: validation

Validate before calling

if t, _ := schema["type"].(string); t != "object" {
    schema["type"] = "object"
    if _, ok := schema["properties"]; !ok { schema["properties"] = map[string]any{} }
}

Type guard

func isObjectSchema(schema map[string]any) bool {
    t, _ := schema["type"].(string)
    return t == "object"
}

Prevention

When it happens

Trigger: Registering a tool whose InputSchema has type "string", "array", "number", is missing a type, or is an empty/non-object schema. CompileToolValidator always passes requireObject=true for input schemas.

Common situations: A developer copies an output/result schema (which may be any type) into the input slot; a tool is registered with InputSchema set to a fragment like {"type":"array"}; the schema is built dynamically and the object wrapper is omitted.

Related errors


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