Skip to content

Commit 10b7743

Browse files
soopersetnszceta
authored andcommitted
fix(jira): migrate Cloud search to v3 API endpoint (sooperset#769)
The Jira Cloud v2 search API (/rest/api/*/search) has been deprecated and is now returning errors. This commit migrates Cloud deployments to the new v3 search endpoint (POST /rest/api/3/search/jql). Changes: - Cloud: Use POST /rest/api/3/search/jql with nextPageToken pagination - Server/DC: Continue using the v2 /rest/api/2/search endpoint unchanged - Updated tests to mock the new v3 API calls for Cloud scenarios The v3 API has some differences: - Uses JSON body instead of query parameters - Uses nextPageToken for pagination instead of startAt - Does not return total count (returns -1) - Max 100 results per request (was 50 for Server/DC) Fixes sooperset#720 Github-Issue:sooperset#720
1 parent beb8a8e commit 10b7743

File tree

2 files changed

+155
-189
lines changed

2 files changed

+155
-189
lines changed

src/mcp_atlassian/jira/search.py

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Module for Jira search operations."""
22

33
import logging
4+
from typing import Any
45

56
import requests
67
from requests.exceptions import HTTPError
@@ -88,55 +89,61 @@ def search_issues(
8889
fields_param = fields
8990

9091
if self.config.is_cloud:
91-
actual_total = -1
92-
try:
93-
# Call 1: Get metadata (including total) using standard search API
94-
metadata_params = {"jql": jql, "maxResults": 1}
95-
metadata_response = self.jira.get(
96-
self.jira.resource_url("search/jql"), params=metadata_params
97-
)
92+
# Cloud: Use v3 API endpoint POST /rest/api/3/search/jql
93+
# The old v2 /rest/api/*/search endpoint is deprecated
94+
# See: https://developer.atlassian.com/changelog/#CHANGE-2046
95+
96+
# Build request body for v3 API
97+
fields_list = fields_param.split(",") if fields_param else ["id", "key"]
98+
request_body: dict[str, Any] = {
99+
"jql": jql,
100+
"maxResults": min(limit, 100), # v3 API max is 100 per request
101+
"fields": fields_list,
102+
}
103+
# Note: v3 API uses 'expand' as a comma-separated string, not an array
104+
if expand:
105+
request_body["expand"] = expand
98106

99-
if (
100-
isinstance(metadata_response, dict)
101-
and "total" in metadata_response
102-
):
103-
try:
104-
actual_total = int(metadata_response["total"])
105-
except (ValueError, TypeError):
106-
logger.warning(
107-
f"Could not parse 'total' from metadata response for JQL: {jql}. Received: {metadata_response.get('total')}"
108-
)
109-
else:
110-
logger.warning(
111-
f"Could not retrieve total count from metadata response for JQL: {jql}. Response type: {type(metadata_response)}"
112-
)
113-
except Exception as meta_err:
114-
logger.error(
115-
f"Error fetching metadata for JQL '{jql}': {str(meta_err)}"
116-
)
107+
# Fetch issues using v3 API with nextPageToken pagination
108+
all_issues: list[dict[str, Any]] = []
109+
next_page_token: str | None = None
117110

118-
# Call 2: Get the actual issues using the enhanced method
119-
issues_response_list = self.jira.enhanced_jql_get_list_of_tickets(
120-
jql, fields=fields_param, limit=limit, expand=expand
121-
)
111+
while len(all_issues) < limit:
112+
if next_page_token:
113+
request_body["nextPageToken"] = next_page_token
122114

123-
if not isinstance(issues_response_list, list):
124-
msg = f"Unexpected return value type from `jira.enhanced_jql_get_list_of_tickets`: {type(issues_response_list)}"
125-
logger.error(msg)
126-
raise TypeError(msg)
115+
response = self.jira.post(
116+
"rest/api/3/search/jql", json=request_body
117+
)
127118

128-
response_dict_for_model = {
129-
"issues": issues_response_list,
130-
"total": actual_total,
119+
if not isinstance(response, dict):
120+
msg = f"Unexpected response type from v3 search API: {type(response)}"
121+
logger.error(msg)
122+
raise TypeError(msg)
123+
124+
issues = response.get("issues", [])
125+
all_issues.extend(issues)
126+
127+
# Check for more pages
128+
next_page_token = response.get("nextPageToken")
129+
if not next_page_token:
130+
break
131+
132+
# Build response dict for model
133+
# Note: v3 API doesn't provide total count, so we use -1
134+
response_dict: dict[str, Any] = {
135+
"issues": all_issues[:limit],
136+
"total": -1,
137+
"startAt": 0,
138+
"maxResults": limit,
131139
}
132140

133141
search_result = JiraSearchResult.from_api_response(
134-
response_dict_for_model,
142+
response_dict,
135143
base_url=self.config.url,
136144
requested_fields=fields_param,
137145
)
138146

139-
# Return the full search result object
140147
return search_result
141148
else:
142149
limit = min(limit, 50)

0 commit comments

Comments
 (0)