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

get_task_messages

Retrieve all messages and comments for a specific task in Goodday project management, using the task ID and optional project name for identification.

Instructions

Retrieve all messages/comments for a specific task.

Args: task_short_id: The short ID of the task (e.g., RAD-434) project_name: Optional project name for disambiguation

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
task_short_idYes
project_nameNo

Implementation Reference

  • The primary handler function implementing the 'get_task_messages' MCP tool. It finds the task by short ID (across projects or in specified project), fetches messages via Goodday API, formats them with user names and timestamps, and returns a formatted string.
    async def get_task_messages(task_short_id: str, project_name: Optional[str] = None) -> str:
        """Retrieve all messages/comments for a specific task.
    
        Args:
            task_short_id: The short ID of the task (e.g., RAD-434)
            project_name: Optional project name for disambiguation
        """
        task_id = None
        found_in_project = None
        
        # If project name is provided, use it to find the project
        if project_name:
            matched_project, available_projects = await find_project_by_name(project_name)
            if not matched_project:
                return f"Project '{project_name}' not found. Available projects: {', '.join(available_projects[:10])}{'...' if len(available_projects) > 10 else ''}"
            project_id = matched_project.get("id")
            found_in_project = matched_project.get("name")
            
            # Find the task in the specified project
            tasks_data = await make_goodday_request(f"project/{project_id}/tasks")
            if isinstance(tasks_data, list):
                for task in tasks_data:
                    if isinstance(task, dict) and task.get("shortId") == task_short_id:
                        task_id = task.get("id")
                        break
            
            if not task_id:
                return f"Task with short ID '{task_short_id}' not found in project '{found_in_project}'."
        else:
            # Search across all projects
            projects_data = await make_goodday_request("projects")
            if not projects_data or not isinstance(projects_data, list):
                return "Unable to fetch projects."
            
            for proj in projects_data:
                if isinstance(proj, dict):
                    proj_id = proj.get("id")
                    tasks_data = await make_goodday_request(f"project/{proj_id}/tasks")
                    if isinstance(tasks_data, list):
                        for task in tasks_data:
                            if isinstance(task, dict) and task.get("shortId") == task_short_id:
                                task_id = task.get("id")
                                found_in_project = proj.get("name")
                                break
                if task_id:
                    break
            
            if not task_id:
                return f"Task with short ID '{task_short_id}' not found in any project."
    
        # Get task messages
        messages_data = await make_goodday_request(f"task/{task_id}/messages")
        if not messages_data:
            return f"No messages found for task '{task_short_id}'."
        
        if isinstance(messages_data, dict) and "error" in messages_data:
            return f"Unable to fetch messages: {messages_data.get('error', 'Unknown error')}"
        
        if not isinstance(messages_data, list):
            return f"Unexpected response format: {str(messages_data)}"
    
        # Get user mapping
        user_id_to_name = await get_user_mapping()
        
        def user_display(user_id):
            if not user_id:
                return "N/A"
            name = user_id_to_name.get(user_id)
            return f"{name} ({user_id})" if name else user_id
    
        # Format messages
        formatted_messages = []
        for msg in messages_data:
            if not isinstance(msg, dict):
                continue
    
            formatted_msg = f"""
    **Message ID:** {msg.get('id', 'N/A')}
    **Date Created:** {format_timestamp_ist(msg.get('dateCreated', 'N/A'))}
    **From User:** {user_display(msg.get('fromUserId'))}
    **To User:** {user_display(msg.get('toUserId'))}
    **Message:** {msg.get('message', 'No message content')}
    **Task Status ID:** {msg.get('taskStatusId', 'N/A')}
    """.strip()
            formatted_messages.append(formatted_msg)
    
        result = "\n---\n".join(formatted_messages)
        return f"**Messages for Task '{task_short_id}' in project '{found_in_project}' - {len(messages_data)} messages:**\n\n{result}"
  • The @mcp.tool() decorator registers this function as the MCP tool named 'get_task_messages'.
    async def get_task_messages(task_short_id: str, project_name: Optional[str] = None) -> str:
  • Helper function to get user ID to name mapping, used in get_task_messages for displaying user names in messages.
    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
  • Helper to format timestamps to IST, used in message formatting.
    def format_timestamp_ist(timestamp_str: str) -> str:
        """Format timestamp to IST timezone."""
        if not timestamp_str or timestamp_str == 'N/A':
            return 'N/A'
        try:
            # Parse the timestamp (assuming it's in ISO format)
            dt = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
            # Convert to IST (UTC+5:30)
            ist_offset = timedelta(hours=5, minutes=30)
            ist_time = dt + ist_offset
            return ist_time.strftime('%Y-%m-%d %H:%M:%S IST')
        except Exception:
            return timestamp_str
  • Helper to resolve project by name, used when project_name is provided.
    async def find_project_by_name(project_name: str) -> tuple[Optional[dict], List[str]]:
        """Find project by name (case-insensitive)."""
        projects_data = await make_goodday_request("projects")
        if not projects_data or not isinstance(projects_data, list):
            return None, []
        
        # Filter out system projects (like sprints) to avoid overwhelming the AI
        filtered_projects = [
            proj for proj in projects_data 
            if isinstance(proj, dict) and proj.get("systemType") != "PROJECT"
        ]
        
        project_name_lower = project_name.lower().strip()
        matched_project = None
        for proj in filtered_projects:
            if not isinstance(proj, dict):
                continue
            current_project_name = proj.get("name", "").lower().strip()
            if current_project_name == project_name_lower:
                matched_project = proj
                break
            if (
                project_name_lower in current_project_name
                or current_project_name in project_name_lower
            ):
                matched_project = proj
                break
        
        available_projects = [
            p.get("name", "Unknown")
            for p in projects_data
            if isinstance(p, dict)
        ]
        return matched_project, available_projects
Behavior2/5

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

With no annotations provided, the description carries full burden for behavioral disclosure. It states this is a retrieval operation but doesn't mention important behavioral aspects: whether this requires authentication, rate limits, pagination behavior, error conditions, or what format the messages/comments are returned in. For a read operation with zero annotation coverage, this leaves significant gaps in understanding how the tool behaves.

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 appropriately sized and front-loaded with the core purpose in the first sentence. The parameter documentation is cleanly separated with 'Args:' formatting. There's minimal waste, though the structure could be slightly improved by integrating parameter explanations more naturally rather than as a separate section.

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?

For a read operation with 2 parameters and no output schema, the description covers the basic purpose and parameters adequately. However, it lacks important context about return format, error handling, authentication requirements, and relationship to sibling tools. Without annotations or output schema, the description should do more to explain what the agent can expect from this tool's behavior and results.

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

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description compensates well by explaining both parameters: 'task_short_id' is described with an example format ('RAD-434'), and 'project_name' is explained as 'Optional project name for disambiguation.' This adds meaningful context beyond what the bare schema provides, though it doesn't elaborate on when disambiguation is needed or how the project name affects results.

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 verb ('Retrieve') and resource ('all messages/comments for a specific task'), making the purpose immediately understandable. It distinguishes from siblings like 'get_task' or 'get_task_details' by focusing specifically on messages/comments rather than task metadata. However, it doesn't explicitly contrast with 'add_task_comment' which is the natural sibling for message operations.

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?

The description provides no guidance on when to use this tool versus alternatives. With siblings like 'get_task_details' that might include messages, 'search_goodday_tasks' for broader queries, and 'add_task_comment' for creating messages, there's no indication of when this specific retrieval tool is preferred. The parameter documentation implies usage but doesn't provide contextual guidance.

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

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