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