Repository: sooperset/mcp-atlassian

Issue:
[Bug]: Error getting issues for an Epic
### 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 call `jira_get_epic_issues` with a valid epic ticket number I get an error.  I can see in our server logs it has found a number of issues for my epic.

But I see this in vscode

```
Error: 'list' object has no attribute 'issues'
```

### Steps to Reproduce

Just using a standard request like this:
```
{
  "epic_key": "PROJ-1234",
  "limit": 50,
  "startAt": 0
}
```

### Expected Behavior

Should return a list of the discovered issues

### Actual Behavior

```shell
Error: 'list' object has no attribute 'issues'
```

### mcp-atlassian Version

0.6.6

### Installation Method

From PyPI (pip install mcp-atlassian / uv add mcp-atlassian)

### Operating System

macOS

### Python Version

3.12

### Atlassian Instance Type

Jira Cloud

### Client Application

vscode

### Additional Context

I can see this at line 1648 in server.py but I think the search_result data type  is already a list as returned from the epics mixin

```
            issues = [issue.to_simplified_dict() for issue in search_result.issues]
```
should it just be this?
```
            issues = [issue.to_simplified_dict() for issue in search_result]
```

Discussion:
@fergoid: Sorry version is 0.7.0 not 0.6.6@sooperset: Thanks @fergoid for spotting this bug! 

You're right - the `jira_get_epic_issues` tool was trying to access `.issues` attribute on a list returned by `get_epic_issues`. Fixed in PR #280 by:
- Updating handler to iterate directly over the list 
- Calculating total from list length
- Adding unit test

Thanks for the detailed report!

Solution patch:
diff --git a/src/mcp_atlassian/server.py b/src/mcp_atlassian/server.py
index 082892f..8d258f4 100644
--- a/src/mcp_atlassian/server.py
+++ b/src/mcp_atlassian/server.py
@@ -1644,14 +1644,14 @@ async def call_tool(name: str, arguments: Any) -> Sequence[TextContent]:
                 epic_key, start=start_at, limit=limit
             )
 
-            # Format results
-            issues = [issue.to_simplified_dict() for issue in search_result.issues]
+            # Format results - iterate directly over the list
+            issues = [issue.to_simplified_dict() for issue in search_result]
 
             # Include metadata in the response
             response = {
-                "total": search_result.total,
-                "start_at": search_result.start_at,
-                "max_results": search_result.max_results,
+                "total": len(search_result),
+                "start_at": start_at,
+                "max_results": limit,
                 "issues": issues,
             }
 


Test patch:
diff --git a/tests/unit/test_server.py b/tests/unit/test_server.py
index cebe72c..461b2ac 100644
--- a/tests/unit/test_server.py
+++ b/tests/unit/test_server.py
@@ -925,3 +925,63 @@ async def test_call_tool_jira_batch_create_issues_invalid_json(
         assert len(result) == 1
         assert result[0].type == "text"
         assert "Invalid JSON in issues" in result[0].text
+
+
+@pytest.mark.anyio
+async def test_call_tool_jira_get_epic_issues(app_context: AppContext) -> None:
+    """Test the jira_get_epic_issues tool correctly processes a list return value.
+
+    Args:
+        app_context: The application context fixture with mocked Jira client.
+    """
+    # Create mock issues to return
+    mock_issues = [
+        MagicMock(
+            to_simplified_dict=MagicMock(
+                return_value={
+                    "key": "TEST-1",
+                    "summary": "Epic Issue 1",
+                    "type": "Task",
+                    "status": "To Do",
+                }
+            )
+        ),
+        MagicMock(
+            to_simplified_dict=MagicMock(
+                return_value={
+                    "key": "TEST-2",
+                    "summary": "Epic Issue 2",
+                    "type": "Bug",
+                    "status": "In Progress",
+                }
+            )
+        ),
+    ]
+
+    # Configure mock for get_epic_issues to return a list of issues (not an object with .issues attribute)
+    app_context.jira.get_epic_issues.return_value = mock_issues
+
+    # Call the tool
+    with mock_request_context(app_context):
+        result = await call_tool(
+            "jira_get_epic_issues",
+            {"epic_key": "TEST-100", "limit": 10, "startAt": 0},
+        )
+
+    # Verify the result
+    assert len(result) == 1
+    assert result[0].type == "text"
+
+    # Parse the response JSON
+    response = json.loads(result[0].text)
+    assert response["total"] == 2  # Should be the length of the list
+    assert response["start_at"] == 0
+    assert response["max_results"] == 10
+    assert len(response["issues"]) == 2
+    assert response["issues"][0]["key"] == "TEST-1"
+    assert response["issues"][1]["key"] == "TEST-2"
+
+    # Verify the mock was called correctly
+    app_context.jira.get_epic_issues.assert_called_once_with(
+        "TEST-100", start=0, limit=10
+    )