diff --git a/src/mcp_atlassian/models/jira/adf.py b/src/mcp_atlassian/models/jira/adf.py new file mode 100644 index 00000000..712917ff --- /dev/null +++ b/src/mcp_atlassian/models/jira/adf.py @@ -0,0 +1,51 @@ +""" +Atlassian Document Format (ADF) utilities. + +This module provides utilities for parsing ADF content from Jira Cloud. +""" + + +def adf_to_text(adf_content: dict | list | str | None) -> str | None: + """ + Convert Atlassian Document Format (ADF) content to plain text. + + ADF is Jira Cloud's rich text format returned for fields like description. + This function recursively extracts text content from the ADF structure. + + Args: + adf_content: ADF document (dict), content list, string, or None + + Returns: + Plain text string or None if no content + """ + if adf_content is None: + return None + + if isinstance(adf_content, str): + return adf_content + + if isinstance(adf_content, list): + texts = [] + for item in adf_content: + text = adf_to_text(item) + if text: + texts.append(text) + return "\n".join(texts) if texts else None + + if isinstance(adf_content, dict): + # Check if this is a text node + if adf_content.get("type") == "text": + return adf_content.get("text", "") + + # Check if this is a hardBreak node + if adf_content.get("type") == "hardBreak": + return "\n" + + # Recursively process content + content = adf_content.get("content") + if content: + return adf_to_text(content) + + return None + + return None diff --git a/src/mcp_atlassian/models/jira/comment.py b/src/mcp_atlassian/models/jira/comment.py index c1ac7643..0befaf9b 100644 --- a/src/mcp_atlassian/models/jira/comment.py +++ b/src/mcp_atlassian/models/jira/comment.py @@ -12,6 +12,7 @@ EMPTY_STRING, JIRA_DEFAULT_ID, ) +from .adf import adf_to_text from .common import JiraUser logger = logging.getLogger(__name__) @@ -58,14 +59,13 @@ def from_api_response(cls, data: dict[str, Any], **kwargs: Any) -> "JiraComment" if comment_id is not None: comment_id = str(comment_id) - # Get the body content + # Get the body content - can be string (legacy) or ADF dict (Jira Cloud) body_content = EMPTY_STRING body = data.get("body") - if isinstance(body, dict) and "content" in body: + if isinstance(body, dict): # Handle Atlassian Document Format (ADF) - # This is a simplified conversion - a proper implementation would - # parse the ADF structure - body_content = str(body.get("content", EMPTY_STRING)) + converted = adf_to_text(body) + body_content = converted if converted else EMPTY_STRING elif body: # Handle plain text or HTML content body_content = str(body) diff --git a/src/mcp_atlassian/models/jira/issue.py b/src/mcp_atlassian/models/jira/issue.py index d036d999..5a1f51c1 100644 --- a/src/mcp_atlassian/models/jira/issue.py +++ b/src/mcp_atlassian/models/jira/issue.py @@ -16,6 +16,7 @@ JIRA_DEFAULT_ID, JIRA_DEFAULT_KEY, ) +from .adf import adf_to_text from .comment import JiraComment from .common import ( JiraAttachment, @@ -267,7 +268,14 @@ def from_api_response(cls, data: dict[str, Any], **kwargs: Any) -> "JiraIssue": issue_id = str(data.get("id", JIRA_DEFAULT_ID)) key = str(data.get("key", JIRA_DEFAULT_KEY)) summary = str(fields.get("summary", EMPTY_STRING)) - description = fields.get("description") + + # Handle description - can be string (legacy) or ADF dict (Jira Cloud new editor) + raw_description = fields.get("description") + if isinstance(raw_description, dict): + # Convert ADF to plain text + description = adf_to_text(raw_description) + else: + description = raw_description # Timestamps created = str(fields.get("created", EMPTY_STRING))