@azure-devops/mcp 2.2.2-nightly.20251125 → 2.2.2-nightly.20251127

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.
@@ -114,31 +114,40 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
114
114
  forkSourceRepositoryId: z.string().optional().describe("The ID of the fork repository that the pull request originates from. Optional, used when creating a pull request from a fork."),
115
115
  labels: z.array(z.string()).optional().describe("Array of label names to add to the pull request after creation."),
116
116
  }, async ({ repositoryId, sourceRefName, targetRefName, title, description, isDraft, workItems, forkSourceRepositoryId, labels }) => {
117
- const connection = await connectionProvider();
118
- const gitApi = await connection.getGitApi();
119
- const workItemRefs = workItems ? workItems.split(" ").map((id) => ({ id: id.trim() })) : [];
120
- const forkSource = forkSourceRepositoryId
121
- ? {
122
- repository: {
123
- id: forkSourceRepositoryId,
124
- },
125
- }
126
- : undefined;
127
- const labelDefinitions = labels ? labels.map((label) => ({ name: label })) : undefined;
128
- const pullRequest = await gitApi.createPullRequest({
129
- sourceRefName,
130
- targetRefName,
131
- title,
132
- description,
133
- isDraft,
134
- workItemRefs: workItemRefs,
135
- forkSource,
136
- labels: labelDefinitions,
137
- }, repositoryId);
138
- const trimmedPullRequest = trimPullRequest(pullRequest, true);
139
- return {
140
- content: [{ type: "text", text: JSON.stringify(trimmedPullRequest, null, 2) }],
141
- };
117
+ try {
118
+ const connection = await connectionProvider();
119
+ const gitApi = await connection.getGitApi();
120
+ const workItemRefs = workItems ? workItems.split(" ").map((id) => ({ id: id.trim() })) : [];
121
+ const forkSource = forkSourceRepositoryId
122
+ ? {
123
+ repository: {
124
+ id: forkSourceRepositoryId,
125
+ },
126
+ }
127
+ : undefined;
128
+ const labelDefinitions = labels ? labels.map((label) => ({ name: label })) : undefined;
129
+ const pullRequest = await gitApi.createPullRequest({
130
+ sourceRefName,
131
+ targetRefName,
132
+ title,
133
+ description,
134
+ isDraft,
135
+ workItemRefs: workItemRefs,
136
+ forkSource,
137
+ labels: labelDefinitions,
138
+ }, repositoryId);
139
+ const trimmedPullRequest = trimPullRequest(pullRequest, true);
140
+ return {
141
+ content: [{ type: "text", text: JSON.stringify(trimmedPullRequest, null, 2) }],
142
+ };
143
+ }
144
+ catch (error) {
145
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
146
+ return {
147
+ content: [{ type: "text", text: `Error creating pull request: ${errorMessage}` }],
148
+ isError: true,
149
+ };
150
+ }
142
151
  });
