{"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":198,"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":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/mcp/tools/database.go#L180-L216","documentation":"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.","triggerScenarios":"Calling the MCP databaseCreate tool with keys given as a JSON object (e.g. {\"name\":...}), a string, a number, or any non-array value.","commonSituations":"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.","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."],"exampleFix":"// before\n{ \"keys\": { \"name\": \"Title\", \"type\": \"text\" } }\n// after\n{ \"keys\": [ { \"name\": \"Title\", \"type\": \"text\" } ] }","handlingStrategy":"type-guard","validationCode":"function assertKeysArray(keys) {\n  if (keys == null) return [];\n  if (!Array.isArray(keys)) throw new TypeError('keys must be an array');\n  return keys;\n}","typeGuard":"function isKeyArray(v) { return v == null || (Array.isArray(v) && v.every(k => typeof k === 'object' && k !== null && !Array.isArray(k))); }","tryCatchPattern":"try {\n  await mcp.call(\"databaseCreate\", { name, keys });\n} catch (e) {\n  if (e.message === \"keys must be an array\") {\n    // fix payload: wrap object into an array or decode the JSON string\n  }\n}","preventionTips":["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"],"tags":["mcp","argument-validation","database"],"backgroundTag":"invalid-argument-format","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"}