1+ import { ToolConfig } from '../types'
2+ import { JiraRetrieveBulkParams , JiraRetrieveResponseBulk } from './types'
3+
4+ export const jiraBulkRetrieveTool : ToolConfig < JiraRetrieveBulkParams , JiraRetrieveResponseBulk > = {
5+ id : 'jira_bulk_read' ,
6+ name : 'Jira Bulk Read' ,
7+ description : 'Retrieve multiple Jira issues in bulk' ,
8+ version : '1.0.0' ,
9+ oauth : {
10+ required : true ,
11+ provider : 'jira' ,
12+ additionalScopes : [
13+ 'read:jira-work' ,
14+ 'read:jira-user' ,
15+ 'read:me' ,
16+ 'offline_access' ,
17+ ] ,
18+ } ,
19+ params : {
20+ accessToken : {
21+ type : 'string' ,
22+ required : true ,
23+ description : 'OAuth access token for Jira' ,
24+ } ,
25+ domain : {
26+ type : 'string' ,
27+ required : true ,
28+ requiredForToolCall : true ,
29+ description : 'Your Jira domain (e.g., yourcompany.atlassian.net)' ,
30+ } ,
31+ projectId : {
32+ type : 'string' ,
33+ required : true ,
34+ description : 'Jira project ID' ,
35+ } ,
36+ cloudId : {
37+ type : 'string' ,
38+ required : false ,
39+ description : 'Jira cloud ID' ,
40+ } ,
41+ } ,
42+ request : {
43+ url : ( params : JiraRetrieveBulkParams ) => {
44+ if ( params . cloudId ) {
45+ return `https://api.atlassian.com/ex/jira/${ params . cloudId } /rest/api/3/issue/picker?currentJQL=project=${ params . projectId } `
46+ }
47+ // If no cloudId, use the accessible resources endpoint
48+ return 'https://api.atlassian.com/oauth/token/accessible-resources'
49+ } ,
50+ method : 'GET' ,
51+ headers : ( params : JiraRetrieveBulkParams ) => ( {
52+ 'Authorization' : `Bearer ${ params . accessToken } ` ,
53+ 'Accept' : 'application/json'
54+ } ) ,
55+ body : ( params : JiraRetrieveBulkParams ) => ( { } )
56+ } ,
57+ transformResponse : async ( response : Response , params ?: JiraRetrieveBulkParams ) => {
58+ if ( ! params ) {
59+ throw new Error ( 'Parameters are required for Jira bulk issue retrieval' )
60+ }
61+
62+ try {
63+ // If we don't have a cloudId, we need to fetch it first
64+ if ( ! params . cloudId ) {
65+ if ( ! response . ok ) {
66+ const errorData = await response . json ( ) . catch ( ( ) => null )
67+ throw new Error ( errorData ?. message || `Failed to fetch accessible resources: ${ response . status } ${ response . statusText } ` )
68+ }
69+
70+ const accessibleResources = await response . json ( )
71+ if ( ! Array . isArray ( accessibleResources ) || accessibleResources . length === 0 ) {
72+ throw new Error ( 'No accessible Jira resources found for this account' )
73+ }
74+
75+ const normalizedInput = `https://${ params . domain } ` . toLowerCase ( )
76+ const matchedResource = accessibleResources . find ( r => r . url . toLowerCase ( ) === normalizedInput )
77+
78+ if ( ! matchedResource ) {
79+ throw new Error ( `Could not find matching Jira site for domain: ${ params . domain } ` )
80+ }
81+
82+ // First get issue keys from picker
83+ const pickerUrl = `https://api.atlassian.com/ex/jira/${ matchedResource . id } /rest/api/3/issue/picker?currentJQL=project=${ params . projectId } `
84+ const pickerResponse = await fetch ( pickerUrl , {
85+ method : 'GET' ,
86+ headers : {
87+ 'Authorization' : `Bearer ${ params . accessToken } ` ,
88+ 'Accept' : 'application/json'
89+ }
90+ } )
91+
92+ if ( ! pickerResponse . ok ) {
93+ const errorData = await pickerResponse . json ( ) . catch ( ( ) => null )
94+ throw new Error ( errorData ?. message || `Failed to retrieve issue keys: ${ pickerResponse . status } ${ pickerResponse . statusText } ` )
95+ }
96+
97+ const pickerData = await pickerResponse . json ( )
98+ const issueKeys = pickerData . sections
99+ . flatMap ( ( section : any ) => section . issues || [ ] )
100+ . map ( ( issue : any ) => issue . key )
101+
102+ if ( issueKeys . length === 0 ) {
103+ return {
104+ success : true ,
105+ output : [ ]
106+ }
107+ }
108+
109+ // Now use bulkfetch to get the full issue details
110+ const bulkfetchUrl = `https://api.atlassian.com/ex/jira/${ matchedResource . id } /rest/api/3/issue/bulkfetch`
111+ const bulkfetchResponse = await fetch ( bulkfetchUrl , {
112+ method : 'POST' ,
113+ headers : {
114+ 'Authorization' : `Bearer ${ params . accessToken } ` ,
115+ 'Accept' : 'application/json' ,
116+ 'Content-Type' : 'application/json'
117+ } ,
118+ body : JSON . stringify ( {
119+ expand : [ "names" ] ,
120+ fields : [ "summary" , "description" , "created" , "updated" ] ,
121+ fieldsByKeys : false ,
122+ issueIdsOrKeys : issueKeys ,
123+ properties : [ ]
124+ } )
125+ } )
126+
127+ if ( ! bulkfetchResponse . ok ) {
128+ const errorData = await bulkfetchResponse . json ( ) . catch ( ( ) => null )
129+ throw new Error ( errorData ?. message || `Failed to retrieve Jira issues: ${ bulkfetchResponse . status } ${ bulkfetchResponse . statusText } ` )
130+ }
131+
132+ const data = await bulkfetchResponse . json ( )
133+ return {
134+ success : true ,
135+ output : data . issues . map ( ( issue : any ) => ( {
136+ ts : new Date ( ) . toISOString ( ) ,
137+ summary : issue . fields . summary ,
138+ description : issue . fields . description ?. content ?. [ 0 ] ?. content ?. [ 0 ] ?. text || '' ,
139+ created : issue . fields . created ,
140+ updated : issue . fields . updated
141+ } ) )
142+ }
143+ }
144+
145+ // If we have a cloudId, this response is from the issue picker
146+ if ( ! response . ok ) {
147+ const errorData = await response . json ( ) . catch ( ( ) => null )
148+ throw new Error ( errorData ?. message || `Failed to retrieve issue keys: ${ response . status } ${ response . statusText } ` )
149+ }
150+
151+ const pickerData = await response . json ( )
152+ const issueKeys = pickerData . sections
153+ . flatMap ( ( section : any ) => section . issues || [ ] )
154+ . map ( ( issue : any ) => issue . key )
155+
156+ if ( issueKeys . length === 0 ) {
157+ return {
158+ success : true ,
159+ output : [ ]
160+ }
161+ }
162+
163+ // Use bulkfetch to get the full issue details
164+ const bulkfetchUrl = `https://api.atlassian.com/ex/jira/${ params . cloudId } /rest/api/3/issue/bulkfetch`
165+ const bulkfetchResponse = await fetch ( bulkfetchUrl , {
166+ method : 'POST' ,
167+ headers : {
168+ 'Authorization' : `Bearer ${ params . accessToken } ` ,
169+ 'Accept' : 'application/json' ,
170+ 'Content-Type' : 'application/json'
171+ } ,
172+ body : JSON . stringify ( {
173+ expand : [ "names" ] ,
174+ fields : [ "summary" , "description" , "created" , "updated" ] ,
175+ fieldsByKeys : false ,
176+ issueIdsOrKeys : issueKeys ,
177+ properties : [ ]
178+ } )
179+ } )
180+
181+ if ( ! bulkfetchResponse . ok ) {
182+ const errorData = await bulkfetchResponse . json ( ) . catch ( ( ) => null )
183+ throw new Error ( errorData ?. message || `Failed to retrieve Jira issues: ${ bulkfetchResponse . status } ${ bulkfetchResponse . statusText } ` )
184+ }
185+
186+ const data = await bulkfetchResponse . json ( )
187+ return {
188+ success : true ,
189+ output : data . issues . map ( ( issue : any ) => ( {
190+ ts : new Date ( ) . toISOString ( ) ,
191+ summary : issue . fields . summary ,
192+ description : issue . fields . description ?. content ?. [ 0 ] ?. content ?. [ 0 ] ?. text || '' ,
193+ created : issue . fields . created ,
194+ updated : issue . fields . updated
195+ } ) )
196+ }
197+ } catch ( error ) {
198+ throw error instanceof Error ? error : new Error ( String ( error ) )
199+ }
200+ } ,
201+ transformError : ( error : any ) => {
202+ return error . message || 'Failed to retrieve Jira issues'
203+ }
204+ }
0 commit comments