Skip to main content
Glama
server.py9.78 kB
"""MCP Server for running Gradle tasks.""" import os from pathlib import Path from fastmcp import Context, FastMCP from pydantic import BaseModel from gradle_mcp.gradle import GradleWrapper # MCP Server instructions for AI agents MCP_INSTRUCTIONS = """ # *gradle-mcp* This MCP server provides tools to interact with Gradle projects without using command-line operations. Use these tools instead of running `./gradlew` or `gradle` commands directly in the terminal. ## Usage Guidelines - use `list_projects` to explore available projects - use `list_project_tasks` to see tasks before running them - Use `run_task` for building, testing, assembling, etc. - Always use `clean` tool for cleaning operations, never `run_task` with clean tasks - Use qualified task names (e.g., ':app:build') when targeting specific subprojects - Pass '--info' or '--debug' in args for verbose output when troubleshooting """ # Initialize FastMCP server mcp = FastMCP(name="gradle-mcp", instructions=MCP_INSTRUCTIONS) class TaskResult(BaseModel): """Result of running a Gradle task.""" success: bool error: str | None = None class ProjectInfo(BaseModel): """Information about a Gradle project.""" name: str path: str description: str | None = None class TaskInfo(BaseModel): """Information about a Gradle task.""" name: str project: str description: str | None = None group: str | None = None class TaskWithDescriptionInfo(BaseModel): """Task with its description.""" name: str description: str class GroupedTasksInfo(BaseModel): """Tasks grouped by their group name. When include_descriptions is True, tasks contains TaskWithDescriptionInfo objects. When include_descriptions is False, tasks contains task name strings. """ group: str tasks: list[TaskWithDescriptionInfo] | list[str] def _get_gradle_wrapper(ctx: Context | None = None) -> GradleWrapper: """Get a GradleWrapper instance, optionally using context to determine project root. Respects the following environment variables: - GRADLE_PROJECT_ROOT: Root directory of Gradle project (default: current directory) - GRADLE_WRAPPER: Path to Gradle wrapper script (optional, auto-detected if not set) Args: ctx: MCP Context (optional). Returns: GradleWrapper instance. Raises: FileNotFoundError: If Gradle wrapper cannot be found. """ project_root = os.getenv("GRADLE_PROJECT_ROOT") or os.getcwd() wrapper_path = os.getenv("GRADLE_WRAPPER") gradle = GradleWrapper(project_root) # If a custom wrapper path is specified, override the auto-detected one if wrapper_path: wrapper_path_obj = Path(wrapper_path) if not wrapper_path_obj.exists(): raise FileNotFoundError( f"Gradle wrapper not found at specified path: {wrapper_path}. " "Please verify GRADLE_WRAPPER environment variable." ) gradle.wrapper_script = wrapper_path_obj return gradle @mcp.tool() async def list_projects(ctx: Context) -> list[ProjectInfo]: """List all Gradle projects in the workspace. Returns: List of Gradle projects. """ try: await ctx.info("Listing all Gradle projects") gradle = _get_gradle_wrapper(ctx) projects = gradle.list_projects() await ctx.info(f"Found {len(projects)} projects: {', '.join(p.name for p in projects)}") return [ ProjectInfo( name=p.name, path=p.path, description=p.description, ) for p in projects ] except Exception as e: raise ValueError(f"Failed to list projects: {str(e)}") from e @mcp.tool() async def list_project_tasks( project: str | None = None, include_descriptions: bool = False, group: str | None = None, ctx: Context | None = None, ) -> list[GroupedTasksInfo]: """List all tasks available in a Gradle project. Returns a nested structure grouped by task group. This is more compact than a flat list and avoids repeating project/group information for each task. Args: project: Project path (e.g., ':app' or 'lib:module'). Use None, empty string, or ':' for root project. include_descriptions: If True, include task descriptions in the response. If False, return only task names for a more compact response. group: Optional group name to filter tasks (e.g., 'Build', 'Verification'). Case-insensitive. If not provided, all groups are returned. Returns: List of grouped tasks. Each group contains a group name and list of tasks. If include_descriptions is True, tasks include name and description. If include_descriptions is False, tasks are just name strings. """ try: if ctx: await ctx.info(f"Listing tasks for project: {project or 'root'}") gradle = _get_gradle_wrapper(ctx) # Normalize root project: None, empty, or ":" all mean root project_arg = project if project and project != "" else ":" grouped_tasks = gradle.list_tasks(project_arg, include_descriptions, group) if ctx: total_tasks = sum(len(g.tasks) for g in grouped_tasks) group_info = f" (filtered by group '{group}')" if group else "" await ctx.info( f"Found {total_tasks} tasks in {len(grouped_tasks)} groups for project {project_arg}{group_info}" ) # Convert to response model result = [] for group in grouped_tasks: if include_descriptions: # Tasks are TaskWithDescription objects tasks = [ TaskWithDescriptionInfo(name=t.name, description=t.description) for t in group.tasks ] else: # Tasks are already strings tasks = group.tasks result.append(GroupedTasksInfo(group=group.group, tasks=tasks)) return result except Exception as e: raise ValueError(f"Failed to list tasks: {str(e)}") from e @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) # run_task handles multiple tasks and progress reporting result = await gradle.run_task(tasks, args, ctx) # 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=str(e), ) @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) # clean now handles progress reporting internally by parsing Gradle output result = await gradle.clean(project, ctx) # 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=str(e), ) def main() -> None: """Run the MCP server.""" mcp.run() if __name__ == "__main__": main()

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