Skip to main content
Glama
cdmx-in
by cdmx-in

search_goodday_tasks

Search for tasks in Goodday projects using natural language queries with optional filters for projects, assigned users, and task status.

Instructions

Search for tasks using vector similarity search with optional filters.

Args: query: Search query (natural language) limit: Maximum number of results to return (default: 10, max: 50) project_name: Optional project name filter (case-insensitive partial match) user_name: Optional user name/email filter for assigned tasks include_closed: Whether to include closed/completed tasks (default: False)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYes
limitNo
project_nameNo
user_nameNo
include_closedNo

Implementation Reference

  • The primary handler function for the 'search_goodday_tasks' MCP tool. It performs vector similarity search on Goodday tasks via an external search API (GOODDAY_SEARCH_URL), formats results with user/project mappings, and handles optional filters like project_name, user_name, and include_closed.
    @mcp.tool()
    async def search_goodday_tasks(
        query: str, 
        limit: int = 10, 
        project_name: Optional[str] = None, 
        user_name: Optional[str] = None,
        include_closed: bool = False
    ) -> str:
        """Search for tasks using vector similarity search with optional filters.
    
        Args:
            query: Search query (natural language)
            limit: Maximum number of results to return (default: 10, max: 50)
            project_name: Optional project name filter (case-insensitive partial match)
            user_name: Optional user name/email filter for assigned tasks
            include_closed: Whether to include closed/completed tasks (default: False)
        """
        # Validate limit
        if limit > 50:
            limit = 50
        elif limit < 1:
            limit = 1
    
        # Build search parameters
        search_params = {
            "query": query,
            "limit": limit,
            "include_closed": include_closed
        }
    
        # Add optional filters
        if project_name:
            search_params["project_name"] = project_name
        if user_name:
            search_params["user_name"] = user_name
    
        try:
            # Make search request
            search_results = await make_search_request("GET", search_params)
            
            if not search_results:
                return "No results found for your search query."
            
            if isinstance(search_results, dict) and "error" in search_results:
                return f"Search error: {search_results.get('error', 'Unknown error')}"
            
            # Handle different response formats
            results = []
            if isinstance(search_results, dict):
                if "results" in search_results:
                    results = search_results["results"]
                elif "tasks" in search_results:
                    results = search_results["tasks"]
                else:
                    # If it's a single result wrapped in a dict
                    results = [search_results]
            elif isinstance(search_results, list):
                results = search_results
            else:
                return f"Unexpected search results format: {type(search_results)}"
    
            if not results:
                return "No tasks found matching your search criteria."
    
            # Get user mapping for display
            user_id_to_name = await get_user_mapping()
            
            def user_display(user_id):
                if not user_id:
                    return "Unassigned"
                name = user_id_to_name.get(user_id)
                return name if name else f"User {user_id}"
    
            # Format results
            formatted_results = []
            for i, task in enumerate(results, 1):
                if not isinstance(task, dict):
                    continue
    
                # Extract task information
                task_id = task.get("shortId", task.get("id", "N/A"))
                task_name = task.get("name", "No title")
                
                # Handle status information
                status = task.get("status", {})
                if isinstance(status, dict):
                    status_name = status.get("name", "Unknown")
                else:
                    status_name = str(status) if status else "Unknown"
                
                # Handle project information
                project = task.get("project", {})
                if isinstance(project, dict):
                    project_name = project.get("name", "Unknown Project")
                else:
                    project_name = str(project) if project else "Unknown Project"
                
                # Handle assigned user
                assigned_user_id = task.get("assignedToUserId")
                assigned_user = user_display(assigned_user_id)
                
                # Handle priority
                priority = task.get("priority", "N/A")
                
                # Handle dates
                start_date = format_timestamp_ist(task.get("startDate")) if task.get("startDate") else "N/A"
                end_date = format_timestamp_ist(task.get("endDate")) if task.get("endDate") else "N/A"
                
                # Handle description/message
                description = task.get("message", task.get("description", "No description"))
                
                # Handle similarity score if available
                score_info = ""
                if "score" in task:
                    score_info = f" (Similarity: {task['score']:.3f})"
                elif "_score" in task:
                    score_info = f" (Similarity: {task['_score']:.3f})"
    
                formatted_result = f"""
    **{i}. {task_id}**: {task_name}{score_info}
    - **Project**: {project_name}
    - **Status**: {status_name}
    - **Assigned To**: {assigned_user}
    - **Priority**: {priority}
    - **Start Date**: {start_date}
    - **End Date**: {end_date}
    - **Description**: {description[:200]}{'...' if len(description) > 200 else ''}
    """.strip()
                formatted_results.append(formatted_result)
    
            result_text = "\n---\n".join(formatted_results)
            filter_info = []
            if project_name:
                filter_info.append(f"Project: {project_name}")
            if user_name:
                filter_info.append(f"User: {user_name}")
            if include_closed:
                filter_info.append("Including closed tasks")
            
            filter_text = f" (Filters: {', '.join(filter_info)})" if filter_info else ""
            
            return f"**Search Results for '{query}'{filter_text}:**\nFound {len(results)} result(s)\n\n{result_text}"
    
        except Exception as e:
            return f"Search error: {str(e)}"
  • Core helper function used by search_goodday_tasks to make authenticated HTTP requests to the external Goodday search API using GOODDAY_SEARCH_BEARER_TOKEN and GOODDAY_SEARCH_URL.
    async def make_search_request(method: str = "GET", params: dict = None) -> dict:
        """Make a request to the search API with bearer token authentication."""
        search_url = os.getenv("GOODDAY_SEARCH_URL", "")
        bearer_token = os.getenv("GOODDAY_SEARCH_BEARER_TOKEN", "")
        
        if not bearer_token:
            raise ValueError("GOODDAY_SEARCH_BEARER_TOKEN environment variable is required for search API")
        
        if not search_url:
            raise ValueError("GOODDAY_SEARCH_URL environment variable is required for search API")
        
        headers = {
            "User-Agent": USER_AGENT,
            "Authorization": f"Bearer {bearer_token}",
            "Content-Type": "application/json"
        }
        
        url = str(search_url).strip()
        
        async with httpx.AsyncClient() as client:
            try:
                if method.upper() == "GET":
                    response = await client.get(url, headers=headers, params=params, timeout=30.0)
                else:
                    response = await client.request(method.upper(), url, headers=headers, params=params, timeout=30.0)
                
                response.raise_for_status()
                return response.json()
            
            except httpx.HTTPStatusError as e:
                raise Exception(f"Search API HTTP error {e.response.status_code}: {e.response.text}")
            except httpx.RequestError as e:
                raise Exception(f"Search API request error: {str(e)}")
            except Exception as e:
                raise Exception(f"Search API unexpected error: {str(e)}")
  • Helper function called by search_goodday_tasks to resolve user IDs to human-readable names in search results.
    async def get_user_mapping() -> dict:
        """Get mapping of user IDs to names."""
        data = await make_goodday_request("users")
        user_id_to_name = {}
        if isinstance(data, list):
            for u in data:
                if isinstance(u, dict):
                    user_id_to_name[u.get("id")] = u.get("name", "Unknown")
        return user_id_to_name
  • The @mcp.tool() decorator registers the search_goodday_tasks function with the FastMCP server.
    @mcp.tool()
    async def search_goodday_tasks(

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/cdmx-in/goodday-mcp'

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