Skip to main content
Glama

get_task_history

Retrieve detailed task history including status changes, assignments, milestones, and activity summaries to track progress and accountability over time.

Instructions

Get comprehensive history for a specific task.

Returns aggregated task history including:

  • Status history: Timeline of status changes with timestamps and responsible users

  • Assignment history: Who worked on the task and when assignments changed

  • Milestones: Key deliverables and completion markers from comments and activities

  • Activity summary: Counts of comments, changes, status updates, assignments, and milestones

Examples: get_task_history(14677921) # Default 30-day history get_task_history(14677921, hours=168) # Last week only get_task_history(14677921,1 hours=24) # Last 24 hours

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
task_idYesThe unique Productive task identifier (internal ID)
hoursNoNumber of hours to look back for activity history (default: 720 = 30 days)

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • Core handler implementation that aggregates task history by fetching recent activities, parsing changesets for status and assignment updates, identifying milestones from comments, and compiling a comprehensive summary.
    async def get_task_history(
        ctx: Context,
        task_id: int,
        hours: int = 720  # Default to 30 days for comprehensive history
    ) -> ToolResult:
        """Get comprehensive history for a specific task.
    
        Developer notes:
        - Aggregates historical data from activities and task events
        - Returns status history, assignment history, milestones, and activity summary
        - Ignores unavailable data gracefully (empty arrays/null values)
        - Uses get_recent_activity with task_id filter for historical data
        - Now extracts status transitions from changeset (workflow_status_id)
        """
        try:
            # Get the task details first to verify it exists
            await ctx.info(f"Fetching history for task {task_id}")
            task_result = await get_task(ctx, task_id)
    
            if not task_result.get("data"):
                await ctx.error(f"Task {task_id} not found")
                return {
                    "task_id": task_id,
                    "error": "Task not found",
                    "status_history": [],
                    "assignment_history": [],
                    "milestones": [],
                    "activity_summary": {}
                }
    
            # Get recent activities for this task (comprehensive history)
            activity_result = await get_recent_activity(
                ctx,
                hours=hours,
                task_id=task_id,
                max_results=200  # Maximum allowed by API
            )
    
            activities = activity_result.get("data", [])
    
            # Parse activities to extract status changes, assignments, milestones
            status_history = []
            assignment_history = []
            milestones = []
    
            for activity in activities:
                attributes = activity.get("attributes", {})
                event_type = attributes.get("event")
                item_type = attributes.get("item_type")
                created_at = attributes.get("created_at")
                person_name = attributes.get("person_name", "Unknown")
    
                # Status changes (workflow_status_id in changeset)
                changeset = attributes.get("changeset", [])
                if item_type and item_type.lower() == "task" and event_type in ["update", "edit"]:
                    for change in changeset:
                        if "workflow_status_id" in change:
                            status_from = change["workflow_status_id"][0]["value"] if len(change["workflow_status_id"]) > 0 else None
                            status_to = change["workflow_status_id"][1]["value"] if len(change["workflow_status_id"]) > 1 else None
                            status_history.append({
                                "from": status_from,
                                "to": status_to,
                                "changed_at": created_at
                            })
    
                # Assignment changes (parse assignee in changeset)
                if item_type and item_type.lower() == "task" and event_type in ["update", "edit"]:
                    for change in changeset:
                        if "assignee" in change:
                            # The changeset shows the new assignee only
                            assignee_to = change["assignee"][0]["value"] if len(change["assignee"]) > 0 else None
                            assignment_history.append({
                                "assigned_to": assignee_to,
                                "changed_at": created_at
                            })
    
                # Milestones (comments with milestone keywords or custom fields)
                if item_type and item_type.lower() == "comment" and "milestone" in attributes.get("item_name", "").lower():
                    milestones.append({
                        "milestone": attributes.get("item_name", "Milestone"),
                        "completed_at": created_at,
                        "completed_by": person_name
                    })
    
            # Build activity summary
            activity_summary = {
                "total_activities": len(activities),
                "total_comments": len([a for a in activities if a.get("attributes", {}).get("item_type") == "Comment"]),
                "total_changes": len([a for a in activities if a.get("attributes", {}).get("item_type") == "Task"]),
                "total_status_changes": len(status_history),
                "total_assignments": len(assignment_history),
                "total_milestones": len(milestones)
            }
    
            # Build final history response
            history_response = {
                "task_id": task_id,
                "status_history": status_history,
                "assignment_history": assignment_history,
                "milestones": milestones,
                "activity_summary": activity_summary
            }
    
            await ctx.info(f"Successfully retrieved history for task {task_id}")
            return history_response
    
        except ProductiveAPIError as e:
            await _handle_productive_api_error(ctx, e, f"task history for {task_id}")
        except Exception as e:
            await ctx.error(f"Unexpected error fetching task history: {str(e)}")
            raise e
  • server.py:271-300 (registration)
    MCP tool registration using @mcp.tool decorator. Includes input schema validation via Annotated Fields with descriptions and constraints, comprehensive docstring with examples, and delegates to the core handler in tools.py.
    @mcp.tool
    async def get_task_history(
        ctx: Context,
        task_id: Annotated[
            int, Field(description="The unique Productive task identifier (internal ID)")
        ],
        hours: Annotated[
            int,
            Field(
                description="Number of hours to look back for activity history (default: 720 = 30 days)",
                ge=1,
                le=8760  # 1 year max
            )
        ] = 720
    ) -> Dict[str, Any]:
        """Get comprehensive history for a specific task.
    
        Returns aggregated task history including:
        - Status history: Timeline of status changes with timestamps and responsible users
        - Assignment history: Who worked on the task and when assignments changed
        - Milestones: Key deliverables and completion markers from comments and activities
        - Activity summary: Counts of comments, changes, status updates, assignments, and milestones
    
        Examples:
            get_task_history(14677921)  # Default 30-day history
            get_task_history(14677921, hours=168)  # Last week only
            get_task_history(14677921,1 hours=24)  # Last 24 hours
        """
        return await tools.get_task_history(ctx, task_id, hours)
  • Pydantic schema definitions for tool inputs using Annotated and Field, specifying types, descriptions, and validation constraints (hours: 1-8760). Part of the tool registration.
    task_id: Annotated[
        int, Field(description="The unique Productive task identifier (internal ID)")
    ],
    hours: Annotated[
        int,
        Field(
            description="Number of hours to look back for activity history (default: 720 = 30 days)",
            ge=1,
            le=8760  # 1 year max
        )
    ] = 720