143
152
  server.tool(REPO_TOOLS.create_branch, "Create a new branch in the repository.", {
144
153
  repositoryId: z.string().describe("The ID of the repository where the branch will be created."),
@@ -146,67 +155,80 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
146
155
  sourceBranchName: z.string().optional().default("main").describe("The name of the source branch to create the new branch from. Defaults to 'main'."),
147
156
  sourceCommitId: z.string().optional().describe("The commit ID to create the branch from. If not provided, uses the latest commit of the source branch."),
148
157
  }, async ({ repositoryId, branchName, sourceBranchName, sourceCommitId }) => {
149
- const connection = await connectionProvider();
150
- const gitApi = await connection.getGitApi();
151
- let commitId = sourceCommitId;
152
- // If no commit ID is provided, get the latest commit from the source branch
153
- if (!commitId) {
154
- const sourceRefName = `refs/heads/${sourceBranchName}`;
158
+ try {
159
+ const connection = await connectionProvider();
160
+ const gitApi = await connection.getGitApi();
161
+ let commitId = sourceCommitId;
162
+ // If no commit ID is provided, get the latest commit from the source branch
163
+ if (!commitId) {
164
+ const sourceRefName = `refs/heads/${sourceBranchName}`;
165
+ try {
166
+ const sourceBranch = await gitApi.getRefs(repositoryId, undefined, "heads/", false, false, undefined, false, undefined, sourceBranchName);
167
+ const branch = sourceBranch.find((b) => b.name === sourceRefName);
168
+ if (!branch || !branch.objectId) {
169
+ return {
170
+ content: [
171
+ {
172
+ type: "text",
173
+ text: `Error: Source branch '${sourceBranchName}' not found in repository ${repositoryId}`,
174
+ },
175
+ ],
176
+ isError: true,
177
+ };
178
+ }
179
+ commitId = branch.objectId;
180
+ }
181
+ catch (error) {
182
+ return {
183
+ content: [
184
+ {
185
+ type: "text",
186
+ text: `Error retrieving source branch '${sourceBranchName}': ${error instanceof Error ? error.message : String(error)}`,
187
+ },
188
+ ],
189
+ isError: true,
190
+ };
191
+ }
192
+ }
193
+ // Create the new branch using updateRefs
194
+ const newRefName = `refs/heads/${branchName}`;
195
+ const refUpdate = {
196
+ name: newRefName,
197
+ newObjectId: commitId,
198
+ oldObjectId: "0000000000000000000000000000000000000000", // All zeros indicates creating a new ref
199
+ };
155
200
  try {
156
- const sourceBranch = await gitApi.getRefs(repositoryId, undefined, "heads/", false, false, undefined, false, undefined, sourceBranchName);
157
- const branch = sourceBranch.find((b) => b.name === sourceRefName);
158
- if (!branch || !branch.objectId) {
201
+ const result = await gitApi.updateRefs([refUpdate], repositoryId);
202
+ // Check if the branch creation was successful
203
+ if (result && result.length > 0 && result[0].success) {
159
204
  return {
160
205
  content: [
161
206
  {
162
207
  type: "text",
163
- text: `Error: Source branch '${sourceBranchName}' not found in repository ${repositoryId}`,
208
+ text: `Branch '${branchName}' created successfully from '${sourceBranchName}' (${commitId})`,
209
+ },
210
+ ],
211
+ };
212
+ }
213
+ else {
214
+ const errorMessage = result && result.length > 0 && result[0].customMessage ? result[0].customMessage : "Unknown error occurred during branch creation";
215
+ return {
216
+ content: [
217
+ {
218
+ type: "text",
219
+ text: `Error creating branch '${branchName}': ${errorMessage}`,
164
220
  },
165
221
  ],
166
222
  isError: true,
167
223
  };
168
224
  }
169
- commitId = branch.objectId;
170
225
  }
171
226
  catch (error) {
172
227
  return {
173
228
  content: [
174
229
  {
175
230
  type: "text",
176
- text: `Error retrieving source branch '${sourceBranchName}': ${error instanceof Error ? error.message : String(error)}`,
177
- },
178
- ],
179
- isError: true,
180
- };
181
- }
182
- }
183
- // Create the new branch using updateRefs
184
- const newRefName = `refs/heads/${branchName}`;
185
- const refUpdate = {
186
- name: newRefName,
187
- newObjectId: commitId,
188
- oldObjectId: "0000000000000000000000000000000000000000", // All zeros indicates creating a new ref
189
- };
190
- try {
191
- const result = await gitApi.updateRefs([refUpdate], repositoryId);
192
- // Check if the branch creation was successful
193
- if (result && result.length > 0 && result[0].success) {
194
- return {
195
- content: [
196
- {
197
- type: "text",
198
- text: `Branch '${branchName}' created successfully from '${sourceBranchName}' (${commitId})`,
199
- },
200
- ],
201
- };
202
- }
203
- else {
204
- const errorMessage = result && result.length > 0 && result[0].customMessage ? result[0].customMessage : "Unknown error occurred during branch creation";
205
- return {
206
- content: [
207
- {
208
- type: "text",
209
- text: `Error creating branch '${branchName}': ${errorMessage}`,
231
+ text: `Error creating branch '${branchName}': ${error instanceof Error ? error.message : String(error)}`,
210
232
  },
211
233
  ],
212
234
  isError: true,
@@ -214,13 +236,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
214
236
  }
215
237
  }
216
238
  catch (error) {
239
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
217
240
  return {
218
- content: [
219
- {
220
- type: "text",
221
- text: `Error creating branch '${branchName}': ${error instanceof Error ? error.message : String(error)}`,
222
- },
223
- ],
241
+ content: [{ type: "text", text: `Error creating branch: ${errorMessage}` }],
224
242
  isError: true,
225
243
  };
226
244
  }
@@ -242,56 +260,65 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
242
260
  transitionWorkItems: z.boolean().optional().default(true).describe("Whether to transition associated work items to the next state when the pull request autocompletes. Defaults to true."),
243
261
  bypassReason: z.string().optional().describe("Reason for bypassing branch policies. When provided, branch policies will be automatically bypassed during autocompletion."),
244
262
  }, async ({ repositoryId, pullRequestId, title, description, isDraft, targetRefName, status, autoComplete, mergeStrategy, deleteSourceBranch, transitionWorkItems, bypassReason }) => {
245
- const connection = await connectionProvider();
246
- const gitApi = await connection.getGitApi();
247
- // Build update object with only provided fields
248
- const updateRequest = {};
249
- if (title !== undefined)
250
- updateRequest.title = title;
251
- if (description !== undefined)
252
- updateRequest.description = description;
253
- if (isDraft !== undefined)
254
- updateRequest.isDraft = isDraft;
255
- if (targetRefName !== undefined)
256
- updateRequest.targetRefName = targetRefName;
257
- if (status !== undefined) {
258
- updateRequest.status = status === "Active" ? PullRequestStatus.Active.valueOf() : PullRequestStatus.Abandoned.valueOf();
259
- }
260
- if (autoComplete !== undefined) {
261
- if (autoComplete) {
262
- const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
263
- const autoCompleteUserId = data.authenticatedUser.id;
264
- updateRequest.autoCompleteSetBy = { id: autoCompleteUserId };
265
- const completionOptions = {
266
- deleteSourceBranch: deleteSourceBranch || false,
267
- transitionWorkItems: transitionWorkItems !== false, // Default to true unless explicitly set to false
268
- bypassPolicy: !!bypassReason, // Automatically set to true if bypassReason is provided
269
- };
270
- if (mergeStrategy) {
271
- completionOptions.mergeStrategy = GitPullRequestMergeStrategy[mergeStrategy];
263
+ try {
264
+ const connection = await connectionProvider();
265
+ const gitApi = await connection.getGitApi();
266
+ // Build update object with only provided fields
267
+ const updateRequest = {};
268
+ if (title !== undefined)
269
+ updateRequest.title = title;
270
+ if (description !== undefined)
271
+ updateRequest.description = description;
272
+ if (isDraft !== undefined)
273
+ updateRequest.isDraft = isDraft;
274
+ if (targetRefName !== undefined)
275
+ updateRequest.targetRefName = targetRefName;
276
+ if (status !== undefined) {
277
+ updateRequest.status = status === "Active" ? PullRequestStatus.Active.valueOf() : PullRequestStatus.Abandoned.valueOf();
278
+ }
279
+ if (autoComplete !== undefined) {
280
+ if (autoComplete) {
281
+ const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
282
+ const autoCompleteUserId = data.authenticatedUser.id;
283
+ updateRequest.autoCompleteSetBy = { id: autoCompleteUserId };
284
+ const completionOptions = {
285
+ deleteSourceBranch: deleteSourceBranch || false,
286
+ transitionWorkItems: transitionWorkItems !== false, // Default to true unless explicitly set to false
287
+ bypassPolicy: !!bypassReason, // Automatically set to true if bypassReason is provided
288
+ };
289
+ if (mergeStrategy) {
290
+ completionOptions.mergeStrategy = GitPullRequestMergeStrategy[mergeStrategy];
291
+ }
292
+ if (bypassReason) {
293
+ completionOptions.bypassReason = bypassReason;
294
+ }
295
+ updateRequest.completionOptions = completionOptions;
272
296
  }
273
- if (bypassReason) {
274
- completionOptions.bypassReason = bypassReason;
297
+ else {
298
+ updateRequest.autoCompleteSetBy = null;
299
+ updateRequest.completionOptions = null;
275
300
  }
276
- updateRequest.completionOptions = completionOptions;
277
301
  }
278
- else {
279
- updateRequest.autoCompleteSetBy = null;
280
- updateRequest.completionOptions = null;
302
+ // Validate that at least one field is provided for update
303
+ if (Object.keys(updateRequest).length === 0) {
304
+ return {
305
+ content: [{ type: "text", text: "Error: At least one field (title, description, isDraft, targetRefName, status, or autoComplete options) must be provided for update." }],
306
+ isError: true,
307
+ };
281
308
  }
309
+ const updatedPullRequest = await gitApi.updatePullRequest(updateRequest, repositoryId, pullRequestId);
310
+ const trimmedUpdatedPullRequest = trimPullRequest(updatedPullRequest, true);
311
+ return {
312
+ content: [{ type: "text", text: JSON.stringify(trimmedUpdatedPullRequest, null, 2) }],
313
+ };
282
314
  }
283
- // Validate that at least one field is provided for update
284
- if (Object.keys(updateRequest).length === 0) {
315
+ catch (error) {
316
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
285
317
  return {
286
- content: [{ type: "text", text: "Error: At least one field (title, description, isDraft, targetRefName, status, or autoComplete options) must be provided for update." }],
318
+ content: [{ type: "text", text: `Error updating pull request: ${errorMessage}` }],
287
319
  isError: true,
288
320
  };
289
321
  }
290
- const updatedPullRequest = await gitApi.updatePullRequest(updateRequest, repositoryId, pullRequestId);
291
- const trimmedUpdatedPullRequest = trimPullRequest(updatedPullRequest, true);
292
- return {
293
- content: [{ type: "text", text: JSON.stringify(trimmedUpdatedPullRequest, null, 2) }],
294
- };
295
322
  });
296
323
  server.tool(REPO_TOOLS.update_pull_request_reviewers, "Add or remove reviewers for an existing pull request.", {
297
324
  repositoryId: z.string().describe("The ID of the repository where the pull request exists."),
@@ -299,29 +326,38 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
299
326
  reviewerIds: z.array(z.string()).describe("List of reviewer ids to add or remove from the pull request."),
300
327
  action: z.enum(["add", "remove"]).describe("Action to perform on the reviewers. Can be 'add' or 'remove'."),
301
328
  }, async ({ repositoryId, pullRequestId, reviewerIds, action }) => {
302
- const connection = await connectionProvider();
303
- const gitApi = await connection.getGitApi();
304
- let updatedPullRequest;
305
- if (action === "add") {
306
- updatedPullRequest = await gitApi.createPullRequestReviewers(reviewerIds.map((id) => ({ id: id })), repositoryId, pullRequestId);
307
- const trimmedResponse = updatedPullRequest.map((item) => ({
308
- displayName: item.displayName,
309
- id: item.id,
310
- uniqueName: item.uniqueName,
311
- vote: item.vote,
312
- hasDeclined: item.hasDeclined,
313
- isFlagged: item.isFlagged,
314
- }));
315
- return {
316
- content: [{ type: "text", text: JSON.stringify(trimmedResponse, null, 2) }],
317
- };
318
- }
319
- else {
320
- for (const reviewerId of reviewerIds) {
321
- await gitApi.deletePullRequestReviewer(repositoryId, pullRequestId, reviewerId);
329
+ try {
330
+ const connection = await connectionProvider();
331
+ const gitApi = await connection.getGitApi();
332
+ let updatedPullRequest;
333
+ if (action === "add") {
334
+ updatedPullRequest = await gitApi.createPullRequestReviewers(reviewerIds.map((id) => ({ id: id })), repositoryId, pullRequestId);
335
+ const trimmedResponse = updatedPullRequest.map((item) => ({
336
+ displayName: item.displayName,
337
+ id: item.id,
338
+ uniqueName: item.uniqueName,
339
+ vote: item.vote,
340
+ hasDeclined: item.hasDeclined,
341
+ isFlagged: item.isFlagged,
342
+ }));
343
+ return {
344
+ content: [{ type: "text", text: JSON.stringify(trimmedResponse, null, 2) }],
345
+ };
322
346
  }
347
+ else {
348
+ for (const reviewerId of reviewerIds) {
349
+ await gitApi.deletePullRequestReviewer(repositoryId, pullRequestId, reviewerId);
350
+ }
351
+ return {
352
+ content: [{ type: "text", text: `Reviewers with IDs ${reviewerIds.join(", ")} removed from pull request ${pullRequestId}.` }],
353
+ };
354
+ }
355
+ }
356
+ catch (error) {
357
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
323
358
  return {
324
- content: [{ type: "text", text: `Reviewers with IDs ${reviewerIds.join(", ")} removed from pull request ${pullRequestId}.` }],
359
+ content: [{ type: "text", text: `Error updating pull request reviewers: ${errorMessage}` }],
360
+ isError: true,
325
361
  };
326
362
  }
327
363
  });
@@ -331,24 +367,33 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
331
367
  skip: z.number().default(0).describe("The number of repositories to skip. Defaults to 0."),
332
368
  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."),
333
369
  }, async ({ project, top, skip, repoNameFilter }) => {
334
- const connection = await connectionProvider();
335
- const gitApi = await connection.getGitApi();
336
- const repositories = await gitApi.getRepositories(project, false, false, false);
337
- const filteredRepositories = repoNameFilter ? filterReposByName(repositories, repoNameFilter) : repositories;
338
- const paginatedRepositories = filteredRepositories?.sort((a, b) => a.name?.localeCompare(b.name ?? "") ?? 0).slice(skip, skip + top);
339
- // Filter out the irrelevant properties
340
- const trimmedRepositories = paginatedRepositories?.map((repo) => ({
341
- id: repo.id,
342
- name: repo.name,
343
- isDisabled: repo.isDisabled,
344
- isFork: repo.isFork,
345
- isInMaintenance: repo.isInMaintenance,
346
- webUrl: repo.webUrl,
347
- size: repo.size,
348
- }));
349
- return {
350
- content: [{ type: "text", text: JSON.stringify(trimmedRepositories, null, 2) }],
351
- };
370
+ try {
371
+ const connection = await connectionProvider();
372
+ const gitApi = await connection.getGitApi();
373
+ const repositories = await gitApi.getRepositories(project, false, false, false);
374
+ const filteredRepositories = repoNameFilter ? filterReposByName(repositories, repoNameFilter) : repositories;
375
+ const paginatedRepositories = filteredRepositories?.sort((a, b) => a.name?.localeCompare(b.name ?? "") ?? 0).slice(skip, skip + top);
376
+ // Filter out the irrelevant properties
377
+ const trimmedRepositories = paginatedRepositories?.map((repo) => ({
378
+ id: repo.id,
379
+ name: repo.name,
380
+ isDisabled: repo.isDisabled,
381
+ isFork: repo.isFork,
382
+ isInMaintenance: repo.isInMaintenance,
383
+ webUrl: repo.webUrl,
384
+ size: repo.size,
385
+ }));
386
+ return {
387
+ content: [{ type: "text", text: JSON.stringify(trimmedRepositories, null, 2) }],
388
+ };
389
+ }
390
+ catch (error) {
391
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
392
+ return {
393
+ content: [{ type: "text", text: `Error listing repositories: ${errorMessage}` }],
394
+ isError: true,
395
+ };
396
+ }
352
397
  });
353
398
  server.tool(REPO_TOOLS.list_pull_requests_by_repo_or_project, "Retrieve a list of pull requests for a given repository. Either repositoryId or project must be provided.", {
354
399
  repositoryId: z.string().optional().describe("The ID of the repository where the pull requests are located."),
@@ -369,103 +414,112 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
369
414
  sourceRefName: z.string().optional().describe("Filter pull requests from this source branch (e.g., 'refs/heads/feature-branch')."),
370
415
  targetRefName: z.string().optional().describe("Filter pull requests into this target branch (e.g., 'refs/heads/main')."),
371
416
  }, async ({ repositoryId, project, top, skip, created_by_me, created_by_user, i_am_reviewer, user_is_reviewer, status, sourceRefName, targetRefName }) => {
372
- const connection = await connectionProvider();
373
- const gitApi = await connection.getGitApi();
374
- // Build the search criteria
375
- const searchCriteria = {
376
- status: pullRequestStatusStringToInt(status),
377
- };
378
- if (!repositoryId && !project) {
379
- return {
380
- content: [
381
- {
382
- type: "text",
383
- text: "Either repositoryId or project must be provided.",
384
- },
385
- ],
386
- isError: true,
417
+ try {
418
+ const connection = await connectionProvider();
419
+ const gitApi = await connection.getGitApi();
420
+ // Build the search criteria
421
+ const searchCriteria = {
422
+ status: pullRequestStatusStringToInt(status),
387
423
  };
388
- }
389
- if (repositoryId) {
390
- searchCriteria.repositoryId = repositoryId;
391
- }
392
- if (sourceRefName) {
393
- searchCriteria.sourceRefName = sourceRefName;
394
- }
395
- if (targetRefName) {
396
- searchCriteria.targetRefName = targetRefName;
397
- }
398
- if (created_by_user) {
399
- try {
400
- const userId = await getUserIdFromEmail(created_by_user, tokenProvider, connectionProvider, userAgentProvider);
401
- searchCriteria.creatorId = userId;
402
- }
403
- catch (error) {
424
+ if (!repositoryId && !project) {
404
425
  return {
405
426
  content: [
406
427
  {
407
428
  type: "text",
408
- text: `Error finding user with email ${created_by_user}: ${error instanceof Error ? error.message : String(error)}`,
429
+ text: "Either repositoryId or project must be provided.",
409
430
  },
410
431
  ],
411
432
  isError: true,
412
433
  };
413
434
  }
414
- }
415
- else if (created_by_me) {
416
- const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
417
- const userId = data.authenticatedUser.id;
418
- searchCriteria.creatorId = userId;
419
- }
420
- if (user_is_reviewer) {
421
- try {
422
- const reviewerUserId = await getUserIdFromEmail(user_is_reviewer, tokenProvider, connectionProvider, userAgentProvider);
423
- searchCriteria.reviewerId = reviewerUserId;
435
+ if (repositoryId) {
436
+ searchCriteria.repositoryId = repositoryId;
424
437
  }
425
- catch (error) {
438
+ if (sourceRefName) {
439
+ searchCriteria.sourceRefName = sourceRefName;
440
+ }
441
+ if (targetRefName) {
442
+ searchCriteria.targetRefName = targetRefName;
443
+ }
444
+ if (created_by_user) {
445
+ try {
446
+ const userId = await getUserIdFromEmail(created_by_user, tokenProvider, connectionProvider, userAgentProvider);
447
+ searchCriteria.creatorId = userId;
448
+ }
449
+ catch (error) {
450
+ return {
451
+ content: [
452
+ {
453
+ type: "text",
454
+ text: `Error finding user with email ${created_by_user}: ${error instanceof Error ? error.message : String(error)}`,
455
+ },
456
+ ],
457
+ isError: true,
458
+ };
459
+ }
460
+ }
461
+ else if (created_by_me) {
462
+ const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
463
+ const userId = data.authenticatedUser.id;
464
+ searchCriteria.creatorId = userId;
465
+ }
466
+ if (user_is_reviewer) {
467
+ try {
468
+ const reviewerUserId = await getUserIdFromEmail(user_is_reviewer, tokenProvider, connectionProvider, userAgentProvider);
469
+ searchCriteria.reviewerId = reviewerUserId;
470
+ }
471
+ catch (error) {
472
+ return {
473
+ content: [
474
+ {
475
+ type: "text",
476
+ text: `Error finding reviewer with email ${user_is_reviewer}: ${error instanceof Error ? error.message : String(error)}`,
477
+ },
478
+ ],
479
+ isError: true,
480
+ };
481
+ }
482
+ }
483
+ else if (i_am_reviewer) {
484
+ const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
485
+ const userId = data.authenticatedUser.id;
486
+ searchCriteria.reviewerId = userId;
487
+ }
488
+ let pullRequests;
489
+ if (repositoryId) {
490
+ pullRequests = await gitApi.getPullRequests(repositoryId, searchCriteria, project, // project
491
+ undefined, // maxCommentLength
492
+ skip, top);
493
+ }
494
+ else if (project) {
495
+ // If only project is provided, use getPullRequestsByProject
496
+ pullRequests = await gitApi.getPullRequestsByProject(project, searchCriteria, undefined, // maxCommentLength
497
+ skip, top);
498
+ }
499
+ else {
500
+ // This case should not occur due to earlier validation, but added for completeness
426
501
  return {
427
502
  content: [
428
503
  {
429
504
  type: "text",
430
- text: `Error finding reviewer with email ${user_is_reviewer}: ${error instanceof Error ? error.message : String(error)}`,
505
+ text: "Either repositoryId or project must be provided.",
431
506
  },
432
507
  ],
433
508
  isError: true,
434
509
  };
435
510
  }
511
+ const filteredPullRequests = pullRequests?.map((pr) => trimPullRequest(pr));
512
+ return {
513
+ content: [{ type: "text", text: JSON.stringify(filteredPullRequests, null, 2) }],
514
+ };
436
515
  }
437
- else if (i_am_reviewer) {
438
- const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
439
- const userId = data.authenticatedUser.id;
440
- searchCriteria.reviewerId = userId;
441
- }
442
- let pullRequests;
443
- if (repositoryId) {
444
- pullRequests = await gitApi.getPullRequests(repositoryId, searchCriteria, project, // project
445
- undefined, // maxCommentLength
446
- skip, top);
447
- }
448
- else if (project) {
449
- // If only project is provided, use getPullRequestsByProject
450
- pullRequests = await gitApi.getPullRequestsByProject(project, searchCriteria, undefined, // maxCommentLength
451
- skip, top);
452
- }
453
- else {
454
- // This case should not occur due to earlier validation, but added for completeness
516
+ catch (error) {
517
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
455
518
  return {
456
- content: [
457
- {
458
- type: "text",
459
- text: "Either repositoryId or project must be provided.",
460
- },
461
- ],
519
+ content: [{ type: "text", text: `Error listing pull requests: ${errorMessage}` }],
462
520
  isError: true,
463
521
  };
464
522
  }
465
- const filteredPullRequests = pullRequests?.map((pr) => trimPullRequest(pr));
466
- return {
467
- content: [{ type: "text", text: JSON.stringify(filteredPullRequests, null, 2) }],
468
- };
469
523
  });
470
524
  server.tool(REPO_TOOLS.list_pull_request_threads, "Retrieve a list of comment threads for a pull request.", {
471
525
  repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
@@ -483,38 +537,47 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
483
537
  authorEmail: z.string().optional().describe("Filter threads by the email of the thread author (first comment author)."),
484
538
  authorDisplayName: z.string().optional().describe("Filter threads by the display name of the thread author (first comment author). Case-insensitive partial matching."),
485
539
  }, async ({ repositoryId, pullRequestId, project, iteration, baseIteration, top, skip, fullResponse, status, authorEmail, authorDisplayName }) => {
486
- const connection = await connectionProvider();
487
- const gitApi = await connection.getGitApi();
488
- const threads = await gitApi.getThreads(repositoryId, pullRequestId, project, iteration, baseIteration);
489
- let filteredThreads = threads;
490
- if (status !== undefined) {
491
- const statusValue = CommentThreadStatus[status];
492
- filteredThreads = filteredThreads?.filter((thread) => thread.status === statusValue);
493
- }
494
- if (authorEmail !== undefined) {
495
- filteredThreads = filteredThreads?.filter((thread) => {
496
- const firstComment = thread.comments?.[0];
497
- return firstComment?.author?.uniqueName?.toLowerCase() === authorEmail.toLowerCase();
498
- });
499
- }
500
- if (authorDisplayName !== undefined) {
501
- const lowerAuthorName = authorDisplayName.toLowerCase();
502
- filteredThreads = filteredThreads?.filter((thread) => {
503
- const firstComment = thread.comments?.[0];
504
- return firstComment?.author?.displayName?.toLowerCase().includes(lowerAuthorName);
505
- });
506
- }
507
- const paginatedThreads = filteredThreads?.sort((a, b) => (a.id ?? 0) - (b.id ?? 0)).slice(skip, skip + top);
508
- if (fullResponse) {
509
- return {
510
- content: [{ type: "text", text: JSON.stringify(paginatedThreads, null, 2) }],
511
- };
512
- }
513
- // Return trimmed thread data focusing on essential information
514
- const trimmedThreads = paginatedThreads?.map((thread) => trimPullRequestThread(thread));
515
- return {
516
- content: [{ type: "text", text: JSON.stringify(trimmedThreads, null, 2) }],
517
- };
540
+ try {
541
+ const connection = await connectionProvider();
542
+ const gitApi = await connection.getGitApi();
543
+ const threads = await gitApi.getThreads(repositoryId, pullRequestId, project, iteration, baseIteration);
544
+ let filteredThreads = threads;
545
+ if (status !== undefined) {
546
+ const statusValue = CommentThreadStatus[status];
547
+ filteredThreads = filteredThreads?.filter((thread) => thread.status === statusValue);
548
+ }
549
+ if (authorEmail !== undefined) {
550
+ filteredThreads = filteredThreads?.filter((thread) => {
551
+ const firstComment = thread.comments?.[0];
552
+ return firstComment?.author?.uniqueName?.toLowerCase() === authorEmail.toLowerCase();
553
+ });
554
+ }
555
+ if (authorDisplayName !== undefined) {
556
+ const lowerAuthorName = authorDisplayName.toLowerCase();
557
+ filteredThreads = filteredThreads?.filter((thread) => {
558
+ const firstComment = thread.comments?.[0];
559
+ return firstComment?.author?.displayName?.toLowerCase().includes(lowerAuthorName);
560
+ });
561
+ }
562
+ const paginatedThreads = filteredThreads?.sort((a, b) => (a.id ?? 0) - (b.id ?? 0)).slice(skip, skip + top);
563
+ if (fullResponse) {
564
+ return {
565
+ content: [{ type: "text", text: JSON.stringify(paginatedThreads, null, 2) }],
566
+ };
567
+ }
568
+ // Return trimmed thread data focusing on essential information
569
+ const trimmedThreads = paginatedThreads?.map((thread) => trimPullRequestThread(thread));
570
+ return {
571
+ content: [{ type: "text", text: JSON.stringify(trimmedThreads, null, 2) }],
572
+ };
573
+ }
574
+ catch (error) {
575
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
576
+ return {
577
+ content: [{ type: "text", text: `Error listing pull request threads: ${errorMessage}` }],
578
+ isError: true,
579
+ };
580
+ }
518
581
  });
519
582
  server.tool(REPO_TOOLS.list_pull_request_thread_comments, "Retrieve a list of comments in a pull request thread.", {
520
583
  repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
@@ -525,97 +588,184 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
525
588
  skip: z.number().default(0).describe("The number of comments to skip."),
526
589
  fullResponse: z.boolean().optional().default(false).describe("Return full comment JSON response instead of trimmed data."),
527
590
  }, async ({ repositoryId, pullRequestId, threadId, project, top, skip, fullResponse }) => {
528
- const connection = await connectionProvider();
529
- const gitApi = await connection.getGitApi();
530
- // Get thread comments - GitApi uses getComments for retrieving comments from a specific thread
531
- const comments = await gitApi.getComments(repositoryId, pullRequestId, threadId, project);
532
- const paginatedComments = comments?.sort((a, b) => (a.id ?? 0) - (b.id ?? 0)).slice(skip, skip + top);
533
- if (fullResponse) {
591
+ try {
592
+ const connection = await connectionProvider();
593
+ const gitApi = await connection.getGitApi();
594
+ // Get thread comments - GitApi uses getComments for retrieving comments from a specific thread
595
+ const comments = await gitApi.getComments(repositoryId, pullRequestId, threadId, project);
596
+ const paginatedComments = comments?.sort((a, b) => (a.id ?? 0) - (b.id ?? 0)).slice(skip, skip + top);
597
+ if (fullResponse) {
598
+ return {
599
+ content: [{ type: "text", text: JSON.stringify(paginatedComments, null, 2) }],
600
+ };
601
+ }
602
+ // Return trimmed comment data focusing on essential information
603
+ const trimmedComments = trimComments(paginatedComments);
534
604
  return {
535
- content: [{ type: "text", text: JSON.stringify(paginatedComments, null, 2) }],
605
+ content: [{ type: "text", text: JSON.stringify(trimmedComments, null, 2) }],
606
+ };
607
+ }
608
+ catch (error) {
609
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
610
+ return {
611
+ content: [{ type: "text", text: `Error listing pull request thread comments: ${errorMessage}` }],
612
+ isError: true,
536
613
  };
537
614
  }
538
- // Return trimmed comment data focusing on essential information
539
- const trimmedComments = trimComments(paginatedComments);
540
- return {
541
- content: [{ type: "text", text: JSON.stringify(trimmedComments, null, 2) }],
542
- };
543
615
  });
544
616
  server.tool(REPO_TOOLS.list_branches_by_repo, "Retrieve a list of branches for a given repository.", {
545
617
  repositoryId: z.string().describe("The ID of the repository where the branches are located."),
546
618
  top: z.number().default(100).describe("The maximum number of branches to return. Defaults to 100."),
547
619
  filterContains: z.string().optional().describe("Filter to find branches that contain this string in their name."),
548
620
  }, async ({ repositoryId, top, filterContains }) => {
549
- const connection = await connectionProvider();
550
- const gitApi = await connection.getGitApi();
551
- const branches = await gitApi.getRefs(repositoryId, undefined, "heads/", undefined, undefined, undefined, undefined, undefined, filterContains);
552
- const filteredBranches = branchesFilterOutIrrelevantProperties(branches, top);
553
- return {
554
- content: [{ type: "text", text: JSON.stringify(filteredBranches, null, 2) }],
555
- };
621
+ try {
622
+ const connection = await connectionProvider();
623
+ const gitApi = await connection.getGitApi();
624
+ const branches = await gitApi.getRefs(repositoryId, undefined, "heads/", undefined, undefined, undefined, undefined, undefined, filterContains);
625
+ const filteredBranches = branchesFilterOutIrrelevantProperties(branches, top);
626
+ return {
627
+ content: [{ type: "text", text: JSON.stringify(filteredBranches, null, 2) }],
628
+ };
629
+ }
630
+ catch (error) {
631
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
632
+ return {
633
+ content: [{ type: "text", text: `Error listing branches: ${errorMessage}` }],
634
+ isError: true,
635
+ };
636
+ }
556
637
  });
557
638
  server.tool(REPO_TOOLS.list_my_branches_by_repo, "Retrieve a list of my branches for a given repository Id.", {
558
639
  repositoryId: z.string().describe("The ID of the repository where the branches are located."),
559
640
  top: z.number().default(100).describe("The maximum number of branches to return."),
560
641
  filterContains: z.string().optional().describe("Filter to find branches that contain this string in their name."),
561
642
  }, async ({ repositoryId, top, filterContains }) => {
562
- const connection = await connectionProvider();
563
- const gitApi = await connection.getGitApi();
564
- const branches = await gitApi.getRefs(repositoryId, undefined, "heads/", undefined, undefined, true, undefined, undefined, filterContains);
565
- const filteredBranches = branchesFilterOutIrrelevantProperties(branches, top);
566
- return {
567
- content: [{ type: "text", text: JSON.stringify(filteredBranches, null, 2) }],
568
- };
643
+ try {
644
+ const connection = await connectionProvider();
645
+ const gitApi = await connection.getGitApi();
646
+ const branches = await gitApi.getRefs(repositoryId, undefined, "heads/", undefined, undefined, true, undefined, undefined, filterContains);
647
+ const filteredBranches = branchesFilterOutIrrelevantProperties(branches, top);
648
+ return {
649
+ content: [{ type: "text", text: JSON.stringify(filteredBranches, null, 2) }],
650
+ };
651
+ }
652
+ catch (error) {
653
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
654
+ return {
655
+ content: [{ type: "text", text: `Error listing my branches: ${errorMessage}` }],
656
+ isError: true,
657
+ };
658
+ }
569
659
  });
570
660
  server.tool(REPO_TOOLS.get_repo_by_name_or_id, "Get the repository by project and repository name or ID.", {
571
661
  project: z.string().describe("Project name or ID where the repository is located."),
572
662
  repositoryNameOrId: z.string().describe("Repository name or ID."),
573
663
  }, async ({ project, repositoryNameOrId }) => {
574
- const connection = await connectionProvider();
575
- const gitApi = await connection.getGitApi();
576
- const repositories = await gitApi.getRepositories(project);
577
- const repository = repositories?.find((repo) => repo.name === repositoryNameOrId || repo.id === repositoryNameOrId);
578
- if (!repository) {
579
- throw new Error(`Repository ${repositoryNameOrId} not found in project ${project}`);
580
- }
581
- return {
582
- content: [{ type: "text", text: JSON.stringify(repository, null, 2) }],
583
- };
664
+ try {
665
+ const connection = await connectionProvider();
666
+ const gitApi = await connection.getGitApi();
667
+ const repositories = await gitApi.getRepositories(project);
668
+ const repository = repositories?.find((repo) => repo.name === repositoryNameOrId || repo.id === repositoryNameOrId);
669
+ if (!repository) {
670
+ return {
671
+ content: [{ type: "text", text: `Repository ${repositoryNameOrId} not found in project ${project}` }],
672
+ isError: true,
673
+ };
674
+ }
675
+ return {
676
+ content: [{ type: "text", text: JSON.stringify(repository, null, 2) }],
677
+ };
678
+ }
679
+ catch (error) {
680
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
681
+ return {
682
+ content: [{ type: "text", text: `Error getting repository: ${errorMessage}` }],
683
+ isError: true,
684
+ };
685
+ }
584
686
  });
585
687
  server.tool(REPO_TOOLS.get_branch_by_name, "Get a branch by its name.", {
586
688
  repositoryId: z.string().describe("The ID of the repository where the branch is located."),
587
689
  branchName: z.string().describe("The name of the branch to retrieve, e.g., 'main' or 'feature-branch'."),
588
690
  }, async ({ repositoryId, branchName }) => {
589
- const connection = await connectionProvider();
590
- const gitApi = await connection.getGitApi();
591
- const branches = await gitApi.getRefs(repositoryId, undefined, "heads/", false, false, undefined, false, undefined, branchName);
592
- const branch = branches.find((branch) => branch.name === `refs/heads/${branchName}` || branch.name === branchName);
593
- if (!branch) {
691
+ try {
692
+ const connection = await connectionProvider();
693
+ const gitApi = await connection.getGitApi();
694
+ const branches = await gitApi.getRefs(repositoryId, undefined, "heads/", false, false, undefined, false, undefined, branchName);
695
+ const branch = branches.find((branch) => branch.name === `refs/heads/${branchName}` || branch.name === branchName);
696
+ if (!branch) {
697
+ return {
698
+ content: [
699
+ {
700
+ type: "text",
701
+ text: `Branch ${branchName} not found in repository ${repositoryId}`,
702
+ },
703
+ ],
704
+ isError: true,
705
+ };
706
+ }
594
707
  return {
595
- content: [
596
- {
597
- type: "text",
598
- text: `Branch ${branchName} not found in repository ${repositoryId}`,
599
- },
600
- ],
708
+ content: [{ type: "text", text: JSON.stringify(branch, null, 2) }],
709
+ };
710
+ }
711
+ catch (error) {
712
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
713
+ return {
714
+ content: [{ type: "text", text: `Error getting branch: ${errorMessage}` }],
601
715
  isError: true,
602
716
  };
603
717
  }
604
- return {
605
- content: [{ type: "text", text: JSON.stringify(branch, null, 2) }],
606
- };
607
718
  });
608
719
  server.tool(REPO_TOOLS.get_pull_request_by_id, "Get a pull request by its ID.", {
609
720
  repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
610
721
  pullRequestId: z.number().describe("The ID of the pull request to retrieve."),
611
722
  includeWorkItemRefs: z.boolean().optional().default(false).describe("Whether to reference work items associated with the pull request."),
612
- }, async ({ repositoryId, pullRequestId, includeWorkItemRefs }) => {
613
- const connection = await connectionProvider();
614
- const gitApi = await connection.getGitApi();
615
- const pullRequest = await gitApi.getPullRequest(repositoryId, pullRequestId, undefined, undefined, undefined, undefined, undefined, includeWorkItemRefs);
616
- return {
617
- content: [{ type: "text", text: JSON.stringify(pullRequest, null, 2) }],
618
- };
723
+ includeLabels: z.boolean().optional().default(false).describe("Whether to include a summary of labels in the response."),
724
+ }, async ({ repositoryId, pullRequestId, includeWorkItemRefs, includeLabels }) => {
725
+ try {
726
+ const connection = await connectionProvider();
727
+ const gitApi = await connection.getGitApi();
728
+ const pullRequest = await gitApi.getPullRequest(repositoryId, pullRequestId, undefined, undefined, undefined, undefined, undefined, includeWorkItemRefs);
729
+ if (includeLabels) {
730
+ try {
731
+ const projectId = pullRequest.repository?.project?.id;
732
+ const projectName = pullRequest.repository?.project?.name;
733
+ const labels = await gitApi.getPullRequestLabels(repositoryId, pullRequestId, projectName, projectId);
734
+ const labelNames = labels.map((label) => label.name).filter((name) => name !== undefined);
735
+ const enhancedResponse = {
736
+ ...pullRequest,
737
+ labelSummary: {
738
+ labels: labelNames,
739
+ labelCount: labelNames.length,
740
+ },
741
+ };
742
+ return {
743
+ content: [{ type: "text", text: JSON.stringify(enhancedResponse, null, 2) }],
744
+ };
745
+ }
746
+ catch (error) {
747
+ console.warn(`Error fetching PR labels: ${error instanceof Error ? error.message : "Unknown error"}`);
748
+ // Fall back to the original response without labels
749
+ const enhancedResponse = {
750
+ ...pullRequest,
751
+ labelSummary: {},
752
+ };
753
+ return {
754
+ content: [{ type: "text", text: JSON.stringify(enhancedResponse, null, 2) }],
755
+ };
756
+ }
757
+ }
758
+ return {
759
+ content: [{ type: "text", text: JSON.stringify(pullRequest, null, 2) }],
760
+ };
761
+ }
762
+ catch (error) {
763
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
764
+ return {
765
+ content: [{ type: "text", text: `Error getting pull request: ${errorMessage}` }],
766
+ isError: true,
767
+ };
768
+ }
619
769
  });
620
770
  server.tool(REPO_TOOLS.reply_to_comment, "Replies to a specific comment on a pull request.", {
621
771
  repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
@@ -625,24 +775,33 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
625
775
  project: z.string().optional().describe("Project ID or project name (optional)"),
626
776
  fullResponse: z.boolean().optional().default(false).describe("Return full comment JSON response instead of a simple confirmation message."),
627
777
  }, async ({ repositoryId, pullRequestId, threadId, content, project, fullResponse }) => {
628
- const connection = await connectionProvider();
629
- const gitApi = await connection.getGitApi();
630
- const comment = await gitApi.createComment({ content }, repositoryId, pullRequestId, threadId, project);
631
- // Check if the comment was successfully created
632
- if (!comment) {
778
+ try {
779
+ const connection = await connectionProvider();
780
+ const gitApi = await connection.getGitApi();
781
+ const comment = await gitApi.createComment({ content }, repositoryId, pullRequestId, threadId, project);
782
+ // Check if the comment was successfully created
783
+ if (!comment) {
784
+ return {
785
+ content: [{ type: "text", text: `Error: Failed to add comment to thread ${threadId}. The comment was not created successfully.` }],
786
+ isError: true,
787
+ };
788
+ }
789
+ if (fullResponse) {
790
+ return {
791
+ content: [{ type: "text", text: JSON.stringify(comment, null, 2) }],
792
+ };
793
+ }
633
794
  return {
634
- content: [{ type: "text", text: `Error: Failed to add comment to thread ${threadId}. The comment was not created successfully.` }],
635
- isError: true,
795
+ content: [{ type: "text", text: `Comment successfully added to thread ${threadId}.` }],
636
796
  };
637
797
  }
638
- if (fullResponse) {
798
+ catch (error) {
799
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
639
800
  return {
640
- content: [{ type: "text", text: JSON.stringify(comment, null, 2) }],
801
+ content: [{ type: "text", text: `Error replying to comment: ${errorMessage}` }],
802
+ isError: true,
641
803
  };
642
804
  }
643
- return {
644
- content: [{ type: "text", text: `Comment successfully added to thread ${threadId}.` }],
645
- };
646
805
  });
647
806
  server.tool(REPO_TOOLS.create_pull_request_thread, "Creates a new comment thread on a pull request.", {
648
807
  repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
@@ -669,42 +828,66 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
669
828
  .optional()
670
829
  .describe("Position of last character of the thread's span in right file. The character offset of a thread's position inside of a line. Must only be set if rightFileEndLine is also specified. (optional)"),
671
830
  }, async ({ repositoryId, pullRequestId, content, project, filePath, status, rightFileStartLine, rightFileStartOffset, rightFileEndLine, rightFileEndOffset }) => {
672
- const connection = await connectionProvider();
673
- const gitApi = await connection.getGitApi();
674
- const normalizedFilePath = filePath && !filePath.startsWith("/") ? `/${filePath}` : filePath;
675
- const threadContext = { filePath: normalizedFilePath };
676
- if (rightFileStartLine !== undefined) {
677
- if (rightFileStartLine < 1) {
678
- throw new Error("rightFileStartLine must be greater than or equal to 1.");
679
- }
680
- threadContext.rightFileStart = { line: rightFileStartLine };
681
- if (rightFileStartOffset !== undefined) {
682
- if (rightFileStartOffset < 1) {
683
- throw new Error("rightFileStartOffset must be greater than or equal to 1.");
831
+ try {
832
+ const connection = await connectionProvider();
833
+ const gitApi = await connection.getGitApi();
834
+ const normalizedFilePath = filePath && !filePath.startsWith("/") ? `/${filePath}` : filePath;
835
+ const threadContext = { filePath: normalizedFilePath };
836
+ if (rightFileStartLine !== undefined) {
837
+ if (rightFileStartLine < 1) {
838
+ return {
839
+ content: [{ type: "text", text: "rightFileStartLine must be greater than or equal to 1." }],
840
+ isError: true,
841
+ };
842
+ }
843
+ threadContext.rightFileStart = { line: rightFileStartLine };
844
+ if (rightFileStartOffset !== undefined) {
845
+ if (rightFileStartOffset < 1) {
846
+ return {
847
+ content: [{ type: "text", text: "rightFileStartOffset must be greater than or equal to 1." }],
848
+ isError: true,
849
+ };
850
+ }
851
+ threadContext.rightFileStart.offset = rightFileStartOffset;
684
852
  }
685
- threadContext.rightFileStart.offset = rightFileStartOffset;
686
- }
687
- }
688
- if (rightFileEndLine !== undefined) {
689
- if (rightFileStartLine === undefined) {
690
- throw new Error("rightFileEndLine must only be specified if rightFileStartLine is also specified.");
691
- }
692
- if (rightFileEndLine < 1) {
693
- throw new Error("rightFileEndLine must be greater than or equal to 1.");
694
853
  }
695
- threadContext.rightFileEnd = { line: rightFileEndLine };
696
- if (rightFileEndOffset !== undefined) {
697
- if (rightFileEndOffset < 1) {
698
- throw new Error("rightFileEndOffset must be greater than or equal to 1.");
854
+ if (rightFileEndLine !== undefined) {
855
+ if (rightFileStartLine === undefined) {
856
+ return {
857
+ content: [{ type: "text", text: "rightFileEndLine must only be specified if rightFileStartLine is also specified." }],
858
+ isError: true,
859
+ };
860
+ }
861
+ if (rightFileEndLine < 1) {
862
+ return {
863
+ content: [{ type: "text", text: "rightFileEndLine must be greater than or equal to 1." }],
864
+ isError: true,
865
+ };
866
+ }
867
+ threadContext.rightFileEnd = { line: rightFileEndLine };
868
+ if (rightFileEndOffset !== undefined) {
869
+ if (rightFileEndOffset < 1) {
870
+ return {
871
+ content: [{ type: "text", text: "rightFileEndOffset must be greater than or equal to 1." }],
872
+ isError: true,
873
+ };
874
+ }
875
+ threadContext.rightFileEnd.offset = rightFileEndOffset;
699
876
  }
700
- threadContext.rightFileEnd.offset = rightFileEndOffset;
701
877
  }
878
+ const thread = await gitApi.createThread({ comments: [{ content: content }], threadContext: threadContext, status: CommentThreadStatus[status] }, repositoryId, pullRequestId, project);
879
+ const trimmedThread = trimPullRequestThread(thread);
880
+ return {
881
+ content: [{ type: "text", text: JSON.stringify(trimmedThread, null, 2) }],
882
+ };
883
+ }
884
+ catch (error) {
885
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
886
+ return {
887
+ content: [{ type: "text", text: `Error creating pull request thread: ${errorMessage}` }],
888
+ isError: true,
889
+ };
702
890
  }
703
- const thread = await gitApi.createThread({ comments: [{ content: content }], threadContext: threadContext, status: CommentThreadStatus[status] }, repositoryId, pullRequestId, project);
704
- const trimmedThread = trimPullRequestThread(thread);
705
- return {
706
- content: [{ type: "text", text: JSON.stringify(trimmedThread, null, 2) }],
707
- };
708
891
  });
709
892
  server.tool(REPO_TOOLS.resolve_comment, "Resolves a specific comment thread on a pull request.", {
710
893
  repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
@@ -712,25 +895,34 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
712
895
  threadId: z.number().describe("The ID of the thread to be resolved."),
713
896
  fullResponse: z.boolean().optional().default(false).describe("Return full thread JSON response instead of a simple confirmation message."),
714
897
  }, async ({ repositoryId, pullRequestId, threadId, fullResponse }) => {
715
- const connection = await connectionProvider();
716
- const gitApi = await connection.getGitApi();
717
- const thread = await gitApi.updateThread({ status: 2 }, // 2 corresponds to "Resolved" status
718
- repositoryId, pullRequestId, threadId);
719
- // Check if the thread was successfully resolved
720
- if (!thread) {
721
- return {
722
- content: [{ type: "text", text: `Error: Failed to resolve thread ${threadId}. The thread status was not updated successfully.` }],
723
- isError: true,
898
+ try {
899
+ const connection = await connectionProvider();
900
+ const gitApi = await connection.getGitApi();
901
+ const thread = await gitApi.updateThread({ status: 2 }, // 2 corresponds to "Resolved" status
902
+ repositoryId, pullRequestId, threadId);
903
+ // Check if the thread was successfully resolved
904
+ if (!thread) {
905
+ return {
906
+ content: [{ type: "text", text: `Error: Failed to resolve thread ${threadId}. The thread status was not updated successfully.` }],
907
+ isError: true,
908
+ };
909
+ }
910
+ if (fullResponse) {
911
+ return {
912
+ content: [{ type: "text", text: JSON.stringify(thread, null, 2) }],
913
+ };
914
+ }
915
+ return {
916
+ content: [{ type: "text", text: `Thread ${threadId} was successfully resolved.` }],
724
917
  };
725
918
  }
726
- if (fullResponse) {
919
+ catch (error) {
920
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
727
921
  return {
728
- content: [{ type: "text", text: JSON.stringify(thread, null, 2) }],
922
+ content: [{ type: "text", text: `Error resolving comment: ${errorMessage}` }],
923
+ isError: true,
729
924
  };
730
925
  }
731
- return {
732
- content: [{ type: "text", text: `Thread ${threadId} was successfully resolved.` }],
733
- };
734
926
  });
735
927
  const gitVersionTypeStrings = Object.values(GitVersionType).filter((value) => typeof value === "string");
736
928
  server.tool(REPO_TOOLS.search_commits, "Search for commits in a repository with comprehensive filtering capabilities. Supports searching by description/comment text, time range, author, committer, specific commit IDs, and more. This is the unified tool for all commit search operations.", {
@@ -820,7 +1012,8 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
820
1012
  if (historySimplificationMode) {
821
1013
  // Note: This parameter might not be directly supported by all ADO API versions
822
1014
  // but we'll include it in the criteria for forward compatibility
823
- searchCriteria.historySimplificationMode = historySimplificationMode;
1015
+ const extendedCriteria = searchCriteria;
1016
+ extendedCriteria.historySimplificationMode = historySimplificationMode;
824
1017
  }
825
1018
  if (version) {
826
1019
  const itemVersion = {
@@ -892,13 +1085,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
892
1085
  };
893
1086
  }
894
1087
  catch (error) {
1088
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
895
1089
  return {
896
- content: [
897
- {
898
- type: "text",
899
- text: `Error querying pull requests by commits: ${error instanceof Error ? error.message : String(error)}`,
900
- },
901
- ],
1090
+ content: [{ type: "text", text: `Error querying pull requests by commits: ${errorMessage}` }],
902
1091
  isError: true,
903
1092
  };
904
1093
  }