@azure-devops/mcp 2.5.0-nightly.20260407 → 2.5.0-nightly.20260409
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/test-plans.js +98 -14
- package/dist/tools/work-items.js +32 -0
- package/dist/tools.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/tools/test-plans.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
import { SuiteExpand } from "azure-devops-node-api/interfaces/TestPlanInterfaces.js";
|
|
4
3
|
import { z } from "zod";
|
|
4
|
+
import { apiVersion } from "../utils.js";
|
|
5
5
|
const Test_Plan_Tools = {
|
|
6
6
|
create_test_plan: "testplan_create_test_plan",
|
|
7
7
|
create_test_case: "testplan_create_test_case",
|
|
@@ -13,7 +13,7 @@ const Test_Plan_Tools = {
|
|
|
13
13
|
list_test_suites: "testplan_list_test_suites",
|
|
14
14
|
create_test_suite: "testplan_create_test_suite",
|
|
15
15
|
};
|
|
16
|
-
function configureTestPlanTools(server,
|
|
16
|
+
function configureTestPlanTools(server, tokenProvider, connectionProvider, userAgentProvider) {
|
|
17
17
|
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.", {
|
|
18
18
|
project: z.string().describe("The unique identifier (ID or name) of the Azure DevOps project."),
|
|
19
19
|
filterActivePlans: z.boolean().default(true).describe("Filter to include only active test plans. Defaults to true."),
|
|
@@ -21,12 +21,42 @@ function configureTestPlanTools(server, _, connectionProvider) {
|
|
|
21
21
|
continuationToken: z.string().optional().describe("Token to continue fetching test plans from a previous request."),
|
|
22
22
|
}, async ({ project, filterActivePlans, includePlanDetails, continuationToken }) => {
|
|
23
23
|
try {
|
|
24
|
-
const owner = ""; //making owner an empty string untill we can figure out how to get owner id
|
|
25
24
|
const connection = await connectionProvider();
|
|
26
|
-
const
|
|
27
|
-
const
|
|
25
|
+
const accessToken = await tokenProvider();
|
|
26
|
+
const params = new URLSearchParams({ "api-version": apiVersion });
|
|
27
|
+
if (filterActivePlans)
|
|
28
|
+
params.append("filterActivePlans", "true");
|
|
29
|
+
if (includePlanDetails)
|
|
30
|
+
params.append("includePlanDetails", "true");
|
|
31
|
+
if (continuationToken)
|
|
32
|
+
params.append("continuationToken", continuationToken);
|
|
33
|
+
const url = `${connection.serverUrl}/${encodeURIComponent(project)}/_apis/testplan/Plans?${params.toString()}`;
|
|
34
|
+
const headers = {
|
|
35
|
+
Authorization: `Bearer ${accessToken}`,
|
|
36
|
+
};
|
|
37
|
+
const userAgent = userAgentProvider?.();
|
|
38
|
+
if (userAgent) {
|
|
39
|
+
headers["User-Agent"] = userAgent;
|
|
40
|
+
}
|
|
41
|
+
const response = await fetch(url, {
|
|
42
|
+
method: "GET",
|
|
43
|
+
headers,
|
|
44
|
+
});
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
const errorText = await response.text();
|
|
47
|
+
throw new Error(`Failed to list test plans (${response.status}): ${errorText}`);
|
|
48
|
+
}
|
|
49
|
+
const body = await response.json();
|
|
50
|
+
const testPlans = body.value ?? [];
|
|
51
|
+
const nextToken = response.headers.get("x-ms-continuationtoken") ?? undefined;
|
|
52
|
+
const result = {
|
|
53
|
+
testPlans: testPlans,
|
|
54
|
+
};
|
|
55
|
+
if (nextToken) {
|
|
56
|
+
result.continuationToken = nextToken;
|
|
57
|
+
}
|
|
28
58
|
return {
|
|
29
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
59
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
30
60
|
};
|
|
31
61
|
}
|
|
32
62
|
catch (error) {
|
|
@@ -258,13 +288,41 @@ function configureTestPlanTools(server, _, connectionProvider) {
|
|
|
258
288
|
project: z.string().describe("The unique identifier (ID or name) of the Azure DevOps project."),
|
|
259
289
|
planid: z.coerce.number().min(1).describe("The ID of the test plan."),
|
|
260
290
|
suiteid: z.coerce.number().min(1).describe("The ID of the test suite."),
|
|
261
|
-
|
|
291
|
+
continuationToken: z.string().optional().describe("Token to continue fetching test cases from a previous request."),
|
|
292
|
+
}, async ({ project, planid, suiteid, continuationToken }) => {
|
|
262
293
|
try {
|
|
263
294
|
const connection = await connectionProvider();
|
|
264
|
-
const
|
|
265
|
-
const
|
|
295
|
+
const accessToken = await tokenProvider();
|
|
296
|
+
const params = new URLSearchParams({ "api-version": "7.2-preview.3" });
|
|
297
|
+
if (continuationToken)
|
|
298
|
+
params.append("continuationToken", continuationToken);
|
|
299
|
+
const url = `${connection.serverUrl}/${encodeURIComponent(project)}/_apis/testplan/Plans/${planid}/Suites/${suiteid}/TestCase?${params.toString()}`;
|
|
300
|
+
const headers = {
|
|
301
|
+
Authorization: `Bearer ${accessToken}`,
|
|
302
|
+
};
|
|
303
|
+
const userAgent = userAgentProvider?.();
|
|
304
|
+
if (userAgent) {
|
|
305
|
+
headers["User-Agent"] = userAgent;
|
|
306
|
+
}
|
|
307
|
+
const response = await fetch(url, {
|
|
308
|
+
method: "GET",
|
|
309
|
+
headers,
|
|
310
|
+
});
|
|
311
|
+
if (!response.ok) {
|
|
312
|
+
const errorText = await response.text();
|
|
313
|
+
throw new Error(`Failed to list test cases (${response.status}): ${errorText}`);
|
|
314
|
+
}
|
|
315
|
+
const body = await response.json();
|
|
316
|
+
const testcases = body.value ?? [];
|
|
317
|
+
const nextToken = response.headers.get("x-ms-continuationtoken") ?? undefined;
|
|
318
|
+
const result = {
|
|
319
|
+
testCases: testcases,
|
|
320
|
+
};
|
|
321
|
+
if (nextToken) {
|
|
322
|
+
result.continuationToken = nextToken;
|
|
323
|
+
}
|
|
266
324
|
return {
|
|
267
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
325
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
268
326
|
};
|
|
269
327
|
}
|
|
270
328
|
catch (error) {
|
|
@@ -334,9 +392,29 @@ function configureTestPlanTools(server, _, connectionProvider) {
|
|
|
334
392
|
}, async ({ project, planId, continuationToken }) => {
|
|
335
393
|
try {
|
|
336
394
|
const connection = await connectionProvider();
|
|
337
|
-
const
|
|
338
|
-
const
|
|
339
|
-
|
|
395
|
+
const accessToken = await tokenProvider();
|
|
396
|
+
const params = new URLSearchParams({ "api-version": apiVersion, "expand": "children" });
|
|
397
|
+
if (continuationToken)
|
|
398
|
+
params.append("continuationToken", continuationToken);
|
|
399
|
+
const url = `${connection.serverUrl}/${encodeURIComponent(project)}/_apis/testplan/Plans/${planId}/Suites?${params.toString()}`;
|
|
400
|
+
const headers = {
|
|
401
|
+
Authorization: `Bearer ${accessToken}`,
|
|
402
|
+
};
|
|
403
|
+
const userAgent = userAgentProvider?.();
|
|
404
|
+
if (userAgent) {
|
|
405
|
+
headers["User-Agent"] = userAgent;
|
|
406
|
+
}
|
|
407
|
+
const response = await fetch(url, {
|
|
408
|
+
method: "GET",
|
|
409
|
+
headers,
|
|
410
|
+
});
|
|
411
|
+
if (!response.ok) {
|
|
412
|
+
const errorText = await response.text();
|
|
413
|
+
throw new Error(`Failed to list test suites (${response.status}): ${errorText}`);
|
|
414
|
+
}
|
|
415
|
+
const body = await response.json();
|
|
416
|
+
const testSuites = body.value ?? [];
|
|
417
|
+
const nextToken = response.headers.get("x-ms-continuationtoken") ?? undefined;
|
|
340
418
|
// The API returns a flat list where the root suite is first, followed by all nested suites
|
|
341
419
|
// We need to build a proper hierarchy by creating a map and assembling the tree
|
|
342
420
|
// Create a map of all suites by ID for quick lookup
|
|
@@ -373,7 +451,13 @@ function configureTestPlanTools(server, _, connectionProvider) {
|
|
|
373
451
|
}
|
|
374
452
|
return cleaned;
|
|
375
453
|
};
|
|
376
|
-
const
|
|
454
|
+
const cleanedSuites = roots.map((root) => cleanSuite(root));
|
|
455
|
+
const result = {
|
|
456
|
+
testSuites: cleanedSuites,
|
|
457
|
+
};
|
|
458
|
+
if (nextToken) {
|
|
459
|
+
result.continuationToken = nextToken;
|
|
460
|
+
}
|
|
377
461
|
return {
|
|
378
462
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
379
463
|
};
|
package/dist/tools/work-items.js
CHANGED
|
@@ -4,6 +4,8 @@ import { WorkItemExpand } from "azure-devops-node-api/interfaces/WorkItemTrackin
|
|
|
4
4
|
import { QueryExpand } from "azure-devops-node-api/interfaces/WorkItemTrackingInterfaces.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { batchApiVersion, markdownCommentsApiVersion, getEnumKeys, safeEnumConvert, encodeFormattedValue } from "../utils.js";
|
|
7
|
+
import { elicitProject } from "../shared/elicitations.js";
|
|
8
|
+
import { createExternalContentResponse } from "../shared/content-safety.js";
|
|
7
9
|
const WORKITEM_TOOLS = {
|
|
8
10
|
my_work_items: "wit_my_work_items",
|
|
9
11
|
list_backlogs: "wit_list_backlogs",
|
|
@@ -27,6 +29,7 @@ const WORKITEM_TOOLS = {
|
|
|
27
29
|
work_item_unlink: "wit_work_item_unlink",
|
|
28
30
|
add_artifact_link: "wit_add_artifact_link",
|
|
29
31
|
get_work_item_attachment: "wit_get_work_item_attachment",
|
|
32
|
+
query_by_wiql: "wit_query_by_wiql",
|
|
30
33
|
};
|
|
31
34
|
function getLinkTypeFromName(name) {
|
|
32
35
|
switch (name.toLowerCase()) {
|
|
@@ -1034,6 +1037,35 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
1034
1037
|
};
|
|
1035
1038
|
}
|
|
1036
1039
|
});
|
|
1040
|
+
server.tool(WORKITEM_TOOLS.query_by_wiql, "Execute a WIQL (Work Item Query Language) query and return the matching work items. If a project is not specified, you will be prompted to select one.", {
|
|
1041
|
+
wiql: z.string().max(32768).describe('The WIQL query string to execute, e.g., "SELECT [System.Id], [System.Title] FROM WorkItems WHERE [System.TeamProject] = @project"'),
|
|
1042
|
+
project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
|
|
1043
|
+
team: z.string().optional().describe("The name or ID of the Azure DevOps team. If not provided, the default team context will be used."),
|
|
1044
|
+
timePrecision: z.boolean().optional().describe("Whether to include time precision in date fields. Defaults to false."),
|
|
1045
|
+
top: z.coerce.number().default(50).describe("The maximum number of results to return. Defaults to 50."),
|
|
1046
|
+
}, async ({ wiql, project, team, timePrecision, top }) => {
|
|
1047
|
+
try {
|
|
1048
|
+
const connection = await connectionProvider();
|
|
1049
|
+
let resolvedProject = project;
|
|
1050
|
+
if (!resolvedProject) {
|
|
1051
|
+
const result = await elicitProject(server, connection, "Select the Azure DevOps project to run the WIQL query against.");
|
|
1052
|
+
if ("response" in result)
|
|
1053
|
+
return result.response;
|
|
1054
|
+
resolvedProject = result.resolved;
|
|
1055
|
+
}
|
|
1056
|
+
const workItemApi = await connection.getWorkItemTrackingApi();
|
|
1057
|
+
const teamContext = { project: resolvedProject, team };
|
|
1058
|
+
const queryResult = await workItemApi.queryByWiql({ query: wiql }, teamContext, timePrecision, top);
|
|
1059
|
+
return createExternalContentResponse(queryResult, "wiql query results");
|
|
1060
|
+
}
|
|
1061
|
+
catch (error) {
|
|
1062
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
1063
|
+
return {
|
|
1064
|
+
content: [{ type: "text", text: `Error executing WIQL query: ${errorMessage}` }],
|
|
1065
|
+
isError: true,
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1037
1069
|
server.tool(WORKITEM_TOOLS.get_work_item_attachment, "Download a work item attachment by its ID and return the content as a base64-encoded resource. Useful for viewing images (e.g. screenshots) attached to work items such as bugs.", {
|
|
1038
1070
|
project: z.string().describe("The name or ID of the Azure DevOps project."),
|
|
1039
1071
|
attachmentId: z.string().describe("The GUID of the attachment. Found in the attachment URL: https://dev.azure.com/{org}/{project}/_apis/wit/attachments/{attachmentId}"),
|
package/dist/tools.js
CHANGED
|
@@ -24,7 +24,7 @@ function configureAllTools(server, tokenProvider, connectionProvider, userAgentP
|
|
|
24
24
|
configureIfDomainEnabled(Domain.REPOSITORIES, () => configureRepoTools(server, tokenProvider, connectionProvider, userAgentProvider));
|
|
25
25
|
configureIfDomainEnabled(Domain.WORK_ITEMS, () => configureWorkItemTools(server, tokenProvider, connectionProvider, userAgentProvider));
|
|
26
26
|
configureIfDomainEnabled(Domain.WIKI, () => configureWikiTools(server, tokenProvider, connectionProvider, userAgentProvider));
|
|
27
|
-
configureIfDomainEnabled(Domain.TEST_PLANS, () => configureTestPlanTools(server, tokenProvider, connectionProvider));
|
|
27
|
+
configureIfDomainEnabled(Domain.TEST_PLANS, () => configureTestPlanTools(server, tokenProvider, connectionProvider, userAgentProvider));
|
|
28
28
|
configureIfDomainEnabled(Domain.SEARCH, () => configureSearchTools(server, tokenProvider, connectionProvider, userAgentProvider));
|
|
29
29
|
configureIfDomainEnabled(Domain.ADVANCED_SECURITY, () => configureAdvSecTools(server, tokenProvider, connectionProvider));
|
|
30
30
|
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const packageVersion = "2.5.0-nightly.
|
|
1
|
+
export const packageVersion = "2.5.0-nightly.20260409";
|