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
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| limit | No | ||
| project_name | No | ||
| user_name | No | ||
| include_closed | No |
Implementation Reference
- goodday_mcp/main.py:1092-1237 (handler)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)}"
- goodday_mcp/main.py:58-93 (helper)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)}")
- goodday_mcp/main.py:165-173 (helper)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
- goodday_mcp/main.py:1092-1093 (registration)The @mcp.tool() decorator registers the search_goodday_tasks function with the FastMCP server.@mcp.tool() async def search_goodday_tasks(