@azure-devops/mcp 2.2.0 → 2.2.1-nightly.20251015
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 +3 -5
- package/dist/auth.js +6 -1
- package/dist/index.js +8 -2
- package/dist/org-tenants.js +0 -0
- package/dist/prompts.js +0 -30
- package/dist/tools/repositories.js +118 -132
- package/dist/tools/test-plans.js +39 -24
- package/dist/tools.js +0 -0
- package/dist/useragent.js +0 -0
- package/dist/utils.js +0 -0
- package/dist/version.js +1 -1
- package/package.json +5 -5
- package/dist/domains.js +0 -1
- package/dist/http.js +0 -52
- package/dist/orgtenants.js +0 -73
- package/dist/server.js +0 -36
- package/dist/tenant.js +0 -73
- package/dist/tools/advsec.js +0 -108
- package/dist/tools/builds.js +0 -271
- package/dist/tools/releases.js +0 -97
- package/dist/tools/repos.js +0 -666
- package/dist/tools/testplans.js +0 -213
- package/dist/tools/workitems.js +0 -809
package/README.md
CHANGED
|
@@ -7,8 +7,6 @@ Easily install the Azure DevOps MCP Server for VS Code or VS Code Insiders:
|
|
|
7
7
|
|
|
8
8
|
This TypeScript project provides a **local** MCP server for Azure DevOps, enabling you to perform a wide range of Azure DevOps tasks directly from your code editor.
|
|
9
9
|
|
|
10
|
-
> 🚨 **Public Preview:** This project is in public preview. Tools and features may change before general availability.
|
|
11
|
-
|
|
12
10
|
## 📄 Table of Contents
|
|
13
11
|
|
|
14
12
|
1. [📺 Overview](#-overview)
|
|
@@ -83,8 +81,7 @@ Interact with these Azure DevOps services:
|
|
|
83
81
|
### 📁 Repositories
|
|
84
82
|
|
|
85
83
|
- **repo_list_repos_by_project**: Retrieve a list of repositories for a given project.
|
|
86
|
-
- **
|
|
87
|
-
- **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.
|
|
88
85
|
- **repo_list_branches_by_repo**: Retrieve a list of branches for a given repository.
|
|
89
86
|
- **repo_list_my_branches_by_repo**: Retrieve a list of your branches for a given repository ID.
|
|
90
87
|
- **repo_list_pull_requests_by_commits**: List pull requests associated with commits.
|
|
@@ -95,7 +92,6 @@ Interact with these Azure DevOps services:
|
|
|
95
92
|
- **repo_get_pull_request_by_id**: Get a pull request by its ID.
|
|
96
93
|
- **repo_create_pull_request**: Create a new pull request.
|
|
97
94
|
- **repo_create_branch**: Create a new branch in the repository.
|
|
98
|
-
- **repo_update_pull_request_status**: Update the status of an existing pull request to active or abandoned.
|
|
99
95
|
- **repo_update_pull_request**: Update various fields of an existing pull request (title, description, draft status, target branch).
|
|
100
96
|
- **repo_update_pull_request_reviewers**: Add or remove reviewers for an existing pull request.
|
|
101
97
|
- **repo_reply_to_comment**: Replies to a specific comment on a pull request.
|
|
@@ -126,6 +122,7 @@ Interact with these Azure DevOps services:
|
|
|
126
122
|
|
|
127
123
|
- **testplan_create_test_plan**: Create a new test plan in the project.
|
|
128
124
|
- **testplan_create_test_case**: Create a new test case work item.
|
|
125
|
+
- **testplan_update_test_case_steps**: Update an existing test case work item's steps.
|
|
129
126
|
- **testplan_add_test_cases_to_suite**: Add existing test cases to a test suite.
|
|
130
127
|
- **testplan_list_test_plans**: Retrieve a paginated list of test plans from an Azure DevOps project. Allows filtering for active plans and toggling detailed information.
|
|
131
128
|
- **testplan_list_test_cases**: Get a list of test cases in the test plan.
|
|
@@ -137,6 +134,7 @@ Interact with these Azure DevOps services:
|
|
|
137
134
|
- **wiki_list_wikis**: Retrieve a list of wikis for an organization or project.
|
|
138
135
|
- **wiki_get_wiki**: Get the wiki by wikiIdentifier.
|
|
139
136
|
- **wiki_list_pages**: Retrieve a list of wiki pages for a specific wiki and project.
|
|
137
|
+
- **wiki_get_page**: Retrieve wiki page metadata by path.
|
|
140
138
|
- **wiki_get_page_content**: Retrieve wiki page content by wikiIdentifier and path.
|
|
141
139
|
- **wiki_create_or_update_page**: Create or update wiki pages with full content support.
|
|
142
140
|
|
package/dist/auth.js
CHANGED
|
@@ -5,14 +5,19 @@ const scopes = ["499b84ac-1321-427f-aa17-267ca6975798/.default"];
|
|
|
5
5
|
class OAuthAuthenticator {
|
|
6
6
|
static clientId = "0d50963b-7bb9-4fe7-94c7-a99af00b5136";
|
|
7
7
|
static defaultAuthority = "https://login.microsoftonline.com/common";
|
|
8
|
+
static zeroTenantId = "00000000-0000-0000-0000-000000000000";
|
|
8
9
|
accountId;
|
|
9
10
|
publicClientApp;
|
|
10
11
|
constructor(tenantId) {
|
|
11
12
|
this.accountId = null;
|
|
13
|
+
let authority = OAuthAuthenticator.defaultAuthority;
|
|
14
|
+
if (tenantId && tenantId !== OAuthAuthenticator.zeroTenantId) {
|
|
15
|
+
authority = `https://login.microsoftonline.com/${tenantId}`;
|
|
16
|
+
}
|
|
12
17
|
this.publicClientApp = new PublicClientApplication({
|
|
13
18
|
auth: {
|
|
14
19
|
clientId: OAuthAuthenticator.clientId,
|
|
15
|
-
authority
|
|
20
|
+
authority,
|
|
16
21
|
},
|
|
17
22
|
});
|
|
18
23
|
}
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import yargs from "yargs";
|
|
|
8
8
|
import { hideBin } from "yargs/helpers";
|
|
9
9
|
import { createAuthenticator } from "./auth.js";
|
|
10
10
|
import { getOrgTenant } from "./org-tenants.js";
|
|
11
|
-
import { configurePrompts } from "./prompts.js";
|
|
11
|
+
//import { configurePrompts } from "./prompts.js";
|
|
12
12
|
import { configureAllTools } from "./tools.js";
|
|
13
13
|
import { UserAgentComposer } from "./useragent.js";
|
|
14
14
|
import { packageVersion } from "./version.js";
|
|
@@ -70,6 +70,11 @@ async function main() {
|
|
|
70
70
|
const server = new McpServer({
|
|
71
71
|
name: "Azure DevOps MCP Server",
|
|
72
72
|
version: packageVersion,
|
|
73
|
+
icons: [
|
|
74
|
+
{
|
|
75
|
+
src: "https://cdn.vsassets.io/content/icons/favicon.ico",
|
|
76
|
+
},
|
|
77
|
+
],
|
|
73
78
|
});
|
|
74
79
|
const userAgentComposer = new UserAgentComposer(packageVersion);
|
|
75
80
|
server.server.oninitialized = () => {
|
|
@@ -77,7 +82,8 @@ async function main() {
|
|
|
77
82
|
};
|
|
78
83
|
const tenantId = (await getOrgTenant(orgName)) ?? argv.tenant;
|
|
79
84
|
const authenticator = createAuthenticator(argv.authentication, tenantId);
|
|
80
|
-
|
|
85
|
+
// removing prompts untill further notice
|
|
86
|
+
// configurePrompts(server);
|
|
81
87
|
configureAllTools(server, authenticator, getAzureDevOpsClient(authenticator, userAgentComposer), () => userAgentComposer.userAgent, enabledDomains);
|
|
82
88
|
const transport = new StdioServerTransport();
|
|
83
89
|
await server.connect(transport);
|
package/dist/org-tenants.js
CHANGED
|
File without changes
|
package/dist/prompts.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
import { z } from "zod";
|
|
4
3
|
import { CORE_TOOLS } from "./tools/core.js";
|
|
5
|
-
import { WORKITEM_TOOLS } from "./tools/work-items.js";
|
|
6
4
|
function configurePrompts(server) {
|
|
7
5
|
server.prompt("Projects", "Lists all projects in the Azure DevOps organization.", {}, () => ({
|
|
8
6
|
messages: [
|
|
@@ -18,33 +16,5 @@ Present the results in alphabetical order in a table with the following columns:
|
|
|
18
16
|
},
|
|
19
17
|
],
|
|
20
18
|
}));
|
|
21
|
-
server.prompt("Teams", "Retrieves all teams for a given Azure DevOps project.", { project: z.string() }, ({ project }) => ({
|
|
22
|
-
messages: [
|
|
23
|
-
{
|
|
24
|
-
role: "user",
|
|
25
|
-
content: {
|
|
26
|
-
type: "text",
|
|
27
|
-
text: String.raw `
|
|
28
|
-
# Task
|
|
29
|
-
Use the '${CORE_TOOLS.list_project_teams}' tool to retrieve all teams for the project '${project}'.
|
|
30
|
-
Present the results in alphabetical order in a table with the following columns: Name and Id`,
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
}));
|
|
35
|
-
server.prompt("getWorkItem", "Retrieves details for a specific Azure DevOps work item by ID.", { id: z.string().describe("The ID of the work item to retrieve."), project: z.string().describe("The name or ID of the Azure DevOps project.") }, ({ id, project }) => ({
|
|
36
|
-
messages: [
|
|
37
|
-
{
|
|
38
|
-
role: "user",
|
|
39
|
-
content: {
|
|
40
|
-
type: "text",
|
|
41
|
-
text: String.raw `
|
|
42
|
-
# Task
|
|
43
|
-
Use the '${WORKITEM_TOOLS.get_work_item}' tool to retrieve details for the work item with ID '${id}' in project '${project}'.
|
|
44
|
-
Present the following fields: ID, Title, State, Assigned To, Work Item Type, Description or Repro Steps, and Created Date.`,
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
}));
|
|
49
19
|
}
|
|
50
20
|
export { configurePrompts };
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
import { PullRequestStatus, GitVersionType, GitPullRequestQueryType, CommentThreadStatus, } from "azure-devops-node-api/interfaces/GitInterfaces.js";
|
|
3
|
+
import { PullRequestStatus, GitVersionType, GitPullRequestQueryType, CommentThreadStatus, GitPullRequestMergeStrategy, } from "azure-devops-node-api/interfaces/GitInterfaces.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
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.", {
|
|
@@ -193,7 +220,7 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
193
220
|
};
|
|
194
221
|
}
|
|
195
222
|
});
|
|
196
|
-
server.tool(REPO_TOOLS.update_pull_request, "Update a Pull Request by ID with specified fields.", {
|
|
223
|
+
server.tool(REPO_TOOLS.update_pull_request, "Update a Pull Request by ID with specified fields, including setting autocomplete with various completion options.", {
|
|
197
224
|
repositoryId: z.string().describe("The ID of the repository where the pull request exists."),
|
|
198
225
|
pullRequestId: z.number().describe("The ID of the pull request to update."),
|
|
199
226
|
title: z.string().optional().describe("The new title for the pull request."),
|
|
@@ -201,7 +228,15 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
201
228
|
isDraft: z.boolean().optional().describe("Whether the pull request should be a draft."),
|
|
202
229
|
targetRefName: z.string().optional().describe("The new target branch name (e.g., 'refs/heads/main')."),
|
|
203
230
|
status: z.enum(["Active", "Abandoned"]).optional().describe("The new status of the pull request. Can be 'Active' or 'Abandoned'."),
|
|
204
|
-
|
|
231
|
+
autoComplete: z.boolean().optional().describe("Set the pull request to autocomplete when all requirements are met."),
|
|
232
|
+
mergeStrategy: z
|
|
233
|
+
.enum(getEnumKeys(GitPullRequestMergeStrategy))
|
|
234
|
+
.optional()
|
|
235
|
+
.describe("The merge strategy to use when the pull request autocompletes. Defaults to 'NoFastForward'."),
|
|
236
|
+
deleteSourceBranch: z.boolean().optional().default(false).describe("Whether to delete the source branch when the pull request autocompletes. Defaults to false."),
|
|
237
|
+
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."),
|
|
238
|
+
bypassReason: z.string().optional().describe("Reason for bypassing branch policies. When provided, branch policies will be automatically bypassed during autocompletion."),
|
|
239
|
+
}, async ({ repositoryId, pullRequestId, title, description, isDraft, targetRefName, status, autoComplete, mergeStrategy, deleteSourceBranch, transitionWorkItems, bypassReason }) => {
|
|
205
240
|
const connection = await connectionProvider();
|
|
206
241
|
const gitApi = await connection.getGitApi();
|
|
207
242
|
// Build update object with only provided fields
|
|
@@ -217,16 +252,40 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
217
252
|
if (status !== undefined) {
|
|
218
253
|
updateRequest.status = status === "Active" ? PullRequestStatus.Active.valueOf() : PullRequestStatus.Abandoned.valueOf();
|
|
219
254
|
}
|
|
255
|
+
if (autoComplete !== undefined) {
|
|
256
|
+
if (autoComplete) {
|
|
257
|
+
const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
|
|
258
|
+
const autoCompleteUserId = data.authenticatedUser.id;
|
|
259
|
+
updateRequest.autoCompleteSetBy = { id: autoCompleteUserId };
|
|
260
|
+
const completionOptions = {
|
|
261
|
+
deleteSourceBranch: deleteSourceBranch || false,
|
|
262
|
+
transitionWorkItems: transitionWorkItems !== false, // Default to true unless explicitly set to false
|
|
263
|
+
bypassPolicy: !!bypassReason, // Automatically set to true if bypassReason is provided
|
|
264
|
+
};
|
|
265
|
+
if (mergeStrategy) {
|
|
266
|
+
completionOptions.mergeStrategy = GitPullRequestMergeStrategy[mergeStrategy];
|
|
267
|
+
}
|
|
268
|
+
if (bypassReason) {
|
|
269
|
+
completionOptions.bypassReason = bypassReason;
|
|
270
|
+
}
|
|
271
|
+
updateRequest.completionOptions = completionOptions;
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
updateRequest.autoCompleteSetBy = null;
|
|
275
|
+
updateRequest.completionOptions = null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
220
278
|
// Validate that at least one field is provided for update
|
|
221
279
|
if (Object.keys(updateRequest).length === 0) {
|
|
222
280
|
return {
|
|
223
|
-
content: [{ type: "text", text: "Error: At least one field (title, description, isDraft, targetRefName, or
|
|
281
|
+
content: [{ type: "text", text: "Error: At least one field (title, description, isDraft, targetRefName, status, or autoComplete options) must be provided for update." }],
|
|
224
282
|
isError: true,
|
|
225
283
|
};
|
|
226
284
|
}
|
|
227
285
|
const updatedPullRequest = await gitApi.updatePullRequest(updateRequest, repositoryId, pullRequestId);
|
|
286
|
+
const trimmedUpdatedPullRequest = trimPullRequest(updatedPullRequest, true);
|
|
228
287
|
return {
|
|
229
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
288
|
+
content: [{ type: "text", text: JSON.stringify(trimmedUpdatedPullRequest, null, 2) }],
|
|
230
289
|
};
|
|
231
290
|
});
|
|
232
291
|
server.tool(REPO_TOOLS.update_pull_request_reviewers, "Add or remove reviewers for an existing pull request.", {
|
|
@@ -240,8 +299,16 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
240
299
|
let updatedPullRequest;
|
|
241
300
|
if (action === "add") {
|
|
242
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
|
+
}));
|
|
243
310
|
return {
|
|
244
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
311
|
+
content: [{ type: "text", text: JSON.stringify(trimmedResponse, null, 2) }],
|
|
245
312
|
};
|
|
246
313
|
}
|
|
247
314
|
else {
|
|
@@ -278,8 +345,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
278
345
|
content: [{ type: "text", text: JSON.stringify(trimmedRepositories, null, 2) }],
|
|
279
346
|
};
|
|
280
347
|
});
|
|
281
|
-
server.tool(REPO_TOOLS.
|
|
282
|
-
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."),
|
|
283
351
|
top: z.number().default(100).describe("The maximum number of pull requests to return."),
|
|
284
352
|
skip: z.number().default(0).describe("The number of pull requests to skip."),
|
|
285
353
|
created_by_me: z.boolean().default(false).describe("Filter pull requests created by the current user."),
|
|
@@ -295,14 +363,27 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
295
363
|
.describe("Filter pull requests by status. Defaults to 'Active'."),
|
|
296
364
|
sourceRefName: z.string().optional().describe("Filter pull requests from this source branch (e.g., 'refs/heads/feature-branch')."),
|
|
297
365
|
targetRefName: z.string().optional().describe("Filter pull requests into this target branch (e.g., 'refs/heads/main')."),
|
|
298
|
-
}, 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 }) => {
|
|
299
367
|
const connection = await connectionProvider();
|
|
300
368
|
const gitApi = await connection.getGitApi();
|
|
301
369
|
// Build the search criteria
|
|
302
370
|
const searchCriteria = {
|
|
303
371
|
status: pullRequestStatusStringToInt(status),
|
|
304
|
-
repositoryId: repositoryId,
|
|
305
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
|
+
}
|
|
306
387
|
if (sourceRefName) {
|
|
307
388
|
searchCriteria.sourceRefName = sourceRefName;
|
|
308
389
|
}
|
|
@@ -353,120 +434,30 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
353
434
|
const userId = data.authenticatedUser.id;
|
|
354
435
|
searchCriteria.reviewerId = userId;
|
|
355
436
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
pullRequestId: pr.pullRequestId,
|
|
362
|
-
codeReviewId: pr.codeReviewId,
|
|
363
|
-
status: pr.status,
|
|
364
|
-
createdBy: {
|
|
365
|
-
displayName: pr.createdBy?.displayName,
|
|
366
|
-
uniqueName: pr.createdBy?.uniqueName,
|
|
367
|
-
},
|
|
368
|
-
creationDate: pr.creationDate,
|
|
369
|
-
title: pr.title,
|
|
370
|
-
isDraft: pr.isDraft,
|
|
371
|
-
sourceRefName: pr.sourceRefName,
|
|
372
|
-
targetRefName: pr.targetRefName,
|
|
373
|
-
}));
|
|
374
|
-
return {
|
|
375
|
-
content: [{ type: "text", text: JSON.stringify(filteredPullRequests, null, 2) }],
|
|
376
|
-
};
|
|
377
|
-
});
|
|
378
|
-
server.tool(REPO_TOOLS.list_pull_requests_by_project, "Retrieve a list of pull requests for a given project Id or Name.", {
|
|
379
|
-
project: z.string().describe("The name or ID of the Azure DevOps project."),
|
|
380
|
-
top: z.number().default(100).describe("The maximum number of pull requests to return."),
|
|
381
|
-
skip: z.number().default(0).describe("The number of pull requests to skip."),
|
|
382
|
-
created_by_me: z.boolean().default(false).describe("Filter pull requests created by the current user."),
|
|
383
|
-
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."),
|
|
384
|
-
i_am_reviewer: z.boolean().default(false).describe("Filter pull requests where the current user is a reviewer."),
|
|
385
|
-
user_is_reviewer: z
|
|
386
|
-
.string()
|
|
387
|
-
.optional()
|
|
388
|
-
.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."),
|
|
389
|
-
status: z
|
|
390
|
-
.enum(getEnumKeys(PullRequestStatus))
|
|
391
|
-
.default("Active")
|
|
392
|
-
.describe("Filter pull requests by status. Defaults to 'Active'."),
|
|
393
|
-
sourceRefName: z.string().optional().describe("Filter pull requests from this source branch (e.g., 'refs/heads/feature-branch')."),
|
|
394
|
-
targetRefName: z.string().optional().describe("Filter pull requests into this target branch (e.g., 'refs/heads/main')."),
|
|
395
|
-
}, async ({ project, top, skip, created_by_me, created_by_user, i_am_reviewer, user_is_reviewer, status, sourceRefName, targetRefName }) => {
|
|
396
|
-
const connection = await connectionProvider();
|
|
397
|
-
const gitApi = await connection.getGitApi();
|
|
398
|
-
// Build the search criteria
|
|
399
|
-
const gitPullRequestSearchCriteria = {
|
|
400
|
-
status: pullRequestStatusStringToInt(status),
|
|
401
|
-
};
|
|
402
|
-
if (sourceRefName) {
|
|
403
|
-
gitPullRequestSearchCriteria.sourceRefName = sourceRefName;
|
|
404
|
-
}
|
|
405
|
-
if (targetRefName) {
|
|
406
|
-
gitPullRequestSearchCriteria.targetRefName = targetRefName;
|
|
407
|
-
}
|
|
408
|
-
if (created_by_user) {
|
|
409
|
-
try {
|
|
410
|
-
const userId = await getUserIdFromEmail(created_by_user, tokenProvider, connectionProvider, userAgentProvider);
|
|
411
|
-
gitPullRequestSearchCriteria.creatorId = userId;
|
|
412
|
-
}
|
|
413
|
-
catch (error) {
|
|
414
|
-
return {
|
|
415
|
-
content: [
|
|
416
|
-
{
|
|
417
|
-
type: "text",
|
|
418
|
-
text: `Error finding user with email ${created_by_user}: ${error instanceof Error ? error.message : String(error)}`,
|
|
419
|
-
},
|
|
420
|
-
],
|
|
421
|
-
isError: true,
|
|
422
|
-
};
|
|
423
|
-
}
|
|
437
|
+
let pullRequests;
|
|
438
|
+
if (repositoryId) {
|
|
439
|
+
pullRequests = await gitApi.getPullRequests(repositoryId, searchCriteria, project, // project
|
|
440
|
+
undefined, // maxCommentLength
|
|
441
|
+
skip, top);
|
|
424
442
|
}
|
|
425
|
-
else if (
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
443
|
+
else if (project) {
|
|
444
|
+
// If only project is provided, use getPullRequestsByProject
|
|
445
|
+
pullRequests = await gitApi.getPullRequestsByProject(project, searchCriteria, undefined, // maxCommentLength
|
|
446
|
+
skip, top);
|
|
429
447
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
},
|
|
442
|
-
],
|
|
443
|
-
isError: true,
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
else if (i_am_reviewer) {
|
|
448
|
-
const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
|
|
449
|
-
const userId = data.authenticatedUser.id;
|
|
450
|
-
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
|
+
};
|
|
451
459
|
}
|
|
452
|
-
const
|
|
453
|
-
skip, top);
|
|
454
|
-
// Filter out the irrelevant properties
|
|
455
|
-
const filteredPullRequests = pullRequests?.map((pr) => ({
|
|
456
|
-
pullRequestId: pr.pullRequestId,
|
|
457
|
-
codeReviewId: pr.codeReviewId,
|
|
458
|
-
repository: pr.repository?.name,
|
|
459
|
-
status: pr.status,
|
|
460
|
-
createdBy: {
|
|
461
|
-
displayName: pr.createdBy?.displayName,
|
|
462
|
-
uniqueName: pr.createdBy?.uniqueName,
|
|
463
|
-
},
|
|
464
|
-
creationDate: pr.creationDate,
|
|
465
|
-
title: pr.title,
|
|
466
|
-
isDraft: pr.isDraft,
|
|
467
|
-
sourceRefName: pr.sourceRefName,
|
|
468
|
-
targetRefName: pr.targetRefName,
|
|
469
|
-
}));
|
|
460
|
+
const filteredPullRequests = pullRequests?.map((pr) => trimPullRequest(pr));
|
|
470
461
|
return {
|
|
471
462
|
content: [{ type: "text", text: JSON.stringify(filteredPullRequests, null, 2) }],
|
|
472
463
|
};
|
|
@@ -491,13 +482,7 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
491
482
|
};
|
|
492
483
|
}
|
|
493
484
|
// Return trimmed thread data focusing on essential information
|
|
494
|
-
const trimmedThreads = paginatedThreads?.map((thread) => (
|
|
495
|
-
id: thread.id,
|
|
496
|
-
publishedDate: thread.publishedDate,
|
|
497
|
-
lastUpdatedDate: thread.lastUpdatedDate,
|
|
498
|
-
status: thread.status,
|
|
499
|
-
comments: trimComments(thread.comments),
|
|
500
|
-
}));
|
|
485
|
+
const trimmedThreads = paginatedThreads?.map((thread) => trimPullRequestThread(thread));
|
|
501
486
|
return {
|
|
502
487
|
content: [{ type: "text", text: JSON.stringify(trimmedThreads, null, 2) }],
|
|
503
488
|
};
|
|
@@ -687,8 +672,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
687
672
|
}
|
|
688
673
|
}
|
|
689
674
|
const thread = await gitApi.createThread({ comments: [{ content: content }], threadContext: threadContext, status: CommentThreadStatus[status] }, repositoryId, pullRequestId, project);
|
|
675
|
+
const trimmedThread = trimPullRequestThread(thread);
|
|
690
676
|
return {
|
|
691
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
677
|
+
content: [{ type: "text", text: JSON.stringify(trimmedThread, null, 2) }],
|
|
692
678
|
};
|
|
693
679
|
});
|
|
694
680
|
server.tool(REPO_TOOLS.resolve_comment, "Resolves a specific comment thread on a pull request.", {
|
package/dist/tools/test-plans.js
CHANGED
|
@@ -4,6 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
const Test_Plan_Tools = {
|
|
5
5
|
create_test_plan: "testplan_create_test_plan",
|
|
6
6
|
create_test_case: "testplan_create_test_case",
|
|
7
|
+
update_test_case_steps: "testplan_update_test_case_steps",
|
|
7
8
|
add_test_cases_to_suite: "testplan_add_test_cases_to_suite",
|
|
8
9
|
test_results_from_build_id: "testplan_show_test_results_from_build_id",
|
|
9
10
|
list_test_cases: "testplan_list_test_cases",
|
|
@@ -11,10 +12,6 @@ const Test_Plan_Tools = {
|
|
|
11
12
|
create_test_suite: "testplan_create_test_suite",
|
|
12
13
|
};
|
|
13
14
|
function configureTestPlanTools(server, _, connectionProvider) {
|
|
14
|
-
/*
|
|
15
|
-
LIST OF TEST PLANS
|
|
16
|
-
get list of test plans by project
|
|
17
|
-
*/
|
|
18
15
|
server.tool(Test_Plan_Tools.list_test_plans, "Retrieve a paginated list of test plans from an Azure DevOps project. Allows filtering for active plans and toggling detailed information.", {
|
|
19
16
|
project: z.string().describe("The unique identifier (ID or name) of the Azure DevOps project."),
|
|
20
17
|
filterActivePlans: z.boolean().default(true).describe("Filter to include only active test plans. Defaults to true."),
|
|
@@ -29,9 +26,6 @@ function configureTestPlanTools(server, _, connectionProvider) {
|
|
|
29
26
|
content: [{ type: "text", text: JSON.stringify(testPlans, null, 2) }],
|
|
30
27
|
};
|
|
31
28
|
});
|
|
32
|
-
/*
|
|
33
|
-
Create Test Plan - CREATE
|
|
34
|
-
*/
|
|
35
29
|
server.tool(Test_Plan_Tools.create_test_plan, "Creates a new test plan in the project.", {
|
|
36
30
|
project: z.string().describe("The unique identifier (ID or name) of the Azure DevOps project where the test plan will be created."),
|
|
37
31
|
name: z.string().describe("The name of the test plan to be created."),
|
|
@@ -56,9 +50,6 @@ function configureTestPlanTools(server, _, connectionProvider) {
|
|
|
56
50
|
content: [{ type: "text", text: JSON.stringify(createdTestPlan, null, 2) }],
|
|
57
51
|
};
|
|
58
52
|
});
|
|
59
|
-
/*
|
|
60
|
-
Create Test Suite - CREATE
|
|
61
|
-
*/
|
|
62
53
|
server.tool(Test_Plan_Tools.create_test_suite, "Creates a new test suite in a test plan.", {
|
|
63
54
|
project: z.string().describe("Project ID or project name"),
|
|
64
55
|
planId: z.number().describe("ID of the test plan that contains the suites"),
|
|
@@ -80,9 +71,6 @@ function configureTestPlanTools(server, _, connectionProvider) {
|
|
|
80
71
|
content: [{ type: "text", text: JSON.stringify(createdTestSuite, null, 2) }],
|
|
81
72
|
};
|
|
82
73
|
});
|
|
83
|
-
/*
|
|
84
|
-
Add Test Cases to Suite - ADD
|
|
85
|
-
*/
|
|
86
74
|
server.tool(Test_Plan_Tools.add_test_cases_to_suite, "Adds existing test cases to a test suite.", {
|
|
87
75
|
project: z.string().describe("The unique identifier (ID or name) of the Azure DevOps project."),
|
|
88
76
|
planId: z.number().describe("The ID of the test plan."),
|
|
@@ -98,9 +86,6 @@ function configureTestPlanTools(server, _, connectionProvider) {
|
|
|
98
86
|
content: [{ type: "text", text: JSON.stringify(addedTestCases, null, 2) }],
|
|
99
87
|
};
|
|
100
88
|
});
|
|
101
|
-
/*
|
|
102
|
-
Create Test Case - CREATE
|
|
103
|
-
*/
|
|
104
89
|
server.tool(Test_Plan_Tools.create_test_case, "Creates a new test case work item.", {
|
|
105
90
|
project: z.string().describe("The unique identifier (ID or name) of the Azure DevOps project."),
|
|
106
91
|
title: z.string().describe("The title of the test case."),
|
|
@@ -111,7 +96,8 @@ function configureTestPlanTools(server, _, connectionProvider) {
|
|
|
111
96
|
priority: z.number().optional().describe("The priority of the test case."),
|
|
112
97
|
areaPath: z.string().optional().describe("The area path for the test case."),
|
|
113
98
|
iterationPath: z.string().optional().describe("The iteration path for the test case."),
|
|
114
|
-
|
|
99
|
+
testsWorkItemId: z.number().optional().describe("Optional work item id that will be set as a Microsoft.VSTS.Common.TestedBy-Reverse link to the test case."),
|
|
100
|
+
}, async ({ project, title, steps, priority, areaPath, iterationPath, testsWorkItemId }) => {
|
|
115
101
|
const connection = await connectionProvider();
|
|
116
102
|
const witClient = await connection.getWorkItemTrackingApi();
|
|
117
103
|
let stepsXml;
|
|
@@ -125,6 +111,16 @@ function configureTestPlanTools(server, _, connectionProvider) {
|
|
|
125
111
|
path: "/fields/System.Title",
|
|
126
112
|
value: title,
|
|
127
113
|
});
|
|
114
|
+
if (testsWorkItemId) {
|
|
115
|
+
patchDocument.push({
|
|
116
|
+
op: "add",
|
|
117
|
+
path: "/relations/-",
|
|
118
|
+
value: {
|
|
119
|
+
rel: "Microsoft.VSTS.Common.TestedBy-Reverse",
|
|
120
|
+
url: `${connection.serverUrl}/${project}/_apis/wit/workItems/${testsWorkItemId}`,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
128
124
|
if (stepsXml) {
|
|
129
125
|
patchDocument.push({
|
|
130
126
|
op: "add",
|
|
@@ -158,10 +154,32 @@ function configureTestPlanTools(server, _, connectionProvider) {
|
|
|
158
154
|
content: [{ type: "text", text: JSON.stringify(workItem, null, 2) }],
|
|
159
155
|
};
|
|
160
156
|
});
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
157
|
+
server.tool(Test_Plan_Tools.update_test_case_steps, "Update an existing test case work item.", {
|
|
158
|
+
id: z.number().describe("The ID of the test case work item to update."),
|
|
159
|
+
steps: z
|
|
160
|
+
.string()
|
|
161
|
+
.describe("The steps to reproduce the test case. Make sure to format each step as '1. Step one|Expected result one\n2. Step two|Expected result two. USE '|' as the delimiter between step and expected result. DO NOT use '|' in the description of the step or expected result."),
|
|
162
|
+
}, async ({ id, steps }) => {
|
|
163
|
+
const connection = await connectionProvider();
|
|
164
|
+
const witClient = await connection.getWorkItemTrackingApi();
|
|
165
|
+
let stepsXml;
|
|
166
|
+
if (steps) {
|
|
167
|
+
stepsXml = convertStepsToXml(steps);
|
|
168
|
+
}
|
|
169
|
+
// Create JSON patch document for work item
|
|
170
|
+
const patchDocument = [];
|
|
171
|
+
if (stepsXml) {
|
|
172
|
+
patchDocument.push({
|
|
173
|
+
op: "add",
|
|
174
|
+
path: "/fields/Microsoft.VSTS.TCM.Steps",
|
|
175
|
+
value: stepsXml,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const workItem = await witClient.updateWorkItem({}, patchDocument, id);
|
|
179
|
+
return {
|
|
180
|
+
content: [{ type: "text", text: JSON.stringify(workItem, null, 2) }],
|
|
181
|
+
};
|
|
182
|
+
});
|
|
165
183
|
server.tool(Test_Plan_Tools.list_test_cases, "Gets a list of test cases in the test plan.", {
|
|
166
184
|
project: z.string().describe("The unique identifier (ID or name) of the Azure DevOps project."),
|
|
167
185
|
planid: z.number().describe("The ID of the test plan."),
|
|
@@ -174,9 +192,6 @@ function configureTestPlanTools(server, _, connectionProvider) {
|
|
|
174
192
|
content: [{ type: "text", text: JSON.stringify(testcases, null, 2) }],
|
|
175
193
|
};
|
|
176
194
|
});
|
|
177
|
-
/*
|
|
178
|
-
Gets a list of test results for a given project and build ID
|
|
179
|
-
*/
|
|
180
195
|
server.tool(Test_Plan_Tools.test_results_from_build_id, "Gets a list of test results for a given project and build ID.", {
|
|
181
196
|
project: z.string().describe("The unique identifier (ID or name) of the Azure DevOps project."),
|
|
182
197
|
buildid: z.number().describe("The ID of the build."),
|
package/dist/tools.js
CHANGED
|
File without changes
|
package/dist/useragent.js
CHANGED
|
File without changes
|
package/dist/utils.js
CHANGED
|
File without changes
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const packageVersion = "2.2.
|
|
1
|
+
export const packageVersion = "2.2.1-nightly.20251015";
|