@azure-devops/mcp 1.0.0 → 1.2.0-daily.20250715

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
+ import { PullRequestStatus, GitVersionType, GitPullRequestQueryType, } from "azure-devops-node-api/interfaces/GitInterfaces.js";
3
4
  import { z } from "zod";
4
5
  import { getCurrentUserDetails } from "./auth.js";
5
6
  const REPO_TOOLS = {
@@ -15,16 +16,41 @@ const REPO_TOOLS = {
15
16
  get_pull_request_by_id: "repo_get_pull_request_by_id",
16
17
  create_pull_request: "repo_create_pull_request",
17
18
  update_pull_request_status: "repo_update_pull_request_status",
19
+ update_pull_request_reviewers: "repo_update_pull_request_reviewers",
18
20
  reply_to_comment: "repo_reply_to_comment",
19
21
  resolve_comment: "repo_resolve_comment",
22
+ search_commits: "repo_search_commits",
23
+ list_pull_requests_by_commits: "repo_list_pull_requests_by_commits",
20
24
  };
21
25
  function branchesFilterOutIrrelevantProperties(branches, top) {
22
26
  return branches
23
27
  ?.flatMap((branch) => (branch.name ? [branch.name] : []))
24
28
  ?.filter((branch) => branch.startsWith("refs/heads/"))
25
29
  .map((branch) => branch.replace("refs/heads/", ""))
30
+ .sort((a, b) => b.localeCompare(a))
26
31
  .slice(0, top);
27
32
  }
33
+ function pullRequestStatusStringToInt(status) {
34
+ switch (status) {
35
+ case "abandoned":
36
+ return PullRequestStatus.Abandoned.valueOf();
37
+ case "active":
38
+ return PullRequestStatus.Active.valueOf();
39
+ case "all":
40
+ return PullRequestStatus.All.valueOf();
41
+ case "completed":
42
+ return PullRequestStatus.Completed.valueOf();
43
+ case "notSet":
44
+ return PullRequestStatus.NotSet.valueOf();
45
+ default:
46
+ throw new Error(`Unknown pull request status: ${status}`);
47
+ }
48
+ }
49
+ function filterReposByName(repositories, repoNameFilter) {
50
+ const lowerCaseFilter = repoNameFilter.toLowerCase();
51
+ const filteredByName = repositories?.filter((repo) => repo.name?.toLowerCase().includes(lowerCaseFilter));
52
+ return filteredByName;
53
+ }
28
54
  function configureRepoTools(server, tokenProvider, connectionProvider) {
29
55
  server.tool(REPO_TOOLS.create_pull_request, "Create a new pull request.", {
30
56
  repositoryId: z.string().describe("The ID of the repository where the pull request will be created."),
@@ -33,15 +59,18 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
33
59
  title: z.string().describe("The title of the pull request."),
34
60
  description: z.string().optional().describe("The description of the pull request. Optional."),
35
61
  isDraft: z.boolean().optional().default(false).describe("Indicates whether the pull request is a draft. Defaults to false."),
36
- }, async ({ repositoryId, sourceRefName, targetRefName, title, description, isDraft, }) => {
62
+ workItems: z.string().optional().describe("Work item IDs to associate with the pull request, space-separated."),
63
+ }, async ({ repositoryId, sourceRefName, targetRefName, title, description, isDraft, workItems }) => {
37
64
  const connection = await connectionProvider();
38
65
  const gitApi = await connection.getGitApi();
66
+ const workItemRefs = workItems ? workItems.split(" ").map((id) => ({ id: id.trim() })) : [];
39
67
  const pullRequest = await gitApi.createPullRequest({
40
68
  sourceRefName,
41
69
  targetRefName,
42
70
  title,
43
71
  description,
44
72
  isDraft,
73
+ workItemRefs: workItemRefs,
45
74
  }, repositoryId);
46
75
  return {
47
76
  content: [{ type: "text", text: JSON.stringify(pullRequest, null, 2) }],
@@ -51,25 +80,52 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
51
80
  repositoryId: z.string().describe("The ID of the repository where the pull request exists."),
52
81
  pullRequestId: z.number().describe("The ID of the pull request to be published."),
53
82
  status: z.enum(["active", "abandoned"]).describe("The new status of the pull request. Can be 'active' or 'abandoned'."),
54
- }, async ({ repositoryId, pullRequestId }) => {
83
+ }, async ({ repositoryId, pullRequestId, status }) => {
55
84
  const connection = await connectionProvider();
56
85
  const gitApi = await connection.getGitApi();
57
86
  const statusValue = status === "active" ? 3 : 2;
58
87
  const updatedPullRequest = await gitApi.updatePullRequest({ status: statusValue }, repositoryId, pullRequestId);
59
88
  return {
60
- content: [
61
- { type: "text", text: JSON.stringify(updatedPullRequest, null, 2) },
62
- ],
89
+ content: [{ type: "text", text: JSON.stringify(updatedPullRequest, null, 2) }],
63
90
  };
64
91
  });
92
+ server.tool(REPO_TOOLS.update_pull_request_reviewers, "Add or remove reviewers for an existing pull request.", {
93
+ repositoryId: z.string().describe("The ID of the repository where the pull request exists."),
94
+ pullRequestId: z.number().describe("The ID of the pull request to update."),
95
+ reviewerIds: z.array(z.string()).describe("List of reviewer ids to add or remove from the pull request."),
96
+ action: z.enum(["add", "remove"]).describe("Action to perform on the reviewers. Can be 'add' or 'remove'."),
97
+ }, async ({ repositoryId, pullRequestId, reviewerIds, action }) => {
98
+ const connection = await connectionProvider();
99
+ const gitApi = await connection.getGitApi();
100
+ let updatedPullRequest;
101
+ if (action === "add") {
102
+ updatedPullRequest = await gitApi.createPullRequestReviewers(reviewerIds.map((id) => ({ id: id })), repositoryId, pullRequestId);
103
+ return {
104
+ content: [{ type: "text", text: JSON.stringify(updatedPullRequest, null, 2) }],
105
+ };
106
+ }
107
+ else {
108
+ for (const reviewerId of reviewerIds) {
109
+ await gitApi.deletePullRequestReviewer(repositoryId, pullRequestId, reviewerId);
110
+ }
111
+ return {
112
+ content: [{ type: "text", text: `Reviewers with IDs ${reviewerIds.join(", ")} removed from pull request ${pullRequestId}.` }],
113
+ };
114
+ }
115
+ });
65
116
  server.tool(REPO_TOOLS.list_repos_by_project, "Retrieve a list of repositories for a given project", {
66
117
  project: z.string().describe("The name or ID of the Azure DevOps project."),
67
- }, async ({ project }) => {
118
+ top: z.number().default(100).describe("The maximum number of repositories to return."),
119
+ skip: z.number().default(0).describe("The number of repositories to skip. Defaults to 0."),
120
+ repoNameFilter: z.string().optional().describe("Optional filter to search for repositories by name. If provided, only repositories with names containing this string will be returned."),
121
+ }, async ({ project, top, skip, repoNameFilter }) => {
68
122
  const connection = await connectionProvider();
69
123
  const gitApi = await connection.getGitApi();
70
124
  const repositories = await gitApi.getRepositories(project, false, false, false);
125
+ const filteredRepositories = repoNameFilter ? filterReposByName(repositories, repoNameFilter) : repositories;
126
+ const paginatedRepositories = filteredRepositories?.sort((a, b) => a.name?.localeCompare(b.name ?? "") ?? 0).slice(skip, skip + top);
71
127
  // Filter out the irrelevant properties
72
- const filteredRepositories = repositories?.map((repo) => ({
128
+ const trimmedRepositories = paginatedRepositories?.map((repo) => ({
73
129
  id: repo.id,
74
130
  name: repo.name,
75
131
  isDisabled: repo.isDisabled,
@@ -79,21 +135,22 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
79
135
  size: repo.size,
80
136
  }));
81
137
  return {
82
- content: [
83
- { type: "text", text: JSON.stringify(filteredRepositories, null, 2) },
84
- ],
138
+ content: [{ type: "text", text: JSON.stringify(trimmedRepositories, null, 2) }],
85
139
  };
86
140
  });
87
141
  server.tool(REPO_TOOLS.list_pull_requests_by_repo, "Retrieve a list of pull requests for a given repository.", {
88
142
  repositoryId: z.string().describe("The ID of the repository where the pull requests are located."),
143
+ top: z.number().default(100).describe("The maximum number of pull requests to return."),
144
+ skip: z.number().default(0).describe("The number of pull requests to skip."),
89
145
  created_by_me: z.boolean().default(false).describe("Filter pull requests created by the current user."),
90
146
  i_am_reviewer: z.boolean().default(false).describe("Filter pull requests where the current user is a reviewer."),
91
- }, async ({ repositoryId, created_by_me, i_am_reviewer }) => {
147
+ status: z.enum(["abandoned", "active", "all", "completed", "notSet"]).default("active").describe("Filter pull requests by status. Defaults to 'active'."),
148
+ }, async ({ repositoryId, top, skip, created_by_me, i_am_reviewer, status }) => {
92
149
  const connection = await connectionProvider();
93
150
  const gitApi = await connection.getGitApi();
94
151
  // Build the search criteria
95
152
  const searchCriteria = {
96
- status: 1,
153
+ status: pullRequestStatusStringToInt(status),
97
154
  repositoryId: repositoryId,
98
155
  };
99
156
  if (created_by_me || i_am_reviewer) {
@@ -106,7 +163,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
106
163
  searchCriteria.reviewerId = userId;
107
164
  }
108
165
  }
109
- const pullRequests = await gitApi.getPullRequests(repositoryId, searchCriteria);
166
+ const pullRequests = await gitApi.getPullRequests(repositoryId, searchCriteria, undefined, // project
167
+ undefined, // maxCommentLength
168
+ skip, top);
110
169
  // Filter out the irrelevant properties
111
170
  const filteredPullRequests = pullRequests?.map((pr) => ({
112
171
  pullRequestId: pr.pullRequestId,
@@ -121,21 +180,22 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
121
180
  isDraft: pr.isDraft,
122
181
  }));
123
182
  return {
124
- content: [
125
- { type: "text", text: JSON.stringify(filteredPullRequests, null, 2) },
126
- ],
183
+ content: [{ type: "text", text: JSON.stringify(filteredPullRequests, null, 2) }],
127
184
  };
128
185
  });
129
186
  server.tool(REPO_TOOLS.list_pull_requests_by_project, "Retrieve a list of pull requests for a given project Id or Name.", {
130
187
  project: z.string().describe("The name or ID of the Azure DevOps project."),
188
+ top: z.number().default(100).describe("The maximum number of pull requests to return."),
189
+ skip: z.number().default(0).describe("The number of pull requests to skip."),
131
190
  created_by_me: z.boolean().default(false).describe("Filter pull requests created by the current user."),
132
191
  i_am_reviewer: z.boolean().default(false).describe("Filter pull requests where the current user is a reviewer."),
133
- }, async ({ project, created_by_me, i_am_reviewer }) => {
192
+ status: z.enum(["abandoned", "active", "all", "completed", "notSet"]).default("active").describe("Filter pull requests by status. Defaults to 'active'."),
193
+ }, async ({ project, top, skip, created_by_me, i_am_reviewer, status }) => {
134
194
  const connection = await connectionProvider();
135
195
  const gitApi = await connection.getGitApi();
136
196
  // Build the search criteria
137
197
  const gitPullRequestSearchCriteria = {
138
- status: 1,
198
+ status: pullRequestStatusStringToInt(status),
139
199
  };
140
200
  if (created_by_me || i_am_reviewer) {
141
201
  const data = await getCurrentUserDetails(tokenProvider, connectionProvider);
@@ -147,7 +207,8 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
147
207
  gitPullRequestSearchCriteria.reviewerId = userId;
148
208
  }
149
209
  }
150
- const pullRequests = await gitApi.getPullRequestsByProject(project, gitPullRequestSearchCriteria);
210
+ const pullRequests = await gitApi.getPullRequestsByProject(project, gitPullRequestSearchCriteria, undefined, // maxCommentLength
211
+ skip, top);
151
212
  // Filter out the irrelevant properties
152
213
  const filteredPullRequests = pullRequests?.map((pr) => ({
153
214
  pullRequestId: pr.pullRequestId,
@@ -163,9 +224,7 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
163
224
  isDraft: pr.isDraft,
164
225
  }));
165
226
  return {
166
- content: [
167
- { type: "text", text: JSON.stringify(filteredPullRequests, null, 2) },
168
- ],
227
+ content: [{ type: "text", text: JSON.stringify(filteredPullRequests, null, 2) }],
169
228
  };
170
229
  });
171
230
  server.tool(REPO_TOOLS.list_pull_request_threads, "Retrieve a list of comment threads for a pull request.", {
@@ -174,12 +233,15 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
174
233
  project: z.string().optional().describe("Project ID or project name (optional)"),
175
234
  iteration: z.number().optional().describe("The iteration ID for which to retrieve threads. Optional, defaults to the latest iteration."),
176
235
  baseIteration: z.number().optional().describe("The base iteration ID for which to retrieve threads. Optional, defaults to the latest base iteration."),
177
- }, async ({ repositoryId, pullRequestId, project, iteration, baseIteration, }) => {
236
+ top: z.number().default(100).describe("The maximum number of threads to return."),
237
+ skip: z.number().default(0).describe("The number of threads to skip."),
238
+ }, async ({ repositoryId, pullRequestId, project, iteration, baseIteration, top, skip }) => {
178
239
  const connection = await connectionProvider();
179
240
  const gitApi = await connection.getGitApi();
180
241
  const threads = await gitApi.getThreads(repositoryId, pullRequestId, project, iteration, baseIteration);
242
+ const paginatedThreads = threads?.sort((a, b) => (a.id ?? 0) - (b.id ?? 0)).slice(skip, skip + top);
181
243
  return {
182
- content: [{ type: "text", text: JSON.stringify(threads, null, 2) }],
244
+ content: [{ type: "text", text: JSON.stringify(paginatedThreads, null, 2) }],
183
245
  };
184
246
  });
185
247
  server.tool(REPO_TOOLS.list_pull_request_thread_comments, "Retrieve a list of comments in a pull request thread.", {
@@ -187,13 +249,16 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
187
249
  pullRequestId: z.number().describe("The ID of the pull request for which to retrieve thread comments."),
188
250
  threadId: z.number().describe("The ID of the thread for which to retrieve comments."),
189
251
  project: z.string().optional().describe("Project ID or project name (optional)"),
190
- }, async ({ repositoryId, pullRequestId, threadId, project }) => {
252
+ top: z.number().default(100).describe("The maximum number of comments to return."),
253
+ skip: z.number().default(0).describe("The number of comments to skip."),
254
+ }, async ({ repositoryId, pullRequestId, threadId, project, top, skip }) => {
191
255
  const connection = await connectionProvider();
192
256
  const gitApi = await connection.getGitApi();
193
257
  // Get thread comments - GitApi uses getComments for retrieving comments from a specific thread
194
258
  const comments = await gitApi.getComments(repositoryId, pullRequestId, threadId, project);
259
+ const paginatedComments = comments?.sort((a, b) => (a.id ?? 0) - (b.id ?? 0)).slice(skip, skip + top);
195
260
  return {
196
- content: [{ type: "text", text: JSON.stringify(comments, null, 2) }],
261
+ content: [{ type: "text", text: JSON.stringify(paginatedComments, null, 2) }],
197
262
  };
198
263
  });
199
264
  server.tool(REPO_TOOLS.list_branches_by_repo, "Retrieve a list of branches for a given repository.", {
@@ -205,19 +270,19 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
205
270
  const branches = await gitApi.getRefs(repositoryId, undefined);
206
271
  const filteredBranches = branchesFilterOutIrrelevantProperties(branches, top);
207
272
  return {
208
- content: [
209
- { type: "text", text: JSON.stringify(filteredBranches, null, 2) },
210
- ],
273
+ content: [{ type: "text", text: JSON.stringify(filteredBranches, null, 2) }],
211
274
  };
212
275
  });
213
276
  server.tool(REPO_TOOLS.list_my_branches_by_repo, "Retrieve a list of my branches for a given repository Id.", {
214
277
  repositoryId: z.string().describe("The ID of the repository where the branches are located."),
215
- }, async ({ repositoryId }) => {
278
+ top: z.number().default(100).describe("The maximum number of branches to return."),
279
+ }, async ({ repositoryId, top }) => {
216
280
  const connection = await connectionProvider();
217
281
  const gitApi = await connection.getGitApi();
218
282
  const branches = await gitApi.getRefs(repositoryId, undefined, undefined, undefined, undefined, true);
283
+ const filteredBranches = branchesFilterOutIrrelevantProperties(branches, top);
219
284
  return {
220
- content: [{ type: "text", text: JSON.stringify(branches, null, 2) }],
285
+ content: [{ type: "text", text: JSON.stringify(filteredBranches, null, 2) }],
221
286
  };
222
287
  });
223
288
  server.tool(REPO_TOOLS.get_repo_by_name_or_id, "Get the repository by project and repository name or ID.", {
@@ -295,5 +360,95 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
295
360
  content: [{ type: "text", text: JSON.stringify(thread, null, 2) }],
296
361
  };
297
362
  });
363
+ const gitVersionTypeStrings = Object.values(GitVersionType).filter((value) => typeof value === "string");
364
+ server.tool(REPO_TOOLS.search_commits, "Searches for commits in a repository", {
365
+ project: z.string().describe("Project name or ID"),
366
+ repository: z.string().describe("Repository name or ID"),
367
+ fromCommit: z.string().optional().describe("Starting commit ID"),
368
+ toCommit: z.string().optional().describe("Ending commit ID"),
369
+ version: z.string().optional().describe("The name of the branch, tag or commit to filter commits by"),
370
+ versionType: z
371
+ .enum(gitVersionTypeStrings)
372
+ .optional()
373
+ .default(GitVersionType[GitVersionType.Branch])
374
+ .describe("The meaning of the version parameter, e.g., branch, tag or commit"),
375
+ skip: z.number().optional().default(0).describe("Number of commits to skip"),
376
+ top: z.number().optional().default(10).describe("Maximum number of commits to return"),
377
+ includeLinks: z.boolean().optional().default(false).describe("Include commit links"),
378
+ includeWorkItems: z.boolean().optional().default(false).describe("Include associated work items"),
379
+ }, async ({ project, repository, fromCommit, toCommit, version, versionType, skip, top, includeLinks, includeWorkItems }) => {
380
+ try {
381
+ const connection = await connectionProvider();
382
+ const gitApi = await connection.getGitApi();
383
+ const searchCriteria = {
384
+ fromCommitId: fromCommit,
385
+ toCommitId: toCommit,
386
+ includeLinks: includeLinks,
387
+ includeWorkItems: includeWorkItems,
388
+ };
389
+ if (version) {
390
+ const itemVersion = {
391
+ version: version,
392
+ versionType: GitVersionType[versionType],
393
+ };
394
+ searchCriteria.itemVersion = itemVersion;
395
+ }
396
+ const commits = await gitApi.getCommits(repository, searchCriteria, project, skip, // skip
397
+ top);
398
+ return {
399
+ content: [{ type: "text", text: JSON.stringify(commits, null, 2) }],
400
+ };
401
+ }
402
+ catch (error) {
403
+ return {
404
+ content: [
405
+ {
406
+ type: "text",
407
+ text: `Error searching commits: ${error instanceof Error ? error.message : String(error)}`,
408
+ },
409
+ ],
410
+ isError: true,
411
+ };
412
+ }
413
+ });
414
+ const pullRequestQueryTypesStrings = Object.values(GitPullRequestQueryType).filter((value) => typeof value === "string");
415
+ server.tool(REPO_TOOLS.list_pull_requests_by_commits, "Lists pull requests by commit IDs to find which pull requests contain specific commits", {
416
+ project: z.string().describe("Project name or ID"),
417
+ repository: z.string().describe("Repository name or ID"),
418
+ commits: z.array(z.string()).describe("Array of commit IDs to query for"),
419
+ queryType: z
420
+ .enum(pullRequestQueryTypesStrings)
421
+ .optional()
422
+ .default(GitPullRequestQueryType[GitPullRequestQueryType.LastMergeCommit])
423
+ .describe("Type of query to perform"),
424
+ }, async ({ project, repository, commits, queryType }) => {
425
+ try {
426
+ const connection = await connectionProvider();
427
+ const gitApi = await connection.getGitApi();
428
+ const query = {
429
+ queries: [
430
+ {
431
+ items: commits,
432
+ type: GitPullRequestQueryType[queryType],
433
+ },
434
+ ],
435
+ };
436
+ const queryResult = await gitApi.getPullRequestQuery(query, repository, project);
437
+ return {
438
+ content: [{ type: "text", text: JSON.stringify(queryResult, null, 2) }],
439
+ };
440
+ }
441
+ catch (error) {
442
+ return {
443
+ content: [
444
+ {
445
+ type: "text",
446
+ text: `Error querying pull requests by commits: ${error instanceof Error ? error.message : String(error)}`,
447
+ },
448
+ ],
449
+ isError: true,
450
+ };
451
+ }
452
+ });
298
453
  }
299
454
  export { REPO_TOOLS, configureRepoTools };
@@ -1,25 +1,27 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
3
  import { z } from "zod";
4
- import { apiVersion, userAgent } from "../utils.js";
4
+ import { apiVersion } from "../utils.js";
5
5
  import { orgName } from "../index.js";
6
6
  import { VersionControlRecursionType } from "azure-devops-node-api/interfaces/GitInterfaces.js";
7
7
  const SEARCH_TOOLS = {
8
8
  search_code: "search_code",
9
9
  search_wiki: "search_wiki",
10
- search_workitem: "search_workitem"
10
+ search_workitem: "search_workitem",
11
11
  };
12
- function configureSearchTools(server, tokenProvider, connectionProvider) {
12
+ function configureSearchTools(server, tokenProvider, connectionProvider, userAgentProvider) {
13
13
  /*
14
14
  CODE SEARCH
15
15
  Get the code search results for a given search text.
16
16
  */
17
17
  server.tool(SEARCH_TOOLS.search_code, "Get the code search results for a given search text.", {
18
- searchRequest: z.object({
18
+ searchRequest: z
19
+ .object({
19
20
  searchText: z.string().describe("Search text to find in code"),
20
21
  $skip: z.number().default(0).describe("Number of results to skip (for pagination)"),
21
22
  $top: z.number().default(5).describe("Number of results to return (for pagination)"),
22
- filters: z.object({
23
+ filters: z
24
+ .object({
23
25
  Project: z.array(z.string()).optional().describe("Filter in these projects"),
24
26
  Repository: z.array(z.string()).optional().describe("Filter in these repositories"),
25
27
  Path: z.array(z.string()).optional().describe("Filter in these paths"),
@@ -30,9 +32,12 @@ function configureSearchTools(server, tokenProvider, connectionProvider) {
30
32
  // If provided, the search will only return results that match the specified code elements.
31
33
  // This is useful for narrowing down the search to specific classes, functions, definitions, or symbols.
32
34
  // Example: CodeElement: ["MyClass", "MyFunction"]
33
- }).partial().optional(),
34
- includeFacets: z.boolean().optional()
35
- }).strict()
35
+ })
36
+ .partial()
37
+ .optional(),
38
+ includeFacets: z.boolean().optional(),
39
+ })
40
+ .strict(),
36
41
  }, async ({ searchRequest }) => {
37
42
  const accessToken = await tokenProvider();
38
43
  const connection = await connectionProvider();
@@ -41,8 +46,8 @@ function configureSearchTools(server, tokenProvider, connectionProvider) {
41
46
  method: "POST",
42
47
  headers: {
43
48
  "Content-Type": "application/json",
44
- Authorization: `Bearer ${accessToken.token}`,
45
- "User-Agent": `${userAgent}`
49
+ "Authorization": `Bearer ${accessToken.token}`,
50
+ "User-Agent": userAgentProvider(),
46
51
  },
47
52
  body: JSON.stringify(searchRequest),
48
53
  });
@@ -51,32 +56,34 @@ function configureSearchTools(server, tokenProvider, connectionProvider) {
51
56
  }
52
57
  const resultText = await response.text();
53
58
  const resultJson = JSON.parse(resultText);
54
- const topResults = Array.isArray(resultJson.results)
55
- ? resultJson.results.slice(0, Math.min(searchRequest.$top, resultJson.results.length))
56
- : [];
59
+ const topResults = Array.isArray(resultJson.results) ? resultJson.results.slice(0, Math.min(searchRequest.$top, resultJson.results.length)) : [];
57
60
  const gitApi = await connection.getGitApi();
58
61
  const combinedResults = await fetchCombinedResults(topResults, gitApi);
59
62
  return {
60
- content: [
61
- { type: "text", text: resultText + JSON.stringify(combinedResults) }
62
- ]
63
+ content: [{ type: "text", text: resultText + JSON.stringify(combinedResults) }],
63
64
  };
64
65
  });
65
66
  /*
66
- WIKI SEARCH
67
- Get wiki search results for a given search text.
68
- */
67
+ WIKI SEARCH
68
+ Get wiki search results for a given search text.
69
+ */
69
70
  server.tool(SEARCH_TOOLS.search_wiki, "Get wiki search results for a given search text.", {
70
- searchRequest: z.object({
71
+ searchRequest: z
72
+ .object({
71
73
  searchText: z.string().describe("Search text to find in wikis"),
72
74
  $skip: z.number().default(0).describe("Number of results to skip (for pagination)"),
73
75
  $top: z.number().default(10).describe("Number of results to return (for pagination)"),
74
- filters: z.object({
76
+ filters: z
77
+ .object({
75
78
  Project: z.array(z.string()).optional().describe("Filter in these projects"),
76
79
  Wiki: z.array(z.string()).optional().describe("Filter in these wiki names"),
77
- }).partial().optional().describe("Filters to apply to the search text"),
78
- includeFacets: z.boolean().optional()
79
- }).strict()
80
+ })
81
+ .partial()
82
+ .optional()
83
+ .describe("Filters to apply to the search text"),
84
+ includeFacets: z.boolean().optional(),
85
+ })
86
+ .strict(),
80
87
  }, async ({ searchRequest }) => {
81
88
  const accessToken = await tokenProvider();
82
89
  const url = `https://almsearch.dev.azure.com/${orgName}/_apis/search/wikisearchresults?api-version=${apiVersion}`;
@@ -84,8 +91,8 @@ function configureSearchTools(server, tokenProvider, connectionProvider) {
84
91
  method: "POST",
85
92
  headers: {
86
93
  "Content-Type": "application/json",
87
- Authorization: `Bearer ${accessToken.token}`,
88
- "User-Agent": `${userAgent}`
94
+ "Authorization": `Bearer ${accessToken.token}`,
95
+ "User-Agent": userAgentProvider(),
89
96
  },
90
97
  body: JSON.stringify(searchRequest),
91
98
  });
@@ -94,29 +101,32 @@ function configureSearchTools(server, tokenProvider, connectionProvider) {
94
101
  }
95
102
  const result = await response.text();
96
103
  return {
97
- content: [
98
- { type: "text", text: result }
99
- ]
104
+ content: [{ type: "text", text: result }],
100
105
  };
101
106
  });
102
107
  /*
103
- WORK ITEM SEARCH
104
- Get work item search results for a given search text.
105
- */
108
+ WORK ITEM SEARCH
109
+ Get work item search results for a given search text.
110
+ */
106
111
  server.tool(SEARCH_TOOLS.search_workitem, "Get work item search results for a given search text.", {
107
- searchRequest: z.object({
112
+ searchRequest: z
113
+ .object({
108
114
  searchText: z.string().describe("Search text to find in work items"),
109
115
  $skip: z.number().default(0).describe("Number of results to skip for pagination"),
110
116
  $top: z.number().default(10).describe("Number of results to return"),
111
- filters: z.object({
117
+ filters: z
118
+ .object({
112
119
  "System.TeamProject": z.array(z.string()).optional().describe("Filter by team project"),
113
120
  "System.AreaPath": z.array(z.string()).optional().describe("Filter by area path"),
114
121
  "System.WorkItemType": z.array(z.string()).optional().describe("Filter by work item type like Bug, Task, User Story"),
115
122
  "System.State": z.array(z.string()).optional().describe("Filter by state"),
116
123
  "System.AssignedTo": z.array(z.string()).optional().describe("Filter by assigned to"),
117
- }).partial().optional(),
118
- includeFacets: z.boolean().optional()
119
- }).strict()
124
+ })
125
+ .partial()
126
+ .optional(),
127
+ includeFacets: z.boolean().optional(),
128
+ })
129
+ .strict(),
120
130
  }, async ({ searchRequest }) => {
121
131
  const accessToken = await tokenProvider();
122
132
  const url = `https://almsearch.dev.azure.com/${orgName}/_apis/search/workitemsearchresults?api-version=${apiVersion}`;
@@ -124,8 +134,8 @@ function configureSearchTools(server, tokenProvider, connectionProvider) {
124
134
  method: "POST",
125
135
  headers: {
126
136
  "Content-Type": "application/json",
127
- Authorization: `Bearer ${accessToken.token}`,
128
- "User-Agent": `${userAgent}`
137
+ "Authorization": `Bearer ${accessToken.token}`,
138
+ "User-Agent": userAgentProvider(),
129
139
  },
130
140
  body: JSON.stringify(searchRequest),
131
141
  });
@@ -134,9 +144,7 @@ function configureSearchTools(server, tokenProvider, connectionProvider) {
134
144
  }
135
145
  const result = await response.text();
136
146
  return {
137
- content: [
138
- { type: "text", text: result }
139
- ]
147
+ content: [{ type: "text", text: result }],
140
148
  };
141
149
  });
142
150
  }
@@ -154,9 +162,7 @@ async function fetchCombinedResults(topSearchResults, gitApi) {
154
162
  });
155
163
  continue;
156
164
  }
157
- const versionDescriptor = changeId
158
- ? { version: changeId, versionType: 2, versionOptions: 0 }
159
- : undefined;
165
+ const versionDescriptor = changeId ? { version: changeId, versionType: 2, versionOptions: 0 } : undefined;
160
166
  const item = await gitApi.getItem(repositoryId, filePath, projectId, undefined, VersionControlRecursionType.None, true, // includeContentMetadata
161
167
  false, // latestProcessedChange
162
168
  false, // download
@@ -165,12 +171,12 @@ async function fetchCombinedResults(topSearchResults, gitApi) {
165
171
  true // sanitize
166
172
  );
167
173
  combinedResults.push({
168
- gitItem: item
174
+ gitItem: item,
169
175
  });
170
176
  }
171
177
  catch (err) {
172
178
  combinedResults.push({
173
- error: err instanceof Error ? err.message : String(err)
179
+ error: err instanceof Error ? err.message : String(err),
174
180
  });
175
181
  }
176
182
  }