{"record":{"id":"745e8b95c6409cb0","repo":"siyuan-note/siyuan","slug":"each-key-must-be-an-object","errorCode":null,"errorMessage":"each key must be an object","messagePattern":"each key must be an object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/tools/database.go","lineNumber":211,"sourceCode":"\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) {\n\toutput := &databaseToolOutput{Action: action, Data: data}\n\tserialized, err := json.Marshal(output)\n\tif nil != err {\n\t\treturn CallToolResult{}, err\n\t}","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/mcp/tools/database.go#L193-L229","documentation":"While iterating the keys array in databaseCreateKeySpecs (kernel/mcp/tools/database.go), each element must itself be a JSON object describing one attribute-view key (name, type, optional icon). A non-object element (string, number, nested array) is rejected with this error.","triggerScenarios":"Calling database create with keys like [\"Title\"] or [[\"Title\",\"text\"]] instead of [{\"name\":\"Title\",\"type\":\"text\"}].","commonSituations":"Clients using a shorthand list of key names; LLM-generated arguments using tuples instead of objects; copy-pasted config where objects were flattened.","solutions":["Wrap each key spec as an object with at least name and type fields","Check each array element is map/object-shaped before the call","Refer to the database create tool schema for the exact key object fields","Add client-side schema validation (e.g. JSON Schema) for the keys argument"],"exampleFix":"// before\nkeys: [\"Title\", \"Date\"]\n// after\nkeys: [{name: \"Title\", type: \"text\"}, {name: \"Date\", type: \"date\"}]","handlingStrategy":"validation","validationCode":"const ok = keys.every(k => k !== null && typeof k === \"object\" && !Array.isArray(k));","typeGuard":"const isKeySpec = (k) => typeof k === \"object\" && k !== null && !Array.isArray(k);","tryCatchPattern":"try { await call(\"database_create\", {keys}) } catch (e) { if (String(e).includes(\"each key must be an object\")) { console.error(\"non-object key element:\", keys.find(k => typeof k !== \"object\")); } throw e; }","preventionTips":["Use {name, type} objects, not strings or tuples","Validate each element client-side","Avoid LLM shorthand formats"],"tags":["mcp","database","validation","schema"],"backgroundTag":"schema-validation-failed","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"}