Skip to main content
Glama
jermeyyy
by jermeyyy

clean

Remove build artifacts from a Gradle project to free disk space and ensure clean builds. Specify a project path or use root project for complete cleanup.

Instructions

Clean build artifacts for a Gradle project.

Args: project: Project path (e.g., ':app'). Use None, empty string, or ':' for root project.

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

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
projectNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
errorNo
successYes

Implementation Reference

  • MCP tool handler for the 'clean' tool. Delegates execution to GradleWrapper.clean while handling MCP context, progress, and logging.
    @mcp.tool()
    async def clean(
        project: str | None = None,
        ctx: Context | None = None,
    ) -> TaskResult:
        """Clean build artifacts for a Gradle project.
    
        Args:
            project: Project path (e.g., ':app').
                    Use None, empty string, or ':' for root project.
    
        Returns:
            TaskResult with success status and error message if failed.
        """
        try:
            if ctx:
                await ctx.info(f"Cleaning project: {project or 'root'}")
    
            gradle = _get_gradle_wrapper(ctx)
            daemon_monitor = _get_daemon_monitor()
    
            # clean now handles progress reporting internally by parsing Gradle output
            result = await gradle.clean(project, 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"Clean completed successfully for project {project or 'root'}")
                else:
                    await ctx.error(
                        f"Clean failed for project {project or 'root'}",
                        extra={"error": result.get("error")},
                    )
    
            return TaskResult(
                success=result["success"],
                error=result.get("error"),
            )
        except Exception as e:
            return TaskResult(
                success=False,
                error=ErrorInfo(
                    summary=str(e),
                    failed_tasks=[],
                    compilation_errors=[],
                ),
            )
  • FastMCP decorator that registers the 'clean' function as an MCP tool.
    @mcp.tool()
  • Pydantic model defining the output schema for the 'clean' tool (and other task tools). Contains success flag and optional structured error info.
    class TaskResult(BaseModel):
        """Result of running a Gradle task."""
    
        success: bool
        error: ErrorInfo | None = None
  • Core implementation in GradleWrapper that executes the actual Gradle clean task via subprocess, handles streaming output, progress, timeouts, error parsing, and dashboard logging.
    async def clean(
        self,
        project: str | None = None,
        ctx: "Context | None" = None,
        timeout: int | None = None,
        daemon_monitor: "DaemonMonitor | None" = None,
    ) -> dict:
        """Run the clean task for a project.
    
        Args:
            project: Project path (e.g., ':app'). Use ':' or empty string or None for root project.
            ctx: FastMCP context for progress reporting and logging.
            timeout: Optional timeout in seconds. If specified, the task will be
                     terminated if it exceeds this duration.
            daemon_monitor: Optional DaemonMonitor for logging to the dashboard.
    
        Returns:
            Dictionary with 'success', 'error' keys.
            - success (bool): True if clean completed successfully
            - error (str or None): Error message if clean failed, None otherwise
    
        Raises:
            subprocess.CalledProcessError: If Gradle command fails.
        """
        # Root project if project is None, empty, or ":"
        is_root = project is None or project == "" or project == ":"
        project_arg = "" if is_root else f"{project}:"
        # Build command - daemon is enabled by default
        # Use --console=plain for clean, parseable output without ANSI codes
        cmd = [str(self.wrapper_script), "--console=plain", f"{project_arg}clean"]
    
        logger.info(f"Executing: {' '.join(cmd)}")
    
        # Track this build in the dashboard
        build_id = None
        try:
            build_id = _log_store.start_build(f"{project_arg}clean")
        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"Clean 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"Clean timed out after {timeout} seconds", "ERROR")
                return {"success": False, "error": f"Clean 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():
                    # Use appropriate log level based on content, not just stderr
                    level = _classify_gradle_log_level(line, is_stderr=True)
                    if level == "ERROR":
                        logger.error(line)
                    elif level == "WARN":
                        logger.warning(line)
                    else:
                        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"Clean completed successfully for project {project or 'root'}")
                # 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, "Clean failed")
                logger.error(f"Clean 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"Clean 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 model used in TaskResult.error for structured error reporting from Gradle clean (and other tasks). Includes failed tasks and 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
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It states the tool performs a 'clean' operation (implying mutation/deletion of artifacts) and mentions the return type 'TaskResult', which adds some behavioral context. However, it doesn't detail side effects (e.g., what artifacts are deleted, whether it's reversible), permissions, or error handling beyond a generic mention, leaving gaps for a mutation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and front-loaded with the core purpose, followed by concise sections for args and returns. Every sentence adds value without redundancy, making it efficient and easy to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity (mutation with one parameter) and the presence of an output schema (which covers return values), the description is mostly complete. It explains the purpose, parameter usage, and return type adequately. However, as a mutation tool with no annotations, it could benefit from more behavioral details like side effects or error specifics.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It provides clear semantics for the single parameter 'project', including examples (':app') and special cases (None, empty string, ':' for root), which adds significant value beyond the bare schema. However, it doesn't explain default behavior or constraints beyond what's shown, keeping it from a perfect score.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('clean build artifacts') and resource ('for a Gradle project'), making the purpose immediately understandable. However, it doesn't explicitly differentiate from sibling tools like 'run_task' which might also perform build operations, so it doesn't reach the highest score.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage context (cleaning build artifacts in Gradle projects) but doesn't provide explicit guidance on when to use this versus alternatives like 'run_task' with a clean target, or mention prerequisites like needing a Gradle project setup. It's adequate but lacks sibling differentiation and exclusion criteria.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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