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
- Pass keys as a JSON array: [{"name":"Title","type":"text"}, ...]
- Omit keys or pass null when creating a database with no keys (an empty array is accepted)
- If you have a serialized JSON string, parse it into an array before passing it
- 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
- Always pass keys as an array of objects
- Use null or omit keys for an empty database
- Validate payloads against the tool's JSON Schema
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
- each key must be an object
- each key requires name and type
- prev must be a string
- attr must be a string or null (got %T)
- --av and --ids are required
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)