Skip to main content
Glama
jermeyyy
by jermeyyy

run_task

Execute Gradle tasks for building, testing, or cleaning projects with optional arguments to customize the build process.

Instructions

Run one or more Gradle tasks.

Args: task: Task(s) to run. Single task, space-separated tasks, or list of tasks. Examples: 'build', ':app:build :core:build', [':core:build', ':app:assemble']. args: Optional Gradle arguments (e.g., ['--info', '-x', 'test']).

Returns: TaskResult with success status and error message if failed.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
taskYes
argsNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
errorNo
successYes

Implementation Reference

  • MCP tool handler for 'run_task': decorated with @mcp.tool(), normalizes input tasks and arguments, creates GradleWrapper instance, calls underlying run_task implementation, handles logging and returns structured TaskResult
    @mcp.tool()
    async def run_task(
        task: str | list[str],
        args: list[str] | None = None,
        ctx: Context | None = None,
    ) -> TaskResult:
        """Run one or more Gradle tasks.
    
        Args:
            task: Task(s) to run. Single task, space-separated tasks, or list of tasks.
                  Examples: 'build', ':app:build :core:build', [':core:build', ':app:assemble'].
            args: Optional Gradle arguments (e.g., ['--info', '-x', 'test']).
    
        Returns:
            TaskResult with success status and error message if failed.
        """
        try:
            # Normalize to list - handle space-separated string or list
            if isinstance(task, str):
                tasks = task.split()
            else:
                tasks = task
            task_str = ", ".join(tasks)
    
            if ctx:
                await ctx.info(f"Running task(s): {task_str}" + (f" with args: {args}" if args else ""))
    
            gradle = _get_gradle_wrapper(ctx)
            daemon_monitor = _get_daemon_monitor()
    
            # run_task handles multiple tasks and progress reporting
            result = await gradle.run_task(tasks, args, ctx, daemon_monitor=daemon_monitor)
    
            # Log Gradle output
            if ctx:
                if result.get("stdout"):
                    await ctx.debug(f"Gradle stdout:\n{result['stdout']}")
                if result.get("stderr"):
                    await ctx.debug(f"Gradle stderr:\n{result['stderr']}")
    
                if result["success"]:
                    await ctx.info(f"Task(s) {task_str} completed successfully")
                else:
                    await ctx.error(f"Task(s) {task_str} failed", extra={"error": result.get("error")})
    
            return TaskResult(
                success=result["success"],
                error=result.get("error"),
            )
        except ValueError as e:
            # Task is a cleaning task
            raise ValueError(str(e)) from e
        except Exception as e:
            # Report error progress
            if ctx:
                await ctx.report_progress(progress=100, total=100)
            return TaskResult(
                success=False,
                error=ErrorInfo(
                    summary=str(e),
                    failed_tasks=[],
                    compilation_errors=[],
                ),
            )
  • Core implementation of run_task in GradleWrapper: executes gradlew subprocess asynchronously, validates security/safety, streams output for progress/dashboard, parses structured errors (failed tasks, compilation errors), handles timeout
    async def run_task(
        self,
        tasks: str | list[str],
        args: list[str] | None = None,
        ctx: "Context | None" = None,
        timeout: int | None = None,
        daemon_monitor: "DaemonMonitor | None" = None,
    ) -> dict:
        """Run one or more Gradle tasks with real-time progress reporting.
    
        Args:
            tasks: Task(s) to run. Single string or list of task names.
            args: Additional Gradle arguments. Daemon is enabled by default.
            ctx: Optional FastMCP Context for progress reporting.
            timeout: Optional timeout in seconds. If specified, the task will be
                     terminated if it exceeds this duration.
    
        Returns:
            Dictionary with 'success' (bool) and 'error' (str or None).
    
        Raises:
            ValueError: If any task is a cleaning task.
        """
        # Normalize to list
        task_list = tasks if isinstance(tasks, list) else [tasks]
    
        # Check all tasks for cleaning patterns
        for task in task_list:
            if self._is_cleaning_task(task):
                raise ValueError(
                    f"Task '{task}' is a cleaning task and cannot be run via run_task. "
                    "Please use the clean tool instead."
                )
    
        # Validate arguments to prevent command injection
        if args:
            self._validate_gradle_args(args, task_list)
    
        # Build command with all tasks - daemon is enabled by default in Gradle
        # Use --console=plain for clean, parseable output without ANSI codes
        cmd = [str(self.wrapper_script), "--console=plain"] + task_list
    
        if args:
            cmd.extend(args)
    
        logger.info(f"Executing: {' '.join(cmd)}")
    
        # Track this build in the dashboard
        build_id = None
        try:
            build_id = _log_store.start_build(" ".join(task_list))
        except Exception:
            pass
    
        process: asyncio.subprocess.Process | None = None
        try:
            process = await asyncio.create_subprocess_exec(
                *cmd,
                cwd=str(self.project_root),
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
                env=self._build_execution_environment(),
            )
    
            # Notify dashboard that a task started (may have spawned a new daemon)
            if _emit_daemons_changed:
                try:
                    _emit_daemons_changed()
                except Exception:
                    pass
    
            # Stream output in real-time while collecting it
            stdout_lines: list[str] = []
            stderr_lines: list[str] = []
    
            async def read_stream(
                stream: asyncio.StreamReader, lines: list[str], is_stderr: bool = False
            ) -> None:
                while True:
                    line = await stream.readline()
                    if not line:
                        break
                    decoded = line.decode().rstrip()
                    lines.append(decoded)
                    # Log to shared log_store for dashboard
                    if _log_store and decoded.strip():
                        level = _classify_gradle_log_level(decoded, is_stderr)
                        _log_store.add_log(decoded, level)
    
            # Handle timeout with streaming
            try:
                if timeout:
                    await asyncio.wait_for(
                        asyncio.gather(
                            read_stream(process.stdout, stdout_lines, False),
                            read_stream(process.stderr, stderr_lines, True),
                        ),
                        timeout=timeout,
                    )
                else:
                    await asyncio.gather(
                        read_stream(process.stdout, stdout_lines, False),
                        read_stream(process.stderr, stderr_lines, True),
                    )
                await process.wait()
            except asyncio.TimeoutError:
                # Graceful termination first
                logger.warning(f"Task timed out after {timeout} seconds, terminating process...")
                process.terminate()
                try:
                    await asyncio.wait_for(process.wait(), timeout=5)
                except asyncio.TimeoutError:
                    # Force kill if graceful termination fails
                    logger.warning("Process did not terminate gracefully, killing...")
                    process.kill()
                    await process.wait()
                if daemon_monitor:
                    daemon_monitor.add_log("gradle", f"Task timed out after {timeout} seconds", "ERROR")
                return {"success": False, "error": f"Task timed out after {timeout} seconds"}
    
            stdout = "\n".join(stdout_lines)
            stderr = "\n".join(stderr_lines)
    
            # Log output for debugging
            for line in stdout_lines:
                if line.strip():
                    logger.debug(line)
            for line in stderr_lines:
                if line.strip():
                    logger.debug(line)
    
            # Report progress if context is available (final progress)
            if ctx:
                await ctx.report_progress(progress=100, total=100)
    
            if process.returncode == 0:
                logger.info(f"Task {task_list} completed successfully")
                # End build tracking
                if build_id and _log_store:
                    try:
                        _log_store.end_build(build_id)
                    except Exception:
                        pass
                return {"success": True, "error": None}
            else:
                # Extract structured error information
                error_info = self._extract_structured_error(stdout, stderr, "Task failed")
                logger.error(f"Task {task_list} failed: {error_info.summary}")
                # End build tracking
                if build_id and _log_store:
                    try:
                        _log_store.end_build(build_id)
                    except Exception:
                        pass
                return {"success": False, "error": error_info}
    
        except Exception as e:
            # Ensure process cleanup on any exception
            if process and process.returncode is None:
                logger.warning(f"Exception occurred, cleaning up process: {e}")
                process.terminate()
                try:
                    await asyncio.wait_for(process.wait(), timeout=5)
                except asyncio.TimeoutError:
                    process.kill()
                    await process.wait()
            logger.error(f"Task {task_list} failed with exception: {e}")
            # End build tracking
            if build_id and _log_store:
                try:
                    _log_store.end_build(build_id)
                except Exception:
                    pass
            # Return structured error for exceptions
            return {
                "success": False,
                "error": ErrorInfo(
                    summary=str(e),
                    failed_tasks=[],
                    compilation_errors=[],
                ),
            }
  • Pydantic output schema for the run_task tool: indicates success and optional structured error details
    class TaskResult(BaseModel):
        """Result of running a Gradle task."""
    
        success: bool
        error: ErrorInfo | None = None
  • Core error schema used in TaskResult: includes build failure summary, list of failed tasks, and parsed compilation errors
    class ErrorInfo(BaseModel):
        """Structured error information."""
    
        summary: str  # e.g., "Build failed: 2 tasks failed with 12 compilation errors"
        failed_tasks: list[FailedTask]  # List of failed tasks
        compilation_errors: list[CompilationError]  # Deduplicated, first occurrence only
  • Schema for individual compilation errors parsed from Gradle output
    class CompilationError(BaseModel):
        """A single compilation error."""
    
        file: str  # Full path without file:// prefix
        line: int
        column: int | None
        message: str
  • Schema for failed Gradle tasks parsed from output
    class FailedTask(BaseModel):
        """Information about a failed task."""
    
        name: str  # e.g., ":composeApp:compileTestKotlinIosArm64"
        reason: str  # e.g., "Compilation finished with errors"

Tool Definition Quality

Score is being calculated. Check back soon.

Install Server

Other Tools

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/jermeyyy/gradle-mcp'

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