siyuan-note/siyuan · error

keys must be an array

Error message

keys must be an array

What it means

databaseCreateKeySpecs (kernel/mcp/tools/database.go) validates the `keys` argument of the database create tool. It must be a JSON array (possibly empty or null); any non-array value — string, object, number — is rejected with this error before individual key specs are parsed.

Solutions

  1. Pass keys as a JSON array: [{"name":"Title","type":"text"}, ...]
  2. Omit keys or pass null when creating a database with no keys (an empty array is accepted)
  3. If you have a serialized JSON string, parse it into an array before passing it
  4. Validate the payload shape client-side before the call

Example fix

// before
keys: {name: "Status", type: "select"}
// after
keys: [{name: "Status", type: "select"}]
Defensive patterns

Strategy: validation

Validate before calling

const ok = keys === undefined || keys === null || (Array.isArray(keys));

Type guard

const isKeyArray = (v) => v === null || Array.isArray(v);

Try / catch

try { await call("database_create", {keys}) } catch (e) { if (String(e).includes("keys must be an array")) { console.error("keys must be a JSON array, got:", typeof keys); } throw e; }

Prevention

When it happens

Trigger: Calling the database create MCP tool with keys given as e.g. {"name":"x"} (object), "name" (string), or a comma-separated string instead of an array of key objects.

Common situations: LLM clients collapsing the key list into a single object; hand-written JSON using braces instead of brackets; clients passing a pre-serialized JSON string as keys.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

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

	result, err := model.CreateAttributeViewDatabase(parentID, previousID, nextID, name, primaryKeyName, av.LayoutType(layoutValue), keySpecs)
	if nil != err {
		return CallToolResult{Content: []ContentItem{{Type: "text", Text: "create database failed: " + err.Error()}}, IsError: true}, nil
	}
	return databaseSuccess("create", map[string]any{
		"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) {

View on GitHub (pinned to 9f775e8a12)