@azure-devops/mcp 2.4.0-nightly.20260317 → 2.5.0-nightly.20260318
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/dist/tools/repositories.js +72 -43
- package/dist/tools/test-plans.js +37 -5
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -122,16 +122,19 @@ function buildVersionDescriptor(version, versionType) {
|
|
|
122
122
|
}
|
|
123
123
|
function configureRepoTools(server, tokenProvider, connectionProvider, userAgentProvider) {
|
|
124
124
|
server.tool(REPO_TOOLS.create_pull_request, "Create a new pull request.", {
|
|
125
|
-
repositoryId: z
|
|
125
|
+
repositoryId: z
|
|
126
|
+
.string()
|
|
127
|
+
.describe("The ID or name of the repository where the pull request will be created. When using a repository name instead of a GUID, the project parameter must also be provided."),
|
|
126
128
|
sourceRefName: z.string().describe("The source branch name for the pull request, e.g., 'refs/heads/feature-branch'."),
|
|
127
129
|
targetRefName: z.string().describe("The target branch name for the pull request, e.g., 'refs/heads/main'."),
|
|
128
130
|
title: z.string().describe("The title of the pull request."),
|
|
129
131
|
description: z.string().max(4000).optional().describe("The description of the pull request. Must not be longer than 4000 characters. Optional."),
|
|
130
132
|
isDraft: z.boolean().optional().default(false).describe("Indicates whether the pull request is a draft. Defaults to false."),
|
|
133
|
+
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a repository name instead of a GUID."),
|
|
131
134
|
workItems: z.string().optional().describe("Work item IDs to associate with the pull request, space-separated."),
|
|
132
135
|
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."),
|
|
133
136
|
labels: z.array(z.string()).optional().describe("Array of label names to add to the pull request after creation."),
|
|
134
|
-
}, async ({ repositoryId, sourceRefName, targetRefName, title, description, isDraft, workItems, forkSourceRepositoryId, labels }) => {
|
|
137
|
+
}, async ({ repositoryId, sourceRefName, targetRefName, title, description, isDraft, project, workItems, forkSourceRepositoryId, labels }) => {
|
|
135
138
|
try {
|
|
136
139
|
const connection = await connectionProvider();
|
|
137
140
|
const gitApi = await connection.getGitApi();
|
|
@@ -154,9 +157,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
154
157
|
forkSource,
|
|
155
158
|
labels: labelDefinitions,
|
|
156
159
|
supportsIterations: true,
|
|
157
|
-
}, repositoryId);
|
|
160
|
+
}, repositoryId, project);
|
|
158
161
|
if (!pullRequest) {
|
|
159
|
-
const prs = await gitApi.getPullRequests(repositoryId, { sourceRefName, targetRefName, status: PullRequestStatus.Active },
|
|
162
|
+
const prs = await gitApi.getPullRequests(repositoryId, { sourceRefName, targetRefName, status: PullRequestStatus.Active }, project, undefined, 0, 1);
|
|
160
163
|
if (prs && prs.length > 0) {
|
|
161
164
|
pullRequest = prs[0];
|
|
162
165
|
}
|
|
@@ -180,11 +183,14 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
180
183
|
}
|
|
181
184
|
});
|
|
182
185
|
server.tool(REPO_TOOLS.create_branch, "Create a new branch in the repository.", {
|
|
183
|
-
repositoryId: z
|
|
186
|
+
repositoryId: z
|
|
187
|
+
.string()
|
|
188
|
+
.describe("The ID or name of the repository where the branch will be created. When using a repository name instead of a GUID, the project parameter must also be provided."),
|
|
184
189
|
branchName: z.string().describe("The name of the new branch to create, e.g., 'feature-branch'."),
|
|
185
190
|
sourceBranchName: z.string().optional().default("main").describe("The name of the source branch to create the new branch from. Defaults to 'main'."),
|
|
186
191
|
sourceCommitId: z.string().optional().describe("The commit ID to create the branch from. If not provided, uses the latest commit of the source branch."),
|
|
187
|
-
|
|
192
|
+
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a repository name instead of a GUID."),
|
|
193
|
+
}, async ({ repositoryId, branchName, sourceBranchName, sourceCommitId, project }) => {
|
|
188
194
|
try {
|
|
189
195
|
const connection = await connectionProvider();
|
|
190
196
|
const gitApi = await connection.getGitApi();
|
|
@@ -193,7 +199,7 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
193
199
|
if (!commitId) {
|
|
194
200
|
const sourceRefName = `refs/heads/${sourceBranchName}`;
|
|
195
201
|
try {
|
|
196
|
-
const sourceBranch = await gitApi.getRefs(repositoryId,
|
|
202
|
+
const sourceBranch = await gitApi.getRefs(repositoryId, project, "heads/", false, false, undefined, false, undefined, sourceBranchName);
|
|
197
203
|
const branch = sourceBranch.find((b) => b.name === sourceRefName);
|
|
198
204
|
if (!branch || !branch.objectId) {
|
|
199
205
|
return {
|
|
@@ -228,7 +234,7 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
228
234
|
oldObjectId: "0000000000000000000000000000000000000000", // All zeros indicates creating a new ref
|
|
229
235
|
};
|
|
230
236
|
try {
|
|
231
|
-
const result = await gitApi.updateRefs([refUpdate], repositoryId);
|
|
237
|
+
const result = await gitApi.updateRefs([refUpdate], repositoryId, project);
|
|
232
238
|
// Check if the branch creation was successful
|
|
233
239
|
if (result && result.length > 0 && result[0].success) {
|
|
234
240
|
return {
|
|
@@ -274,8 +280,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
274
280
|
}
|
|
275
281
|
});
|
|
276
282
|
server.tool(REPO_TOOLS.update_pull_request, "Update a Pull Request by ID with specified fields, including setting autocomplete with various completion options.", {
|
|
277
|
-
repositoryId: z.string().describe("The ID of the repository where the pull request exists."),
|
|
283
|
+
repositoryId: z.string().describe("The ID or name of the repository where the pull request exists. When using a repository name instead of a GUID, the project parameter must also be provided."),
|
|
278
284
|
pullRequestId: z.number().describe("The ID of the pull request to update."),
|
|
285
|
+
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a repository name instead of a GUID."),
|
|
279
286
|
title: z.string().optional().describe("The new title for the pull request."),
|
|
280
287
|
description: z.string().max(4000).optional().describe("The new description for the pull request. Must not be longer than 4000 characters."),
|
|
281
288
|
isDraft: z.boolean().optional().describe("Whether the pull request should be a draft."),
|
|
@@ -290,7 +297,7 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
290
297
|
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."),
|
|
291
298
|
bypassReason: z.string().optional().describe("Reason for bypassing branch policies. When provided, branch policies will be automatically bypassed during autocompletion."),
|
|
292
299
|
labels: z.array(z.string()).optional().describe("Array of label names to replace existing labels on the pull request. This will remove all current labels and add the specified ones."),
|
|
293
|
-
}, async ({ repositoryId, pullRequestId, title, description, isDraft, targetRefName, status, autoComplete, mergeStrategy, deleteSourceBranch, transitionWorkItems, bypassReason, labels }) => {
|
|
300
|
+
}, async ({ repositoryId, pullRequestId, project, title, description, isDraft, targetRefName, status, autoComplete, mergeStrategy, deleteSourceBranch, transitionWorkItems, bypassReason, labels, }) => {
|
|
294
301
|
try {
|
|
295
302
|
const connection = await connectionProvider();
|
|
296
303
|
const gitApi = await connection.getGitApi();
|
|
@@ -339,23 +346,23 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
339
346
|
}
|
|
340
347
|
// Update labels if provided
|
|
341
348
|
if (labels) {
|
|
342
|
-
const currentLabels = await gitApi.getPullRequestLabels(repositoryId, pullRequestId);
|
|
349
|
+
const currentLabels = await gitApi.getPullRequestLabels(repositoryId, pullRequestId, project);
|
|
343
350
|
for (const currentLabel of currentLabels) {
|
|
344
351
|
if (currentLabel.id) {
|
|
345
|
-
await gitApi.deletePullRequestLabels(repositoryId, pullRequestId, currentLabel.id);
|
|
352
|
+
await gitApi.deletePullRequestLabels(repositoryId, pullRequestId, currentLabel.id, project);
|
|
346
353
|
}
|
|
347
354
|
}
|
|
348
355
|
for (const label of labels) {
|
|
349
|
-
await gitApi.createPullRequestLabel({ name: label }, repositoryId, pullRequestId);
|
|
356
|
+
await gitApi.createPullRequestLabel({ name: label }, repositoryId, pullRequestId, project);
|
|
350
357
|
}
|
|
351
358
|
}
|
|
352
359
|
let updatedPullRequest;
|
|
353
360
|
if (Object.keys(updateRequest).length > 0) {
|
|
354
|
-
updatedPullRequest = await gitApi.updatePullRequest(updateRequest, repositoryId, pullRequestId);
|
|
361
|
+
updatedPullRequest = await gitApi.updatePullRequest(updateRequest, repositoryId, pullRequestId, project);
|
|
355
362
|
}
|
|
356
363
|
else {
|
|
357
364
|
// If only labels were updated, get the current pull request
|
|
358
|
-
updatedPullRequest = await gitApi.getPullRequest(repositoryId, pullRequestId);
|
|
365
|
+
updatedPullRequest = await gitApi.getPullRequest(repositoryId, pullRequestId, project);
|
|
359
366
|
}
|
|
360
367
|
const trimmedUpdatedPullRequest = trimPullRequest(updatedPullRequest, true);
|
|
361
368
|
return {
|
|
@@ -371,17 +378,18 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
371
378
|
}
|
|
372
379
|
});
|
|
373
380
|
server.tool(REPO_TOOLS.update_pull_request_reviewers, "Add or remove reviewers for an existing pull request.", {
|
|
374
|
-
repositoryId: z.string().describe("The ID of the repository where the pull request exists."),
|
|
381
|
+
repositoryId: z.string().describe("The ID or name of the repository where the pull request exists. When using a repository name instead of a GUID, the project parameter must also be provided."),
|
|
375
382
|
pullRequestId: z.number().describe("The ID of the pull request to update."),
|
|
376
383
|
reviewerIds: z.array(z.string()).describe("List of reviewer ids to add or remove from the pull request."),
|
|
377
384
|
action: z.enum(["add", "remove"]).describe("Action to perform on the reviewers. Can be 'add' or 'remove'."),
|
|
378
|
-
|
|
385
|
+
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a repository name instead of a GUID."),
|
|
386
|
+
}, async ({ repositoryId, pullRequestId, reviewerIds, action, project }) => {
|
|
379
387
|
try {
|
|
380
388
|
const connection = await connectionProvider();
|
|
381
389
|
const gitApi = await connection.getGitApi();
|
|
382
390
|
let updatedPullRequest;
|
|
383
391
|
if (action === "add") {
|
|
384
|
-
updatedPullRequest = await gitApi.createPullRequestReviewers(reviewerIds.map((id) => ({ id: id })), repositoryId, pullRequestId);
|
|
392
|
+
updatedPullRequest = await gitApi.createPullRequestReviewers(reviewerIds.map((id) => ({ id: id })), repositoryId, pullRequestId, project);
|
|
385
393
|
const trimmedResponse = updatedPullRequest.map((item) => ({
|
|
386
394
|
displayName: item.displayName,
|
|
387
395
|
id: item.id,
|
|
@@ -396,7 +404,7 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
396
404
|
}
|
|
397
405
|
else {
|
|
398
406
|
for (const reviewerId of reviewerIds) {
|
|
399
|
-
await gitApi.deletePullRequestReviewer(repositoryId, pullRequestId, reviewerId);
|
|
407
|
+
await gitApi.deletePullRequestReviewer(repositoryId, pullRequestId, reviewerId, project);
|
|
400
408
|
}
|
|
401
409
|
return {
|
|
402
410
|
content: [{ type: "text", text: `Reviewers with IDs ${reviewerIds.join(", ")} removed from pull request ${pullRequestId}.` }],
|
|
@@ -446,8 +454,11 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
446
454
|
}
|
|
447
455
|
});
|
|
448
456
|
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.", {
|
|
449
|
-
repositoryId: z
|
|
450
|
-
|
|
457
|
+
repositoryId: z
|
|
458
|
+
.string()
|
|
459
|
+
.optional()
|
|
460
|
+
.describe("The ID or name of the repository where the pull requests are located. When using a repository name instead of a GUID, the project parameter must also be provided."),
|
|
461
|
+
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a repository name instead of a GUID, or to scope the search to a specific project."),
|
|
451
462
|
top: z.number().default(100).describe("The maximum number of pull requests to return."),
|
|
452
463
|
skip: z.number().default(0).describe("The number of pull requests to skip."),
|
|
453
464
|
created_by_me: z.boolean().default(false).describe("Filter pull requests created by the current user."),
|
|
@@ -572,9 +583,11 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
572
583
|
}
|
|
573
584
|
});
|
|
574
585
|
server.tool(REPO_TOOLS.list_pull_request_threads, "Retrieve a list of comment threads for a pull request.", {
|
|
575
|
-
repositoryId: z
|
|
586
|
+
repositoryId: z
|
|
587
|
+
.string()
|
|
588
|
+
.describe("The ID or name of the repository where the pull request is located. When using a repository name instead of a GUID, the project parameter must also be provided."),
|
|
576
589
|
pullRequestId: z.number().describe("The ID of the pull request for which to retrieve threads."),
|
|
577
|
-
project: z.string().optional().describe("Project ID or project name
|
|
590
|
+
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a repository name instead of a GUID."),
|
|
578
591
|
iteration: z.number().optional().describe("The iteration ID for which to retrieve threads. Optional, defaults to the latest iteration."),
|
|
579
592
|
baseIteration: z.number().optional().describe("The base iteration ID for which to retrieve threads. Optional, defaults to the latest base iteration."),
|
|
580
593
|
top: z.number().default(100).describe("The maximum number of threads to return after filtering."),
|
|
@@ -630,10 +643,12 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
630
643
|
}
|
|
631
644
|
});
|
|
632
645
|
server.tool(REPO_TOOLS.list_pull_request_thread_comments, "Retrieve a list of comments in a pull request thread.", {
|
|
633
|
-
repositoryId: z
|
|
646
|
+
repositoryId: z
|
|
647
|
+
.string()
|
|
648
|
+
.describe("The ID or name of the repository where the pull request is located. When using a repository name instead of a GUID, the project parameter must also be provided."),
|
|
634
649
|
pullRequestId: z.number().describe("The ID of the pull request for which to retrieve thread comments."),
|
|
635
650
|
threadId: z.number().describe("The ID of the thread for which to retrieve comments."),
|
|
636
|
-
project: z.string().optional().describe("Project ID or project name
|
|
651
|
+
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a repository name instead of a GUID."),
|
|
637
652
|
top: z.number().default(100).describe("The maximum number of comments to return."),
|
|
638
653
|
skip: z.number().default(0).describe("The number of comments to skip."),
|
|
639
654
|
fullResponse: z.boolean().optional().default(false).describe("Return full comment JSON response instead of trimmed data."),
|
|
@@ -664,14 +679,17 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
664
679
|
}
|
|
665
680
|
});
|
|
666
681
|
server.tool(REPO_TOOLS.list_branches_by_repo, "Retrieve a list of branches for a given repository.", {
|
|
667
|
-
repositoryId: z
|
|
682
|
+
repositoryId: z
|
|
683
|
+
.string()
|
|
684
|
+
.describe("The ID or name of the repository where the branches are located. When using a repository name instead of a GUID, the project parameter must also be provided."),
|
|
668
685
|
top: z.number().default(100).describe("The maximum number of branches to return. Defaults to 100."),
|
|
669
686
|
filterContains: z.string().optional().describe("Filter to find branches that contain this string in their name."),
|
|
670
|
-
|
|
687
|
+
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a repository name instead of a GUID."),
|
|
688
|
+
}, async ({ repositoryId, top, filterContains, project }) => {
|
|
671
689
|
try {
|
|
672
690
|
const connection = await connectionProvider();
|
|
673
691
|
const gitApi = await connection.getGitApi();
|
|
674
|
-
const branches = await gitApi.getRefs(repositoryId,
|
|
692
|
+
const branches = await gitApi.getRefs(repositoryId, project, "heads/", undefined, undefined, undefined, undefined, undefined, filterContains);
|
|
675
693
|
const filteredBranches = branchesFilterOutIrrelevantProperties(branches, top);
|
|
676
694
|
return {
|
|
677
695
|
content: [{ type: "text", text: JSON.stringify(filteredBranches, null, 2) }],
|
|
@@ -686,14 +704,17 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
686
704
|
}
|
|
687
705
|
});
|
|
688
706
|
server.tool(REPO_TOOLS.list_my_branches_by_repo, "Retrieve a list of my branches for a given repository Id.", {
|
|
689
|
-
repositoryId: z
|
|
707
|
+
repositoryId: z
|
|
708
|
+
.string()
|
|
709
|
+
.describe("The ID or name of the repository where the branches are located. When using a repository name instead of a GUID, the project parameter must also be provided."),
|
|
690
710
|
top: z.number().default(100).describe("The maximum number of branches to return."),
|
|
691
711
|
filterContains: z.string().optional().describe("Filter to find branches that contain this string in their name."),
|
|
692
|
-
|
|
712
|
+
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a repository name instead of a GUID."),
|
|
713
|
+
}, async ({ repositoryId, top, filterContains, project }) => {
|
|
693
714
|
try {
|
|
694
715
|
const connection = await connectionProvider();
|
|
695
716
|
const gitApi = await connection.getGitApi();
|
|
696
|
-
const branches = await gitApi.getRefs(repositoryId,
|
|
717
|
+
const branches = await gitApi.getRefs(repositoryId, project, "heads/", undefined, undefined, true, undefined, undefined, filterContains);
|
|
697
718
|
const filteredBranches = branchesFilterOutIrrelevantProperties(branches, top);
|
|
698
719
|
return {
|
|
699
720
|
content: [{ type: "text", text: JSON.stringify(filteredBranches, null, 2) }],
|
|
@@ -735,13 +756,14 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
735
756
|
}
|
|
736
757
|
});
|
|
737
758
|
server.tool(REPO_TOOLS.get_branch_by_name, "Get a branch by its name.", {
|
|
738
|
-
repositoryId: z.string().describe("The ID of the repository where the branch is located."),
|
|
759
|
+
repositoryId: z.string().describe("The ID or name of the repository where the branch is located. When using a repository name instead of a GUID, the project parameter must also be provided."),
|
|
739
760
|
branchName: z.string().describe("The name of the branch to retrieve, e.g., 'main' or 'feature-branch'."),
|
|
740
|
-
|
|
761
|
+
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a repository name instead of a GUID."),
|
|
762
|
+
}, async ({ repositoryId, branchName, project }) => {
|
|
741
763
|
try {
|
|
742
764
|
const connection = await connectionProvider();
|
|
743
765
|
const gitApi = await connection.getGitApi();
|
|
744
|
-
const branches = await gitApi.getRefs(repositoryId,
|
|
766
|
+
const branches = await gitApi.getRefs(repositoryId, project, "heads/", false, false, undefined, false, undefined, branchName);
|
|
745
767
|
const branch = branches.find((branch) => branch.name === `refs/heads/${branchName}` || branch.name === branchName);
|
|
746
768
|
if (!branch) {
|
|
747
769
|
return {
|
|
@@ -821,11 +843,13 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
821
843
|
}
|
|
822
844
|
});
|
|
823
845
|
server.tool(REPO_TOOLS.reply_to_comment, "Replies to a specific comment on a pull request.", {
|
|
824
|
-
repositoryId: z
|
|
846
|
+
repositoryId: z
|
|
847
|
+
.string()
|
|
848
|
+
.describe("The ID or name of the repository where the pull request is located. When using a repository name instead of a GUID, the project parameter must also be provided."),
|
|
825
849
|
pullRequestId: z.number().describe("The ID of the pull request where the comment thread exists."),
|
|
826
850
|
threadId: z.number().describe("The ID of the thread to which the comment will be added."),
|
|
827
851
|
content: z.string().describe("The content of the comment to be added."),
|
|
828
|
-
project: z.string().optional().describe("Project ID or project name
|
|
852
|
+
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a repository name instead of a GUID."),
|
|
829
853
|
fullResponse: z.boolean().optional().default(false).describe("Return full comment JSON response instead of a simple confirmation message."),
|
|
830
854
|
}, async ({ repositoryId, pullRequestId, threadId, content, project, fullResponse }) => {
|
|
831
855
|
try {
|
|
@@ -857,10 +881,12 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
857
881
|
}
|
|
858
882
|
});
|
|
859
883
|
server.tool(REPO_TOOLS.create_pull_request_thread, "Creates a new comment thread on a pull request.", {
|
|
860
|
-
repositoryId: z
|
|
884
|
+
repositoryId: z
|
|
885
|
+
.string()
|
|
886
|
+
.describe("The ID or name of the repository where the pull request is located. When using a repository name instead of a GUID, the project parameter must also be provided."),
|
|
861
887
|
pullRequestId: z.number().describe("The ID of the pull request where the comment thread exists."),
|
|
862
888
|
content: z.string().describe("The content of the comment to be added."),
|
|
863
|
-
project: z.string().optional().describe("Project ID or project name
|
|
889
|
+
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a repository name instead of a GUID."),
|
|
864
890
|
filePath: z.string().optional().describe("The path of the file where the comment thread will be created. (optional)"),
|
|
865
891
|
status: z
|
|
866
892
|
.enum(getEnumKeys(CommentThreadStatus))
|
|
@@ -971,10 +997,12 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
971
997
|
}
|
|
972
998
|
});
|
|
973
999
|
server.tool(REPO_TOOLS.update_pull_request_thread, "Updates an existing comment thread on a pull request.", {
|
|
974
|
-
repositoryId: z
|
|
1000
|
+
repositoryId: z
|
|
1001
|
+
.string()
|
|
1002
|
+
.describe("The ID or name of the repository where the pull request is located. When using a repository name instead of a GUID, the project parameter must also be provided."),
|
|
975
1003
|
pullRequestId: z.number().describe("The ID of the pull request where the comment thread exists."),
|
|
976
1004
|
threadId: z.number().describe("The ID of the thread to update."),
|
|
977
|
-
project: z.string().optional().describe("Project ID or project name
|
|
1005
|
+
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a repository name instead of a GUID."),
|
|
978
1006
|
status: z
|
|
979
1007
|
.enum(getEnumKeys(CommentThreadStatus))
|
|
980
1008
|
.optional()
|
|
@@ -1182,10 +1210,11 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
1182
1210
|
}
|
|
1183
1211
|
});
|
|
1184
1212
|
server.tool(REPO_TOOLS.vote_pull_request, "Cast a vote on a pull request. Automatically adds the current user as a reviewer if they are not already one.", {
|
|
1185
|
-
repositoryId: z.string().describe("The ID of the repository."),
|
|
1213
|
+
repositoryId: z.string().describe("The ID or name of the repository. When using a repository name instead of a GUID, the project parameter must also be provided."),
|
|
1186
1214
|
pullRequestId: z.number().describe("The ID of the pull request."),
|
|
1187
1215
|
vote: z.enum(["Approved", "ApprovedWithSuggestions", "NoVote", "WaitingForAuthor", "Rejected"]).describe("The vote to cast: Approved(10), Suggestions(5), None(0), Waiting(-5), Rejected(-10)."),
|
|
1188
|
-
|
|
1216
|
+
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a repository name instead of a GUID."),
|
|
1217
|
+
}, async ({ repositoryId, pullRequestId, vote, project }) => {
|
|
1189
1218
|
const connection = await connectionProvider();
|
|
1190
1219
|
const gitApi = await connection.getGitApi();
|
|
1191
1220
|
const userDetails = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
|
|
@@ -1200,7 +1229,7 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
1200
1229
|
WaitingForAuthor: -5,
|
|
1201
1230
|
Rejected: -10,
|
|
1202
1231
|
};
|
|
1203
|
-
await gitApi.createPullRequestReviewer({ vote: voteMap[vote], id: userId }, repositoryId, pullRequestId, userId);
|
|
1232
|
+
await gitApi.createPullRequestReviewer({ vote: voteMap[vote], id: userId }, repositoryId, pullRequestId, userId, project);
|
|
1204
1233
|
return {
|
|
1205
1234
|
content: [
|
|
1206
1235
|
{
|
package/dist/tools/test-plans.js
CHANGED
|
@@ -275,16 +275,48 @@ function configureTestPlanTools(server, _, connectionProvider) {
|
|
|
275
275
|
};
|
|
276
276
|
}
|
|
277
277
|
});
|
|
278
|
-
server.tool(Test_Plan_Tools.test_results_from_build_id, "Gets a list of test results for a given project and build ID.", {
|
|
278
|
+
server.tool(Test_Plan_Tools.test_results_from_build_id, "Gets a list of test results for a given project and build ID. Can filter by test outcome (e.g. Failed, Passed, Aborted). Returns test case titles, error messages, stack traces, and outcomes. Efficiently handles builds with large numbers of test runs.", {
|
|
279
279
|
project: z.string().describe("The unique identifier (ID or name) of the Azure DevOps project."),
|
|
280
280
|
buildid: z.number().describe("The ID of the build."),
|
|
281
|
-
|
|
281
|
+
outcomes: z.array(z.string()).optional().describe("Filter results by test outcome, e.g. ['Failed', 'Passed', 'Aborted']."),
|
|
282
|
+
}, async ({ project, buildid, outcomes }) => {
|
|
282
283
|
try {
|
|
283
284
|
const connection = await connectionProvider();
|
|
284
|
-
const
|
|
285
|
-
|
|
285
|
+
const testResultsApi = await connection.getTestResultsApi();
|
|
286
|
+
// Build filter expression for outcomes if specified
|
|
287
|
+
const outcomeFilter = outcomes?.map((o) => `Outcome eq '${o}'`).join(" or ");
|
|
288
|
+
// Fetch test result details for the build in a single API call
|
|
289
|
+
// This is more efficient than getTestRuns + getTestResults per run,
|
|
290
|
+
// especially for builds with many test runs (e.g., cloud testing with one run per test case)
|
|
291
|
+
const testResultDetails = await testResultsApi.getTestResultDetailsForBuild(project, buildid, undefined, // publishContext
|
|
292
|
+
undefined, // groupBy
|
|
293
|
+
outcomeFilter, // filter by outcome
|
|
294
|
+
undefined, // orderby
|
|
295
|
+
true // shouldIncludeResults - get individual test results, not just aggregates
|
|
296
|
+
);
|
|
297
|
+
// Extract individual test results from the grouped response
|
|
298
|
+
const allResults = [];
|
|
299
|
+
if (testResultDetails.resultsForGroup) {
|
|
300
|
+
for (const group of testResultDetails.resultsForGroup) {
|
|
301
|
+
if (group.results) {
|
|
302
|
+
allResults.push(...group.results);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// Format results to extract useful fields
|
|
307
|
+
const formattedResults = allResults.map((r) => ({
|
|
308
|
+
id: r.id,
|
|
309
|
+
testCaseTitle: r.testCaseTitle,
|
|
310
|
+
outcome: r.outcome,
|
|
311
|
+
errorMessage: r.errorMessage,
|
|
312
|
+
stackTrace: r.stackTrace,
|
|
313
|
+
automatedTestName: r.automatedTestName,
|
|
314
|
+
automatedTestStorage: r.automatedTestStorage,
|
|
315
|
+
durationInMs: r.durationInMs,
|
|
316
|
+
runId: r.testRun?.id,
|
|
317
|
+
}));
|
|
286
318
|
return {
|
|
287
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
319
|
+
content: [{ type: "text", text: JSON.stringify(formattedResults, null, 2) }],
|
|
288
320
|
};
|
|
289
321
|
}
|
|
290
322
|
catch (error) {
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const packageVersion = "2.
|
|
1
|
+
export const packageVersion = "2.5.0-nightly.20260318";
|