{"record":{"id":"bc23f4c583567cf9","repo":"siyuan-note/siyuan","slug":"keys-must-be-an-array","errorCode":null,"errorMessage":"keys must be an array","messagePattern":"keys must be an array","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/tools/database.go","lineNumber":206,"sourceCode":"\tresult, err := model.CreateAttributeViewDatabase(parentID, previousID, nextID, name, primaryKeyName, av.LayoutType(layoutValue), keySpecs)\n\tif nil != err {\n\t\treturn CallToolResult{Content: []ContentItem{{Type: \"text\", Text: \"create database failed: \" + err.Error()}}, IsError: true}, nil\n\t}\n\treturn databaseSuccess(\"create\", map[string]any{\n\t\t\"blockID\":  result.BlockID,\n\t\t\"avID\":     result.AvID,\n\t\t\"viewID\":   result.ViewID,\n\t\t\"database\": model.NewAttributeViewMetadata(result.AttributeView),\n\t})\n}\n\nfunc databaseCreateKeySpecs(value any) (ret []*model.AttributeViewCreateKey, err error) {\n\tif nil == value {\n\t\treturn []*model.AttributeViewCreateKey{}, nil\n\t}\n\titems, ok := value.([]any)\n\tif !ok {\n\t\treturn nil, errors.New(\"keys must be an array\")\n\t}\n\tfor _, item := range items {\n\t\tdata, itemOK := item.(map[string]any)\n\t\tif !itemOK {\n\t\t\treturn nil, errors.New(\"each key must be an object\")\n\t\t}\n\t\tname, _ := data[\"name\"].(string)\n\t\tkeyType, _ := data[\"type\"].(string)\n\t\ticon, _ := data[\"icon\"].(string)\n\t\tif \"\" == strings.TrimSpace(name) || \"\" == strings.TrimSpace(keyType) {\n\t\t\treturn nil, errors.New(\"each key requires name and type\")\n\t\t}\n\t\tret = append(ret, &model.AttributeViewCreateKey{Name: name, Type: keyType, Icon: icon})\n\t}\n\treturn\n}\n\nfunc databaseSuccess(action string, data any) (CallToolResult, error) {","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/mcp/tools/database.go#L188-L224","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nkeys: {name: \"Status\", type: \"select\"}\n// after\nkeys: [{name: \"Status\", type: \"select\"}]","handlingStrategy":"validation","validationCode":"const ok = keys === undefined || keys === null || (Array.isArray(keys));","typeGuard":"const isKeyArray = (v) => v === null || Array.isArray(v);","tryCatchPattern":"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; }","preventionTips":["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"],"tags":["mcp","database","validation","argument-format"],"backgroundTag":"invalid-argument-format","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}