{"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":203,"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":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/mcp/tools/database.go#L185-L221","documentation":"Within databaseCreateKeySpecs, each element of the keys array must itself be a JSON object (map[string]any) describing a key. If any element is a scalar, string, or nested array, the tool returns 'each key must be an object' and aborts the database creation.","triggerScenarios":"Calling databaseCreate with keys like [\"Title\", \"Date\"] or [[\"name\",\"Title\"]] instead of an array of objects [{\"name\":\"Title\",...}].","commonSituations":"Agents generating a list of key names instead of full key specs; clients converting objects into arrays of arrays; template placeholders expanded into plain strings.","solutions":["Send each key as an object with at least name and type fields: [{\"name\":\"Title\",\"type\":\"block\"}].","Review the full schema — required fields per key are name, type, and optionally icon.","If you only have key names, decide the appropriate type for each before calling.","Validate the payload shape in the client (array of plain objects) before invoking."],"exampleFix":"// before\n{ \"keys\": [\"Title\", \"Date\"] }\n// after\n{ \"keys\": [ { \"name\": \"Title\", \"type\": \"block\" }, { \"name\": \"Date\", \"type\": \"date\" } ] }","handlingStrategy":"type-guard","validationCode":"function assertKeySpecs(keys) {\n  return keys.map(k => {\n    if (typeof k !== 'object' || k === null || Array.isArray(k)) {\n      throw new TypeError('each key must be an object, got: ' + JSON.stringify(k));\n    }\n    return k;\n  });\n}","typeGuard":"function isKeySpec(k) { return typeof k === 'object' && k !== null && !Array.isArray(k); }","tryCatchPattern":"try {\n  await mcp.call(\"databaseCreate\", { name, keys });\n} catch (e) {\n  if (e.message === \"each key must be an object\") {\n    // convert [\"Title\"] into [{ name: \"Title\", type: ... }]\n  }\n}","preventionTips":["Represent key specs as objects with name/type from the start","Never send raw key-name strings or nested arrays","Unit-test the payload builder against the documented schema"],"tags":["mcp","argument-validation","database"],"backgroundTag":"schema-validation-failed","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}