Repository: sooperset/mcp-atlassian

Issue:
Unable to fetch timetracking field and specifically originalestimate
When using jira_search or jira_get_issue, even specifyin the field specifically, it is not included in the result
e.g. 
`{
  "issue_key": "PROJ-1212121",
  "fields": "timetracking"
}`

When trying to use jira_get_workload it is returned empty as well, when the fields originalestimate and reamainingestimate exist indeed and are being returned by REST. 
Example REST response:
`{
  "expand": "operations,versionedRepresentations,editmeta,changelog,renderedFields",
  "id": "XXXXXXX",
  "self": "https://XXXXX.atlassian.net/rest/api/2/issue/1212121",
  "key": "DMI-12047",
  "fields": {
    "timetracking": {
      "originalEstimate": "2h",
      "remainingEstimate": "2h",
      "originalEstimateSeconds": 7200,
      "remainingEstimateSeconds": 7200
    }
  }
}`

Hints from issue comments:
@sooperset: Hi, @ryacobi  thanks for reporting! I've implemented support for the Jira timetracking field in #217. If you still have further issues, please let me know!

Solution patch:
diff --git a/src/mcp_atlassian/models/jira.py b/src/mcp_atlassian/models/jira.py
index 2b59af3..5db8490 100644
--- a/src/mcp_atlassian/models/jira.py
+++ b/src/mcp_atlassian/models/jira.py
@@ -444,6 +444,50 @@ class JiraAttachment(ApiModel):
         return result
 
 
