Repository: sooperset/mcp-atlassian

Issue:
Parent Field Restriction Breaking Task Assignment
Recently, a change was made in Jira where the Parent field is now only available for Sub-tasks and has been removed from additional fields. This change prevents me from assigning a Task to a parent issue, which is breaking my workflow.

Expected Behavior:
	•	The Parent field should be available for Tasks, allowing them to be assigned to a parent issue (e.g., an Epic).

Issue:
	•	The Parent field is now restricted to Sub-tasks only.
	•	This prevents creating a Task with a parent issue.
	•	My workflow is broken because I can’t assign Tasks properly.

Proposed Solutions:
	1.	Allow the Parent field in the additional fields configuration so it can be used for Tasks.
	2.	Include Tasks in the list of issue types that are allowed to have a Parent.

Let me know if you need any changes to this! 🚀

Hints from issue comments:
@sooperset: Thanks for reporting! let me look into the issue!

Solution patch:
diff --git a/src/mcp_atlassian/jira/issues.py b/src/mcp_atlassian/jira/issues.py
index 21bf532..a01df03 100644
--- a/src/mcp_atlassian/jira/issues.py
+++ b/src/mcp_atlassian/jira/issues.py
@@ -431,6 +431,9 @@ class IssuesMixin(UsersMixin):
             # Prepare parent field if this is a subtask
             if issue_type.lower() == "subtask" or issue_type.lower() == "sub-task":
                 self._prepare_parent_fields(fields, kwargs)
+            # Allow parent field for all issue types when explicitly provided
+            elif "parent" in kwargs:
+                self._prepare_parent_fields(fields, kwargs)
 
             # Add custom fields
             self._add_custom_fields(fields, kwargs)
