{"record":{"id":"65efd0e4a505d259","repo":"bytebase/bytebase","slug":"database-is-required","errorCode":null,"errorMessage":"database is required","messagePattern":"database is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/api/mcp/tool_change.go","lineNumber":167,"sourceCode":"**Notes:**\n- v1 supports single database targets only. For batch changes across multiple databases, use get_skill(\"database-change\").\n- Plan checks run automatically; results included in response when available.\n- Requires bb.sheets.create, bb.plans.create, bb.issues.create permissions.\n- If createRollout=true but policy gates aren't satisfied, returns success with rolloutCreated=false and a reason.\n- Masked data: a masked column reads back as \"******\". That is a placeholder, not a value anything holds. Any change containing it is refused, because writing it back would overwrite the real value. Ask a human to make the change in the Bytebase console instead.\n`\n\nfunc (s *Server) registerChangeTool() {\n\tmcp.AddTool(s.mcpServer, &mcp.Tool{\n\t\tName:        \"propose_database_change\",\n\t\tDescription: proposeChangeDescription,\n\t}, s.handleChange)\n}\n\nfunc (s *Server) handleChange(ctx context.Context, req *mcp.CallToolRequest, input ChangeInput) (*mcp.CallToolResult, any, error) {\n\t// Step 1: Validate input.\n\tif input.Database == \"\" {\n\t\treturn nil, nil, errors.New(\"database is required\")\n\t}\n\tif input.SQL == \"\" {\n\t\treturn nil, nil, errors.New(\"sql is required\")\n\t}\n\tif input.Title == \"\" {\n\t\treturn nil, nil, errors.New(\"title is required\")\n\t}\n\n\tchangeType := input.ChangeType\n\tif changeType == \"\" {\n\t\tchangeType = changeTypeMigrate\n\t}\n\tif changeType != changeTypeMigrate && changeType != changeTypeSDL {\n\t\treturn formatToolError(&toolError{\n\t\t\tCode:       \"INVALID_ARGUMENT\",\n\t\t\tMessage:    fmt.Sprintf(\"invalid changeType %q\", changeType),\n\t\t\tSuggestion: \"allowed values: MIGRATE, SDL\",\n\t\t}), nil, nil","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/bytebase/bytebase/blob/1870550677fe08f0d2a78c07acd27541464eb945/backend/api/mcp/tool_change.go#L149-L185","documentation":"The MCP change tool (handleChange) validates its input in step 1: database, sql, and title are all mandatory. An empty input.Database cannot identify the target database for the schema-change pipeline, so it throws this error first.","triggerScenarios":"Invoking the change tool with input.database empty — omitted field in the tool call, an LLM agent that filled only sql/title, or a client binding that maps the database argument to the wrong key.","commonSituations":"AI agent constructs a change request without resolving which database to target; manual tool invocation in tests missing the database argument; renamed/renumbered input fields breaking argument deserialization so Database defaults to \"\".","solutions":["Provide the database resource name (e.g. instances/{instance}/databases/{db}) in the change tool input.","List available databases first (search/list tool) and pick a valid database name before calling change.","Verify the MCP client serializes the database field with the exact expected key.","Client-side: validate required fields (database, sql, title) before dispatching the tool call."],"exampleFix":"// before\nawait callTool('change', { sql: 'CREATE TABLE t(id INT)', title: 'add t' });\n// after\nawait callTool('change', { database: 'instances/prod/databases/appdb', sql: 'CREATE TABLE t(id INT)', title: 'add t' });","handlingStrategy":"validation","validationCode":"for (const field of ['database', 'sql', 'title'] as const) {\n  if (!input[field]) throw new Error(`${field} is required`);\n}","typeGuard":"function isCompleteChangeInput(i: ChangeInput): boolean {\n  return i.database !== '' && i.sql !== '' && i.title !== '';\n}","tryCatchPattern":"try {\n  return await callTool('change', input);\n} catch (e) {\n  if (String(e).includes('database is required')) {\n    const dbs = await callTool('search_database', {});\n    return await callTool('change', { ...input, database: dbs[0].name });\n  }\n  throw e;\n}","preventionTips":["Resolve and set the target database name before composing the change request","List databases via the discovery tool when the target is ambiguous","Validate all required fields (database, sql, title) in the client wrapper before dispatch"],"tags":["mcp","validation","tool-call","backend"],"backgroundTag":"missing-required-argument","analyzedSha":"1870550677fe08f0d2a78c07acd27541464eb945","analyzedAt":"2026-09-06T21:16:13.665Z","contentChangedAt":"2026-09-06T21:16:13.665Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}