Repository: sooperset/mcp-atlassian

Issue:
[Bug]: Updating the status of an issue fails with "could not find transition"
### Prerequisites

- [x] I have searched the [existing issues](https://github.com/sooperset/mcp-atlassian/issues) to make sure this bug has not already been reported.
- [x] I have checked the [README](https://github.com/sooperset/mcp-atlassian/blob/main/README.md) for relevant information.

### Bug Description

When I try to call the `jira_update_issue` tool asking for a status update the status update fails because the tool cannot find the status transition.

### Steps to Reproduce

 tool with the following parameters:
```

{
  "issue_key": "ticket-123",
  "fields": {
    "status": "In Progress",
    "assignee": "<user-id>"
  }
}
```

The tool call fails. In the log I can see that identifying the correct transition fails:


```
INFO - mcp.server.lowlevel.server - Processing request of type CallToolRequest
INFO - mcp-jira - Looking for transition to status name: 'In Progress'
ERROR - mcp-jira - Could not find transition to status 'In Progress'. Available statuses: , , , , , ,
ERROR - mcp-jira - Error updating issue GENEXT-9744: Could not find transition to status 'In Progress'. Available statuses: , , , , , ,
ERROR - mcp_atlassian.servers.jira - Error updating issue GENEXT-9744: Failed to update issue GENEXT-9744: Could not find transition to status 'In Progress'. Available statuses: , , , , , ,

```

It seems that the status names cannot be identified correctly. Not sure whether this is due to the configuration of the JIRA instance. Actually there are transitions available for the JIRA issue to the status "In Progress" in this case.


### Expected Behavior

The status transition should be found and conducted.

### Actual Behavior

```shell
The tool call fails with above mentioned error message
```

### mcp-atlassian Version

0.11.1

### Installation Method

Docker

### Operating System

Windows

### Python Version

using the docker image

### Atlassian Instance Type

Jira Server / Data Center

### Client Application

VSCode

### Additional Context

_No response_


Discussion:
@albert0815 Thanks for reporting this issue. I've resolved this in PR #536 and I'll release the new version with this. Please let me know if you still have the same issue!



Patch:
diff --git a/src/mcp_atlassian/jira/issues.py b/src/mcp_atlassian/jira/issues.py
index 463ca2e5..fbbe6d7f 100644
--- a/src/mcp_atlassian/jira/issues.py
+++ b/src/mcp_atlassian/jira/issues.py
@@ -1016,8 +1016,8 @@ def _update_issue_with_status(
                 raise TypeError(msg)
             return JiraIssue.from_api_response(issue_data)
 
-        # Get available transitions
-        transitions = self.get_available_transitions(issue_key)
+        # Get available transitions (uses TransitionsMixin's normalized implementation)
+        transitions = self.get_available_transitions(issue_key)  # type: ignore[attr-defined]
 
         # Extract status name or ID depending on what we received
         status_name = None
@@ -1053,9 +1053,8 @@ def _update_issue_with_status(
         # Find the appropriate transition
         transition_id = None
         for transition in transitions:
-            to_status = transition.get("to", {})
-            transition_status_name = to_status.get("name", "")
-            transition_status_id = to_status.get("id")
+            # TransitionsMixin returns normalized transitions with 'to_status' field
+            transition_status_name = transition.get("to_status", "")
 
             # Match by name (case-insensitive)
             if (
@@ -1069,18 +1068,6 @@ def _update_issue_with_status(
                 )
                 break
 
-            # Match by ID
-            if (
-                status_id
-                and transition_status_id
-                and str(transition_status_id) == str(status_id)
-            ):
-                transition_id = transition.get("id")
-                logger.info(
-                    f"Found transition ID {transition_id} matching status ID '{status_id}'"
-                )
-                break
-
             # Direct transition ID match (if status is actually a transition ID)
             if status_id and str(transition.get("id", "")) == str(status_id):
                 transition_id = transition.get("id")
@@ -1088,12 +1075,23 @@ def _update_issue_with_status(
                 break
 
         if not transition_id:
-            available_statuses = ", ".join(
-                [t.get("to", {}).get("name", "") for t in transitions]
+            # Build list of available statuses from normalized transitions
+            available_statuses = []
+            for t in transitions:
+                # Include transition name and target status if available
+                transition_name = t.get("name", "")
+                to_status = t.get("to_status", "")
+                if to_status:
+                    available_statuses.append(f"{transition_name} -> {to_status}")
+                elif transition_name:
+                    available_statuses.append(transition_name)
+
+            available_statuses_str = (
+                ", ".join(available_statuses) if available_statuses else "None found"
             )
             error_msg = (
                 f"Could not find transition to status '{status}'. "
-                f"Available statuses: {available_statuses}"
+                f"Available transitions: {available_statuses_str}"
             )
             logger.error(error_msg)
             raise ValueError(error_msg)
@@ -1183,15 +1181,19 @@ def _process_field_for_epic_data(
         except Exception as e:
             logger.warning(f"Error processing field for epic data: {str(e)}")
 
-    def get_available_transitions(self, issue_key: str) -> list[dict]:
+    def _get_raw_transitions(self, issue_key: str) -> list[dict]:
         """
-        Get all available transitions for an issue.
+        Get raw transition data from the Jira API.
+
+        This is an internal method that returns unprocessed transition data.
+        For normalized transitions with proper structure, use get_available_transitions()
+        from TransitionsMixin instead.
 
         Args:
             issue_key: The key of the issue
 
         Returns:
-            List of available transitions
+            List of raw transition data from the API
 
         Raises:
             Exception: If there is an error getting transitions


Test patch:
diff --git a/tests/unit/jira/test_issues.py b/tests/unit/jira/test_issues.py
index 73471390..b85b2c6f 100644
--- a/tests/unit/jira/test_issues.py
+++ b/tests/unit/jira/test_issues.py
@@ -676,13 +676,13 @@ def test_update_issue_with_status(self, issues_mixin: IssuesMixin):
             return_value=JiraIssue(key="TEST-123", description="")
         )
 
-        # Mock available transitions
+        # Mock available transitions (using TransitionsMixin's normalized format)
         issues_mixin.get_available_transitions = MagicMock(
             return_value=[
                 {
                     "id": "21",
                     "name": "In Progress",
-                    "to": {"name": "In Progress", "id": "3"},
+                    "to_status": "In Progress",
                 }
             ]
         )