@azure-devops/mcp 2.9.0 → 2.10.0
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 +70 -107
- package/dist/auth.js +66 -6
- package/dist/index.js +5 -16
- package/dist/shared/content-safety.js +28 -1
- package/dist/tools/auth.js +10 -6
- package/dist/tools/pipelines.dto.js +11 -3
- package/dist/tools/pipelines.js +113 -104
- package/dist/tools/repositories.js +50 -13
- package/dist/tools/search.js +6 -11
- package/dist/tools/work-items.js +110 -30
- package/dist/tools.js +35 -2
- package/dist/utils.js +13 -0
- package/dist/version.js +1 -1
- package/package.json +10 -3
package/dist/tools/work-items.js
CHANGED
|
@@ -8,6 +8,7 @@ import { z } from "zod";
|
|
|
8
8
|
import { batchApiVersion, markdownCommentsApiVersion, getEnumKeys, safeEnumConvert, encodeFormattedValue } from "../utils.js";
|
|
9
9
|
import { elicitProject, elicitTeam } from "../shared/elicitations.js";
|
|
10
10
|
import { createExternalContentResponse } from "../shared/content-safety.js";
|
|
11
|
+
import { getUserIdentityFromEmail } from "./auth.js";
|
|
11
12
|
const WORKITEM_TOOLS = {
|
|
12
13
|
wit_work_item: "wit_work_item",
|
|
13
14
|
wit_query: "wit_query",
|
|
@@ -43,6 +44,8 @@ function getLinkTypeFromName(name) {
|
|
|
43
44
|
return "Microsoft.VSTS.Common.Affects-Reverse";
|
|
44
45
|
case "artifact":
|
|
45
46
|
return "ArtifactLink";
|
|
47
|
+
case "hyperlink":
|
|
48
|
+
return "Hyperlink";
|
|
46
49
|
default:
|
|
47
50
|
throw new Error(`Unknown link type: ${name}`);
|
|
48
51
|
}
|
|
@@ -55,6 +58,36 @@ function getArtifactLinkAttributeName(linkType) {
|
|
|
55
58
|
return linkType;
|
|
56
59
|
}
|
|
57
60
|
}
|
|
61
|
+
function escapeHtml(value) {
|
|
62
|
+
const entities = {
|
|
63
|
+
"&": "&",
|
|
64
|
+
"<": "<",
|
|
65
|
+
">": ">",
|
|
66
|
+
'"': """,
|
|
67
|
+
"'": "'",
|
|
68
|
+
};
|
|
69
|
+
return value.replace(/[&<>"']/g, (character) => entities[character]);
|
|
70
|
+
}
|
|
71
|
+
async function resolveCommentMentions(text, format, tokenProvider, connectionProvider, userAgentProvider) {
|
|
72
|
+
const emailMatches = [...text.matchAll(/@<([^<>\s]+@[^<>\s]+)>/g)];
|
|
73
|
+
if (emailMatches.length === 0)
|
|
74
|
+
return text;
|
|
75
|
+
const identities = new Map();
|
|
76
|
+
for (const email of new Set(emailMatches.map((match) => match[1]))) {
|
|
77
|
+
try {
|
|
78
|
+
identities.set(email, await getUserIdentityFromEmail(email, tokenProvider, connectionProvider, userAgentProvider));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Leave mentions unchanged when their identities cannot be resolved.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return text.replace(/@<([^<>\s]+@[^<>\s]+)>/g, (mention, email) => {
|
|
85
|
+
const identity = identities.get(email);
|
|
86
|
+
if (!identity)
|
|
87
|
+
return escapeHtml(mention);
|
|
88
|
+
return format === "Markdown" || format === undefined ? `@<${identity.id}>` : `<a href="#" data-vss-mention="version:2.0,${identity.id}">@${escapeHtml(identity.displayName)}</a>`;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
58
91
|
function configureWorkItemTools(server, tokenProvider, connectionProvider, userAgentProvider) {
|
|
59
92
|
// --- wit_work_item ----------------------------------------------------------
|
|
60
93
|
server.tool(WORKITEM_TOOLS.wit_work_item, "Retrieve work item data for a project. Use the action parameter to specify the operation.", {
|
|
@@ -309,11 +342,24 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
309
342
|
});
|
|
310
343
|
// --- wit_backlog ------------------------------------------------------------
|
|
311
344
|
server.tool(WORKITEM_TOOLS.wit_backlog, "Retrieve backlog data for a project and team. Use the action parameter to specify the operation.", {
|
|
312
|
-
action: z
|
|
345
|
+
action: z
|
|
346
|
+
.enum(["list", "list_work_items", "reorder"])
|
|
347
|
+
.describe("The action to perform. Options: list (list backlog levels for a team), list_work_items (list work items in a specific backlog level), reorder (move work items to a new position in a backlog or iteration)."),
|
|
313
348
|
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."),
|
|
314
349
|
team: z.string().optional().describe("The name or ID of the Azure DevOps team. Reuse from prior context if already known. If not provided, a team selection prompt will be shown."),
|
|
315
350
|
backlogId: z.string().optional().describe("The ID of the backlog category to retrieve work items from. Required for: list_work_items."),
|
|
316
|
-
|
|
351
|
+
ids: z.array(z.coerce.number().int().min(1)).min(1).optional().describe("The IDs of the work items to reorder. Required for: reorder."),
|
|
352
|
+
previousId: z.coerce
|
|
353
|
+
.number()
|
|
354
|
+
.int()
|
|
355
|
+
.min(0)
|
|
356
|
+
.optional()
|
|
357
|
+
.describe("The ID of the work item that should be before the reordered items. Use 0 to specify the beginning of the list. Optional for: reorder."),
|
|
358
|
+
nextId: z.coerce.number().int().min(0).optional().describe("The ID of the work item that should be after the reordered items. Use 0 to specify the end of the list. Optional for: reorder."),
|
|
359
|
+
parentId: z.coerce.number().int().min(0).optional().describe("The parent ID for all work items involved in the operation. Use 0 to indicate the items have no parent. Optional for: reorder."),
|
|
360
|
+
iterationPath: z.string().optional().describe("The iteration path for the reorder operation. Used when reordering items in an iteration backlog. Optional for: reorder."),
|
|
361
|
+
iterationId: z.string().optional().describe("The iteration ID. When provided, reorder items in that iteration instead of the team backlog. Used for: reorder."),
|
|
362
|
+
}, async ({ action, project, team, backlogId, ids, previousId, nextId, parentId, iterationPath, iterationId }) => {
|
|
317
363
|
try {
|
|
318
364
|
const connection = await connectionProvider();
|
|
319
365
|
let resolvedProject = project;
|
|
@@ -344,6 +390,13 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
344
390
|
const workItems = await workApi.getBacklogLevelWorkItems(teamContext, backlogId);
|
|
345
391
|
return { content: [{ type: "text", text: JSON.stringify(workItems, null, 2) }] };
|
|
346
392
|
}
|
|
393
|
+
if (action === "reorder") {
|
|
394
|
+
if (!ids?.length)
|
|
395
|
+
return { content: [{ type: "text", text: "ids is required for reorder" }], isError: true };
|
|
396
|
+
const operation = { ids, previousId, nextId, parentId, iterationPath };
|
|
397
|
+
const reorderedItems = iterationId ? await workApi.reorderIterationWorkItems(operation, teamContext, iterationId) : await workApi.reorderBacklogWorkItems(operation, teamContext);
|
|
398
|
+
return { content: [{ type: "text", text: JSON.stringify(reorderedItems, null, 2) }] };
|
|
399
|
+
}
|
|
347
400
|
return { content: [{ type: "text", text: `Unknown action: ${action}` }], isError: true };
|
|
348
401
|
}
|
|
349
402
|
catch (error) {
|
|
@@ -351,6 +404,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
351
404
|
const msgs = {
|
|
352
405
|
list: `Error listing backlogs: ${errorMessage}`,
|
|
353
406
|
list_work_items: `Error listing backlog work items: ${errorMessage}`,
|
|
407
|
+
reorder: `Error reordering backlog work items: ${errorMessage}`,
|
|
354
408
|
};
|
|
355
409
|
return { content: [{ type: "text", text: msgs[action] ?? `Error: ${errorMessage}` }], isError: true };
|
|
356
410
|
}
|
|
@@ -404,9 +458,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
404
458
|
}
|
|
405
459
|
const mimeType = getMimeType(fileName);
|
|
406
460
|
if (mimeType.startsWith("text/")) {
|
|
407
|
-
return
|
|
408
|
-
content: [{ type: "text", text: buffer.toString("utf-8") }],
|
|
409
|
-
};
|
|
461
|
+
return createExternalContentResponse(buffer.toString("utf-8"), "work item attachment");
|
|
410
462
|
}
|
|
411
463
|
const base64Data = buffer.toString("base64");
|
|
412
464
|
return {
|
|
@@ -451,14 +503,17 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
451
503
|
op: z
|
|
452
504
|
.string()
|
|
453
505
|
.transform((val) => val.toLowerCase())
|
|
454
|
-
.pipe(z.enum(["add", "replace", "remove"]))
|
|
506
|
+
.pipe(z.enum(["add", "replace", "remove", "test"]))
|
|
455
507
|
.default("add")
|
|
456
|
-
.describe("The operation to perform."),
|
|
457
|
-
path: z.string().describe("The
|
|
458
|
-
value: z
|
|
508
|
+
.describe("The operation to perform. Use 'test' with path '/rev' to enforce optimistic concurrency."),
|
|
509
|
+
path: z.string().describe("The path to operate on, e.g. '/fields/System.Title' or '/rev' for a revision test."),
|
|
510
|
+
value: z
|
|
511
|
+
.union([z.string(), z.number(), z.boolean(), z.null()])
|
|
512
|
+
.optional()
|
|
513
|
+
.describe("The operation value. Required for add, replace, and test; omit for remove. For a test on '/rev', pass the numeric revision previously read."),
|
|
459
514
|
}))
|
|
460
515
|
.optional()
|
|
461
|
-
.describe(
|
|
516
|
+
.describe('Field updates for a single work item. Required for: update. For a safe read-modify-write, prepend a test operation on "/rev" with value set to the numeric revision returned by the preceding read; Azure DevOps rejects the entire update if the current revision differs.'),
|
|
462
517
|
batchUpdates: z
|
|
463
518
|
.array(z.object({
|
|
464
519
|
op: z.enum(["Add", "Replace", "Remove"]).default("Add").describe("The operation to perform."),
|
|
@@ -521,6 +576,10 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
521
576
|
return { content: [{ type: "text", text: "id is required for update" }], isError: true };
|
|
522
577
|
if (!updates || updates.length === 0)
|
|
523
578
|
return { content: [{ type: "text", text: "updates is required for update" }], isError: true };
|
|
579
|
+
const updateWithoutValue = updates.find((update) => update.op !== "remove" && update.value === undefined);
|
|
580
|
+
if (updateWithoutValue) {
|
|
581
|
+
return { content: [{ type: "text", text: `value is required for ${updateWithoutValue.op}` }], isError: true };
|
|
582
|
+
}
|
|
524
583
|
const workItemApi = await connection.getWorkItemTrackingApi();
|
|
525
584
|
const apiUpdates = updates.map((update) => ({ ...update, op: update.op }));
|
|
526
585
|
const updatedWorkItem = await workItemApi.updateWorkItem(null, apiUpdates, id);
|
|
@@ -593,8 +652,6 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
593
652
|
const ops = [
|
|
594
653
|
{ op: "add", path: "/id", value: `-${x + 1}` },
|
|
595
654
|
{ op: "add", path: "/fields/System.Title", value: item.title },
|
|
596
|
-
{ op: "add", path: "/fields/System.Description", value: encodedDescription },
|
|
597
|
-
{ op: "add", path: "/fields/Microsoft.VSTS.TCM.ReproSteps", value: encodedDescription },
|
|
598
655
|
{
|
|
599
656
|
op: "add",
|
|
600
657
|
path: "/relations/-",
|
|
@@ -610,9 +667,19 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
610
667
|
if (item.iterationPath && item.iterationPath.trim().length > 0) {
|
|
611
668
|
ops.push({ op: "add", path: "/fields/System.IterationPath", value: item.iterationPath });
|
|
612
669
|
}
|
|
613
|
-
if
|
|
614
|
-
|
|
615
|
-
|
|
670
|
+
// check if the work item type is "Bug" to determine which field to use for the description
|
|
671
|
+
// ReproSteps is used for Bugs, while Description is used for other work item types
|
|
672
|
+
if (workItemType.toLowerCase() === "bug") {
|
|
673
|
+
ops.push({ op: "add", path: "/fields/Microsoft.VSTS.TCM.ReproSteps", value: encodedDescription });
|
|
674
|
+
if (item.format && item.format === "Markdown") {
|
|
675
|
+
ops.push({ op: "add", path: "/multilineFieldsFormat/Microsoft.VSTS.TCM.ReproSteps", value: item.format });
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
ops.push({ op: "add", path: "/fields/System.Description", value: encodedDescription });
|
|
680
|
+
if (item.format && item.format === "Markdown") {
|
|
681
|
+
ops.push({ op: "add", path: "/multilineFieldsFormat/System.Description", value: item.format });
|
|
682
|
+
}
|
|
616
683
|
}
|
|
617
684
|
return {
|
|
618
685
|
method: "PATCH",
|
|
@@ -641,9 +708,12 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
641
708
|
}
|
|
642
709
|
catch (error) {
|
|
643
710
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
711
|
+
const statusCode = typeof error === "object" && error !== null && "statusCode" in error && typeof error.statusCode === "number" ? error.statusCode : undefined;
|
|
712
|
+
const statusText = statusCode === 409 ? " Conflict" : statusCode === 412 ? " Precondition Failed" : "";
|
|
713
|
+
const updateStatus = statusCode !== undefined ? ` [HTTP ${statusCode}${statusText}]` : "";
|
|
644
714
|
const msgs = {
|
|
645
715
|
create: `Error creating work item: ${errorMessage}`,
|
|
646
|
-
update: `Error updating work item: ${errorMessage}`,
|
|
716
|
+
update: `Error updating work item${updateStatus}: ${errorMessage}`,
|
|
647
717
|
update_batch: `Error updating work items in batch: ${errorMessage}`,
|
|
648
718
|
add_child: `Error creating child work items: ${errorMessage}`,
|
|
649
719
|
};
|
|
@@ -676,6 +746,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
676
746
|
const orgUrl = connection.serverUrl;
|
|
677
747
|
const accessToken = await tokenProvider();
|
|
678
748
|
const formatParameter = (format ?? "Markdown") === "Markdown" ? 0 : 1;
|
|
749
|
+
const resolvedText = await resolveCommentMentions(text, format, tokenProvider, connectionProvider, userAgentProvider);
|
|
679
750
|
if (action === "add") {
|
|
680
751
|
const response = await fetch(`${orgUrl}/${encodeURIComponent(resolvedProject)}/_apis/wit/workItems/${workItemId}/comments?format=${formatParameter}&api-version=${markdownCommentsApiVersion}`, {
|
|
681
752
|
method: "POST",
|
|
@@ -684,7 +755,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
684
755
|
"Content-Type": "application/json",
|
|
685
756
|
"User-Agent": userAgentProvider(),
|
|
686
757
|
},
|
|
687
|
-
body: JSON.stringify({ text }),
|
|
758
|
+
body: JSON.stringify({ text: resolvedText }),
|
|
688
759
|
});
|
|
689
760
|
if (!response.ok) {
|
|
690
761
|
throw new Error(`Failed to add a work item comment: ${response.statusText}`);
|
|
@@ -701,7 +772,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
701
772
|
"Content-Type": "application/json",
|
|
702
773
|
"User-Agent": userAgentProvider(),
|
|
703
774
|
},
|
|
704
|
-
body: JSON.stringify({ text }),
|
|
775
|
+
body: JSON.stringify({ text: resolvedText }),
|
|
705
776
|
});
|
|
706
777
|
if (!response.ok) {
|
|
707
778
|
throw new Error(`Failed to update work item comment: ${response.statusText}`);
|
|
@@ -729,9 +800,10 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
729
800
|
updates: z
|
|
730
801
|
.array(z.object({
|
|
731
802
|
id: z.coerce.number().min(1).describe("The ID of the work item to update."),
|
|
732
|
-
linkToId: z.coerce.number().min(1).describe("The ID of the work item to link to."),
|
|
803
|
+
linkToId: z.coerce.number().min(1).optional().describe("The ID of the work item to link to. Required unless type is 'hyperlink'."),
|
|
804
|
+
url: z.string().optional().describe("The URL for a hyperlink. Required when type is 'hyperlink'."),
|
|
733
805
|
type: z
|
|
734
|
-
.enum(["parent", "child", "duplicate", "duplicate of", "related", "successor", "predecessor", "tested by", "tests", "affects", "affected by"])
|
|
806
|
+
.enum(["parent", "child", "duplicate", "duplicate of", "related", "successor", "predecessor", "tested by", "tests", "affects", "affected by", "hyperlink"])
|
|
735
807
|
.default("related")
|
|
736
808
|
.describe("Type of link. Defaults to 'related'."),
|
|
737
809
|
comment: z.string().optional().describe("Optional comment for the link."),
|
|
@@ -741,7 +813,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
741
813
|
// unlink
|
|
742
814
|
id: z.coerce.number().min(1).optional().describe("Work item ID to remove links from. Required for: unlink."),
|
|
743
815
|
type: z
|
|
744
|
-
.enum(["parent", "child", "duplicate", "duplicate of", "related", "successor", "predecessor", "tested by", "tests", "affects", "affected by", "artifact"])
|
|
816
|
+
.enum(["parent", "child", "duplicate", "duplicate of", "related", "successor", "predecessor", "tested by", "tests", "affects", "affected by", "artifact", "hyperlink"])
|
|
745
817
|
.optional()
|
|
746
818
|
.describe("Link type to remove. Required for: unlink."),
|
|
747
819
|
url: z.string().optional().describe("URL to match when removing a link. Used for: unlink. If not provided, all links of the specified type are removed."),
|
|
@@ -801,15 +873,23 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
801
873
|
headers: { "Content-Type": "application/json-patch+json" },
|
|
802
874
|
body: updates
|
|
803
875
|
.filter((update) => update.id === uid)
|
|
804
|
-
.map(({ linkToId, type: linkTypeName, comment: linkComment }) =>
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
876
|
+
.map(({ linkToId, url: linkUrl, type: linkTypeName, comment: linkComment }) => {
|
|
877
|
+
if (linkTypeName === "hyperlink" && !linkUrl) {
|
|
878
|
+
throw new Error("url is required for hyperlink links");
|
|
879
|
+
}
|
|
880
|
+
if (linkTypeName !== "hyperlink" && !linkToId) {
|
|
881
|
+
throw new Error("linkToId is required for work item links");
|
|
882
|
+
}
|
|
883
|
+
return {
|
|
884
|
+
op: "add",
|
|
885
|
+
path: "/relations/-",
|
|
886
|
+
value: {
|
|
887
|
+
rel: getLinkTypeFromName(linkTypeName),
|
|
888
|
+
url: linkTypeName === "hyperlink" ? linkUrl : `${orgUrl}/${resolvedProject}/_apis/wit/workItems/${linkToId}`,
|
|
889
|
+
attributes: { comment: linkComment || "" },
|
|
890
|
+
},
|
|
891
|
+
};
|
|
892
|
+
}),
|
|
813
893
|
}));
|
|
814
894
|
const response = await fetch(`${orgUrl}/_apis/wit/$batch?api-version=${batchApiVersion}`, {
|
|
815
895
|
method: "PATCH",
|
package/dist/tools.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
+
import { wrapExternalToolResponse } from "./shared/content-safety.js";
|
|
3
4
|
import { Domain } from "./shared/domains.js";
|
|
4
5
|
import { configureAdvSecTools } from "./tools/advanced-security.js";
|
|
5
6
|
import { configureMcpAppsTools } from "./tools/mcp-apps.js";
|
|
@@ -14,11 +15,13 @@ import { configureWorkItemTools } from "./tools/work-items.js";
|
|
|
14
15
|
function configureAllTools(server, tokenProvider, connectionProvider, userAgentProvider, enabledDomains) {
|
|
15
16
|
const configureIfDomainEnabled = (domain, configureFn) => {
|
|
16
17
|
if (enabledDomains.has(domain)) {
|
|
17
|
-
configureFn
|
|
18
|
+
configureToolsWithContentSafety(server, domain, configureFn);
|
|
18
19
|
}
|
|
19
20
|
};
|
|
20
21
|
configureIfDomainEnabled(Domain.CORE, () => configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider));
|
|
21
|
-
|
|
22
|
+
// This is a local health-check response and contains no Azure DevOps content.
|
|
23
|
+
if (enabledDomains.has(Domain.MCP_APPS))
|
|
24
|
+
configureMcpAppsTools(server);
|
|
22
25
|
configureIfDomainEnabled(Domain.WORK, () => configureWorkTools(server, tokenProvider, connectionProvider));
|
|
23
26
|
configureIfDomainEnabled(Domain.PIPELINES, () => configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider));
|
|
24
27
|
configureIfDomainEnabled(Domain.REPOSITORIES, () => configureRepoTools(server, tokenProvider, connectionProvider, userAgentProvider));
|
|
@@ -28,4 +31,34 @@ function configureAllTools(server, tokenProvider, connectionProvider, userAgentP
|
|
|
28
31
|
configureIfDomainEnabled(Domain.SEARCH, () => configureSearchTools(server, tokenProvider, connectionProvider, userAgentProvider));
|
|
29
32
|
configureIfDomainEnabled(Domain.ADVANCED_SECURITY, () => configureAdvSecTools(server, tokenProvider, connectionProvider));
|
|
30
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Centralizes the untrusted-content boundary for tool responses. Tool registration
|
|
36
|
+
* is synchronous, so the original method is restored before this function returns.
|
|
37
|
+
*/
|
|
38
|
+
function configureToolsWithContentSafety(server, domain, configureFn) {
|
|
39
|
+
const originalTool = server.tool;
|
|
40
|
+
const originalRegisterTool = server.registerTool;
|
|
41
|
+
const wrapRegistrationMethod = (registrationMethod) => new Proxy(registrationMethod, {
|
|
42
|
+
apply(target, thisArg, argumentsList) {
|
|
43
|
+
const callbackIndex = argumentsList.length - 1;
|
|
44
|
+
const callback = argumentsList[callbackIndex];
|
|
45
|
+
if (typeof callback === "function") {
|
|
46
|
+
argumentsList[callbackIndex] = async (...callbackArgs) => {
|
|
47
|
+
const response = (await Reflect.apply(callback, undefined, callbackArgs));
|
|
48
|
+
return wrapExternalToolResponse(response, `Azure DevOps ${domain}`);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return Reflect.apply(target, thisArg, argumentsList);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
server.tool = wrapRegistrationMethod(originalTool);
|
|
55
|
+
server.registerTool = wrapRegistrationMethod(originalRegisterTool);
|
|
56
|
+
try {
|
|
57
|
+
configureFn();
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
server.tool = originalTool;
|
|
61
|
+
server.registerTool = originalRegisterTool;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
31
64
|
export { configureAllTools };
|
package/dist/utils.js
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
export const apiVersion = "7.2-preview.1";
|
|
4
4
|
export const batchApiVersion = "5.0";
|
|
5
5
|
export const markdownCommentsApiVersion = "7.2-preview.4";
|
|
6
|
+
/**
|
|
7
|
+
* Returns the user-supplied CLI arguments.
|
|
8
|
+
*
|
|
9
|
+
* The server is always started script-style — `[runtime, scriptPath, ...args]` — on Node
|
|
10
|
+
* and on Electron hosts alike, so the first two entries are always dropped.
|
|
11
|
+
*
|
|
12
|
+
* Do not replace this with yargs' `hideBin`: it drops a single entry whenever
|
|
13
|
+
* `process.versions.electron` is set and `process.defaultApp` is not, so on an Electron
|
|
14
|
+
* host the script path survives and is parsed as the organization name.
|
|
15
|
+
*/
|
|
16
|
+
export function getCliArgs(argv = process.argv) {
|
|
17
|
+
return argv.slice(2);
|
|
18
|
+
}
|
|
6
19
|
export function createEnumMapping(enumObject) {
|
|
7
20
|
const mapping = {};
|
|
8
21
|
for (const [key, value] of Object.entries(enumObject)) {
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const packageVersion = "2.
|
|
1
|
+
export const packageVersion = "2.10.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@azure-devops/mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"mcpName": "microsoft.com/azure-devops",
|
|
5
5
|
"description": "MCP server for interacting with Azure DevOps",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,12 +37,15 @@
|
|
|
37
37
|
"test": "jest"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@azure/identity": "^4.
|
|
41
|
-
"@azure/
|
|
40
|
+
"@azure/identity": "^4.13.0",
|
|
41
|
+
"@azure/logger": "^1.3.0",
|
|
42
|
+
"@azure/msal-node": "^5.5.0",
|
|
43
|
+
"@azure/msal-node-extensions": "^5.3.5",
|
|
42
44
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
43
45
|
"azure-devops-extension-api": "^5.272.3",
|
|
44
46
|
"azure-devops-extension-sdk": "^4.0.2",
|
|
45
47
|
"azure-devops-node-api": "^15.1.2",
|
|
48
|
+
"open": "^10.2.0",
|
|
46
49
|
"winston": "^3.18.3",
|
|
47
50
|
"yargs": "^18.0.0",
|
|
48
51
|
"zod": "^3.25.63",
|
|
@@ -69,5 +72,9 @@
|
|
|
69
72
|
"**/*.(js|ts|jsx|tsx|json|css|md)": [
|
|
70
73
|
"npm run format"
|
|
71
74
|
]
|
|
75
|
+
},
|
|
76
|
+
"allowScripts": {
|
|
77
|
+
"keytar@7.9.0": true,
|
|
78
|
+
"@azure/msal-node-extensions@5.3.5": true
|
|
72
79
|
}
|
|
73
80
|
}
|