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

  1. Pass keys as a JSON array of objects, e.g. [{"name":"Title","type":"text"}].
  2. If no keys are needed, omit the field or pass null — both are accepted.
  3. If the array is JSON-encoded inside a string, decode it before sending.
  4. 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

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


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