siyuan-note/siyuan · error

each key must be an object

Error message

each key must be an object

What it means

While iterating the keys array in databaseCreateKeySpecs (kernel/mcp/tools/database.go), each element must itself be a JSON object describing one attribute-view key (name, type, optional icon). A non-object element (string, number, nested array) is rejected with this error.

Solutions

  1. Wrap each key spec as an object with at least name and type fields
  2. Check each array element is map/object-shaped before the call
  3. Refer to the database create tool schema for the exact key object fields
  4. Add client-side schema validation (e.g. JSON Schema) for the keys argument

Example fix

// before
keys: ["Title", "Date"]
// after
keys: [{name: "Title", type: "text"}, {name: "Date", type: "date"}]
Defensive patterns

Strategy: validation

Validate before calling

const ok = keys.every(k => k !== null && typeof k === "object" && !Array.isArray(k));

Type guard

const isKeySpec = (k) => typeof k === "object" && k !== null && !Array.isArray(k);

Try / catch

try { await call("database_create", {keys}) } catch (e) { if (String(e).includes("each key must be an object")) { console.error("non-object key element:", keys.find(k => typeof k !== "object")); } throw e; }

Prevention

When it happens

Trigger: Calling database create with keys like ["Title"] or [["Title","text"]] instead of [{"name":"Title","type":"text"}].

Common situations: Clients using a shorthand list of key names; LLM-generated arguments using tuples instead of objects; copy-pasted config where objects were flattened.

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/745e8b95c6409cb0. Report an issue: GitHub.

Appendix: source

Thrown at kernel/mcp/tools/database.go:211

		"blockID":  result.BlockID,
		"avID":     result.AvID,
		"viewID":   result.ViewID,
		"database": model.NewAttributeViewMetadata(result.AttributeView),
	})
}

func databaseCreateKeySpecs(value any) (ret []*model.AttributeViewCreateKey, err error) {
	if nil == value {
		return []*model.AttributeViewCreateKey{}, nil
	}
	items, ok := value.([]any)
	if !ok {
		return nil, errors.New("keys must be an array")
	}
	for _, item := range items {
		data, itemOK := item.(map[string]any)
		if !itemOK {
			return nil, errors.New("each key must be an object")
		}
		name, _ := data["name"].(string)
		keyType, _ := data["type"].(string)
		icon, _ := data["icon"].(string)
		if "" == strings.TrimSpace(name) || "" == strings.TrimSpace(keyType) {
			return nil, errors.New("each key requires name and type")
		}
		ret = append(ret, &model.AttributeViewCreateKey{Name: name, Type: keyType, Icon: icon})
	}
	return
}

func databaseSuccess(action string, data any) (CallToolResult, error) {
	output := &databaseToolOutput{Action: action, Data: data}
	serialized, err := json.Marshal(output)
	if nil != err {
		return CallToolResult{}, err
	}

View on GitHub (pinned to 9f775e8a12)