build_models
Execute DBT build operations to run and test data models in dependency order, with options for state-based or manual model selection.
Instructions
Run DBT build (run + test in DAG order).
State-based selection modes (uses dbt state:modified selector):
select_state_modified: Build only models modified since last successful run (state:modified)
select_state_modified_plus_downstream: Build modified + downstream dependencies (state:modified+) Note: Requires select_state_modified=True
Manual selection (alternative to state-based):
select: dbt selector syntax (e.g., "customers", "tag:mart", "stg_*")
exclude: Exclude specific models
Args: select: Manual selector exclude: Exclude selector select_state_modified: Use state:modified selector (changed models only) select_state_modified_plus_downstream: Extend to state:modified+ (changed + downstream) full_refresh: Force full refresh of incremental models fail_fast: Stop execution on first failure
Returns: Build results with status, models run/tested, and timing info
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| select | No | ||
| exclude | No | ||
| select_state_modified | No | ||
| select_state_modified_plus_downstream | No | ||
| full_refresh | No | ||
| fail_fast | No |
Implementation Reference
- src/dbt_core_mcp/server.py:1026-1106 (handler)Main handler function that implements the build_models tool logic: prepares dbt build command with selectors (state:modified, manual select/exclude), executes via runner, handles progress reporting, parses results from run_results.json, saves state manifest for future modified runs.self, ctx: Context | None, select: str | None = None, exclude: str | None = None, select_state_modified: bool = False, select_state_modified_plus_downstream: bool = False, full_refresh: bool = False, fail_fast: bool = False, ) -> dict[str, Any]: """Implementation of build_models tool.""" # Prepare state-based selection (validates and returns selector) selector = await self._prepare_state_based_selection(select_state_modified, select_state_modified_plus_downstream, select) # Early return if state-based requested but no state exists if select_state_modified and not selector: return { "status": "success", "message": "No previous state found - cannot determine modifications", "results": [], "elapsed_time": 0, } # Build command args args = ["build"] # Add selector if we have one (state-based or manual) if selector: args.extend(["-s", selector, "--state", "target/state_last_run"]) elif select: args.extend(["-s", select]) if exclude: args.extend(["--exclude", exclude]) if full_refresh: args.append("--full-refresh") if fail_fast: args.append("--fail-fast") # Execute with progress reporting logger.info(f"Running DBT build with args: {args}") # Define progress callback if context available async def progress_callback(current: int, total: int, message: str) -> None: if ctx: await ctx.report_progress(progress=current, total=total, message=message) result = await self.runner.invoke(args, progress_callback=progress_callback if ctx else None) # type: ignore if not result.success: error_msg = str(result.exception) if result.exception else "Build failed" response = { "status": "error", "message": error_msg, "command": " ".join(args), } # Include dbt output for debugging if result.stdout: response["dbt_output"] = result.stdout if result.stderr: response["stderr"] = result.stderr return response # Save state on success for next modified run if result.success and self.project_dir: state_dir = self.project_dir / "target" / "state_last_run" state_dir.mkdir(parents=True, exist_ok=True) manifest_path = self.runner.get_manifest_path() # type: ignore shutil.copy(manifest_path, state_dir / "manifest.json") # Parse run_results.json for details run_results = self._parse_run_results() return { "status": "success", "command": " ".join(args), "results": run_results.get("results", []), "elapsed_time": run_results.get("elapsed_time"), }
- src/dbt_core_mcp/server.py:1604-1637 (registration)MCP tool registration for 'build_models' using @app.tool() decorator. Defines input parameters and docstring schema. Calls the internal toolImpl_build_models handler after initialization.async def build_models( ctx: Context, select: str | None = None, exclude: str | None = None, select_state_modified: bool = False, select_state_modified_plus_downstream: bool = False, full_refresh: bool = False, fail_fast: bool = False, ) -> dict[str, Any]: """Run DBT build (run + test in DAG order). State-based selection modes (uses dbt state:modified selector): - select_state_modified: Build only models modified since last successful run (state:modified) - select_state_modified_plus_downstream: Build modified + downstream dependencies (state:modified+) Note: Requires select_state_modified=True Manual selection (alternative to state-based): - select: dbt selector syntax (e.g., "customers", "tag:mart", "stg_*") - exclude: Exclude specific models Args: select: Manual selector exclude: Exclude selector select_state_modified: Use state:modified selector (changed models only) select_state_modified_plus_downstream: Extend to state:modified+ (changed + downstream) full_refresh: Force full refresh of incremental models fail_fast: Stop execution on first failure Returns: Build results with status, models run/tested, and timing info """ await self._ensure_initialized_with_context(ctx) return await self.toolImpl_build_models(ctx, select, exclude, select_state_modified, select_state_modified_plus_downstream, full_refresh, fail_fast)
- src/dbt_core_mcp/server.py:480-521 (helper)Helper function to validate and prepare dbt state-based selectors (state:modified or state:modified+) for build_models and similar tools. Handles conflicts and checks for existing state.self, select_state_modified: bool, select_state_modified_plus_downstream: bool, select: str | None, ) -> str | None: """Validate and prepare state-based selection. Args: select_state_modified: Use state:modified selector select_state_modified_plus_downstream: Extend to state:modified+ select: Manual selector (conflicts with state-based) Returns: The dbt selector string to use ("state:modified" or "state:modified+"), or None if: - Not using state-based selection - No previous state exists (cannot determine modifications) Raises: ValueError: If validation fails """ # Validate: hierarchical requirement if select_state_modified_plus_downstream and not select_state_modified: raise ValueError("select_state_modified_plus_downstream requires select_state_modified=True") # Validate: can't use both state-based and manual selection if select_state_modified and select: raise ValueError("Cannot use both select_state_modified* flags and select parameter") # If not using state-based selection, return None if not select_state_modified: return None # Check if state exists state_dir = self.project_dir / "target" / "state_last_run" # type: ignore if not state_dir.exists(): # No state - cannot determine modifications return None # Return selector (state exists) return "state:modified+" if select_state_modified_plus_downstream else "state:modified" async def toolImpl_get_resource_info(
- src/dbt_core_mcp/server.py:305-341 (helper)Helper to parse dbt's run_results.json after build/run/test, simplifying results for tool output."""Parse target/run_results.json after dbt run/test/build. Returns: Dictionary with results array and metadata """ if not self.project_dir: return {"results": [], "elapsed_time": 0} run_results_path = self.project_dir / "target" / "run_results.json" if not run_results_path.exists(): return {"results": [], "elapsed_time": 0} try: with open(run_results_path) as f: data = json.load(f) # Simplify results for output simplified_results = [] for result in data.get("results", []): simplified_results.append( { "unique_id": result.get("unique_id"), "status": result.get("status"), "message": result.get("message"), "execution_time": result.get("execution_time"), "failures": result.get("failures"), } ) return { "results": simplified_results, "elapsed_time": data.get("elapsed_time", 0), } except Exception as e: logger.warning(f"Failed to parse run_results.json: {e}") return {"results": [], "elapsed_time": 0}