@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 +1 -2
- package/dist/tools/repositories.js +83 -128
- package/dist/version.js +1 -1
- package/package.json +2 -2
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
|
-
- **
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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 (
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|