Skip to main content
Glama
NiclasOlofsson

DBT Core MCP Server

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

TableJSON Schema
NameRequiredDescriptionDefault
selectNo
excludeNo
select_state_modifiedNo
select_state_modified_plus_downstreamNo
full_refreshNo
fail_fastNo

Implementation Reference

  • 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"),
        }
  • 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)
  • 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(
  • 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}

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/NiclasOlofsson/dbt-core-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server