@azure-devops/mcp 2.2.2 → 2.3.0-nightly.20251203
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 +1 -105
- package/dist/auth.js +27 -1
- package/dist/index.js +12 -1
- package/dist/logger.js +34 -0
- package/dist/org-tenants.js +4 -3
- package/dist/prompts.js +0 -0
- package/dist/shared/domains.js +2 -1
- package/dist/tools/pipelines.js +52 -4
- package/dist/tools/repositories.js +621 -360
- package/dist/tools/test-plans.js +256 -123
- package/dist/tools/work-items.js +398 -210
- package/dist/tools/work.js +22 -3
- 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 +16 -7
- 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
|
@@ -20,6 +20,7 @@ const REPO_TOOLS = {
|
|
|
20
20
|
update_pull_request_reviewers: "repo_update_pull_request_reviewers",
|
|
21
21
|
reply_to_comment: "repo_reply_to_comment",
|
|
22
22
|
create_pull_request_thread: "repo_create_pull_request_thread",
|
|
23
|
+
update_pull_request_thread: "repo_update_pull_request_thread",
|
|
23
24
|
resolve_comment: "repo_resolve_comment",
|
|
24
25
|
search_commits: "repo_search_commits",
|
|
25
26
|
list_pull_requests_by_commits: "repo_list_pull_requests_by_commits",
|
|
@@ -94,6 +95,7 @@ function trimPullRequest(pr, includeDescription = false) {
|
|
|
94
95
|
uniqueName: pr.createdBy?.uniqueName,
|
|
95
96
|
},
|
|
96
97
|
creationDate: pr.creationDate,
|
|
98
|
+
closedDate: pr.closedDate,
|
|
97
99
|
title: pr.title,
|
|
98
100
|
...(includeDescription ? { description: pr.description ?? "" } : {}),
|
|
99
101
|
isDraft: pr.isDraft,
|
|
@@ -111,30 +113,42 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
111
113
|
isDraft: z.boolean().optional().default(false).describe("Indicates whether the pull request is a draft. Defaults to false."),
|
|
112
114
|
workItems: z.string().optional().describe("Work item IDs to associate with the pull request, space-separated."),
|
|
113
115
|
forkSourceRepositoryId: z.string().optional().describe("The ID of the fork repository that the pull request originates from. Optional, used when creating a pull request from a fork."),
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
? {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
116
|
+
labels: z.array(z.string()).optional().describe("Array of label names to add to the pull request after creation."),
|
|
117
|
+
}, async ({ repositoryId, sourceRefName, targetRefName, title, description, isDraft, workItems, forkSourceRepositoryId, labels }) => {
|
|
118
|
+
try {
|
|
119
|
+
const connection = await connectionProvider();
|
|
120
|
+
const gitApi = await connection.getGitApi();
|
|
121
|
+
const workItemRefs = workItems ? workItems.split(" ").map((id) => ({ id: id.trim() })) : [];
|
|
122
|
+
const forkSource = forkSourceRepositoryId
|
|
123
|
+
? {
|
|
124
|
+
repository: {
|
|
125
|
+
id: forkSourceRepositoryId,
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
: undefined;
|
|
129
|
+
const labelDefinitions = labels ? labels.map((label) => ({ name: label })) : undefined;
|
|
130
|
+
const pullRequest = await gitApi.createPullRequest({
|
|
131
|
+
sourceRefName,
|
|
132
|
+
targetRefName,
|
|
133
|
+
title,
|
|
134
|
+
description,
|
|
135
|
+
isDraft,
|
|
136
|
+
workItemRefs: workItemRefs,
|
|
137
|
+
forkSource,
|
|
138
|
+
labels: labelDefinitions,
|
|
139
|
+
}, repositoryId);
|
|
140
|
+
const trimmedPullRequest = trimPullRequest(pullRequest, true);
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: "text", text: JSON.stringify(trimmedPullRequest, null, 2) }],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
147
|
+
return {
|
|
148
|
+
content: [{ type: "text", text: `Error creating pull request: ${errorMessage}` }],
|
|
149
|
+
isError: true,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
138
152
|
});
|
|
139
153
|
server.tool(REPO_TOOLS.create_branch, "Create a new branch in the repository.", {
|
|
140
154
|
repositoryId: z.string().describe("The ID of the repository where the branch will be created."),
|
|
@@ -142,67 +156,80 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
142
156
|
sourceBranchName: z.string().optional().default("main").describe("The name of the source branch to create the new branch from. Defaults to 'main'."),
|
|
143
157
|
sourceCommitId: z.string().optional().describe("The commit ID to create the branch from. If not provided, uses the latest commit of the source branch."),
|
|
144
158
|
}, async ({ repositoryId, branchName, sourceBranchName, sourceCommitId }) => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
159
|
+
try {
|
|
160
|
+
const connection = await connectionProvider();
|
|
161
|
+
const gitApi = await connection.getGitApi();
|
|
162
|
+
let commitId = sourceCommitId;
|
|
163
|
+
// If no commit ID is provided, get the latest commit from the source branch
|
|
164
|
+
if (!commitId) {
|
|
165
|
+
const sourceRefName = `refs/heads/${sourceBranchName}`;
|
|
166
|
+
try {
|
|
167
|
+
const sourceBranch = await gitApi.getRefs(repositoryId, undefined, "heads/", false, false, undefined, false, undefined, sourceBranchName);
|
|
168
|
+
const branch = sourceBranch.find((b) => b.name === sourceRefName);
|
|
169
|
+
if (!branch || !branch.objectId) {
|
|
170
|
+
return {
|
|
171
|
+
content: [
|
|
172
|
+
{
|
|
173
|
+
type: "text",
|
|
174
|
+
text: `Error: Source branch '${sourceBranchName}' not found in repository ${repositoryId}`,
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
isError: true,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
commitId = branch.objectId;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
return {
|
|
184
|
+
content: [
|
|
185
|
+
{
|
|
186
|
+
type: "text",
|
|
187
|
+
text: `Error retrieving source branch '${sourceBranchName}': ${error instanceof Error ? error.message : String(error)}`,
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
isError: true,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Create the new branch using updateRefs
|
|
195
|
+
const newRefName = `refs/heads/${branchName}`;
|
|
196
|
+
const refUpdate = {
|
|
197
|
+
name: newRefName,
|
|
198
|
+
newObjectId: commitId,
|
|
199
|
+
oldObjectId: "0000000000000000000000000000000000000000", // All zeros indicates creating a new ref
|
|
200
|
+
};
|
|
151
201
|
try {
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
if (
|
|
202
|
+
const result = await gitApi.updateRefs([refUpdate], repositoryId);
|
|
203
|
+
// Check if the branch creation was successful
|
|
204
|
+
if (result && result.length > 0 && result[0].success) {
|
|
155
205
|
return {
|
|
156
206
|
content: [
|
|
157
207
|
{
|
|
158
208
|
type: "text",
|
|
159
|
-
text: `
|
|
209
|
+
text: `Branch '${branchName}' created successfully from '${sourceBranchName}' (${commitId})`,
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
const errorMessage = result && result.length > 0 && result[0].customMessage ? result[0].customMessage : "Unknown error occurred during branch creation";
|
|
216
|
+
return {
|
|
217
|
+
content: [
|
|
218
|
+
{
|
|
219
|
+
type: "text",
|
|
220
|
+
text: `Error creating branch '${branchName}': ${errorMessage}`,
|
|
160
221
|
},
|
|
161
222
|
],
|
|
162
223
|
isError: true,
|
|
163
224
|
};
|
|
164
225
|
}
|
|
165
|
-
commitId = branch.objectId;
|
|
166
226
|
}
|
|
167
227
|
catch (error) {
|
|
168
228
|
return {
|
|
169
229
|
content: [
|
|
170
230
|
{
|
|
171
231
|
type: "text",
|
|
172
|
-
text: `Error
|
|
173
|
-
},
|
|
174
|
-
],
|
|
175
|
-
isError: true,
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
// Create the new branch using updateRefs
|
|
180
|
-
const newRefName = `refs/heads/${branchName}`;
|
|
181
|
-
const refUpdate = {
|
|
182
|
-
name: newRefName,
|
|
183
|
-
newObjectId: commitId,
|
|
184
|
-
oldObjectId: "0000000000000000000000000000000000000000", // All zeros indicates creating a new ref
|
|
185
|
-
};
|
|
186
|
-
try {
|
|
187
|
-
const result = await gitApi.updateRefs([refUpdate], repositoryId);
|
|
188
|
-
// Check if the branch creation was successful
|
|
189
|
-
if (result && result.length > 0 && result[0].success) {
|
|
190
|
-
return {
|
|
191
|
-
content: [
|
|
192
|
-
{
|
|
193
|
-
type: "text",
|
|
194
|
-
text: `Branch '${branchName}' created successfully from '${sourceBranchName}' (${commitId})`,
|
|
195
|
-
},
|
|
196
|
-
],
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
const errorMessage = result && result.length > 0 && result[0].customMessage ? result[0].customMessage : "Unknown error occurred during branch creation";
|
|
201
|
-
return {
|
|
202
|
-
content: [
|
|
203
|
-
{
|
|
204
|
-
type: "text",
|
|
205
|
-
text: `Error creating branch '${branchName}': ${errorMessage}`,
|
|
232
|
+
text: `Error creating branch '${branchName}': ${error instanceof Error ? error.message : String(error)}`,
|
|
206
233
|
},
|
|
207
234
|
],
|
|
208
235
|
isError: true,
|
|
@@ -210,13 +237,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
210
237
|
}
|
|
211
238
|
}
|
|
212
239
|
catch (error) {
|
|
240
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
213
241
|
return {
|
|
214
|
-
content: [
|
|
215
|
-
{
|
|
216
|
-
type: "text",
|
|
217
|
-
text: `Error creating branch '${branchName}': ${error instanceof Error ? error.message : String(error)}`,
|
|
218
|
-
},
|
|
219
|
-
],
|
|
242
|
+
content: [{ type: "text", text: `Error creating branch: ${errorMessage}` }],
|
|
220
243
|
isError: true,
|
|
221
244
|
};
|
|
222
245
|
}
|
|
@@ -238,56 +261,65 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
238
261
|
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."),
|
|
239
262
|
bypassReason: z.string().optional().describe("Reason for bypassing branch policies. When provided, branch policies will be automatically bypassed during autocompletion."),
|
|
240
263
|
}, async ({ repositoryId, pullRequestId, title, description, isDraft, targetRefName, status, autoComplete, mergeStrategy, deleteSourceBranch, transitionWorkItems, bypassReason }) => {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if (autoComplete) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
264
|
+
try {
|
|
265
|
+
const connection = await connectionProvider();
|
|
266
|
+
const gitApi = await connection.getGitApi();
|
|
267
|
+
// Build update object with only provided fields
|
|
268
|
+
const updateRequest = {};
|
|
269
|
+
if (title !== undefined)
|
|
270
|
+
updateRequest.title = title;
|
|
271
|
+
if (description !== undefined)
|
|
272
|
+
updateRequest.description = description;
|
|
273
|
+
if (isDraft !== undefined)
|
|
274
|
+
updateRequest.isDraft = isDraft;
|
|
275
|
+
if (targetRefName !== undefined)
|
|
276
|
+
updateRequest.targetRefName = targetRefName;
|
|
277
|
+
if (status !== undefined) {
|
|
278
|
+
updateRequest.status = status === "Active" ? PullRequestStatus.Active.valueOf() : PullRequestStatus.Abandoned.valueOf();
|
|
279
|
+
}
|
|
280
|
+
if (autoComplete !== undefined) {
|
|
281
|
+
if (autoComplete) {
|
|
282
|
+
const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
|
|
283
|
+
const autoCompleteUserId = data.authenticatedUser.id;
|
|
284
|
+
updateRequest.autoCompleteSetBy = { id: autoCompleteUserId };
|
|
285
|
+
const completionOptions = {
|
|
286
|
+
deleteSourceBranch: deleteSourceBranch || false,
|
|
287
|
+
transitionWorkItems: transitionWorkItems !== false, // Default to true unless explicitly set to false
|
|
288
|
+
bypassPolicy: !!bypassReason, // Automatically set to true if bypassReason is provided
|
|
289
|
+
};
|
|
290
|
+
if (mergeStrategy) {
|
|
291
|
+
completionOptions.mergeStrategy = GitPullRequestMergeStrategy[mergeStrategy];
|
|
292
|
+
}
|
|
293
|
+
if (bypassReason) {
|
|
294
|
+
completionOptions.bypassReason = bypassReason;
|
|
295
|
+
}
|
|
296
|
+
updateRequest.completionOptions = completionOptions;
|
|
268
297
|
}
|
|
269
|
-
|
|
270
|
-
|
|
298
|
+
else {
|
|
299
|
+
updateRequest.autoCompleteSetBy = null;
|
|
300
|
+
updateRequest.completionOptions = null;
|
|
271
301
|
}
|
|
272
|
-
updateRequest.completionOptions = completionOptions;
|
|
273
302
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
303
|
+
// Validate that at least one field is provided for update
|
|
304
|
+
if (Object.keys(updateRequest).length === 0) {
|
|
305
|
+
return {
|
|
306
|
+
content: [{ type: "text", text: "Error: At least one field (title, description, isDraft, targetRefName, status, or autoComplete options) must be provided for update." }],
|
|
307
|
+
isError: true,
|
|
308
|
+
};
|
|
277
309
|
}
|
|
310
|
+
const updatedPullRequest = await gitApi.updatePullRequest(updateRequest, repositoryId, pullRequestId);
|
|
311
|
+
const trimmedUpdatedPullRequest = trimPullRequest(updatedPullRequest, true);
|
|
312
|
+
return {
|
|
313
|
+
content: [{ type: "text", text: JSON.stringify(trimmedUpdatedPullRequest, null, 2) }],
|
|
314
|
+
};
|
|
278
315
|
}
|
|
279
|
-
|
|
280
|
-
|
|
316
|
+
catch (error) {
|
|
317
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
281
318
|
return {
|
|
282
|
-
content: [{ type: "text", text:
|
|
319
|
+
content: [{ type: "text", text: `Error updating pull request: ${errorMessage}` }],
|
|
283
320
|
isError: true,
|
|
284
321
|
};
|
|
285
322
|
}
|
|
286
|
-
const updatedPullRequest = await gitApi.updatePullRequest(updateRequest, repositoryId, pullRequestId);
|
|
287
|
-
const trimmedUpdatedPullRequest = trimPullRequest(updatedPullRequest, true);
|
|
288
|
-
return {
|
|
289
|
-
content: [{ type: "text", text: JSON.stringify(trimmedUpdatedPullRequest, null, 2) }],
|
|
290
|
-
};
|
|
291
323
|
});
|
|
292
324
|
server.tool(REPO_TOOLS.update_pull_request_reviewers, "Add or remove reviewers for an existing pull request.", {
|
|
293
325
|
repositoryId: z.string().describe("The ID of the repository where the pull request exists."),
|
|
@@ -295,29 +327,38 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
295
327
|
reviewerIds: z.array(z.string()).describe("List of reviewer ids to add or remove from the pull request."),
|
|
296
328
|
action: z.enum(["add", "remove"]).describe("Action to perform on the reviewers. Can be 'add' or 'remove'."),
|
|
297
329
|
}, async ({ repositoryId, pullRequestId, reviewerIds, action }) => {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
else {
|
|
316
|
-
for (const reviewerId of reviewerIds) {
|
|
317
|
-
await gitApi.deletePullRequestReviewer(repositoryId, pullRequestId, reviewerId);
|
|
330
|
+
try {
|
|
331
|
+
const connection = await connectionProvider();
|
|
332
|
+
const gitApi = await connection.getGitApi();
|
|
333
|
+
let updatedPullRequest;
|
|
334
|
+
if (action === "add") {
|
|
335
|
+
updatedPullRequest = await gitApi.createPullRequestReviewers(reviewerIds.map((id) => ({ id: id })), repositoryId, pullRequestId);
|
|
336
|
+
const trimmedResponse = updatedPullRequest.map((item) => ({
|
|
337
|
+
displayName: item.displayName,
|
|
338
|
+
id: item.id,
|
|
339
|
+
uniqueName: item.uniqueName,
|
|
340
|
+
vote: item.vote,
|
|
341
|
+
hasDeclined: item.hasDeclined,
|
|
342
|
+
isFlagged: item.isFlagged,
|
|
343
|
+
}));
|
|
344
|
+
return {
|
|
345
|
+
content: [{ type: "text", text: JSON.stringify(trimmedResponse, null, 2) }],
|
|
346
|
+
};
|
|
318
347
|
}
|
|
348
|
+
else {
|
|
349
|
+
for (const reviewerId of reviewerIds) {
|
|
350
|
+
await gitApi.deletePullRequestReviewer(repositoryId, pullRequestId, reviewerId);
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
content: [{ type: "text", text: `Reviewers with IDs ${reviewerIds.join(", ")} removed from pull request ${pullRequestId}.` }],
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
319
359
|
return {
|
|
320
|
-
content: [{ type: "text", text: `
|
|
360
|
+
content: [{ type: "text", text: `Error updating pull request reviewers: ${errorMessage}` }],
|
|
361
|
+
isError: true,
|
|
321
362
|
};
|
|
322
363
|
}
|
|
323
364
|
});
|
|
@@ -327,24 +368,33 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
327
368
|
skip: z.number().default(0).describe("The number of repositories to skip. Defaults to 0."),
|
|
328
369
|
repoNameFilter: z.string().optional().describe("Optional filter to search for repositories by name. If provided, only repositories with names containing this string will be returned."),
|
|
329
370
|
}, async ({ project, top, skip, repoNameFilter }) => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
371
|
+
try {
|
|
372
|
+
const connection = await connectionProvider();
|
|
373
|
+
const gitApi = await connection.getGitApi();
|
|
374
|
+
const repositories = await gitApi.getRepositories(project, false, false, false);
|
|
375
|
+
const filteredRepositories = repoNameFilter ? filterReposByName(repositories, repoNameFilter) : repositories;
|
|
376
|
+
const paginatedRepositories = filteredRepositories?.sort((a, b) => a.name?.localeCompare(b.name ?? "") ?? 0).slice(skip, skip + top);
|
|
377
|
+
// Filter out the irrelevant properties
|
|
378
|
+
const trimmedRepositories = paginatedRepositories?.map((repo) => ({
|
|
379
|
+
id: repo.id,
|
|
380
|
+
name: repo.name,
|
|
381
|
+
isDisabled: repo.isDisabled,
|
|
382
|
+
isFork: repo.isFork,
|
|
383
|
+
isInMaintenance: repo.isInMaintenance,
|
|
384
|
+
webUrl: repo.webUrl,
|
|
385
|
+
size: repo.size,
|
|
386
|
+
}));
|
|
387
|
+
return {
|
|
388
|
+
content: [{ type: "text", text: JSON.stringify(trimmedRepositories, null, 2) }],
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
393
|
+
return {
|
|
394
|
+
content: [{ type: "text", text: `Error listing repositories: ${errorMessage}` }],
|
|
395
|
+
isError: true,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
348
398
|
});
|
|
349
399
|
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.", {
|
|
350
400
|
repositoryId: z.string().optional().describe("The ID of the repository where the pull requests are located."),
|
|
@@ -365,103 +415,112 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
365
415
|
sourceRefName: z.string().optional().describe("Filter pull requests from this source branch (e.g., 'refs/heads/feature-branch')."),
|
|
366
416
|
targetRefName: z.string().optional().describe("Filter pull requests into this target branch (e.g., 'refs/heads/main')."),
|
|
367
417
|
}, async ({ repositoryId, project, top, skip, created_by_me, created_by_user, i_am_reviewer, user_is_reviewer, status, sourceRefName, targetRefName }) => {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (!repositoryId && !project) {
|
|
375
|
-
return {
|
|
376
|
-
content: [
|
|
377
|
-
{
|
|
378
|
-
type: "text",
|
|
379
|
-
text: "Either repositoryId or project must be provided.",
|
|
380
|
-
},
|
|
381
|
-
],
|
|
382
|
-
isError: true,
|
|
418
|
+
try {
|
|
419
|
+
const connection = await connectionProvider();
|
|
420
|
+
const gitApi = await connection.getGitApi();
|
|
421
|
+
// Build the search criteria
|
|
422
|
+
const searchCriteria = {
|
|
423
|
+
status: pullRequestStatusStringToInt(status),
|
|
383
424
|
};
|
|
384
|
-
|
|
385
|
-
if (repositoryId) {
|
|
386
|
-
searchCriteria.repositoryId = repositoryId;
|
|
387
|
-
}
|
|
388
|
-
if (sourceRefName) {
|
|
389
|
-
searchCriteria.sourceRefName = sourceRefName;
|
|
390
|
-
}
|
|
391
|
-
if (targetRefName) {
|
|
392
|
-
searchCriteria.targetRefName = targetRefName;
|
|
393
|
-
}
|
|
394
|
-
if (created_by_user) {
|
|
395
|
-
try {
|
|
396
|
-
const userId = await getUserIdFromEmail(created_by_user, tokenProvider, connectionProvider, userAgentProvider);
|
|
397
|
-
searchCriteria.creatorId = userId;
|
|
398
|
-
}
|
|
399
|
-
catch (error) {
|
|
425
|
+
if (!repositoryId && !project) {
|
|
400
426
|
return {
|
|
401
427
|
content: [
|
|
402
428
|
{
|
|
403
429
|
type: "text",
|
|
404
|
-
text:
|
|
430
|
+
text: "Either repositoryId or project must be provided.",
|
|
405
431
|
},
|
|
406
432
|
],
|
|
407
433
|
isError: true,
|
|
408
434
|
};
|
|
409
435
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
|
|
413
|
-
const userId = data.authenticatedUser.id;
|
|
414
|
-
searchCriteria.creatorId = userId;
|
|
415
|
-
}
|
|
416
|
-
if (user_is_reviewer) {
|
|
417
|
-
try {
|
|
418
|
-
const reviewerUserId = await getUserIdFromEmail(user_is_reviewer, tokenProvider, connectionProvider, userAgentProvider);
|
|
419
|
-
searchCriteria.reviewerId = reviewerUserId;
|
|
436
|
+
if (repositoryId) {
|
|
437
|
+
searchCriteria.repositoryId = repositoryId;
|
|
420
438
|
}
|
|
421
|
-
|
|
439
|
+
if (sourceRefName) {
|
|
440
|
+
searchCriteria.sourceRefName = sourceRefName;
|
|
441
|
+
}
|
|
442
|
+
if (targetRefName) {
|
|
443
|
+
searchCriteria.targetRefName = targetRefName;
|
|
444
|
+
}
|
|
445
|
+
if (created_by_user) {
|
|
446
|
+
try {
|
|
447
|
+
const userId = await getUserIdFromEmail(created_by_user, tokenProvider, connectionProvider, userAgentProvider);
|
|
448
|
+
searchCriteria.creatorId = userId;
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
return {
|
|
452
|
+
content: [
|
|
453
|
+
{
|
|
454
|
+
type: "text",
|
|
455
|
+
text: `Error finding user with email ${created_by_user}: ${error instanceof Error ? error.message : String(error)}`,
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
isError: true,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
else if (created_by_me) {
|
|
463
|
+
const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
|
|
464
|
+
const userId = data.authenticatedUser.id;
|
|
465
|
+
searchCriteria.creatorId = userId;
|
|
466
|
+
}
|
|
467
|
+
if (user_is_reviewer) {
|
|
468
|
+
try {
|
|
469
|
+
const reviewerUserId = await getUserIdFromEmail(user_is_reviewer, tokenProvider, connectionProvider, userAgentProvider);
|
|
470
|
+
searchCriteria.reviewerId = reviewerUserId;
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
return {
|
|
474
|
+
content: [
|
|
475
|
+
{
|
|
476
|
+
type: "text",
|
|
477
|
+
text: `Error finding reviewer with email ${user_is_reviewer}: ${error instanceof Error ? error.message : String(error)}`,
|
|
478
|
+
},
|
|
479
|
+
],
|
|
480
|
+
isError: true,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
else if (i_am_reviewer) {
|
|
485
|
+
const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
|
|
486
|
+
const userId = data.authenticatedUser.id;
|
|
487
|
+
searchCriteria.reviewerId = userId;
|
|
488
|
+
}
|
|
489
|
+
let pullRequests;
|
|
490
|
+
if (repositoryId) {
|
|
491
|
+
pullRequests = await gitApi.getPullRequests(repositoryId, searchCriteria, project, // project
|
|
492
|
+
undefined, // maxCommentLength
|
|
493
|
+
skip, top);
|
|
494
|
+
}
|
|
495
|
+
else if (project) {
|
|
496
|
+
// If only project is provided, use getPullRequestsByProject
|
|
497
|
+
pullRequests = await gitApi.getPullRequestsByProject(project, searchCriteria, undefined, // maxCommentLength
|
|
498
|
+
skip, top);
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
// This case should not occur due to earlier validation, but added for completeness
|
|
422
502
|
return {
|
|
423
503
|
content: [
|
|
424
504
|
{
|
|
425
505
|
type: "text",
|
|
426
|
-
text:
|
|
506
|
+
text: "Either repositoryId or project must be provided.",
|
|
427
507
|
},
|
|
428
508
|
],
|
|
429
509
|
isError: true,
|
|
430
510
|
};
|
|
431
511
|
}
|
|
512
|
+
const filteredPullRequests = pullRequests?.map((pr) => trimPullRequest(pr));
|
|
513
|
+
return {
|
|
514
|
+
content: [{ type: "text", text: JSON.stringify(filteredPullRequests, null, 2) }],
|
|
515
|
+
};
|
|
432
516
|
}
|
|
433
|
-
|
|
434
|
-
const
|
|
435
|
-
const userId = data.authenticatedUser.id;
|
|
436
|
-
searchCriteria.reviewerId = userId;
|
|
437
|
-
}
|
|
438
|
-
let pullRequests;
|
|
439
|
-
if (repositoryId) {
|
|
440
|
-
pullRequests = await gitApi.getPullRequests(repositoryId, searchCriteria, project, // project
|
|
441
|
-
undefined, // maxCommentLength
|
|
442
|
-
skip, top);
|
|
443
|
-
}
|
|
444
|
-
else if (project) {
|
|
445
|
-
// If only project is provided, use getPullRequestsByProject
|
|
446
|
-
pullRequests = await gitApi.getPullRequestsByProject(project, searchCriteria, undefined, // maxCommentLength
|
|
447
|
-
skip, top);
|
|
448
|
-
}
|
|
449
|
-
else {
|
|
450
|
-
// This case should not occur due to earlier validation, but added for completeness
|
|
517
|
+
catch (error) {
|
|
518
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
451
519
|
return {
|
|
452
|
-
content: [
|
|
453
|
-
{
|
|
454
|
-
type: "text",
|
|
455
|
-
text: "Either repositoryId or project must be provided.",
|
|
456
|
-
},
|
|
457
|
-
],
|
|
520
|
+
content: [{ type: "text", text: `Error listing pull requests: ${errorMessage}` }],
|
|
458
521
|
isError: true,
|
|
459
522
|
};
|
|
460
523
|
}
|
|
461
|
-
const filteredPullRequests = pullRequests?.map((pr) => trimPullRequest(pr));
|
|
462
|
-
return {
|
|
463
|
-
content: [{ type: "text", text: JSON.stringify(filteredPullRequests, null, 2) }],
|
|
464
|
-
};
|
|
465
524
|
});
|
|
466
525
|
server.tool(REPO_TOOLS.list_pull_request_threads, "Retrieve a list of comment threads for a pull request.", {
|
|
467
526
|
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
@@ -469,24 +528,57 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
469
528
|
project: z.string().optional().describe("Project ID or project name (optional)"),
|
|
470
529
|
iteration: z.number().optional().describe("The iteration ID for which to retrieve threads. Optional, defaults to the latest iteration."),
|
|
471
530
|
baseIteration: z.number().optional().describe("The base iteration ID for which to retrieve threads. Optional, defaults to the latest base iteration."),
|
|
472
|
-
top: z.number().default(100).describe("The maximum number of threads to return."),
|
|
473
|
-
skip: z.number().default(0).describe("The number of threads to skip."),
|
|
531
|
+
top: z.number().default(100).describe("The maximum number of threads to return after filtering."),
|
|
532
|
+
skip: z.number().default(0).describe("The number of threads to skip after filtering."),
|
|
474
533
|
fullResponse: z.boolean().optional().default(false).describe("Return full thread JSON response instead of trimmed data."),
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
534
|
+
status: z
|
|
535
|
+
.enum(getEnumKeys(CommentThreadStatus))
|
|
536
|
+
.optional()
|
|
537
|
+
.describe("Filter threads by status. If not specified, returns threads of all statuses."),
|
|
538
|
+
authorEmail: z.string().optional().describe("Filter threads by the email of the thread author (first comment author)."),
|
|
539
|
+
authorDisplayName: z.string().optional().describe("Filter threads by the display name of the thread author (first comment author). Case-insensitive partial matching."),
|
|
540
|
+
}, async ({ repositoryId, pullRequestId, project, iteration, baseIteration, top, skip, fullResponse, status, authorEmail, authorDisplayName }) => {
|
|
541
|
+
try {
|
|
542
|
+
const connection = await connectionProvider();
|
|
543
|
+
const gitApi = await connection.getGitApi();
|
|
544
|
+
const threads = await gitApi.getThreads(repositoryId, pullRequestId, project, iteration, baseIteration);
|
|
545
|
+
let filteredThreads = threads;
|
|
546
|
+
if (status !== undefined) {
|
|
547
|
+
const statusValue = CommentThreadStatus[status];
|
|
548
|
+
filteredThreads = filteredThreads?.filter((thread) => thread.status === statusValue);
|
|
549
|
+
}
|
|
550
|
+
if (authorEmail !== undefined) {
|
|
551
|
+
filteredThreads = filteredThreads?.filter((thread) => {
|
|
552
|
+
const firstComment = thread.comments?.[0];
|
|
553
|
+
return firstComment?.author?.uniqueName?.toLowerCase() === authorEmail.toLowerCase();
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
if (authorDisplayName !== undefined) {
|
|
557
|
+
const lowerAuthorName = authorDisplayName.toLowerCase();
|
|
558
|
+
filteredThreads = filteredThreads?.filter((thread) => {
|
|
559
|
+
const firstComment = thread.comments?.[0];
|
|
560
|
+
return firstComment?.author?.displayName?.toLowerCase().includes(lowerAuthorName);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
const paginatedThreads = filteredThreads?.sort((a, b) => (a.id ?? 0) - (b.id ?? 0)).slice(skip, skip + top);
|
|
564
|
+
if (fullResponse) {
|
|
565
|
+
return {
|
|
566
|
+
content: [{ type: "text", text: JSON.stringify(paginatedThreads, null, 2) }],
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
// Return trimmed thread data focusing on essential information
|
|
570
|
+
const trimmedThreads = paginatedThreads?.map((thread) => trimPullRequestThread(thread));
|
|
571
|
+
return {
|
|
572
|
+
content: [{ type: "text", text: JSON.stringify(trimmedThreads, null, 2) }],
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
481
577
|
return {
|
|
482
|
-
content: [{ type: "text", text:
|
|
578
|
+
content: [{ type: "text", text: `Error listing pull request threads: ${errorMessage}` }],
|
|
579
|
+
isError: true,
|
|
483
580
|
};
|
|
484
581
|
}
|
|
485
|
-
// Return trimmed thread data focusing on essential information
|
|
486
|
-
const trimmedThreads = paginatedThreads?.map((thread) => trimPullRequestThread(thread));
|
|
487
|
-
return {
|
|
488
|
-
content: [{ type: "text", text: JSON.stringify(trimmedThreads, null, 2) }],
|
|
489
|
-
};
|
|
490
582
|
});
|
|
491
583
|
server.tool(REPO_TOOLS.list_pull_request_thread_comments, "Retrieve a list of comments in a pull request thread.", {
|
|
492
584
|
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
@@ -497,97 +589,184 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
497
589
|
skip: z.number().default(0).describe("The number of comments to skip."),
|
|
498
590
|
fullResponse: z.boolean().optional().default(false).describe("Return full comment JSON response instead of trimmed data."),
|
|
499
591
|
}, async ({ repositoryId, pullRequestId, threadId, project, top, skip, fullResponse }) => {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
592
|
+
try {
|
|
593
|
+
const connection = await connectionProvider();
|
|
594
|
+
const gitApi = await connection.getGitApi();
|
|
595
|
+
// Get thread comments - GitApi uses getComments for retrieving comments from a specific thread
|
|
596
|
+
const comments = await gitApi.getComments(repositoryId, pullRequestId, threadId, project);
|
|
597
|
+
const paginatedComments = comments?.sort((a, b) => (a.id ?? 0) - (b.id ?? 0)).slice(skip, skip + top);
|
|
598
|
+
if (fullResponse) {
|
|
599
|
+
return {
|
|
600
|
+
content: [{ type: "text", text: JSON.stringify(paginatedComments, null, 2) }],
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
// Return trimmed comment data focusing on essential information
|
|
604
|
+
const trimmedComments = trimComments(paginatedComments);
|
|
506
605
|
return {
|
|
507
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
606
|
+
content: [{ type: "text", text: JSON.stringify(trimmedComments, null, 2) }],
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
611
|
+
return {
|
|
612
|
+
content: [{ type: "text", text: `Error listing pull request thread comments: ${errorMessage}` }],
|
|
613
|
+
isError: true,
|
|
508
614
|
};
|
|
509
615
|
}
|
|
510
|
-
// Return trimmed comment data focusing on essential information
|
|
511
|
-
const trimmedComments = trimComments(paginatedComments);
|
|
512
|
-
return {
|
|
513
|
-
content: [{ type: "text", text: JSON.stringify(trimmedComments, null, 2) }],
|
|
514
|
-
};
|
|
515
616
|
});
|
|
516
617
|
server.tool(REPO_TOOLS.list_branches_by_repo, "Retrieve a list of branches for a given repository.", {
|
|
517
618
|
repositoryId: z.string().describe("The ID of the repository where the branches are located."),
|
|
518
619
|
top: z.number().default(100).describe("The maximum number of branches to return. Defaults to 100."),
|
|
519
620
|
filterContains: z.string().optional().describe("Filter to find branches that contain this string in their name."),
|
|
520
621
|
}, async ({ repositoryId, top, filterContains }) => {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
622
|
+
try {
|
|
623
|
+
const connection = await connectionProvider();
|
|
624
|
+
const gitApi = await connection.getGitApi();
|
|
625
|
+
const branches = await gitApi.getRefs(repositoryId, undefined, "heads/", undefined, undefined, undefined, undefined, undefined, filterContains);
|
|
626
|
+
const filteredBranches = branchesFilterOutIrrelevantProperties(branches, top);
|
|
627
|
+
return {
|
|
628
|
+
content: [{ type: "text", text: JSON.stringify(filteredBranches, null, 2) }],
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
catch (error) {
|
|
632
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
633
|
+
return {
|
|
634
|
+
content: [{ type: "text", text: `Error listing branches: ${errorMessage}` }],
|
|
635
|
+
isError: true,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
528
638
|
});
|
|
529
639
|
server.tool(REPO_TOOLS.list_my_branches_by_repo, "Retrieve a list of my branches for a given repository Id.", {
|
|
530
640
|
repositoryId: z.string().describe("The ID of the repository where the branches are located."),
|
|
531
641
|
top: z.number().default(100).describe("The maximum number of branches to return."),
|
|
532
642
|
filterContains: z.string().optional().describe("Filter to find branches that contain this string in their name."),
|
|
533
643
|
}, async ({ repositoryId, top, filterContains }) => {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
644
|
+
try {
|
|
645
|
+
const connection = await connectionProvider();
|
|
646
|
+
const gitApi = await connection.getGitApi();
|
|
647
|
+
const branches = await gitApi.getRefs(repositoryId, undefined, "heads/", undefined, undefined, true, undefined, undefined, filterContains);
|
|
648
|
+
const filteredBranches = branchesFilterOutIrrelevantProperties(branches, top);
|
|
649
|
+
return {
|
|
650
|
+
content: [{ type: "text", text: JSON.stringify(filteredBranches, null, 2) }],
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
654
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
655
|
+
return {
|
|
656
|
+
content: [{ type: "text", text: `Error listing my branches: ${errorMessage}` }],
|
|
657
|
+
isError: true,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
541
660
|
});
|
|
542
661
|
server.tool(REPO_TOOLS.get_repo_by_name_or_id, "Get the repository by project and repository name or ID.", {
|
|
543
662
|
project: z.string().describe("Project name or ID where the repository is located."),
|
|
544
663
|
repositoryNameOrId: z.string().describe("Repository name or ID."),
|
|
545
664
|
}, async ({ project, repositoryNameOrId }) => {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
665
|
+
try {
|
|
666
|
+
const connection = await connectionProvider();
|
|
667
|
+
const gitApi = await connection.getGitApi();
|
|
668
|
+
const repositories = await gitApi.getRepositories(project);
|
|
669
|
+
const repository = repositories?.find((repo) => repo.name === repositoryNameOrId || repo.id === repositoryNameOrId);
|
|
670
|
+
if (!repository) {
|
|
671
|
+
return {
|
|
672
|
+
content: [{ type: "text", text: `Repository ${repositoryNameOrId} not found in project ${project}` }],
|
|
673
|
+
isError: true,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
content: [{ type: "text", text: JSON.stringify(repository, null, 2) }],
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
catch (error) {
|
|
681
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
682
|
+
return {
|
|
683
|
+
content: [{ type: "text", text: `Error getting repository: ${errorMessage}` }],
|
|
684
|
+
isError: true,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
556
687
|
});
|
|
557
688
|
server.tool(REPO_TOOLS.get_branch_by_name, "Get a branch by its name.", {
|
|
558
689
|
repositoryId: z.string().describe("The ID of the repository where the branch is located."),
|
|
559
690
|
branchName: z.string().describe("The name of the branch to retrieve, e.g., 'main' or 'feature-branch'."),
|
|
560
691
|
}, async ({ repositoryId, branchName }) => {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
692
|
+
try {
|
|
693
|
+
const connection = await connectionProvider();
|
|
694
|
+
const gitApi = await connection.getGitApi();
|
|
695
|
+
const branches = await gitApi.getRefs(repositoryId, undefined, "heads/", false, false, undefined, false, undefined, branchName);
|
|
696
|
+
const branch = branches.find((branch) => branch.name === `refs/heads/${branchName}` || branch.name === branchName);
|
|
697
|
+
if (!branch) {
|
|
698
|
+
return {
|
|
699
|
+
content: [
|
|
700
|
+
{
|
|
701
|
+
type: "text",
|
|
702
|
+
text: `Branch ${branchName} not found in repository ${repositoryId}`,
|
|
703
|
+
},
|
|
704
|
+
],
|
|
705
|
+
isError: true,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
566
708
|
return {
|
|
567
|
-
content: [
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
709
|
+
content: [{ type: "text", text: JSON.stringify(branch, null, 2) }],
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
714
|
+
return {
|
|
715
|
+
content: [{ type: "text", text: `Error getting branch: ${errorMessage}` }],
|
|
573
716
|
isError: true,
|
|
574
717
|
};
|
|
575
718
|
}
|
|
576
|
-
return {
|
|
577
|
-
content: [{ type: "text", text: JSON.stringify(branch, null, 2) }],
|
|
578
|
-
};
|
|
579
719
|
});
|
|
580
720
|
server.tool(REPO_TOOLS.get_pull_request_by_id, "Get a pull request by its ID.", {
|
|
581
721
|
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
582
722
|
pullRequestId: z.number().describe("The ID of the pull request to retrieve."),
|
|
583
723
|
includeWorkItemRefs: z.boolean().optional().default(false).describe("Whether to reference work items associated with the pull request."),
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
724
|
+
includeLabels: z.boolean().optional().default(false).describe("Whether to include a summary of labels in the response."),
|
|
725
|
+
}, async ({ repositoryId, pullRequestId, includeWorkItemRefs, includeLabels }) => {
|
|
726
|
+
try {
|
|
727
|
+
const connection = await connectionProvider();
|
|
728
|
+
const gitApi = await connection.getGitApi();
|
|
729
|
+
const pullRequest = await gitApi.getPullRequest(repositoryId, pullRequestId, undefined, undefined, undefined, undefined, undefined, includeWorkItemRefs);
|
|
730
|
+
if (includeLabels) {
|
|
731
|
+
try {
|
|
732
|
+
const projectId = pullRequest.repository?.project?.id;
|
|
733
|
+
const projectName = pullRequest.repository?.project?.name;
|
|
734
|
+
const labels = await gitApi.getPullRequestLabels(repositoryId, pullRequestId, projectName, projectId);
|
|
735
|
+
const labelNames = labels.map((label) => label.name).filter((name) => name !== undefined);
|
|
736
|
+
const enhancedResponse = {
|
|
737
|
+
...pullRequest,
|
|
738
|
+
labelSummary: {
|
|
739
|
+
labels: labelNames,
|
|
740
|
+
labelCount: labelNames.length,
|
|
741
|
+
},
|
|
742
|
+
};
|
|
743
|
+
return {
|
|
744
|
+
content: [{ type: "text", text: JSON.stringify(enhancedResponse, null, 2) }],
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
catch (error) {
|
|
748
|
+
console.warn(`Error fetching PR labels: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
749
|
+
// Fall back to the original response without labels
|
|
750
|
+
const enhancedResponse = {
|
|
751
|
+
...pullRequest,
|
|
752
|
+
labelSummary: {},
|
|
753
|
+
};
|
|
754
|
+
return {
|
|
755
|
+
content: [{ type: "text", text: JSON.stringify(enhancedResponse, null, 2) }],
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
content: [{ type: "text", text: JSON.stringify(pullRequest, null, 2) }],
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
catch (error) {
|
|
764
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
765
|
+
return {
|
|
766
|
+
content: [{ type: "text", text: `Error getting pull request: ${errorMessage}` }],
|
|
767
|
+
isError: true,
|
|
768
|
+
};
|
|
769
|
+
}
|
|
591
770
|
});
|
|
592
771
|
server.tool(REPO_TOOLS.reply_to_comment, "Replies to a specific comment on a pull request.", {
|
|
593
772
|
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
@@ -597,24 +776,33 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
597
776
|
project: z.string().optional().describe("Project ID or project name (optional)"),
|
|
598
777
|
fullResponse: z.boolean().optional().default(false).describe("Return full comment JSON response instead of a simple confirmation message."),
|
|
599
778
|
}, async ({ repositoryId, pullRequestId, threadId, content, project, fullResponse }) => {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
779
|
+
try {
|
|
780
|
+
const connection = await connectionProvider();
|
|
781
|
+
const gitApi = await connection.getGitApi();
|
|
782
|
+
const comment = await gitApi.createComment({ content }, repositoryId, pullRequestId, threadId, project);
|
|
783
|
+
// Check if the comment was successfully created
|
|
784
|
+
if (!comment) {
|
|
785
|
+
return {
|
|
786
|
+
content: [{ type: "text", text: `Error: Failed to add comment to thread ${threadId}. The comment was not created successfully.` }],
|
|
787
|
+
isError: true,
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
if (fullResponse) {
|
|
791
|
+
return {
|
|
792
|
+
content: [{ type: "text", text: JSON.stringify(comment, null, 2) }],
|
|
793
|
+
};
|
|
794
|
+
}
|
|
605
795
|
return {
|
|
606
|
-
content: [{ type: "text", text: `
|
|
607
|
-
isError: true,
|
|
796
|
+
content: [{ type: "text", text: `Comment successfully added to thread ${threadId}.` }],
|
|
608
797
|
};
|
|
609
798
|
}
|
|
610
|
-
|
|
799
|
+
catch (error) {
|
|
800
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
611
801
|
return {
|
|
612
|
-
content: [{ type: "text", text:
|
|
802
|
+
content: [{ type: "text", text: `Error replying to comment: ${errorMessage}` }],
|
|
803
|
+
isError: true,
|
|
613
804
|
};
|
|
614
805
|
}
|
|
615
|
-
return {
|
|
616
|
-
content: [{ type: "text", text: `Comment successfully added to thread ${threadId}.` }],
|
|
617
|
-
};
|
|
618
806
|
});
|
|
619
807
|
server.tool(REPO_TOOLS.create_pull_request_thread, "Creates a new comment thread on a pull request.", {
|
|
620
808
|
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
@@ -641,42 +829,109 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
641
829
|
.optional()
|
|
642
830
|
.describe("Position of last character of the thread's span in right file. The character offset of a thread's position inside of a line. Must only be set if rightFileEndLine is also specified. (optional)"),
|
|
643
831
|
}, async ({ repositoryId, pullRequestId, content, project, filePath, status, rightFileStartLine, rightFileStartOffset, rightFileEndLine, rightFileEndOffset }) => {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
if (rightFileStartLine
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
832
|
+
try {
|
|
833
|
+
const connection = await connectionProvider();
|
|
834
|
+
const gitApi = await connection.getGitApi();
|
|
835
|
+
const normalizedFilePath = filePath && !filePath.startsWith("/") ? `/${filePath}` : filePath;
|
|
836
|
+
const threadContext = { filePath: normalizedFilePath };
|
|
837
|
+
if (rightFileStartLine !== undefined) {
|
|
838
|
+
if (rightFileStartLine < 1) {
|
|
839
|
+
return {
|
|
840
|
+
content: [{ type: "text", text: "rightFileStartLine must be greater than or equal to 1." }],
|
|
841
|
+
isError: true,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
threadContext.rightFileStart = { line: rightFileStartLine };
|
|
845
|
+
if (rightFileStartOffset !== undefined) {
|
|
846
|
+
if (rightFileStartOffset < 1) {
|
|
847
|
+
return {
|
|
848
|
+
content: [{ type: "text", text: "rightFileStartOffset must be greater than or equal to 1." }],
|
|
849
|
+
isError: true,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
threadContext.rightFileStart.offset = rightFileStartOffset;
|
|
656
853
|
}
|
|
657
|
-
threadContext.rightFileStart.offset = rightFileStartOffset;
|
|
658
854
|
}
|
|
855
|
+
if (rightFileEndLine !== undefined) {
|
|
856
|
+
if (rightFileStartLine === undefined) {
|
|
857
|
+
return {
|
|
858
|
+
content: [{ type: "text", text: "rightFileEndLine must only be specified if rightFileStartLine is also specified." }],
|
|
859
|
+
isError: true,
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
if (rightFileEndLine < 1) {
|
|
863
|
+
return {
|
|
864
|
+
content: [{ type: "text", text: "rightFileEndLine must be greater than or equal to 1." }],
|
|
865
|
+
isError: true,
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
threadContext.rightFileEnd = { line: rightFileEndLine };
|
|
869
|
+
if (rightFileEndOffset !== undefined) {
|
|
870
|
+
if (rightFileEndOffset < 1) {
|
|
871
|
+
return {
|
|
872
|
+
content: [{ type: "text", text: "rightFileEndOffset must be greater than or equal to 1." }],
|
|
873
|
+
isError: true,
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
threadContext.rightFileEnd.offset = rightFileEndOffset;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
const thread = await gitApi.createThread({ comments: [{ content: content }], threadContext: threadContext, status: CommentThreadStatus[status] }, repositoryId, pullRequestId, project);
|
|
880
|
+
const trimmedThread = trimPullRequestThread(thread);
|
|
881
|
+
return {
|
|
882
|
+
content: [{ type: "text", text: JSON.stringify(trimmedThread, null, 2) }],
|
|
883
|
+
};
|
|
659
884
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
885
|
+
catch (error) {
|
|
886
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
887
|
+
return {
|
|
888
|
+
content: [{ type: "text", text: `Error creating pull request thread: ${errorMessage}` }],
|
|
889
|
+
isError: true,
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
server.tool(REPO_TOOLS.update_pull_request_thread, "Updates an existing comment thread on a pull request.", {
|
|
894
|
+
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
895
|
+
pullRequestId: z.number().describe("The ID of the pull request where the comment thread exists."),
|
|
896
|
+
threadId: z.number().describe("The ID of the thread to update."),
|
|
897
|
+
project: z.string().optional().describe("Project ID or project name (optional)"),
|
|
898
|
+
status: z
|
|
899
|
+
.enum(getEnumKeys(CommentThreadStatus))
|
|
900
|
+
.optional()
|
|
901
|
+
.describe("The new status for the comment thread."),
|
|
902
|
+
}, async ({ repositoryId, pullRequestId, threadId, project, status }) => {
|
|
903
|
+
try {
|
|
904
|
+
const connection = await connectionProvider();
|
|
905
|
+
const gitApi = await connection.getGitApi();
|
|
906
|
+
const updateRequest = {};
|
|
907
|
+
if (status !== undefined) {
|
|
908
|
+
updateRequest.status = CommentThreadStatus[status];
|
|
663
909
|
}
|
|
664
|
-
if (
|
|
665
|
-
|
|
910
|
+
if (Object.keys(updateRequest).length === 0) {
|
|
911
|
+
return {
|
|
912
|
+
content: [{ type: "text", text: "Error: At least one field (status) must be provided for update." }],
|
|
913
|
+
isError: true,
|
|
914
|
+
};
|
|
666
915
|
}
|
|
667
|
-
|
|
668
|
-
if (
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
916
|
+
const thread = await gitApi.updateThread(updateRequest, repositoryId, pullRequestId, threadId, project);
|
|
917
|
+
if (!thread) {
|
|
918
|
+
return {
|
|
919
|
+
content: [{ type: "text", text: `Error: Failed to update thread ${threadId}. The thread was not updated successfully.` }],
|
|
920
|
+
isError: true,
|
|
921
|
+
};
|
|
673
922
|
}
|
|
923
|
+
const trimmedThread = trimPullRequestThread(thread);
|
|
924
|
+
return {
|
|
925
|
+
content: [{ type: "text", text: JSON.stringify(trimmedThread, null, 2) }],
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
catch (error) {
|
|
929
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
930
|
+
return {
|
|
931
|
+
content: [{ type: "text", text: `Error updating pull request thread: ${errorMessage}` }],
|
|
932
|
+
isError: true,
|
|
933
|
+
};
|
|
674
934
|
}
|
|
675
|
-
const thread = await gitApi.createThread({ comments: [{ content: content }], threadContext: threadContext, status: CommentThreadStatus[status] }, repositoryId, pullRequestId, project);
|
|
676
|
-
const trimmedThread = trimPullRequestThread(thread);
|
|
677
|
-
return {
|
|
678
|
-
content: [{ type: "text", text: JSON.stringify(trimmedThread, null, 2) }],
|
|
679
|
-
};
|
|
680
935
|
});
|
|
681
936
|
server.tool(REPO_TOOLS.resolve_comment, "Resolves a specific comment thread on a pull request.", {
|
|
682
937
|
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
@@ -684,25 +939,34 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
684
939
|
threadId: z.number().describe("The ID of the thread to be resolved."),
|
|
685
940
|
fullResponse: z.boolean().optional().default(false).describe("Return full thread JSON response instead of a simple confirmation message."),
|
|
686
941
|
}, async ({ repositoryId, pullRequestId, threadId, fullResponse }) => {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
942
|
+
try {
|
|
943
|
+
const connection = await connectionProvider();
|
|
944
|
+
const gitApi = await connection.getGitApi();
|
|
945
|
+
const thread = await gitApi.updateThread({ status: 2 }, // 2 corresponds to "Resolved" status
|
|
946
|
+
repositoryId, pullRequestId, threadId);
|
|
947
|
+
// Check if the thread was successfully resolved
|
|
948
|
+
if (!thread) {
|
|
949
|
+
return {
|
|
950
|
+
content: [{ type: "text", text: `Error: Failed to resolve thread ${threadId}. The thread status was not updated successfully.` }],
|
|
951
|
+
isError: true,
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
if (fullResponse) {
|
|
955
|
+
return {
|
|
956
|
+
content: [{ type: "text", text: JSON.stringify(thread, null, 2) }],
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
return {
|
|
960
|
+
content: [{ type: "text", text: `Thread ${threadId} was successfully resolved.` }],
|
|
696
961
|
};
|
|
697
962
|
}
|
|
698
|
-
|
|
963
|
+
catch (error) {
|
|
964
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
699
965
|
return {
|
|
700
|
-
content: [{ type: "text", text:
|
|
966
|
+
content: [{ type: "text", text: `Error resolving comment: ${errorMessage}` }],
|
|
967
|
+
isError: true,
|
|
701
968
|
};
|
|
702
969
|
}
|
|
703
|
-
return {
|
|
704
|
-
content: [{ type: "text", text: `Thread ${threadId} was successfully resolved.` }],
|
|
705
|
-
};
|
|
706
970
|
});
|
|
707
971
|
const gitVersionTypeStrings = Object.values(GitVersionType).filter((value) => typeof value === "string");
|
|
708
972
|
server.tool(REPO_TOOLS.search_commits, "Search for commits in a repository with comprehensive filtering capabilities. Supports searching by description/comment text, time range, author, committer, specific commit IDs, and more. This is the unified tool for all commit search operations.", {
|
|
@@ -792,7 +1056,8 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
792
1056
|
if (historySimplificationMode) {
|
|
793
1057
|
// Note: This parameter might not be directly supported by all ADO API versions
|
|
794
1058
|
// but we'll include it in the criteria for forward compatibility
|
|
795
|
-
|
|
1059
|
+
const extendedCriteria = searchCriteria;
|
|
1060
|
+
extendedCriteria.historySimplificationMode = historySimplificationMode;
|
|
796
1061
|
}
|
|
797
1062
|
if (version) {
|
|
798
1063
|
const itemVersion = {
|
|
@@ -864,13 +1129,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
864
1129
|
};
|
|
865
1130
|
}
|
|
866
1131
|
catch (error) {
|
|
1132
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
867
1133
|
return {
|
|
868
|
-
content: [
|
|
869
|
-
{
|
|
870
|
-
type: "text",
|
|
871
|
-
text: `Error querying pull requests by commits: ${error instanceof Error ? error.message : String(error)}`,
|
|
872
|
-
},
|
|
873
|
-
],
|
|
1134
|
+
content: [{ type: "text", text: `Error querying pull requests by commits: ${errorMessage}` }],
|
|
874
1135
|
isError: true,
|
|
875
1136
|
};
|
|
876
1137
|
}
|