+class JiraTimetracking(ApiModel):
+    """
+    Model representing the time tracking information for a Jira issue.
+    """
+
+    original_estimate: str | None = Field(None, alias="originalEstimate")
+    remaining_estimate: str | None = Field(None, alias="remainingEstimate")
+    time_spent: str | None = Field(None, alias="timeSpent")
+    original_estimate_seconds: int | None = Field(None, alias="originalEstimateSeconds")
+    remaining_estimate_seconds: int | None = Field(
+        None, alias="remainingEstimateSeconds"
+    )
+    time_spent_seconds: int | None = Field(None, alias="timeSpentSeconds")
+
+    model_config = {
+        "populate_by_name": True,
+        "str_strip_whitespace": True,
+    }
+
+    @classmethod
+    def from_api_response(
+        cls, data: dict[str, Any], **kwargs: Any
+    ) -> "JiraTimetracking | None":
+        """
+        Create a JiraTimetracking instance from an API response dictionary.
+        Args:
+            data: The timetracking data from the Jira API or None
+            **kwargs: Additional options
+        Returns:
+            A new JiraTimetracking instance or None if data is None
+        """
+        if data is None:
+            return None
+
+        return cls.model_validate(data)
+
+    def to_simplified_dict(self) -> dict[str, Any]:
+        """Convert to a simplified dictionary representation."""
+        # Get the model data with aliases (camelCase keys)
+        data = self.model_dump(by_alias=True)
+        # Exclude None values
+        return {k: v for k, v in data.items() if v is not None}
+
+
 class JiraIssue(ApiModel, TimestampMixin):
     """
     Model representing a Jira issue.
@@ -467,6 +511,7 @@ class JiraIssue(ApiModel, TimestampMixin):
     components: list[str] = Field(default_factory=list)
     comments: list[JiraComment] = Field(default_factory=list)
     attachments: list[JiraAttachment] = Field(default_factory=list)
+    timetracking: JiraTimetracking | None = None
     url: str | None = None
     epic_key: str | None = None
     epic_name: str | None = None
@@ -696,6 +741,12 @@ class JiraIssue(ApiModel, TimestampMixin):
                 if isinstance(attachment, dict):
                     attachments.append(JiraAttachment.from_api_response(attachment))
 
+        # Process timetracking
+        timetracking = None
+        timetracking_data = fields.get("timetracking")
+        if timetracking_data:
+            timetracking = JiraTimetracking.from_api_response(timetracking_data)
+
         # Extract fixVersions safely
         fix_versions = []
         fix_versions_data = fields.get("fixVersions", [])
@@ -842,6 +893,7 @@ class JiraIssue(ApiModel, TimestampMixin):
             fix_versions=fix_versions,
             custom_fields=custom_fields,
             requested_fields=requested_fields,
+            timetracking=timetracking,
         )
 
     def to_simplified_dict(self) -> dict[str, Any]:
@@ -908,6 +960,10 @@ class JiraIssue(ApiModel, TimestampMixin):
                 }
             )
 
+            # Add timetracking if available
+            if self.timetracking:
+                result["timetracking"] = self.timetracking.to_simplified_dict()
+
             # Add all custom fields
             for field_id, value in self.custom_fields.items():
                 result[field_id] = value
@@ -952,6 +1008,9 @@ class JiraIssue(ApiModel, TimestampMixin):
                 "epic_key": lambda: self.epic_key,
                 "epic_name": lambda: self.epic_name,
                 "fix_versions": lambda: self.fix_versions,
+                "timetracking": lambda: self.timetracking.to_simplified_dict()
+                if self.timetracking
+                else None,
             }
 
             # Process each requested field


Test patch:
diff --git a/tests/unit/models/test_jira_models.py b/tests/unit/models/test_jira_models.py
index 154570a..2c4f51a 100644
--- a/tests/unit/models/test_jira_models.py
+++ b/tests/unit/models/test_jira_models.py
@@ -18,6 +18,7 @@ from src.mcp_atlassian.models.jira import (
     JiraSearchResult,
     JiraStatus,
     JiraStatusCategory,
+    JiraTimetracking,
     JiraTransition,
     JiraUser,
     JiraWorklog,
@@ -349,6 +350,70 @@ class TestJiraComment:
         assert simplified["author"] == "Comment User"
 
 
+class TestJiraTimetracking:
+    """Tests for the JiraTimetracking model."""
+
+    def test_from_api_response_with_valid_data(self):
+        """Test creating a JiraTimetracking from valid API data."""
+        data = {
+            "originalEstimate": "2h",
+            "remainingEstimate": "1h 30m",
+            "timeSpent": "30m",
+            "originalEstimateSeconds": 7200,
+            "remainingEstimateSeconds": 5400,
+            "timeSpentSeconds": 1800,
+        }
+
+        timetracking = JiraTimetracking.from_api_response(data)
+
+        assert timetracking.original_estimate == "2h"
+        assert timetracking.remaining_estimate == "1h 30m"
+        assert timetracking.time_spent == "30m"
+        assert timetracking.original_estimate_seconds == 7200
+        assert timetracking.remaining_estimate_seconds == 5400
+        assert timetracking.time_spent_seconds == 1800
+
+    def test_from_api_response_with_empty_data(self):
+        """Test creating a JiraTimetracking from empty data."""
+        timetracking = JiraTimetracking.from_api_response({})
+
+        # Should use default values
+        assert timetracking.original_estimate is None
+        assert timetracking.remaining_estimate is None
+        assert timetracking.time_spent is None
+        assert timetracking.original_estimate_seconds is None
+        assert timetracking.remaining_estimate_seconds is None
+        assert timetracking.time_spent_seconds is None
+
+    def test_from_api_response_with_none_data(self):
+        """Test creating a JiraTimetracking from None data."""
+        timetracking = JiraTimetracking.from_api_response(None)
+
+        # Should return None
+        assert timetracking is None
+
+    def test_to_simplified_dict(self):
+        """Test converting JiraTimetracking to a simplified dictionary."""
+        timetracking = JiraTimetracking(
+            original_estimate="2h",
+            remaining_estimate="1h 30m",
+            time_spent="30m",
+            original_estimate_seconds=7200,
+            remaining_estimate_seconds=5400,
+            time_spent_seconds=1800,
+        )
+
+        simplified = timetracking.to_simplified_dict()
+
+        assert isinstance(simplified, dict)
+        assert simplified["originalEstimate"] == "2h"
+        assert simplified["remainingEstimate"] == "1h 30m"
+        assert simplified["timeSpent"] == "30m"
+        assert simplified["originalEstimateSeconds"] == 7200
+        assert simplified["remainingEstimateSeconds"] == 5400
+        assert simplified["timeSpentSeconds"] == 1800
+
+
 class TestJiraIssue:
     """Tests for the JiraIssue model."""
 
@@ -730,6 +795,54 @@ class TestJiraIssue:
         assert "customfield_10001" in simplified
         assert "customfield_10002" in simplified
 
+    def test_timetracking_field_processing(self):
+        """Test that timetracking data is properly processed."""
+        issue_data = {
+            "id": "10000",
+            "key": "TEST-123",
+            "fields": {
+                "summary": "Test Issue",
+                "description": "Test Description",
+                "timetracking": {
+                    "originalEstimate": "2h",
+                    "remainingEstimate": "1h 30m",
+                    "timeSpent": "30m",
+                    "originalEstimateSeconds": 7200,
+                    "remainingEstimateSeconds": 5400,
+                    "timeSpentSeconds": 1800,
+                },
+            },
+        }
+
+        issue = JiraIssue.from_api_response(issue_data)
+
+        # Verify timetracking field is populated
+        assert issue.timetracking is not None
+        assert issue.timetracking.original_estimate == "2h"
+        assert issue.timetracking.remaining_estimate == "1h 30m"
+        assert issue.timetracking.time_spent == "30m"
+        assert issue.timetracking.original_estimate_seconds == 7200
+        assert issue.timetracking.remaining_estimate_seconds == 5400
+        assert issue.timetracking.time_spent_seconds == 1800
+
+        # Verify timetracking is included in simplified dict with *all fields
+        issue.requested_fields = "*all"
+        simplified = issue.to_simplified_dict()
+        assert "timetracking" in simplified
+        assert simplified["timetracking"]["originalEstimate"] == "2h"
+        assert simplified["timetracking"]["remainingEstimate"] == "1h 30m"
+
+        # Verify timetracking is included when specifically requested
+        issue.requested_fields = ["summary", "timetracking"]
+        simplified = issue.to_simplified_dict()
+        assert "timetracking" in simplified
+        assert simplified["timetracking"]["originalEstimate"] == "2h"
+
+        # Verify timetracking is not included in default essential fields
+        issue.requested_fields = None
+        simplified = issue.to_simplified_dict()
+        assert "timetracking" not in simplified
+
 
 class TestJiraSearchResult:
     """Tests for the JiraSearchResult model."""