@@ -18,51 +18,49 @@ const SEARCH_TOOLS = {
18
18
} ;
19
19
20
20
function configureSearchTools ( server : McpServer , tokenProvider : ( ) => Promise < AccessToken > , connectionProvider : ( ) => Promise < WebApi > , userAgentProvider : ( ) => string ) {
21
- /*
22
- CODE SEARCH
23
- Get the code search results for a given search text.
24
- */
25
21
server . tool (
26
22
SEARCH_TOOLS . search_code ,
27
- "Get the code search results for a given search text. " ,
23
+ "Search Azure DevOps Repositories for a given search text" ,
28
24
{
29
- searchRequest : z
30
- . object ( {
31
- searchText : z . string ( ) . describe ( "Search text to find in code" ) ,
32
- $skip : z . number ( ) . default ( 0 ) . describe ( "Number of results to skip (for pagination)" ) ,
33
- $top : z . number ( ) . default ( 5 ) . describe ( "Number of results to return (for pagination)" ) ,
34
- filters : z
35
- . object ( {
36
- Project : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter in these projects" ) ,
37
- Repository : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter in these repositories" ) ,
38
- Path : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter in these paths" ) ,
39
- Branch : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter in these branches" ) ,
40
- CodeElement : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter for these code elements (e.g., classes, functions, symbols)" ) ,
41
- // Note: CodeElement is optional and can be used to filter results by specific code elements.
42
- // It can be a string or an array of strings.
43
- // If provided, the search will only return results that match the specified code elements.
44
- // This is useful for narrowing down the search to specific classes, functions, definitions, or symbols.
45
- // Example: CodeElement: ["MyClass", "MyFunction"]
46
- } )
47
- . partial ( )
48
- . optional ( ) ,
49
- includeFacets : z . boolean ( ) . optional ( ) ,
50
- } )
51
- . strict ( ) ,
25
+ searchText : z . string ( ) . describe ( "Keywords to search for in code repositories" ) ,
26
+ project : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by projects" ) ,
27
+ repository : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by repositories" ) ,
28
+ path : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by paths" ) ,
29
+ branch : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by branches" ) ,
30
+ includeFacets : z . boolean ( ) . default ( false ) . describe ( "Include facets in the search results" ) ,
31
+ $skip : z . number ( ) . default ( 0 ) . describe ( "Number of results to skip" ) ,
32
+ $top : z . number ( ) . default ( 5 ) . describe ( "Maximum number of results to return" ) ,
52
33
} ,
53
- async ( { searchRequest } ) => {
34
+ async ( { searchText , project , repository , path , branch , includeFacets , $skip , $top } ) => {
54
35
const accessToken = await tokenProvider ( ) ;
55
36
const connection = await connectionProvider ( ) ;
56
37
const url = `https://almsearch.dev.azure.com/${ orgName } /_apis/search/codesearchresults?api-version=${ apiVersion } ` ;
57
38
39
+ const requestBody : Record < string , unknown > = {
40
+ searchText,
41
+ includeFacets,
42
+ $skip,
43
+ $top,
44
+ } ;
45
+
46
+ const filters : Record < string , string [ ] > = { } ;
47
+ if ( project && project . length > 0 ) filters . Project = project ;
48
+ if ( repository && repository . length > 0 ) filters . Repository = repository ;
49
+ if ( path && path . length > 0 ) filters . Path = path ;
50
+ if ( branch && branch . length > 0 ) filters . Branch = branch ;
51
+
52
+ if ( Object . keys ( filters ) . length > 0 ) {
53
+ requestBody . filters = filters ;
54
+ }
55
+
58
56
const response = await fetch ( url , {
59
57
method : "POST" ,
60
58
headers : {
61
59
"Content-Type" : "application/json" ,
62
60
"Authorization" : `Bearer ${ accessToken . token } ` ,
63
61
"User-Agent" : userAgentProvider ( ) ,
64
62
} ,
65
- body : JSON . stringify ( searchRequest ) ,
63
+ body : JSON . stringify ( requestBody ) ,
66
64
} ) ;
67
65
68
66
if ( ! response . ok ) {
@@ -72,54 +70,53 @@ function configureSearchTools(server: McpServer, tokenProvider: () => Promise<Ac
72
70
const resultText = await response . text ( ) ;
73
71
const resultJson = JSON . parse ( resultText ) as { results ?: SearchResult [ ] } ;
74
72
75
- const topResults : SearchResult [ ] = Array . isArray ( resultJson . results ) ? resultJson . results . slice ( 0 , Math . min ( searchRequest . $top , resultJson . results . length ) ) : [ ] ;
76
-
77
73
const gitApi = await connection . getGitApi ( ) ;
78
- const combinedResults = await fetchCombinedResults ( topResults , gitApi ) ;
74
+ const combinedResults = await fetchCombinedResults ( resultJson . results ?? [ ] , gitApi ) ;
79
75
80
76
return {
81
77
content : [ { type : "text" , text : resultText + JSON . stringify ( combinedResults ) } ] ,
82
78
} ;
83
79
}
84
80
) ;
85
81
86
- /*
87
- WIKI SEARCH
88
- Get wiki search results for a given search text.
89
- */
90
82
server . tool (
91
83
SEARCH_TOOLS . search_wiki ,
92
- "Get wiki search results for a given search text. " ,
84
+ "Search Azure DevOps Wiki for a given search text" ,
93
85
{
94
- searchRequest : z
95
- . object ( {
96
- searchText : z . string ( ) . describe ( "Search text to find in wikis" ) ,
97
- $skip : z . number ( ) . default ( 0 ) . describe ( "Number of results to skip (for pagination)" ) ,
98
- $top : z . number ( ) . default ( 10 ) . describe ( "Number of results to return (for pagination)" ) ,
99
- filters : z
100
- . object ( {
101
- Project : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter in these projects" ) ,
102
- Wiki : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter in these wiki names" ) ,
103
- } )
104
- . partial ( )
105
- . optional ( )
106
- . describe ( "Filters to apply to the search text" ) ,
107
- includeFacets : z . boolean ( ) . optional ( ) ,
108
- } )
109
- . strict ( ) ,
86
+ searchText : z . string ( ) . describe ( "Keywords to search for wiki pages" ) ,
87
+ project : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by projects" ) ,
88
+ wiki : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by wiki names" ) ,
89
+ includeFacets : z . boolean ( ) . default ( false ) . describe ( "Include facets in the search results" ) ,
90
+ $skip : z . number ( ) . default ( 0 ) . describe ( "Number of results to skip" ) ,
91
+ $top : z . number ( ) . default ( 10 ) . describe ( "Maximum number of results to return" ) ,
110
92
} ,
111
- async ( { searchRequest } ) => {
93
+ async ( { searchText , project , wiki , includeFacets , $skip , $top } ) => {
112
94
const accessToken = await tokenProvider ( ) ;
113
95
const url = `https://almsearch.dev.azure.com/${ orgName } /_apis/search/wikisearchresults?api-version=${ apiVersion } ` ;
114
96
97
+ const requestBody : Record < string , unknown > = {
98
+ searchText,
99
+ includeFacets,
100
+ $skip,
101
+ $top,
102
+ } ;
103
+
104
+ const filters : Record < string , string [ ] > = { } ;
105
+ if ( project && project . length > 0 ) filters . Project = project ;
106
+ if ( wiki && wiki . length > 0 ) filters . Wiki = wiki ;
107
+
108
+ if ( Object . keys ( filters ) . length > 0 ) {
109
+ requestBody . filters = filters ;
110
+ }
111
+
115
112
const response = await fetch ( url , {
116
113
method : "POST" ,
117
114
headers : {
118
115
"Content-Type" : "application/json" ,
119
116
"Authorization" : `Bearer ${ accessToken . token } ` ,
120
117
"User-Agent" : userAgentProvider ( ) ,
121
118
} ,
122
- body : JSON . stringify ( searchRequest ) ,
119
+ body : JSON . stringify ( requestBody ) ,
123
120
} ) ;
124
121
125
122
if ( ! response . ok ) {
@@ -133,45 +130,50 @@ function configureSearchTools(server: McpServer, tokenProvider: () => Promise<Ac
133
130
}
134
131
) ;
135
132
136
- /*
137
- WORK ITEM SEARCH
138
- Get work item search results for a given search text.
139
- */
140
133
server . tool (
141
134
SEARCH_TOOLS . search_workitem ,
142
- "Get work item search results for a given search text. " ,
135
+ "Get Azure DevOps Work Item search results for a given search text" ,
143
136
{
144
- searchRequest : z
145
- . object ( {
146
- searchText : z . string ( ) . describe ( "Search text to find in work items" ) ,
147
- $skip : z . number ( ) . default ( 0 ) . describe ( "Number of results to skip for pagination" ) ,
148
- $top : z . number ( ) . default ( 10 ) . describe ( "Number of results to return" ) ,
149
- filters : z
150
- . object ( {
151
- "System.TeamProject" : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by team project" ) ,
152
- "System.AreaPath" : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by area path" ) ,
153
- "System.WorkItemType" : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by work item type like Bug, Task, User Story" ) ,
154
- "System.State" : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by state" ) ,
155
- "System.AssignedTo" : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by assigned to" ) ,
156
- } )
157
- . partial ( )
158
- . optional ( ) ,
159
- includeFacets : z . boolean ( ) . optional ( ) ,
160
- } )
161
- . strict ( ) ,
137
+ searchText : z . string ( ) . describe ( "Search text to find in work items" ) ,
138
+ project : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by projects" ) ,
139
+ areaPath : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by area paths" ) ,
140
+ workItemType : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by work item types" ) ,
141
+ state : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by work item states" ) ,
142
+ assignedTo : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter by assigned to users" ) ,
143
+ includeFacets : z . boolean ( ) . default ( false ) . describe ( "Include facets in the search results" ) ,
144
+ $skip : z . number ( ) . default ( 0 ) . describe ( "Number of results to skip for pagination" ) ,
145
+ $top : z . number ( ) . default ( 10 ) . describe ( "Number of results to return" ) ,
162
146
} ,
163
- async ( { searchRequest } ) => {
147
+ async ( { searchText , project , areaPath , workItemType , state , assignedTo , includeFacets , $skip , $top } ) => {
164
148
const accessToken = await tokenProvider ( ) ;
165
149
const url = `https://almsearch.dev.azure.com/${ orgName } /_apis/search/workitemsearchresults?api-version=${ apiVersion } ` ;
166
150
151
+ const requestBody : Record < string , unknown > = {
152
+ searchText,
153
+ includeFacets,
154
+ $skip,
155
+ $top,
156
+ } ;
157
+
158
+ const filters : Record < string , unknown > = { } ;
159
+ if ( project && project . length > 0 ) filters [ "System.TeamProject" ] = project ;
160
+ if ( areaPath && areaPath . length > 0 ) filters [ "System.AreaPath" ] = areaPath ;
161
+ if ( workItemType && workItemType . length > 0 ) filters [ "System.WorkItemType" ] = workItemType ;
162
+ if ( state && state . length > 0 ) filters [ "System.State" ] = state ;
163
+ if ( assignedTo && assignedTo . length > 0 ) filters [ "System.AssignedTo" ] = assignedTo ;
164
+
165
+ if ( Object . keys ( filters ) . length > 0 ) {
166
+ requestBody . filters = filters ;
167
+ }
168
+
167
169
const response = await fetch ( url , {
168
170
method : "POST" ,
169
171
headers : {
170
172
"Content-Type" : "application/json" ,
171
173
"Authorization" : `Bearer ${ accessToken . token } ` ,
172
174
"User-Agent" : userAgentProvider ( ) ,
173
175
} ,
174
- body : JSON . stringify ( searchRequest ) ,
176
+ body : JSON . stringify ( requestBody ) ,
175
177
} ) ;
176
178
177
179
if ( ! response . ok ) {
@@ -186,10 +188,6 @@ function configureSearchTools(server: McpServer, tokenProvider: () => Promise<Ac
186
188
) ;
187
189
}
188
190
189
- /*
190
- Fetch git repo file content for top 5(default) search results.
191
- */
192
-
193
191
interface SearchResult {
194
192
project ?: { id ?: string } ;
195
193
repository ?: { id ?: string } ;
0 commit comments