Behavior3/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 describes what data is returned (status history, assignment history, milestones, activity summary) and includes examples with time ranges, but does not disclose behavioral traits like rate limits, authentication needs, or error handling. It adds some context but lacks comprehensive behavioral details.

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 well-structured with a clear purpose statement, bulleted list of return components, and practical examples. It is front-loaded and efficient, though the examples could be slightly more concise (e.g., redundant '1 hours=24' typo).

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

Completeness4/5

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

Given the tool's moderate complexity, 100% schema coverage, and presence of an output schema, the description is fairly complete. It covers purpose, return data, and parameter usage, but could improve by adding more behavioral context (e.g., permissions, limits) since no annotations are provided.

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?

Schema description coverage is 100%, providing good documentation for task_id and hours. The description adds value by explaining the default 30-day history and giving examples with different hour values, clarifying practical usage beyond the schema's technical details.

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

Purpose5/5

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

The description clearly states the verb 'Get' and resource 'comprehensive history for a specific task', specifying it returns aggregated task history with detailed components. It distinguishes from siblings like get_task (likely basic info) or get_recent_activity (broader scope) by focusing on historical data for one task.

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

Usage Guidelines3/5

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

The description implies usage for retrieving historical data on a specific task, but does not explicitly state when to use this vs. alternatives like get_task (for current state) or get_recent_activity (for general activity). The examples show parameter usage but lack contextual guidance on tool selection.

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/druellan/Productive-GET-MCP'

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