@azure-devops/mcp 2.2.0-nightly.20251008 → 2.2.0-nightly.20251009

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.
package/README.md CHANGED
@@ -81,8 +81,7 @@ Interact with these Azure DevOps services:
81
81
  ### 📁 Repositories
82
82
 
83
83
  - **repo_list_repos_by_project**: Retrieve a list of repositories for a given project.
84
- - **repo_list_pull_requests_by_repo**: Retrieve a list of pull requests for a given repository.
85
- - **repo_list_pull_requests_by_project**: Retrieve a list of pull requests for a given project ID or name.
84
+ - **repo_list_pull_requests_by_repo_or_project**: Retrieve a list of pull requests for a given repository or project.
86
85
  - **repo_list_branches_by_repo**: Retrieve a list of branches for a given repository.
87
86
  - **repo_list_my_branches_by_repo**: Retrieve a list of your branches for a given repository ID.
88
87
  - **repo_list_pull_requests_by_commits**: List pull requests associated with commits.
@@ -6,8 +6,7 @@ import { getCurrentUserDetails, getUserIdFromEmail } from "./auth.js";
6
6
  import { getEnumKeys } from "../utils.js";
7
7
  const REPO_TOOLS = {
8
8
  list_repos_by_project: "repo_list_repos_by_project",
9
- list_pull_requests_by_repo: "repo_list_pull_requests_by_repo",
10
- list_pull_requests_by_project: "repo_list_pull_requests_by_project",
9
+ list_pull_requests_by_repo_or_project: "repo_list_pull_requests_by_repo_or_project",
11
10
  list_branches_by_repo: "repo_list_branches_by_repo",
12
11
  list_my_branches_by_repo: "repo_list_my_branches_by_repo",
13
12
  list_pull_request_threads: "repo_list_pull_request_threads",
@@ -33,6 +32,15 @@ function branchesFilterOutIrrelevantProperties(branches, top) {
33
32
  .sort((a, b) => b.localeCompare(a))
34
33
  .slice(0, top);
35
34
  }
35
+ function trimPullRequestThread(thread) {
36
+ return {
37
+ id: thread.id,
38
+ publishedDate: thread.publishedDate,
39
+ lastUpdatedDate: thread.lastUpdatedDate,
40
+ status: thread.status,
41
+ comments: trimComments(thread.comments),
42
+ };
43
+ }
36
44
  /**
37
45
  * Trims comment data to essential properties, filtering out deleted comments
38
46
  * @param comments Array of comments to trim (can be undefined/null)
@@ -74,6 +82,24 @@ function filterReposByName(repositories, repoNameFilter) {
74
82
  const filteredByName = repositories?.filter((repo) => repo.name?.toLowerCase().includes(lowerCaseFilter));
75
83
  return filteredByName;
76
84
  }
85
+ function trimPullRequest(pr, includeDescription = false) {
86
+ return {
87
+ pullRequestId: pr.pullRequestId,
88
+ codeReviewId: pr.codeReviewId,
89
+ repository: pr.repository?.name,
90
+ status: pr.status,
91
+ createdBy: {
92
+ displayName: pr.createdBy?.displayName,
93
+ uniqueName: pr.createdBy?.uniqueName,
94
+ },
95
+ creationDate: pr.creationDate,
96
+ title: pr.title,
97
+ ...(includeDescription ? { description: pr.description ?? "" } : {}),
98
+ isDraft: pr.isDraft,
99
+ sourceRefName: pr.sourceRefName,
100
+ targetRefName: pr.targetRefName,
101
+ };
102
+ }
77
103
  function configureRepoTools(server, tokenProvider, connectionProvider, userAgentProvider) {
78
104
  server.tool(REPO_TOOLS.create_pull_request, "Create a new pull request.", {
79
105
  repositoryId: z.string().describe("The ID of the repository where the pull request will be created."),
@@ -104,8 +130,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
104
130
  workItemRefs: workItemRefs,
105
131
  forkSource,
106
132
  }, repositoryId);
133
+ const trimmedPullRequest = trimPullRequest(pullRequest, true);
107
134
  return {
108
- content: [{ type: "text", text: JSON.stringify(pullRequest, null, 2) }],
135
+ content: [{ type: "text", text: JSON.stringify(trimmedPullRequest, null, 2) }],
109
136
  };
110
137
  });
111
138
  server.tool(REPO_TOOLS.create_branch, "Create a new branch in the repository.", {
@@ -256,8 +283,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
256
283
  };
257
284
  }
258
285
  const updatedPullRequest = await gitApi.updatePullRequest(updateRequest, repositoryId, pullRequestId);
286
+ const trimmedUpdatedPullRequest = trimPullRequest(updatedPullRequest, true);
259
287
  return {
260
- content: [{ type: "text", text: JSON.stringify(updatedPullRequest, null, 2) }],
288
+ content: [{ type: "text", text: JSON.stringify(trimmedUpdatedPullRequest, null, 2) }],
261
289
  };
262
290
  });
263
291
  server.tool(REPO_TOOLS.update_pull_request_reviewers, "Add or remove reviewers for an existing pull request.", {
@@ -271,8 +299,16 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
271
299
  let updatedPullRequest;
272
300
  if (action === "add") {
273
301
  updatedPullRequest = await gitApi.createPullRequestReviewers(reviewerIds.map((id) => ({ id: id })), repositoryId, pullRequestId);
302
+ const trimmedResponse = updatedPullRequest.map((item) => ({
303
+ displayName: item.displayName,
304
+ id: item.id,
305
+ uniqueName: item.uniqueName,
306
+ vote: item.vote,
307
+ hasDeclined: item.hasDeclined,
308
+ isFlagged: item.isFlagged,
309
+ }));
274
310
  return {
275
- content: [{ type: "text", text: JSON.stringify(updatedPullRequest, null, 2) }],
311
+ content: [{ type: "text", text: JSON.stringify(trimmedResponse, null, 2) }],
276
312
  };
277
313
  }
278
314
  else {
@@ -309,8 +345,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
309
345
  content: [{ type: "text", text: JSON.stringify(trimmedRepositories, null, 2) }],
310
346
  };
311
347
  });
312
- server.tool(REPO_TOOLS.list_pull_requests_by_repo, "Retrieve a list of pull requests for a given repository.", {
313
- repositoryId: z.string().describe("The ID of the repository where the pull requests are located."),
348
+ 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.", {
349
+ repositoryId: z.string().optional().describe("The ID of the repository where the pull requests are located."),
350
+ project: z.string().optional().describe("The ID of the project where the pull requests are located."),
314
351
  top: z.number().default(100).describe("The maximum number of pull requests to return."),
315
352
  skip: z.number().default(0).describe("The number of pull requests to skip."),
316
353
  created_by_me: z.boolean().default(false).describe("Filter pull requests created by the current user."),
@@ -326,14 +363,27 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
326
363
  .describe("Filter pull requests by status. Defaults to 'Active'."),
327
364
  sourceRefName: z.string().optional().describe("Filter pull requests from this source branch (e.g., 'refs/heads/feature-branch')."),
328
365
  targetRefName: z.string().optional().describe("Filter pull requests into this target branch (e.g., 'refs/heads/main')."),
329
- }, async ({ repositoryId, top, skip, created_by_me, created_by_user, i_am_reviewer, user_is_reviewer, status, sourceRefName, targetRefName }) => {
366
+ }, async ({ repositoryId, project, top, skip, created_by_me, created_by_user, i_am_reviewer, user_is_reviewer, status, sourceRefName, targetRefName }) => {
330
367
  const connection = await connectionProvider();
331
368
  const gitApi = await connection.getGitApi();
332
369
  // Build the search criteria
333
370
  const searchCriteria = {
334
371
  status: pullRequestStatusStringToInt(status),
335
- repositoryId: repositoryId,
336
372
  };
373
+ if (!repositoryId && !project) {
374
+ return {
375
+ content: [
376
+ {
377
+ type: "text",
378
+ text: "Either repositoryId or project must be provided.",
379
+ },
380
+ ],
381
+ isError: true,
382
+ };
383
+ }
384
+ if (repositoryId) {
385
+ searchCriteria.repositoryId = repositoryId;
386
+ }
337
387
  if (sourceRefName) {
338
388
  searchCriteria.sourceRefName = sourceRefName;
339
389
  }
@@ -384,120 +434,30 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
384
434
  const userId = data.authenticatedUser.id;
385
435
  searchCriteria.reviewerId = userId;
386
436
  }
387
- const pullRequests = await gitApi.getPullRequests(repositoryId, searchCriteria, undefined, // project
388
- undefined, // maxCommentLength
389
- skip, top);
390
- // Filter out the irrelevant properties
391
- const filteredPullRequests = pullRequests?.map((pr) => ({
392
- pullRequestId: pr.pullRequestId,
393
- codeReviewId: pr.codeReviewId,
394
- status: pr.status,
395
- createdBy: {
396
- displayName: pr.createdBy?.displayName,
397
- uniqueName: pr.createdBy?.uniqueName,
398
- },
399
- creationDate: pr.creationDate,
400
- title: pr.title,
401
- isDraft: pr.isDraft,
402
- sourceRefName: pr.sourceRefName,
403
- targetRefName: pr.targetRefName,
404
- }));
405
- return {
406
- content: [{ type: "text", text: JSON.stringify(filteredPullRequests, null, 2) }],
407
- };
408
- });
409
- server.tool(REPO_TOOLS.list_pull_requests_by_project, "Retrieve a list of pull requests for a given project Id or Name.", {
410
- project: z.string().describe("The name or ID of the Azure DevOps project."),
411
- top: z.number().default(100).describe("The maximum number of pull requests to return."),
412
- skip: z.number().default(0).describe("The number of pull requests to skip."),
413
- created_by_me: z.boolean().default(false).describe("Filter pull requests created by the current user."),
414
- created_by_user: z.string().optional().describe("Filter pull requests created by a specific user (provide email or unique name). Takes precedence over created_by_me if both are provided."),
415
- i_am_reviewer: z.boolean().default(false).describe("Filter pull requests where the current user is a reviewer."),
416
- user_is_reviewer: z
417
- .string()
418
- .optional()
419
- .describe("Filter pull requests where a specific user is a reviewer (provide email or unique name). Takes precedence over i_am_reviewer if both are provided."),
420
- status: z
421
- .enum(getEnumKeys(PullRequestStatus))
422
- .default("Active")
423
- .describe("Filter pull requests by status. Defaults to 'Active'."),
424
- sourceRefName: z.string().optional().describe("Filter pull requests from this source branch (e.g., 'refs/heads/feature-branch')."),
425
- targetRefName: z.string().optional().describe("Filter pull requests into this target branch (e.g., 'refs/heads/main')."),
426
- }, async ({ project, top, skip, created_by_me, created_by_user, i_am_reviewer, user_is_reviewer, status, sourceRefName, targetRefName }) => {
427
- const connection = await connectionProvider();
428
- const gitApi = await connection.getGitApi();
429
- // Build the search criteria
430
- const gitPullRequestSearchCriteria = {
431
- status: pullRequestStatusStringToInt(status),
432
- };
433
- if (sourceRefName) {
434
- gitPullRequestSearchCriteria.sourceRefName = sourceRefName;
435
- }
436
- if (targetRefName) {
437
- gitPullRequestSearchCriteria.targetRefName = targetRefName;
438
- }
439
- if (created_by_user) {
440
- try {
441
- const userId = await getUserIdFromEmail(created_by_user, tokenProvider, connectionProvider, userAgentProvider);
442
- gitPullRequestSearchCriteria.creatorId = userId;
443
- }
444
- catch (error) {
445
- return {
446
- content: [
447
- {
448
- type: "text",
449
- text: `Error finding user with email ${created_by_user}: ${error instanceof Error ? error.message : String(error)}`,
450
- },
451
- ],
452
- isError: true,
453
- };
454
- }
455
- }
456
- else if (created_by_me) {
457
- const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
458
- const userId = data.authenticatedUser.id;
459
- gitPullRequestSearchCriteria.creatorId = userId;
437
+ let pullRequests;
438
+ if (repositoryId) {
439
+ pullRequests = await gitApi.getPullRequests(repositoryId, searchCriteria, project, // project
440
+ undefined, // maxCommentLength
441
+ skip, top);
460
442
  }
461
- if (user_is_reviewer) {
462
- try {
463
- const reviewerUserId = await getUserIdFromEmail(user_is_reviewer, tokenProvider, connectionProvider, userAgentProvider);
464
- gitPullRequestSearchCriteria.reviewerId = reviewerUserId;
465
- }
466
- catch (error) {
467
- return {
468
- content: [
469
- {
470
- type: "text",
471
- text: `Error finding reviewer with email ${user_is_reviewer}: ${error instanceof Error ? error.message : String(error)}`,
472
- },
473
- ],
474
- isError: true,
475
- };
476
- }
443
+ else if (project) {
444
+ // If only project is provided, use getPullRequestsByProject
445
+ pullRequests = await gitApi.getPullRequestsByProject(project, searchCriteria, undefined, // maxCommentLength
446
+ skip, top);
477
447
  }
478
- else if (i_am_reviewer) {
479
- const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
480
- const userId = data.authenticatedUser.id;
481
- gitPullRequestSearchCriteria.reviewerId = userId;
448
+ else {
449
+ // This case should not occur due to earlier validation, but added for completeness
450
+ return {
451
+ content: [
452
+ {
453
+ type: "text",
454
+ text: "Either repositoryId or project must be provided.",
455
+ },
456
+ ],
457
+ isError: true,
458
+ };
482
459
  }
483
- const pullRequests = await gitApi.getPullRequestsByProject(project, gitPullRequestSearchCriteria, undefined, // maxCommentLength
484
- skip, top);
485
- // Filter out the irrelevant properties
486
- const filteredPullRequests = pullRequests?.map((pr) => ({
487
- pullRequestId: pr.pullRequestId,
488
- codeReviewId: pr.codeReviewId,
489
- repository: pr.repository?.name,
490
- status: pr.status,
491
- createdBy: {
492
- displayName: pr.createdBy?.displayName,
493
- uniqueName: pr.createdBy?.uniqueName,
494
- },
495
- creationDate: pr.creationDate,
496
- title: pr.title,
497
- isDraft: pr.isDraft,
498
- sourceRefName: pr.sourceRefName,
499
- targetRefName: pr.targetRefName,
500
- }));
460
+ const filteredPullRequests = pullRequests?.map((pr) => trimPullRequest(pr));
501
461
  return {
502
462
  content: [{ type: "text", text: JSON.stringify(filteredPullRequests, null, 2) }],
503
463
  };
@@ -522,13 +482,7 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
522
482
  };
523
483
  }
524
484
  // Return trimmed thread data focusing on essential information
525
- const trimmedThreads = paginatedThreads?.map((thread) => ({
526
- id: thread.id,
527
- publishedDate: thread.publishedDate,
528
- lastUpdatedDate: thread.lastUpdatedDate,
529
- status: thread.status,
530
- comments: trimComments(thread.comments),
531
- }));
485
+ const trimmedThreads = paginatedThreads?.map((thread) => trimPullRequestThread(thread));
532
486
  return {
533
487
  content: [{ type: "text", text: JSON.stringify(trimmedThreads, null, 2) }],
534
488
  };
@@ -718,8 +672,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
718
672
  }
719
673
  }
720
674
  const thread = await gitApi.createThread({ comments: [{ content: content }], threadContext: threadContext, status: CommentThreadStatus[status] }, repositoryId, pullRequestId, project);
675
+ const trimmedThread = trimPullRequestThread(thread);
721
676
  return {
722
- content: [{ type: "text", text: JSON.stringify(thread, null, 2) }],
677
+ content: [{ type: "text", text: JSON.stringify(trimmedThread, null, 2) }],
723
678
  };
724
679
  });
725
680
  server.tool(REPO_TOOLS.resolve_comment, "Resolves a specific comment thread on a pull request.", {
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const packageVersion = "2.2.0-nightly.20251008";
1
+ export const packageVersion = "2.2.0-nightly.20251009";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-devops/mcp",
3
- "version": "2.2.0-nightly.20251008",
3
+ "version": "2.2.0-nightly.20251009",
4
4
  "description": "MCP server for interacting with Azure DevOps",
5
5
  "license": "MIT",
6
6
  "author": "Microsoft Corporation",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@azure/identity": "^4.10.0",
41
- "@modelcontextprotocol/sdk": "1.18.2",
41
+ "@modelcontextprotocol/sdk": "1.19.1",
42
42
  "azure-devops-extension-api": "^4.252.0",
43
43
  "azure-devops-extension-sdk": "^4.0.2",
44
44
  "azure-devops-node-api": "^15.1.0",