@@ -479,7 +482,7 @@ class IssuesMixin(UsersMixin):
         self, fields: dict[str, Any], kwargs: dict[str, Any]
     ) -> None:
         """
-        Prepare fields for subtask creation.
+        Prepare fields for parent relationship.
 
         Args:
             fields: The fields dictionary to update
@@ -494,7 +497,11 @@ class IssuesMixin(UsersMixin):
                 fields["parent"] = {"key": parent_key}
             # Remove parent from kwargs to avoid double processing
             kwargs.pop("parent", None)
-        else:
+        elif "issuetype" in fields and fields["issuetype"]["name"].lower() in (
+            "subtask",
+            "sub-task",
+        ):
+            # Only raise error if issue type is subtask and parent is missing
             raise ValueError(
                 "Issue type is a sub-task but parent issue key or id not specified. Please provide a 'parent' parameter with the parent issue key."
             )
diff --git a/src/mcp_atlassian/server.py b/src/mcp_atlassian/server.py
index adbf061..9f2d600 100644
--- a/src/mcp_atlassian/server.py
+++ b/src/mcp_atlassian/server.py
@@ -627,7 +627,7 @@ async def list_tools() -> list[Tool]:
                                 '- Set priority: {"priority": {"name": "High"}}\n'
                                 '- Add labels: {"labels": ["frontend", "urgent"]}\n'
                                 '- Add components: {"components": [{"name": "UI"}]}\n'
-                                '- Link to parent (for subtasks): {"parent": "PROJ-123"}\n'
+                                '- Link to parent (for any issue type): {"parent": "PROJ-123"}\n'
                                 '- Custom fields: {"customfield_10010": "value"}',
                                 "default": "{}",
                             },


Test patch:
diff --git a/scripts/test_with_real_data.sh b/scripts/test_with_real_data.sh
index dd2ecde..7c756bd 100755
--- a/scripts/test_with_real_data.sh
+++ b/scripts/test_with_real_data.sh
@@ -130,7 +130,7 @@ run_api_tests() {
         sleep 5
 
         # Run the write operation tests
-        uv run pytest tests/test_real_api_validation.py::test_jira_create_issue tests/test_real_api_validation.py::test_jira_create_subtask tests/test_real_api_validation.py::test_jira_add_comment tests/test_real_api_validation.py::test_confluence_create_page tests/test_real_api_validation.py::test_confluence_update_page $VERBOSITY
+        uv run pytest tests/test_real_api_validation.py::test_jira_create_issue tests/test_real_api_validation.py::test_jira_create_subtask tests/test_real_api_validation.py::test_jira_create_task_with_parent tests/test_real_api_validation.py::test_jira_add_comment tests/test_real_api_validation.py::test_confluence_create_page tests/test_real_api_validation.py::test_confluence_update_page $VERBOSITY
 
         # Run the skipped transition test if explicitly requested write tests
         echo ""
diff --git a/tests/test_real_api_validation.py b/tests/test_real_api_validation.py
index b1e7fbe..595dafd 100644
--- a/tests/test_real_api_validation.py
+++ b/tests/test_real_api_validation.py
@@ -535,6 +535,69 @@ async def test_jira_create_subtask(
         cleanup_resources()
 
 
+@pytest.mark.anyio
+async def test_jira_create_task_with_parent(
+    jira_client: JiraFetcher,
+    test_project_key: str,
+    test_epic_key: str,
+    resource_tracker: ResourceTracker,
+    cleanup_resources: Callable[[], None],
+) -> None:
+    """Test creating a task in Jira with a parent issue (non-subtask)."""
+    # Generate unique identifiers for this test
+    test_id = str(uuid.uuid4())[:8]
+    task_summary = f"Task with Parent Test {test_id}"
+
+    try:
+        # Use the epic as parent instead of a regular issue
+        parent_issue_key = test_epic_key
+
+        try:
+            # Create a task linked to a parent issue
+            task_issue = jira_client.create_issue(
+                project_key=test_project_key,
+                summary=task_summary,
+                description=f"This is a test task linked to parent {parent_issue_key}",
+                issue_type="Task",  # Not a subtask
+                parent=parent_issue_key,  # Link to parent
+            )
+
+            # Track the task for cleanup
+            resource_tracker.add_jira_issue(task_issue.key)
+
+            # Verify the task response
+            assert task_issue is not None
+            assert task_issue.key.startswith(test_project_key)
+            assert task_issue.summary == task_summary
+
+            # Verify we can retrieve the created task
+            retrieved_task = jira_client.get_issue(task_issue.key)
+            assert retrieved_task is not None
+            assert retrieved_task.key == task_issue.key
+
+            # Verify parent relationship if the fields are accessible
+            # This might vary depending on how the Jira instance returns data
+            if hasattr(retrieved_task, "fields"):
+                if hasattr(retrieved_task.fields, "parent"):
+                    assert retrieved_task.fields.parent.key == parent_issue_key
+
+            print(f"\nCreated task {task_issue.key} with parent {parent_issue_key}")
+
+        except Exception as e:
+            # If we get a hierarchy error, it means the feature works but is limited by Jira config
+            if "hierarchy" in str(e).lower():
+                pytest.skip(
+                    f"Parent-child relationship not allowed by Jira configuration: {str(e)}"
+                )
+            else:
+                # Re-raise if it's not a hierarchy issue
+                raise
+
+    finally:
+        # Clean up resources even if the test fails
+        cleanup_resources()
+
+
 @pytest.mark.anyio
 async def test_jira_add_comment(
     jira_client: JiraFetcher,
diff --git a/tests/unit/jira/test_issues.py b/tests/unit/jira/test_issues.py
index 08546ca..462d8bc 100644
--- a/tests/unit/jira/test_issues.py
+++ b/tests/unit/jira/test_issues.py
@@ -419,3 +419,56 @@ class TestIssuesMixin:
         # Verify result
         assert "Epic Link" in result
         assert result["Epic Link"] == "customfield_10100"
+
+    def test_create_issue_with_parent_for_task(self, issues_mixin):
+        """Test creating a regular task issue with a parent field."""
+        # Setup mock response for create_issue
+        create_response = {
+            "id": "12345",
+            "key": "TEST-456",
+            "self": "https://jira.example.com/rest/api/2/issue/12345",
+        }
+        issues_mixin.jira.create_issue.return_value = create_response
+
+        # Setup mock response for issue retrieval
+        issue_response = {
+            "id": "12345",
+            "key": "TEST-456",
+            "fields": {
+                "summary": "Test Task with Parent",
+                "description": "This is a test",
+                "status": {"name": "Open"},
+                "issuetype": {"name": "Task"},
+                "parent": {"key": "TEST-123"},
+            },
+        }
+        issues_mixin.jira.issue.return_value = issue_response
+
+        issues_mixin._get_account_id = MagicMock(return_value="user123")
+
+        # Execute - create a Task with parent field
+        result = issues_mixin.create_issue(
+            project_key="TEST",
+            summary="Test Task with Parent",
+            issue_type="Task",
+            description="This is a test",
+            assignee="jdoe",
+            parent="TEST-123",  # Adding parent for a non-subtask
+        )
+
+        # Verify
+        issues_mixin.jira.create_issue.assert_called_once()
+        call_kwargs = issues_mixin.jira.create_issue.call_args[1]
+        assert "fields" in call_kwargs
+        fields = call_kwargs["fields"]
+
+        # Verify parent field was included
+        assert "parent" in fields
+        assert fields["parent"] == {"key": "TEST-123"}
+
+        # Verify issue method was called after creation
+        issues_mixin.jira.issue.assert_called_once_with("TEST-456")
+
+        # Verify the issue was created successfully
+        assert result is not None
+        assert result.key == "TEST-456"