Skip to main content
Glama
stevereiner
by stevereiner

search_content

Search for content in Alfresco using AFTS query language to find documents and files based on specific criteria.

Instructions

Search for content in Alfresco using AFTS query language.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYes
max_resultsNo
node_typeNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • Core handler implementation: Performs Alfresco content search using AFTS query syntax. Validates inputs, ensures connection, executes search via search_client, formats results with node details (name, ID, type, created date). Handles progress reporting and errors.
    async def search_content_impl(
        search_query: str,
        max_results: int = 25,
        node_type: str = "cm:content",
        ctx: Optional[Context] = None
    ) -> str:
        """Search for content in Alfresco repository.
        
        Args:
            search_query: Search query string
            max_results: Maximum number of results to return (default: 25)
            node_type: Type of nodes to search for (default: "cm:content" - searches documents)
            ctx: MCP context for progress reporting
        
        Returns:
            Formatted search results
        """
        # Parameter validation and extraction
        try:
            # Extract parameters with fallback handling
            if hasattr(search_query, 'value'):
                actual_query = str(search_query.value)
            else:
                actual_query = str(search_query)
                
            if hasattr(max_results, 'value'):
                actual_max_results = int(max_results.value)
            else:
                actual_max_results = int(max_results)
                
            if hasattr(node_type, 'value'):
                actual_node_type = str(node_type.value)
            else:
                actual_node_type = str(node_type)
            
            # Default to cm:content if empty
            if not actual_node_type.strip():
                actual_node_type = "cm:content"
            
            # Clean and normalize for display (prevent Unicode encoding issues)
            safe_query_display = safe_format_output(str(actual_query))
            safe_node_type_display = safe_format_output(str(actual_node_type))
            
        except Exception as e:
            logger.error(f"Parameter extraction error: {e}")
            return safe_format_output(f"ERROR: Parameter error: {str(e)}")
        
        if not actual_query.strip():
            return """Content Search Tool
    
    Usage: Provide a search query to search Alfresco repository content.
    
    Example searches:
    - admin (finds items with 'admin' in name or content)
    - name:test* (finds items with names starting with 'test')
    - modified:[2024-01-01 TO 2024-12-31] (finds items modified in 2024)
    - TYPE:"cm:content" (finds all documents)
    - TYPE:"cm:folder" (finds all folders)
    
    Search uses AFTS (Alfresco Full Text Search) syntax for flexible content discovery.
    By default, searches for documents (cm:content) unless a different type is specified.
    """
        
        if ctx:
            await ctx.info(safe_format_output(f"Content search for: '{safe_query_display}'"))
            await ctx.report_progress(0.0)
        
        try:
            # Get all clients that ensure_connection() already created
            master_client = await ensure_connection()
            
            # Import search_utils
            from python_alfresco_api.utils import search_utils
            
            # Access the search client that was already created
            search_client = master_client.search
            
            logger.info(f"Content search for: '{safe_query_display}', type: '{safe_node_type_display}'")
            
            if ctx:
                await ctx.report_progress(0.3)
            
            # Build search query to include node_type filter
            final_query = actual_query
            
            # Add node_type filter if not already in query
            has_type_in_query = "TYPE:" in final_query.upper()
            if not has_type_in_query:
                if final_query == "*":
                    final_query = f'TYPE:"{actual_node_type}"'
                else:
                    final_query = f'({final_query}) AND TYPE:"{actual_node_type}"'
            
            # Use the correct working pattern: search_utils.simple_search with existing search_client
            try:
                search_results = search_utils.simple_search(search_client, final_query, max_items=actual_max_results)
                
                if search_results and hasattr(search_results, 'list_'):
                    entries_list = search_results.list_.entries if search_results.list_  else []
                    logger.info(f"Found {len(entries_list)} content search results")
                    
                    if ctx:
                        await ctx.report_progress(1.0)
                    
                    if not entries_list:
                        return "0"
                    
                    result_text = f"Found {len(entries_list)} item(s) matching the search query:\n\n"
                    
                    for i, entry in enumerate(entries_list, 1):
                        # Debug: Log the entry structure
                        logger.debug(f"Entry {i} type: {type(entry)}, content: {entry}")
                        
                        # Handle different possible entry structures
                        node = None
                        if isinstance(entry, dict):
                            if 'entry' in entry:
                                node = entry['entry']
                            elif 'name' in entry:  # Direct node structure
                                node = entry
                            else:
                                logger.warning(f"Unknown entry structure: {entry}")
                                continue
                        elif hasattr(entry, 'entry'):  # ResultSetRowEntry object
                            node = entry.entry
                        else:
                            logger.warning(f"Entry is not a dict or ResultSetRowEntry: {type(entry)}")
                            continue
                        
                        if node:
                            # Handle both dict and ResultNode objects
                            if isinstance(node, dict):
                                name = str(node.get('name', 'Unknown'))
                                node_id = str(node.get('id', 'Unknown'))
                                node_type_actual = str(node.get('nodeType', 'Unknown'))
                                created_at = str(node.get('createdAt', 'Unknown'))
                            else:
                                # ResultNode object - access attributes directly
                                name = str(getattr(node, 'name', 'Unknown'))
                                node_id = str(getattr(node, 'id', 'Unknown'))
                                node_type_actual = str(getattr(node, 'node_type', 'Unknown'))
                                created_at = str(getattr(node, 'created_at', 'Unknown'))
                            
                            # Clean JSON-friendly formatting (no markdown syntax)
                            # Apply safe formatting to individual fields to prevent emoji encoding issues
                            safe_name = safe_format_output(name)
                            safe_node_id = safe_format_output(node_id)
                            safe_node_type = safe_format_output(node_type_actual)
                            safe_created_at = safe_format_output(created_at)
                            
                            result_text += f"{i}. {safe_name}\n"
                            result_text += f"   - ID: {safe_node_id}\n"
                            result_text += f"   - Type: {safe_node_type}\n"
                            result_text += f"   - Created: {safe_created_at}\n\n"
                    
                    return safe_format_output(result_text)
                else:
                    return safe_format_output(f"ERROR: Content search failed - invalid response from Alfresco")
                    
            except Exception as e:
                logger.error(f"Content search failed: {e}")
                return safe_format_output(f"ERROR: Content search failed: {str(e)}")
            
        except Exception as e:
            # Preserve Unicode characters in error messages
            error_msg = f"ERROR: Content search failed: {str(e)}"
            if ctx:
                await ctx.error(safe_format_output(error_msg))
            return safe_format_output(error_msg)
    
        if ctx:
            await ctx.info(safe_format_output("Content search completed!")) 
  • Tool registration using @mcp.tool decorator in FastMCP server. Defines input schema via type hints and docstring, delegates execution to search_content_impl.
    @mcp.tool
    async def search_content(
        query: str, 
        max_results: int = 25,
        node_type: str = "",
        ctx: Context = None
    ) -> str:
        """Search for content in Alfresco using AFTS query language."""
        return await search_content_impl(query, max_results, node_type, ctx)
  • Input schema defined by function parameters and type annotations for the search_content tool.
    async def search_content(
        query: str, 
        max_results: int = 25,
        node_type: str = "",
        ctx: Context = None
    ) -> str:
        """Search for content in Alfresco using AFTS query language."""
        return await search_content_impl(query, max_results, node_type, ctx)
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. It mentions 'AFTS query language' which hints at syntax requirements, but lacks critical behavioral details: whether this is read-only, pagination behavior (implied by max_results but not explained), rate limits, authentication needs, or what 'content' includes (documents, folders, etc.).

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

Conciseness4/5

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

The description is a single, efficient sentence with no wasted words. It's appropriately sized for a search tool, though it could be more front-loaded with key differentiators given the sibling context.

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?

With 3 parameters (1 required), 0% schema coverage, no annotations, but an output schema exists, the description is minimally adequate. The output schema likely covers return values, but the description lacks context on search scope, AFTS syntax examples, and differentiation from siblings, leaving gaps for effective use.

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

Parameters2/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 only mentions 'AFTS query language' for the query parameter, but provides no context for max_results (default 25 but no range/limits) or node_type (no examples or valid values). The description adds minimal value beyond the bare schema.

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

Purpose3/5

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

The description states the action ('Search for content') and target ('in Alfresco'), but is vague about scope and differentiation. It mentions 'AFTS query language' which adds specificity, but doesn't clarify what 'content' encompasses or how this differs from sibling tools like 'search_by_metadata' or 'cmis_search'.

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. With multiple search-related siblings (advanced_search, cmis_search, search_by_metadata), the description offers no context about use cases, prerequisites, or exclusions, leaving the agent to guess based on tool names alone.

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/stevereiner/python-alfresco-mcp-server'

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