Skip to main content
Glama

dbt_compile

Generate SQL code for dbt models without executing it to validate syntax, preview transformations, or inspect how dbt interprets models before database execution.

Instructions

Compile dbt models. An AI agent should use this tool when it needs to generate the SQL that will be executed without actually running it against the database. This is valuable for validating SQL syntax, previewing transformations, or investigating how dbt interprets models before committing to execution.

Returns: Output from the dbt compile command as text (this command does not support JSON output format)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
modelsNoSpecific models to compile, using the dbt selection syntax
selectorNoNamed selector to use
excludeNoModels to exclude
project_dirNoABSOLUTE PATH to the directory containing the dbt project (e.g. '/Users/username/projects/dbt_project' not '.').
profiles_dirNoDirectory containing the profiles.yml file (defaults to project_dir if not specified)

Implementation Reference

  • The primary handler function for the 'dbt_compile' MCP tool. Decorated with @mcp.tool(), defines input schema via Pydantic Field descriptions, constructs the dbt compile command, executes it, and processes the result.
    @mcp.tool() async def dbt_compile( models: Optional[str] = Field( default=None, description="Specific models to compile, using the dbt selection syntax" ), selector: Optional[str] = Field( default=None, description="Named selector to use" ), exclude: Optional[str] = Field( default=None, description="Models to exclude" ), project_dir: str = Field( default=".", description="ABSOLUTE PATH to the directory containing the dbt project (e.g. '/Users/username/projects/dbt_project' not '.')" ), profiles_dir: Optional[str] = Field( default=None, description="Directory containing the profiles.yml file (defaults to project_dir if not specified)" ) ) -> str: """Compile dbt models. An AI agent should use this tool when it needs to generate the SQL that will be executed without actually running it against the database. This is valuable for validating SQL syntax, previewing transformations, or investigating how dbt interprets models before committing to execution. Returns: Output from the dbt compile command as text (this command does not support JSON output format) """ command = ["compile"] if models: command.extend(["-s", models]) if selector: command.extend(["--selector", selector]) if exclude: command.extend(["--exclude", exclude]) # The --no-print flag is not supported by dbt Cloud CLI # We'll rely on proper parsing to handle any print macros result = await execute_dbt_command(command, project_dir, profiles_dir) # Use the centralized result processor return await process_command_result(result, command_name="compile")
  • src/server.py:88-89 (registration)
    Registers all MCP tools, including dbt_compile, by calling register_tools(mcp) on the FastMCP server instance.
    # Register tools register_tools(mcp)
  • Key helper function called by dbt_compile to execute the actual dbt CLI 'compile' command asynchronously, managing environment variables, subprocess execution, output parsing (including JSON detection), and error handling.
    async def execute_dbt_command( command: List[str], project_dir: str = ".", profiles_dir: Optional[str] = None ) -> Dict[str, Any]: """ Execute a dbt command and return the result. Args: command: List of command arguments (without the dbt executable) project_dir: Directory containing the dbt project profiles_dir: Directory containing the profiles.yml file (defaults to project_dir if not specified) Returns: Dictionary containing command result: { "success": bool, "output": str or dict, "error": str or None, "returncode": int } """ # Get dbt path from config dbt_path = get_config("dbt_path", "dbt") full_command = [dbt_path] + command # Load environment variables env_vars = load_environment(project_dir) # Explicitly set HOME environment variable in os.environ os.environ["HOME"] = str(Path.home()) logger.debug(f"Explicitly setting HOME environment variable in os.environ to {os.environ['HOME']}") # Set DBT_PROFILES_DIR based on profiles_dir or project_dir if profiles_dir is not None: # Use the explicitly provided profiles_dir abs_profiles_dir = str(Path(profiles_dir).resolve()) os.environ["DBT_PROFILES_DIR"] = abs_profiles_dir logger.debug(f"Setting DBT_PROFILES_DIR in os.environ to {abs_profiles_dir} (from profiles_dir)") else: # Check if there's a value from the .env file if "DBT_PROFILES_DIR" in env_vars: os.environ["DBT_PROFILES_DIR"] = env_vars["DBT_PROFILES_DIR"] logger.debug(f"Setting DBT_PROFILES_DIR from env_vars to {env_vars['DBT_PROFILES_DIR']}") else: # Default to project_dir abs_project_dir = str(Path(project_dir).resolve()) os.environ["DBT_PROFILES_DIR"] = abs_project_dir logger.debug(f"Setting DBT_PROFILES_DIR in os.environ to {abs_project_dir} (from project_dir)") # Update env_vars with the current os.environ env_vars.update(os.environ) logger.debug(f"Executing command: {' '.join(full_command)} in {project_dir}") try: # Execute the command process = await asyncio.create_subprocess_exec( *full_command, cwd=project_dir, env=env_vars, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) # Communicate with the process stdout_bytes, stderr_bytes = await process.communicate() stdout = stdout_bytes.decode('utf-8') if stdout_bytes else "" stderr = stderr_bytes.decode('utf-8') if stderr_bytes else "" success = process.returncode == 0 # Special case for 'show' command: detect "does not match any enabled nodes" as an error # Only check if --quiet is not in the command, as --quiet suppresses this output if success and command[0] == "show" and "--quiet" not in command and "does not match any enabled nodes" in stdout: success = False # For commands that failed, combine stdout and stderr for comprehensive output if not success and stderr: # If there's output from both stdout and stderr, combine them if stdout: output = f"{stdout}\n\nSTDERR:\n{stderr}" else: output = stderr else: # For successful commands, use stdout output = stdout # Check if this is dbt Cloud CLI output format with embedded JSON in log lines if stdout.strip().startswith('[') and '"name":' in stdout: try: # Parse the entire output as JSON array json_array = json.loads(stdout) # If it's an array of log objects with name field (dbt Cloud CLI format) if isinstance(json_array, list) and all(isinstance(item, dict) and "name" in item for item in json_array): logger.debug(f"Detected dbt Cloud CLI output format with {len(json_array)} items") output = json_array except json.JSONDecodeError: # Not valid JSON array, keep as string logger.debug("Failed to parse stdout as JSON array, keeping as string") pass else: # Try standard JSON parsing try: output = json.loads(stdout) except json.JSONDecodeError: # Not JSON, keep as string logger.debug("Failed to parse stdout as standard JSON, keeping as string") pass result = { "success": success, "output": output, "error": stderr if not success else None, "returncode": process.returncode } if not success: logger.warning(f"Command failed with exit code {process.returncode}: {stderr}") # Log full environment for debugging logger.debug(f"Full environment variables: {env_vars}") logger.debug(f"Current directory: {project_dir}") logger.debug(f"Full command: {' '.join(full_command)}") return result except Exception as e: import traceback stack_trace = traceback.format_exc() logger.error(f"Error executing command: {e}\nStack trace: {stack_trace}") return { "success": False, "output": None, "error": f"{str(e)}\nStack trace: {stack_trace}", "returncode": -1 }
  • Helper function used by dbt_compile to format and return the processed result from the dbt command execution, handling success/error cases and optional formatting.
    async def process_command_result( result: Dict[str, Any], command_name: str, output_formatter: Optional[Callable] = None, include_debug_info: bool = False ) -> str: """ Process the result of a dbt command execution. Args: result: The result dictionary from execute_dbt_command command_name: The name of the dbt command (e.g. "run", "test") output_formatter: Optional function to format successful output include_debug_info: Whether to include additional debug info in error messages Returns: Formatted output or error message """ logger.info(f"Processing command result for {command_name}") logger.info(f"Result success: {result['success']}, returncode: {result.get('returncode')}") # Log the output type and a sample if "output" in result: if isinstance(result["output"], str): logger.info(f"Output type: str, first 100 chars: {result['output'][:100]}") elif isinstance(result["output"], (dict, list)): logger.info(f"Output type: {type(result['output'])}, sample: {json.dumps(result['output'])[:100]}") else: logger.info(f"Output type: {type(result['output'])}") # For errors, simply return the raw command output if available if not result["success"]: logger.warning(f"Command {command_name} failed with returncode {result.get('returncode')}") # If we have command output, return it directly if "output" in result and result["output"]: logger.info(f"Returning error output: {str(result['output'])[:100]}...") return str(result["output"]) # If no command output, return the error message if result["error"]: logger.info(f"Returning error message: {str(result['error'])[:100]}...") return str(result["error"]) # If neither output nor error is available, return a generic message logger.info("No output or error available, returning generic message") return f"Command failed with exit code {result.get('returncode', 'unknown')}" # Format successful output if output_formatter: logger.info(f"Using custom formatter for {command_name}") formatted_result = output_formatter(result["output"]) logger.info(f"Formatted result type: {type(formatted_result)}, first 100 chars: {str(formatted_result)[:100]}") return formatted_result # Default output formatting logger.info(f"Using default formatting for {command_name}") if isinstance(result["output"], (dict, list)): json_result = json.dumps(result["output"]) logger.info(f"JSON result length: {len(json_result)}, first 100 chars: {json_result[:100]}") return json_result else: str_result = str(result["output"]) logger.info(f"String result length: {len(str_result)}, first 100 chars: {str_result[:100]}") return str_result

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/MammothGrowth/dbt-cli-mcp'

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