@azure-devops/mcp 2.9.0 → 2.10.0-nightly.20260910
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/logger.js +0 -0
- package/dist/org-tenants.js +0 -0
- package/dist/prompts.js +0 -0
- 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 +115 -38
- package/dist/tools.js +35 -2
- package/dist/useragent.js +0 -0
- package/dist/utils.js +13 -0
- package/dist/version.js +1 -1
- package/package.json +11 -4
- package/dist/shared/command.js +0 -34
package/dist/tools/pipelines.js
CHANGED
|
@@ -8,111 +8,102 @@ import { ConfigurationType, RepositoryType } from "azure-devops-node-api/interfa
|
|
|
8
8
|
import { mkdirSync, createWriteStream } from "fs";
|
|
9
9
|
import { createExternalContentResponse } from "../shared/content-safety.js";
|
|
10
10
|
import { join, posix, resolve, win32 } from "path";
|
|
11
|
-
import { dispatchAction, errorResult } from "../shared/command.js";
|
|
12
11
|
import { pipelinesWriteShape } from "./pipelines.dto.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
* The registry is the lookup table that couples each action to its command.
|
|
105
|
-
* Adding a new write action means registering one entry here — the dispatcher
|
|
106
|
-
* (`dispatchAction`) never changes.
|
|
107
|
-
*/
|
|
108
|
-
const pipelinesWriteCommands = {
|
|
109
|
-
run_pipeline: runPipelineCommand,
|
|
110
|
-
create_pipeline: createPipelineCommand,
|
|
111
|
-
update_build_stage: updateBuildStageCommand,
|
|
112
|
-
};
|
|
12
|
+
const errorResult = (text) => ({ content: [{ type: "text", text }], isError: true });
|
|
13
|
+
async function runPipeline(args, connectionProvider) {
|
|
14
|
+
if (!args.pipelineId)
|
|
15
|
+
return errorResult("pipelineId is required for run_pipeline");
|
|
16
|
+
if (!args.previewRun && args.yamlOverride)
|
|
17
|
+
throw new Error("Parameter 'yamlOverride' can only be specified together with parameter 'previewRun'.");
|
|
18
|
+
const connection = await connectionProvider();
|
|
19
|
+
const pipelinesApi = await connection.getPipelinesApi();
|
|
20
|
+
const runRequest = {
|
|
21
|
+
previewRun: args.previewRun,
|
|
22
|
+
resources: { ...args.resources },
|
|
23
|
+
stagesToSkip: args.stagesToSkip,
|
|
24
|
+
templateParameters: args.templateParameters,
|
|
25
|
+
variables: args.variables,
|
|
26
|
+
yamlOverride: args.yamlOverride,
|
|
27
|
+
};
|
|
28
|
+
const pipelineRun = await pipelinesApi.runPipeline(runRequest, args.project, args.pipelineId, args.pipelineVersion);
|
|
29
|
+
if (pipelineRun.id === undefined)
|
|
30
|
+
throw new Error("Failed to get build ID from pipeline run");
|
|
31
|
+
return { content: [{ type: "text", text: JSON.stringify(pipelineRun, null, 2) }] };
|
|
32
|
+
}
|
|
33
|
+
async function createPipeline(args, connectionProvider) {
|
|
34
|
+
if (!args.name)
|
|
35
|
+
return errorResult("name is required for create_pipeline");
|
|
36
|
+
if (!args.yamlPath)
|
|
37
|
+
return errorResult("yamlPath is required for create_pipeline");
|
|
38
|
+
if (!args.repositoryType)
|
|
39
|
+
return errorResult("repositoryType is required for create_pipeline");
|
|
40
|
+
if (!args.repositoryName)
|
|
41
|
+
return errorResult("repositoryName is required for create_pipeline");
|
|
42
|
+
const connection = await connectionProvider();
|
|
43
|
+
const pipelinesApi = await connection.getPipelinesApi();
|
|
44
|
+
const repositoryTypeEnumValue = safeEnumConvert(RepositoryType, args.repositoryType);
|
|
45
|
+
const repositoryPayload = { type: args.repositoryType };
|
|
46
|
+
if (repositoryTypeEnumValue === RepositoryType.AzureReposGit) {
|
|
47
|
+
repositoryPayload.id = args.repositoryId;
|
|
48
|
+
repositoryPayload.name = args.repositoryName;
|
|
49
|
+
}
|
|
50
|
+
else if (repositoryTypeEnumValue === RepositoryType.GitHub) {
|
|
51
|
+
if (!args.repositoryConnectionId)
|
|
52
|
+
throw new Error("Parameter 'repositoryConnectionId' is required for GitHub repositories.");
|
|
53
|
+
repositoryPayload.connection = { id: args.repositoryConnectionId };
|
|
54
|
+
repositoryPayload.fullname = args.repositoryName;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
throw new Error("Unsupported repository type");
|
|
58
|
+
}
|
|
59
|
+
const yamlConfigurationType = getEnumKeys(ConfigurationType).find((k) => ConfigurationType[k] === ConfigurationType.Yaml);
|
|
60
|
+
const createParams = {
|
|
61
|
+
name: args.name,
|
|
62
|
+
folder: args.folder || "\\",
|
|
63
|
+
configuration: { type: yamlConfigurationType, path: args.yamlPath, repository: repositoryPayload, variables: undefined },
|
|
64
|
+
};
|
|
65
|
+
const newPipeline = await pipelinesApi.createPipeline(createParams, args.project);
|
|
66
|
+
return { content: [{ type: "text", text: JSON.stringify(newPipeline, null, 2) }] };
|
|
67
|
+
}
|
|
68
|
+
async function renamePipeline(args, connectionProvider) {
|
|
69
|
+
if (!args.pipelineId)
|
|
70
|
+
return errorResult("pipelineId is required for rename_pipeline");
|
|
71
|
+
if (!args.name)
|
|
72
|
+
return errorResult("name is required for rename_pipeline");
|
|
73
|
+
const connection = await connectionProvider();
|
|
74
|
+
const buildApi = await connection.getBuildApi();
|
|
75
|
+
const definition = await buildApi.getDefinition(args.project, args.pipelineId);
|
|
76
|
+
const updatedDefinition = await buildApi.updateDefinition({ ...definition, name: args.name }, args.project, args.pipelineId);
|
|
77
|
+
return { content: [{ type: "text", text: JSON.stringify(updatedDefinition, null, 2) }] };
|
|
78
|
+
}
|
|
79
|
+
async function updateBuildStage(args, connectionProvider, tokenProvider, userAgentProvider) {
|
|
80
|
+
if (!args.buildId)
|
|
81
|
+
return errorResult("buildId is required for update_build_stage");
|
|
82
|
+
if (!args.stageName)
|
|
83
|
+
return errorResult("stageName is required for update_build_stage");
|
|
84
|
+
if (!args.status)
|
|
85
|
+
return errorResult("status is required for update_build_stage");
|
|
86
|
+
const connection = await connectionProvider();
|
|
87
|
+
const orgUrl = connection.serverUrl;
|
|
88
|
+
const endpoint = `${orgUrl}/${encodeURIComponent(args.project)}/_apis/build/builds/${args.buildId}/stages/${encodeURIComponent(args.stageName)}?api-version=${apiVersion}`;
|
|
89
|
+
const token = await tokenProvider();
|
|
90
|
+
const body = { forceRetryAllJobs: args.forceRetryAllJobs, state: safeEnumConvert(StageUpdateType, args.status) };
|
|
91
|
+
const response = await fetch(endpoint, {
|
|
92
|
+
method: "PATCH",
|
|
93
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}`, "User-Agent": userAgentProvider() },
|
|
94
|
+
body: JSON.stringify(body),
|
|
95
|
+
});
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
const errorText = await response.text();
|
|
98
|
+
throw new Error(`Failed to update build stage: ${response.status} ${errorText}`);
|
|
99
|
+
}
|
|
100
|
+
const updatedBuild = await response.text();
|
|
101
|
+
return { content: [{ type: "text", text: JSON.stringify(updatedBuild, null, 2) }] };
|
|
102
|
+
}
|
|
113
103
|
const pipelinesWriteErrorPrefixes = {
|
|
114
104
|
run_pipeline: "Error running pipeline: ",
|
|
115
105
|
create_pipeline: "Error creating pipeline: ",
|
|
106
|
+
rename_pipeline: "Error renaming pipeline: ",
|
|
116
107
|
update_build_stage: "Error updating build stage: ",
|
|
117
108
|
};
|
|
118
109
|
const PIPELINE_TOOLS = {
|
|
@@ -385,8 +376,26 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
|
|
|
385
376
|
});
|
|
386
377
|
// ─── pipelines_write ────────────────────────────────────────────────────────
|
|
387
378
|
server.tool(PIPELINE_TOOLS.pipelines_write, "Write operations for pipelines and builds. Use the action parameter to specify the operation.", pipelinesWriteShape, async (args) => {
|
|
388
|
-
|
|
389
|
-
|
|
379
|
+
try {
|
|
380
|
+
switch (args.action) {
|
|
381
|
+
case "run_pipeline":
|
|
382
|
+
return await runPipeline(args, connectionProvider);
|
|
383
|
+
case "create_pipeline":
|
|
384
|
+
return await createPipeline(args, connectionProvider);
|
|
385
|
+
case "rename_pipeline":
|
|
386
|
+
return await renamePipeline(args, connectionProvider);
|
|
387
|
+
case "update_build_stage":
|
|
388
|
+
return await updateBuildStage(args, connectionProvider, tokenProvider, userAgentProvider);
|
|
389
|
+
default: {
|
|
390
|
+
const unsupportedAction = args.action;
|
|
391
|
+
return errorResult(`Unknown action: ${unsupportedAction}. Supported actions: ${Object.keys(pipelinesWriteErrorPrefixes).sort().join(", ")}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
397
|
+
return errorResult(`${pipelinesWriteErrorPrefixes[args.action]}${message}`);
|
|
398
|
+
}
|
|
390
399
|
});
|
|
391
400
|
}
|
|
392
|
-
export { PIPELINE_TOOLS, configurePipelineTools,
|
|
401
|
+
export { PIPELINE_TOOLS, configurePipelineTools, runPipeline, createPipeline, renamePipeline, updateBuildStage };
|
|
@@ -5,6 +5,7 @@ import { z } from "zod";
|
|
|
5
5
|
import { getCurrentUserDetails, getUserIdFromEmail } from "./auth.js";
|
|
6
6
|
import { extractAdoStreamError, getEnumKeys, streamToString, apiVersion } from "../utils.js";
|
|
7
7
|
import { orgName } from "../index.js";
|
|
8
|
+
import { createExternalContentResponse } from "../shared/content-safety.js";
|
|
8
9
|
const REPO_TOOLS = {
|
|
9
10
|
repo_repository: "repo_repository",
|
|
10
11
|
repo_pull_request: "repo_pull_request",
|
|
@@ -32,6 +33,7 @@ function trimPullRequestThread(thread) {
|
|
|
32
33
|
status: thread.status,
|
|
33
34
|
comments: trimComments(thread.comments),
|
|
34
35
|
threadContext: thread.threadContext,
|
|
36
|
+
pullRequestThreadContext: thread.pullRequestThreadContext,
|
|
35
37
|
};
|
|
36
38
|
}
|
|
37
39
|
function trimComments(comments) {
|
|
@@ -222,6 +224,8 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
222
224
|
changedFilesSummary: {
|
|
223
225
|
changeEntries: changes?.changeEntries ?? [],
|
|
224
226
|
fileCount: changes?.changeEntries?.length ?? 0,
|
|
227
|
+
firstComparingIteration: Math.max(0, latestIteration.id - 1),
|
|
228
|
+
secondComparingIteration: latestIteration.id,
|
|
225
229
|
nextSkip: changes?.nextSkip,
|
|
226
230
|
nextTop: changes?.nextTop,
|
|
227
231
|
},
|
|
@@ -240,7 +244,7 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
240
244
|
enhancedResponse = { ...enhancedResponse, changedFilesSummary: {} };
|
|
241
245
|
}
|
|
242
246
|
}
|
|
243
|
-
return
|
|
247
|
+
return createExternalContentResponse(enhancedResponse, "pull request");
|
|
244
248
|
}
|
|
245
249
|
if (action === "list") {
|
|
246
250
|
if (!repositoryId && !project) {
|
|
@@ -454,7 +458,7 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
454
458
|
if (streamError) {
|
|
455
459
|
return { content: [{ type: "text", text: `Error getting file content for '${path}': ${streamError}` }], isError: true };
|
|
456
460
|
}
|
|
457
|
-
return
|
|
461
|
+
return createExternalContentResponse(content, "repository file");
|
|
458
462
|
}
|
|
459
463
|
if (action === "list_directory") {
|
|
460
464
|
const versionDescriptor = buildVersionDescriptor(version, versionType === "Commit" ? "Branch" : versionType);
|
|
@@ -575,11 +579,12 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
575
579
|
mergeCommitMessage: z.string().optional().describe("Commit message for autocomplete. Used for update."),
|
|
576
580
|
deleteSourceBranch: z.boolean().optional().default(false).describe("Delete source branch on autocomplete. Used for update."),
|
|
577
581
|
transitionWorkItems: z.boolean().optional().default(true).describe("Transition work items on autocomplete. Used for update."),
|
|
578
|
-
|
|
582
|
+
bypassPolicy: z.boolean().optional().default(false).describe("Explicitly bypass branch policies on autocomplete. Used for update and requires bypassReason when true."),
|
|
583
|
+
bypassReason: z.string().optional().describe("Reason for bypassing branch policies. Used for update only when bypassPolicy is true."),
|
|
579
584
|
reviewerIds: z.array(z.string()).optional().describe("List of reviewer IDs. Required for update_reviewers."),
|
|
580
585
|
reviewerAction: z.enum(["add", "remove"]).optional().describe("Whether to add or remove reviewers. Required for update_reviewers."),
|
|
581
586
|
vote: z.enum(["Approved", "ApprovedWithSuggestions", "NoVote", "WaitingForAuthor", "Rejected"]).optional().describe("The vote to cast. Required for vote."),
|
|
582
|
-
}, async ({ action, repositoryId, pullRequestId, project, sourceRefName, targetRefName, title, description, isDraft, workItems, forkSourceRepositoryId, labels, status, autoComplete, mergeStrategy, mergeCommitMessage, deleteSourceBranch, transitionWorkItems, bypassReason, reviewerIds, reviewerAction, vote, }) => {
|
|
587
|
+
}, async ({ action, repositoryId, pullRequestId, project, sourceRefName, targetRefName, title, description, isDraft, workItems, forkSourceRepositoryId, labels, status, autoComplete, mergeStrategy, mergeCommitMessage, deleteSourceBranch, transitionWorkItems, bypassPolicy, bypassReason, reviewerIds, reviewerAction, vote, }) => {
|
|
583
588
|
try {
|
|
584
589
|
const connection = await connectionProvider();
|
|
585
590
|
const gitApi = await connection.getGitApi();
|
|
@@ -629,18 +634,21 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
629
634
|
}
|
|
630
635
|
if (autoComplete !== undefined) {
|
|
631
636
|
if (autoComplete) {
|
|
637
|
+
if (bypassPolicy && !bypassReason) {
|
|
638
|
+
return { content: [{ type: "text", text: "bypassReason is required when bypassPolicy is true" }], isError: true };
|
|
639
|
+
}
|
|
632
640
|
const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
|
|
633
641
|
updateRequest.autoCompleteSetBy = { id: data.authenticatedUser.id };
|
|
634
642
|
const completionOptions = {
|
|
635
643
|
deleteSourceBranch: deleteSourceBranch || false,
|
|
636
644
|
transitionWorkItems: transitionWorkItems !== false,
|
|
637
|
-
bypassPolicy:
|
|
645
|
+
bypassPolicy: bypassPolicy === true,
|
|
638
646
|
};
|
|
639
647
|
if (mergeStrategy)
|
|
640
648
|
completionOptions.mergeStrategy = GitPullRequestMergeStrategy[mergeStrategy];
|
|
641
649
|
if (mergeCommitMessage)
|
|
642
650
|
completionOptions.mergeCommitMessage = mergeCommitMessage;
|
|
643
|
-
if (bypassReason)
|
|
651
|
+
if (bypassPolicy && bypassReason)
|
|
644
652
|
completionOptions.bypassReason = bypassReason;
|
|
645
653
|
updateRequest.completionOptions = completionOptions;
|
|
646
654
|
}
|
|
@@ -747,25 +755,29 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
747
755
|
// --- repo_pull_request_thread_write ----------------------------------------
|
|
748
756
|
server.tool(REPO_TOOLS.repo_pull_request_thread_write, "Write operations for pull request comment threads. Use the action parameter to specify the operation.", {
|
|
749
757
|
action: z
|
|
750
|
-
.enum(["create", "reply", "update_status"])
|
|
751
|
-
.describe("The action to perform. Options: create (create a new comment thread on a pull request), reply (reply to a comment in a thread), update_status (update the status of a comment thread)."),
|
|
758
|
+
.enum(["create", "reply", "update", "update_status"])
|
|
759
|
+
.describe("The action to perform. Options: create (create a new comment thread on a pull request), reply (reply to a comment in a thread), update (update an existing comment), update_status (update the status of a comment thread)."),
|
|
752
760
|
repositoryId: z.string().describe("The ID or name of the repository. When using a name instead of a GUID, project must also be provided."),
|
|
753
761
|
pullRequestId: z.coerce.number().min(1).describe("The ID of the pull request."),
|
|
754
762
|
project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a name instead of a GUID."),
|
|
755
|
-
threadId: z.coerce.number().min(1).optional().describe("The ID of the thread. Required for reply and update_status."),
|
|
756
|
-
|
|
763
|
+
threadId: z.coerce.number().min(1).optional().describe("The ID of the thread. Required for reply, update, and update_status."),
|
|
764
|
+
commentId: z.coerce.number().min(1).optional().describe("The ID of the comment to update. Required for update."),
|
|
765
|
+
content: z.string().optional().describe("The content of the comment. Required for create, reply, and update."),
|
|
757
766
|
status: z
|
|
758
767
|
.enum(getEnumKeys(CommentThreadStatus))
|
|
759
768
|
.optional()
|
|
760
769
|
.default(CommentThreadStatus[CommentThreadStatus.Active])
|
|
761
770
|
.describe("The thread status. Used for create (defaults to 'Active') and required for update_status."),
|
|
762
771
|
filePath: z.string().optional().describe("The file path for the comment thread. Used for create."),
|
|
763
|
-
fullResponse: z.boolean().optional().default(false).describe("Return full JSON response. Used for reply."),
|
|
772
|
+
fullResponse: z.boolean().optional().default(false).describe("Return full JSON response. Used for reply and update."),
|
|
764
773
|
rightFileStartLine: z.coerce.number().min(1).optional().describe("Start line in the right file. Used for create."),
|
|
765
774
|
rightFileStartOffset: z.number().optional().describe("Start character offset in the right file. Used for create."),
|
|
766
775
|
rightFileEndLine: z.number().optional().describe("End line in the right file. Used for create."),
|
|
767
776
|
rightFileEndOffset: z.number().optional().describe("End character offset in the right file. Used for create."),
|
|
768
|
-
|
|
777
|
+
changeTrackingId: z.coerce.number().int().min(1).optional().describe("The file change tracking ID from the pull request iteration changes. Used for create."),
|
|
778
|
+
firstComparingIteration: z.coerce.number().int().min(0).optional().describe("The iteration on the left side of the diff. Used for create."),
|
|
779
|
+
secondComparingIteration: z.coerce.number().int().min(1).optional().describe("The iteration on the right side of the diff. Used for create."),
|
|
780
|
+
}, async ({ action, repositoryId, pullRequestId, project, threadId, commentId, content, status, filePath, fullResponse, rightFileStartLine, rightFileStartOffset, rightFileEndLine, rightFileEndOffset, changeTrackingId, firstComparingIteration, secondComparingIteration, }) => {
|
|
769
781
|
try {
|
|
770
782
|
const connection = await connectionProvider();
|
|
771
783
|
const gitApi = await connection.getGitApi();
|
|
@@ -815,7 +827,17 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
815
827
|
return { content: [{ type: "text", text: "rightFileEndOffset must be greater than or equal to rightFileStartOffset when both are on the same line." }], isError: true };
|
|
816
828
|
}
|
|
817
829
|
}
|
|
818
|
-
const
|
|
830
|
+
const iterationContextValues = [changeTrackingId, firstComparingIteration, secondComparingIteration];
|
|
831
|
+
if (iterationContextValues.some((value) => value !== undefined) && iterationContextValues.some((value) => value === undefined)) {
|
|
832
|
+
return {
|
|
833
|
+
content: [{ type: "text", text: "changeTrackingId, firstComparingIteration, and secondComparingIteration must all be specified together." }],
|
|
834
|
+
isError: true,
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
const pullRequestThreadContext = changeTrackingId !== undefined && firstComparingIteration !== undefined && secondComparingIteration !== undefined
|
|
838
|
+
? { changeTrackingId, iterationContext: { firstComparingIteration, secondComparingIteration } }
|
|
839
|
+
: undefined;
|
|
840
|
+
const thread = await gitApi.createThread({ comments: [{ content, commentType: 1 }], threadContext, pullRequestThreadContext, status: CommentThreadStatus[status] }, repositoryId, pullRequestId, project);
|
|
819
841
|
return { content: [{ type: "text", text: JSON.stringify(trimPullRequestThread(thread), null, 2) }] };
|
|
820
842
|
}
|
|
821
843
|
if (action === "reply") {
|
|
@@ -831,6 +853,21 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
831
853
|
return { content: [{ type: "text", text: JSON.stringify(comment, null, 2) }] };
|
|
832
854
|
return { content: [{ type: "text", text: `Comment successfully added to thread ${threadId}.` }] };
|
|
833
855
|
}
|
|
856
|
+
if (action === "update") {
|
|
857
|
+
if (!threadId)
|
|
858
|
+
return { content: [{ type: "text", text: "threadId is required for update" }], isError: true };
|
|
859
|
+
if (!commentId)
|
|
860
|
+
return { content: [{ type: "text", text: "commentId is required for update" }], isError: true };
|
|
861
|
+
if (!content)
|
|
862
|
+
return { content: [{ type: "text", text: "content is required for update" }], isError: true };
|
|
863
|
+
const comment = await gitApi.updateComment({ content }, repositoryId, pullRequestId, threadId, commentId, project);
|
|
864
|
+
if (!comment) {
|
|
865
|
+
return { content: [{ type: "text", text: `Error: Failed to update comment ${commentId} in thread ${threadId}. The comment was not updated successfully.` }], isError: true };
|
|
866
|
+
}
|
|
867
|
+
if (fullResponse)
|
|
868
|
+
return { content: [{ type: "text", text: JSON.stringify(comment, null, 2) }] };
|
|
869
|
+
return { content: [{ type: "text", text: `Comment ${commentId} successfully updated in thread ${threadId}.` }] };
|
|
870
|
+
}
|
|
834
871
|
if (action === "update_status") {
|
|
835
872
|
if (!threadId)
|
|
836
873
|
return { content: [{ type: "text", text: "threadId is required for update_status" }], isError: true };
|
package/dist/tools/search.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
+
import { VersionControlRecursionType } from "azure-devops-node-api/interfaces/GitInterfaces.js";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { apiVersion } from "../utils.js";
|
|
5
6
|
import { orgName } from "../index.js";
|
|
6
|
-
import {
|
|
7
|
+
import { createExternalContentResponse } from "../shared/content-safety.js";
|
|
7
8
|
const SEARCH_TOOLS = {
|
|
8
9
|
search_code: "search_code",
|
|
9
10
|
search_wiki: "search_wiki",
|
|
@@ -60,9 +61,7 @@ function configureSearchTools(server, tokenProvider, connectionProvider, userAge
|
|
|
60
61
|
const resultJson = JSON.parse(resultText);
|
|
61
62
|
const gitApi = await connection.getGitApi();
|
|
62
63
|
const combinedResults = await fetchCombinedResults(resultJson.results ?? [], gitApi);
|
|
63
|
-
return
|
|
64
|
-
content: [{ type: "text", text: resultText + JSON.stringify(combinedResults) }],
|
|
65
|
-
};
|
|
64
|
+
return createExternalContentResponse(resultText + JSON.stringify(combinedResults), "code search results");
|
|
66
65
|
});
|
|
67
66
|
server.tool(SEARCH_TOOLS.search_wiki, "Search Azure DevOps Wiki for a given search text", {
|
|
68
67
|
searchText: z.string().describe("Keywords to search for wiki pages"),
|
|
@@ -101,9 +100,7 @@ function configureSearchTools(server, tokenProvider, connectionProvider, userAge
|
|
|
101
100
|
throw new Error(`Azure DevOps Wiki Search API error: ${response.status} ${response.statusText}`);
|
|
102
101
|
}
|
|
103
102
|
const result = await response.text();
|
|
104
|
-
return
|
|
105
|
-
content: [{ type: "text", text: result }],
|
|
106
|
-
};
|
|
103
|
+
return createExternalContentResponse(result, "wiki search results");
|
|
107
104
|
});
|
|
108
105
|
server.tool(SEARCH_TOOLS.search_workitem, "Get Azure DevOps Work Item search results for a given search text", {
|
|
109
106
|
searchText: z.string().describe("Search text to find in work items"),
|
|
@@ -151,9 +148,7 @@ function configureSearchTools(server, tokenProvider, connectionProvider, userAge
|
|
|
151
148
|
throw new Error(`Azure DevOps Work Item Search API error: ${response.status} ${response.statusText}`);
|
|
152
149
|
}
|
|
153
150
|
const result = await response.text();
|
|
154
|
-
return
|
|
155
|
-
content: [{ type: "text", text: result }],
|
|
156
|
-
};
|
|
151
|
+
return createExternalContentResponse(result, "work item search results");
|
|
157
152
|
});
|
|
158
153
|
}
|
|
159
154
|
async function fetchCombinedResults(topSearchResults, gitApi) {
|
|
@@ -170,7 +165,7 @@ async function fetchCombinedResults(topSearchResults, gitApi) {
|
|
|
170
165
|
});
|
|
171
166
|
continue;
|
|
172
167
|
}
|
|
173
|
-
const versionDescriptor =
|
|
168
|
+
const versionDescriptor = { version: changeId, versionType: 2, versionOptions: 0 };
|
|
174
169
|
const item = await gitApi.getItem(repositoryId, filePath, projectId, undefined, VersionControlRecursionType.None, true, // includeContentMetadata
|
|
175
170
|
false, // latestProcessedChange
|
|
176
171
|
false, // download
|