siyuan-note/siyuan · error
keys must be an array
Error message
keys must be an array
What it means
databaseCreateKeySpecs validates the 'keys' argument of the databaseCreate tool. The value must be a JSON array (or null/absent, which means no keys). If the decoded argument is not []any, the tool returns 'keys must be an array' instead of attempting to build the attribute-view key specs.
Source
Thrown at kernel/mcp/tools/database.go:198
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 8641553a1f)
Solutions
- Pass keys as a JSON array of objects, e.g. [{"name":"Title","type":"text"}].
- If no keys are needed, omit the field or pass null — both are accepted.
- If the array is JSON-encoded inside a string, decode it before sending.
- Fix the tool-call schema in the client to enforce type array.
Example fix
// before
{ "keys": { "name": "Title", "type": "text" } }
// after
{ "keys": [ { "name": "Title", "type": "text" } ] } Defensive patterns
Strategy: type-guard
Validate before calling
function assertKeysArray(keys) {
if (keys == null) return [];
if (!Array.isArray(keys)) throw new TypeError('keys must be an array');
return keys;
} Type guard
function isKeyArray(v) { return v == null || (Array.isArray(v) && v.every(k => typeof k === 'object' && k !== null && !Array.isArray(k))); } Try / catch
try {
await mcp.call("databaseCreate", { name, keys });
} catch (e) {
if (e.message === "keys must be an array") {
// fix payload: wrap object into an array or decode the JSON string
}
} Prevention
- Always build keys as an array of objects, never an object map
- Omit or null the field when no keys are needed
- Add JSON-schema validation (type: array) on the client before calling
When it happens
Trigger: Calling the MCP databaseCreate tool with keys given as a JSON object (e.g. {"name":...}), a string, a number, or any non-array value.
Common situations: LLM agents producing keys as an object keyed by name instead of an array of objects; clients double-encoding the array as a JSON string; hand-written tool schemas using the wrong type.
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
- --av is required
- prev must be a string
- previous key not found in current view: %s
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/bc23f4c583567cf9.
Report an issue: GitHub.