Skip to main content
Glama

list_resources

List and filter resources in Android projects using Apktool MCP Server. Specify resource types to locate specific files efficiently.

Instructions

List resources in a project, optionally filtered by resource type.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_dirYes
resource_typeNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The handler function implementing the list_resources tool. Lists resources from an APKTool decoded project directory, either all resource types with counts or files in a specific resource type directory. Supports pagination via offset and count parameters. Includes input validation and detailed error handling.
    async def list_resources(
        project_dir: str,
        resource_type: Optional[str] = None,
        offset: int = 0,
        count: int = 0
    ) -> Dict:
        """
        List resources with pagination support and enhanced metadata.
        
        Args:
            project_dir: Path to the APKTool project directory
            resource_type: Optional resource type to filter by (e.g., "layout", "drawable")
            offset: Starting offset for pagination
            count: Number of items to return (0 means use default)
            
        Returns:
            Paginated dictionary with list of resources and metadata
        """
        # Input validation
        path_validation = ValidationUtils.validate_path(project_dir, must_exist=True)
        if not path_validation["valid"]:
            return {"success": False, "error": path_validation["error"]}
        
        res_path = os.path.join(project_dir, "res")
        if not os.path.exists(res_path):
            return {
                "success": False,
                "error": f"Resources directory not found: {res_path}"
            }
        
        try:
            if resource_type:
                # List resources of specific type
                type_path = os.path.join(res_path, resource_type)
                if not os.path.exists(type_path):
                    # Get available resource types
                    resource_types = [
                        d for d in os.listdir(res_path)
                        if os.path.isdir(os.path.join(res_path, d))
                    ]
                    
                    return {
                        "success": False,
                        "error": f"Resource type directory not found: {resource_type}",
                        "available_types": resource_types
                    }
                
                resources = []
                for item in os.listdir(type_path):
                    item_path = os.path.join(type_path, item)
                    if os.path.isfile(item_path):
                        resources.append({
                            "name": item,
                            "path": item_path,
                            "size": os.path.getsize(item_path),
                            "type": resource_type,
                            "extension": os.path.splitext(item)[1],
                            "modified_time": os.path.getmtime(item_path)
                        })
                
                # Sort by name
                resources.sort(key=lambda x: x["name"])
                
                # Apply pagination
                paginated_result = PaginationUtils.handle_pagination(
                    items=resources,
                    offset=offset,
                    count=count,
                    data_type="resources",
                    items_key="resources"
                )
                
                paginated_result["success"] = True
                paginated_result["resource_type"] = resource_type
                paginated_result["resource_path"] = type_path
                
                return paginated_result
            
            else:
                # List all resource types with counts
                resource_types = []
                for item in os.listdir(res_path):
                    type_path = os.path.join(res_path, item)
                    if os.path.isdir(type_path):
                        try:
                            files = [f for f in os.listdir(type_path) if os.path.isfile(os.path.join(type_path, f))]
                            resource_count = len(files)
                            
                            # Calculate total size
                            total_size = 0
                            for f in files:
                                try:
                                    total_size += os.path.getsize(os.path.join(type_path, f))
                                except:
                                    pass
                            
                            resource_types.append({
                                "type": item,
                                "path": type_path,
                                "count": resource_count,
                                "total_size": total_size
                            })
                        except Exception as e:
                            logger.warning(f"Error processing resource type {item}: {e}")
                            resource_types.append({
                                "type": item,
                                "path": type_path,
                                "count": 0,
                                "total_size": 0,
                                "error": str(e)
                            })
                
                # Sort by type name
                resource_types.sort(key=lambda x: x["type"])
                
                # Apply pagination
                paginated_result = PaginationUtils.handle_pagination(
                    items=resource_types,
                    offset=offset,
                    count=count,
                    data_type="resource-types",
                    items_key="resource_types"
                )
                
                paginated_result["success"] = True
                
                return paginated_result
            
        except Exception as e:
            logger.error(f"Error listing resources: {str(e)}")
            return {
                "success": False,
                "error": f"Failed to list resources: {str(e)}"
            }
  • The @mcp.tool() decorator registers the list_resources function as an MCP tool.
    async def list_resources(
  • PaginationUtils class providing handle_pagination method used by list_resources for paginated responses.
    class PaginationUtils:
        """Utility class for handling pagination across different MCP tools"""
        
        # Configuration constants
        DEFAULT_PAGE_SIZE = 100
        MAX_PAGE_SIZE = 10000
        MAX_OFFSET = 1000000
        
        @staticmethod
        def validate_pagination_params(offset: int, count: int) -> tuple[int, int]:
            """Validate and normalize pagination parameters"""
            offset = max(0, min(offset, PaginationUtils.MAX_OFFSET))
            count = max(0, min(count, PaginationUtils.MAX_PAGE_SIZE))
            return offset, count
        
        @staticmethod
        def handle_pagination(
            items: List[Any],
            offset: int = 0,
            count: int = 0,
            data_type: str = "paginated-list",
            items_key: str = "items",
            item_transformer: Optional[Callable[[Any], Any]] = None
        ) -> Dict[str, Any]:
            """
            Generic pagination handler for list data
            
            Args:
                items: List of items to paginate
                offset: Starting offset
                count: Number of items to return (0 means use default)
                data_type: Type identifier for the response
                items_key: Key name for items in response
                item_transformer: Optional function to transform items
                
            Returns:
                Paginated response dictionary
            """
            if items is None:
                items = []
                
            total_items = len(items)
            
            # Validate parameters
            offset, count = PaginationUtils.validate_pagination_params(offset, count)
            
            # Determine effective limit
            if count == 0:
                effective_limit = min(PaginationUtils.DEFAULT_PAGE_SIZE, max(0, total_items - offset))
            else:
                effective_limit = min(count, max(0, total_items - offset))
            
            # Calculate bounds
            start_index = min(offset, total_items)
            end_index = min(start_index + effective_limit, total_items)
            has_more = end_index < total_items
            
            # Extract and transform paginated subset
            paginated_items = items[start_index:end_index]
            if item_transformer:
                paginated_items = [item_transformer(item) for item in paginated_items]
            
            # Build response
            result = {
                "type": data_type,
                items_key: paginated_items,
                "pagination": {
                    "total": total_items,
                    "offset": offset,
                    "limit": effective_limit,
                    "count": len(paginated_items),
                    "has_more": has_more
                }
            }
            
            # Add navigation helpers
            if has_more:
                result["pagination"]["next_offset"] = end_index
                
            if offset > 0:
                prev_offset = max(0, offset - effective_limit)
                result["pagination"]["prev_offset"] = prev_offset
                
            # Page calculations
            if effective_limit > 0:
                current_page = (offset // effective_limit) + 1
                total_pages = (total_items + effective_limit - 1) // effective_limit
                result["pagination"]["current_page"] = current_page
                result["pagination"]["total_pages"] = total_pages
                result["pagination"]["page_size"] = effective_limit
                
            return result
  • ValidationUtils class providing validate_path used at the start of list_resources for input validation.
    class ValidationUtils:
        """Utility class for input validation"""
        
        @staticmethod
        def validate_path(path: str, must_exist: bool = True) -> Dict[str, Union[bool, str]]:
            """Validate file/directory path"""
            if not path or not isinstance(path, str):
                return {"valid": False, "error": "Path cannot be empty"}
                
            if must_exist and not os.path.exists(path):
                return {"valid": False, "error": f"Path does not exist: {path}"}
                
            return {"valid": True}
        
        @staticmethod
        def validate_class_name(class_name: str) -> Dict[str, Union[bool, str]]:
            """Validate Java class name format"""
            if not class_name or not isinstance(class_name, str):
                return {"valid": False, "error": "Class name cannot be empty"}
                
            if not class_name.replace('.', '').replace('_', '').replace('$', '').replace('/', '').isalnum():
                return {"valid": False, "error": "Invalid class name format"}
                
            return {"valid": True}
         
        @staticmethod
        def validate_search_pattern(pattern: str) -> Dict[str, Union[bool, str]]:
            """Validate search pattern"""
            if not pattern or not isinstance(pattern, str):
                return {"valid": False, "error": "Search pattern cannot be empty"}
                
            if len(pattern) > 1000:
                return {"valid": False, "error": "Search pattern too long (max 1000 characters)"}
                
            return {"valid": True}
Behavior2/5

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

No annotations are provided, so the description carries full burden but lacks behavioral details. It doesn't specify if this is a read-only operation, what the output format is (though an output schema exists), or any performance considerations like pagination. The description is minimal and doesn't add meaningful context beyond the basic action.

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 a single, efficient sentence that front-loads the core purpose and includes the optional filtering detail. There is no wasted verbiage, making it highly concise and well-structured for quick understanding.

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

Completeness3/5

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

Given the tool's low complexity (2 parameters, no nested objects) and the presence of an output schema, the description is minimally adequate. However, with no annotations and low schema coverage, it should provide more context on usage and parameters to be fully complete, especially for a tool in a set with many siblings.

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

Parameters3/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 but only partially does. It mentions optional filtering by resource type, which aligns with the resource_type parameter, but doesn't explain project_dir or provide examples of resource types. The description adds some meaning but doesn't fully cover the two parameters.

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 ('List resources') and target ('in a project'), with optional filtering by resource type. It distinguishes from siblings like list_smali_directories or list_smali_files by focusing on general resources rather than specific file types, though it doesn't explicitly contrast them.

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

Usage Guidelines2/5

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

No guidance is provided on when to use this tool versus alternatives. For example, it doesn't mention when to choose list_resources over get_resource_file for accessing specific resources or how it relates to search_in_files for finding content. The optional filtering is noted but without context on typical use cases.

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

Related 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/zinja-coder/apktool-mcp-server'

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