@azure-devops/mcp 2.2.2-nightly.20251125 → 2.2.2-nightly.20251127
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tools/repositories.js +570 -381
- package/dist/tools/test-plans.js +256 -123
- package/dist/version.js +1 -1
- package/package.json +3 -3
|
@@ -114,31 +114,40 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
114
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."),
|
|
115
115
|
labels: z.array(z.string()).optional().describe("Array of label names to add to the pull request after creation."),
|
|
116
116
|
}, async ({ repositoryId, sourceRefName, targetRefName, title, description, isDraft, workItems, forkSourceRepositoryId, labels }) => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
+
}
|
|
142
151
|
});
|
|
143
152
|
server.tool(REPO_TOOLS.create_branch, "Create a new branch in the repository.", {
|
|
144
153
|
repositoryId: z.string().describe("The ID of the repository where the branch will be created."),
|
|
@@ -146,67 +155,80 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
146
155
|
sourceBranchName: z.string().optional().default("main").describe("The name of the source branch to create the new branch from. Defaults to 'main'."),
|
|
147
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."),
|
|
148
157
|
}, async ({ repositoryId, branchName, sourceBranchName, sourceCommitId }) => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
+
};
|
|
155
200
|
try {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
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) {
|
|
159
204
|
return {
|
|
160
205
|
content: [
|
|
161
206
|
{
|
|
162
207
|
type: "text",
|
|
163
|
-
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";
|
|
215
|
+
return {
|
|
216
|
+
content: [
|
|
217
|
+
{
|
|
218
|
+
type: "text",
|
|
219
|
+
text: `Error creating branch '${branchName}': ${errorMessage}`,
|
|
164
220
|
},
|
|
165
221
|
],
|
|
166
222
|
isError: true,
|
|
167
223
|
};
|
|
168
224
|
}
|
|
169
|
-
commitId = branch.objectId;
|
|
170
225
|
}
|
|
171
226
|
catch (error) {
|
|
172
227
|
return {
|
|
173
228
|
content: [
|
|
174
229
|
{
|
|
175
230
|
type: "text",
|
|
176
|
-
text: `Error
|
|
177
|
-
},
|
|
178
|
-
],
|
|
179
|
-
isError: true,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// Create the new branch using updateRefs
|
|
184
|
-
const newRefName = `refs/heads/${branchName}`;
|
|
185
|
-
const refUpdate = {
|
|
186
|
-
name: newRefName,
|
|
187
|
-
newObjectId: commitId,
|
|
188
|
-
oldObjectId: "0000000000000000000000000000000000000000", // All zeros indicates creating a new ref
|
|
189
|
-
};
|
|
190
|
-
try {
|
|
191
|
-
const result = await gitApi.updateRefs([refUpdate], repositoryId);
|
|
192
|
-
// Check if the branch creation was successful
|
|
193
|
-
if (result && result.length > 0 && result[0].success) {
|
|
194
|
-
return {
|
|
195
|
-
content: [
|
|
196
|
-
{
|
|
197
|
-
type: "text",
|
|
198
|
-
text: `Branch '${branchName}' created successfully from '${sourceBranchName}' (${commitId})`,
|
|
199
|
-
},
|
|
200
|
-
],
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
const errorMessage = result && result.length > 0 && result[0].customMessage ? result[0].customMessage : "Unknown error occurred during branch creation";
|
|
205
|
-
return {
|
|
206
|
-
content: [
|
|
207
|
-
{
|
|
208
|
-
type: "text",
|
|
209
|
-
text: `Error creating branch '${branchName}': ${errorMessage}`,
|
|
231
|
+
text: `Error creating branch '${branchName}': ${error instanceof Error ? error.message : String(error)}`,
|
|
210
232
|
},
|
|
211
233
|
],
|
|
212
234
|
isError: true,
|
|
@@ -214,13 +236,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
214
236
|
}
|
|
215
237
|
}
|
|
216
238
|
catch (error) {
|
|
239
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
217
240
|
return {
|
|
218
|
-
content: [
|
|
219
|
-
{
|
|
220
|
-
type: "text",
|
|
221
|
-
text: `Error creating branch '${branchName}': ${error instanceof Error ? error.message : String(error)}`,
|
|
222
|
-
},
|
|
223
|
-
],
|
|
241
|
+
content: [{ type: "text", text: `Error creating branch: ${errorMessage}` }],
|
|
224
242
|
isError: true,
|
|
225
243
|
};
|
|
226
244
|
}
|
|
@@ -242,56 +260,65 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
242
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."),
|
|
243
261
|
bypassReason: z.string().optional().describe("Reason for bypassing branch policies. When provided, branch policies will be automatically bypassed during autocompletion."),
|
|
244
262
|
}, async ({ repositoryId, pullRequestId, title, description, isDraft, targetRefName, status, autoComplete, mergeStrategy, deleteSourceBranch, transitionWorkItems, bypassReason }) => {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (autoComplete) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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;
|
|
272
296
|
}
|
|
273
|
-
|
|
274
|
-
|
|
297
|
+
else {
|
|
298
|
+
updateRequest.autoCompleteSetBy = null;
|
|
299
|
+
updateRequest.completionOptions = null;
|
|
275
300
|
}
|
|
276
|
-
updateRequest.completionOptions = completionOptions;
|
|
277
301
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
+
};
|
|
281
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
|
+
};
|
|
282
314
|
}
|
|
283
|
-
|
|
284
|
-
|
|
315
|
+
catch (error) {
|
|
316
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
285
317
|
return {
|
|
286
|
-
content: [{ type: "text", text:
|
|
318
|
+
content: [{ type: "text", text: `Error updating pull request: ${errorMessage}` }],
|
|
287
319
|
isError: true,
|
|
288
320
|
};
|
|
289
321
|
}
|
|
290
|
-
const updatedPullRequest = await gitApi.updatePullRequest(updateRequest, repositoryId, pullRequestId);
|
|
291
|
-
const trimmedUpdatedPullRequest = trimPullRequest(updatedPullRequest, true);
|
|
292
|
-
return {
|
|
293
|
-
content: [{ type: "text", text: JSON.stringify(trimmedUpdatedPullRequest, null, 2) }],
|
|
294
|
-
};
|
|
295
322
|
});
|
|
296
323
|
server.tool(REPO_TOOLS.update_pull_request_reviewers, "Add or remove reviewers for an existing pull request.", {
|
|
297
324
|
repositoryId: z.string().describe("The ID of the repository where the pull request exists."),
|
|
@@ -299,29 +326,38 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
299
326
|
reviewerIds: z.array(z.string()).describe("List of reviewer ids to add or remove from the pull request."),
|
|
300
327
|
action: z.enum(["add", "remove"]).describe("Action to perform on the reviewers. Can be 'add' or 'remove'."),
|
|
301
328
|
}, async ({ repositoryId, pullRequestId, reviewerIds, action }) => {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
else {
|
|
320
|
-
for (const reviewerId of reviewerIds) {
|
|
321
|
-
await gitApi.deletePullRequestReviewer(repositoryId, pullRequestId, reviewerId);
|
|
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
|
+
};
|
|
322
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
|
+
};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
323
358
|
return {
|
|
324
|
-
content: [{ type: "text", text: `
|
|
359
|
+
content: [{ type: "text", text: `Error updating pull request reviewers: ${errorMessage}` }],
|
|
360
|
+
isError: true,
|
|
325
361
|
};
|
|
326
362
|
}
|
|
327
363
|
});
|
|
@@ -331,24 +367,33 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
331
367
|
skip: z.number().default(0).describe("The number of repositories to skip. Defaults to 0."),
|
|
332
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."),
|
|
333
369
|
}, async ({ project, top, skip, repoNameFilter }) => {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
+
}
|
|
352
397
|
});
|
|
353
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.", {
|
|
354
399
|
repositoryId: z.string().optional().describe("The ID of the repository where the pull requests are located."),
|
|
@@ -369,103 +414,112 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
369
414
|
sourceRefName: z.string().optional().describe("Filter pull requests from this source branch (e.g., 'refs/heads/feature-branch')."),
|
|
370
415
|
targetRefName: z.string().optional().describe("Filter pull requests into this target branch (e.g., 'refs/heads/main')."),
|
|
371
416
|
}, async ({ repositoryId, project, top, skip, created_by_me, created_by_user, i_am_reviewer, user_is_reviewer, status, sourceRefName, targetRefName }) => {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (!repositoryId && !project) {
|
|
379
|
-
return {
|
|
380
|
-
content: [
|
|
381
|
-
{
|
|
382
|
-
type: "text",
|
|
383
|
-
text: "Either repositoryId or project must be provided.",
|
|
384
|
-
},
|
|
385
|
-
],
|
|
386
|
-
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),
|
|
387
423
|
};
|
|
388
|
-
|
|
389
|
-
if (repositoryId) {
|
|
390
|
-
searchCriteria.repositoryId = repositoryId;
|
|
391
|
-
}
|
|
392
|
-
if (sourceRefName) {
|
|
393
|
-
searchCriteria.sourceRefName = sourceRefName;
|
|
394
|
-
}
|
|
395
|
-
if (targetRefName) {
|
|
396
|
-
searchCriteria.targetRefName = targetRefName;
|
|
397
|
-
}
|
|
398
|
-
if (created_by_user) {
|
|
399
|
-
try {
|
|
400
|
-
const userId = await getUserIdFromEmail(created_by_user, tokenProvider, connectionProvider, userAgentProvider);
|
|
401
|
-
searchCriteria.creatorId = userId;
|
|
402
|
-
}
|
|
403
|
-
catch (error) {
|
|
424
|
+
if (!repositoryId && !project) {
|
|
404
425
|
return {
|
|
405
426
|
content: [
|
|
406
427
|
{
|
|
407
428
|
type: "text",
|
|
408
|
-
text:
|
|
429
|
+
text: "Either repositoryId or project must be provided.",
|
|
409
430
|
},
|
|
410
431
|
],
|
|
411
432
|
isError: true,
|
|
412
433
|
};
|
|
413
434
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
|
|
417
|
-
const userId = data.authenticatedUser.id;
|
|
418
|
-
searchCriteria.creatorId = userId;
|
|
419
|
-
}
|
|
420
|
-
if (user_is_reviewer) {
|
|
421
|
-
try {
|
|
422
|
-
const reviewerUserId = await getUserIdFromEmail(user_is_reviewer, tokenProvider, connectionProvider, userAgentProvider);
|
|
423
|
-
searchCriteria.reviewerId = reviewerUserId;
|
|
435
|
+
if (repositoryId) {
|
|
436
|
+
searchCriteria.repositoryId = repositoryId;
|
|
424
437
|
}
|
|
425
|
-
|
|
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
|
|
426
501
|
return {
|
|
427
502
|
content: [
|
|
428
503
|
{
|
|
429
504
|
type: "text",
|
|
430
|
-
text:
|
|
505
|
+
text: "Either repositoryId or project must be provided.",
|
|
431
506
|
},
|
|
432
507
|
],
|
|
433
508
|
isError: true,
|
|
434
509
|
};
|
|
435
510
|
}
|
|
511
|
+
const filteredPullRequests = pullRequests?.map((pr) => trimPullRequest(pr));
|
|
512
|
+
return {
|
|
513
|
+
content: [{ type: "text", text: JSON.stringify(filteredPullRequests, null, 2) }],
|
|
514
|
+
};
|
|
436
515
|
}
|
|
437
|
-
|
|
438
|
-
const
|
|
439
|
-
const userId = data.authenticatedUser.id;
|
|
440
|
-
searchCriteria.reviewerId = userId;
|
|
441
|
-
}
|
|
442
|
-
let pullRequests;
|
|
443
|
-
if (repositoryId) {
|
|
444
|
-
pullRequests = await gitApi.getPullRequests(repositoryId, searchCriteria, project, // project
|
|
445
|
-
undefined, // maxCommentLength
|
|
446
|
-
skip, top);
|
|
447
|
-
}
|
|
448
|
-
else if (project) {
|
|
449
|
-
// If only project is provided, use getPullRequestsByProject
|
|
450
|
-
pullRequests = await gitApi.getPullRequestsByProject(project, searchCriteria, undefined, // maxCommentLength
|
|
451
|
-
skip, top);
|
|
452
|
-
}
|
|
453
|
-
else {
|
|
454
|
-
// 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";
|
|
455
518
|
return {
|
|
456
|
-
content: [
|
|
457
|
-
{
|
|
458
|
-
type: "text",
|
|
459
|
-
text: "Either repositoryId or project must be provided.",
|
|
460
|
-
},
|
|
461
|
-
],
|
|
519
|
+
content: [{ type: "text", text: `Error listing pull requests: ${errorMessage}` }],
|
|
462
520
|
isError: true,
|
|
463
521
|
};
|
|
464
522
|
}
|
|
465
|
-
const filteredPullRequests = pullRequests?.map((pr) => trimPullRequest(pr));
|
|
466
|
-
return {
|
|
467
|
-
content: [{ type: "text", text: JSON.stringify(filteredPullRequests, null, 2) }],
|
|
468
|
-
};
|
|
469
523
|
});
|
|
470
524
|
server.tool(REPO_TOOLS.list_pull_request_threads, "Retrieve a list of comment threads for a pull request.", {
|
|
471
525
|
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
@@ -483,38 +537,47 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
483
537
|
authorEmail: z.string().optional().describe("Filter threads by the email of the thread author (first comment author)."),
|
|
484
538
|
authorDisplayName: z.string().optional().describe("Filter threads by the display name of the thread author (first comment author). Case-insensitive partial matching."),
|
|
485
539
|
}, async ({ repositoryId, pullRequestId, project, iteration, baseIteration, top, skip, fullResponse, status, authorEmail, authorDisplayName }) => {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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";
|
|
576
|
+
return {
|
|
577
|
+
content: [{ type: "text", text: `Error listing pull request threads: ${errorMessage}` }],
|
|
578
|
+
isError: true,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
518
581
|
});
|
|
519
582
|
server.tool(REPO_TOOLS.list_pull_request_thread_comments, "Retrieve a list of comments in a pull request thread.", {
|
|
520
583
|
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
@@ -525,97 +588,184 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
525
588
|
skip: z.number().default(0).describe("The number of comments to skip."),
|
|
526
589
|
fullResponse: z.boolean().optional().default(false).describe("Return full comment JSON response instead of trimmed data."),
|
|
527
590
|
}, async ({ repositoryId, pullRequestId, threadId, project, top, skip, fullResponse }) => {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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);
|
|
534
604
|
return {
|
|
535
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
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";
|
|
610
|
+
return {
|
|
611
|
+
content: [{ type: "text", text: `Error listing pull request thread comments: ${errorMessage}` }],
|
|
612
|
+
isError: true,
|
|
536
613
|
};
|
|
537
614
|
}
|
|
538
|
-
// Return trimmed comment data focusing on essential information
|
|
539
|
-
const trimmedComments = trimComments(paginatedComments);
|
|
540
|
-
return {
|
|
541
|
-
content: [{ type: "text", text: JSON.stringify(trimmedComments, null, 2) }],
|
|
542
|
-
};
|
|
543
615
|
});
|
|
544
616
|
server.tool(REPO_TOOLS.list_branches_by_repo, "Retrieve a list of branches for a given repository.", {
|
|
545
617
|
repositoryId: z.string().describe("The ID of the repository where the branches are located."),
|
|
546
618
|
top: z.number().default(100).describe("The maximum number of branches to return. Defaults to 100."),
|
|
547
619
|
filterContains: z.string().optional().describe("Filter to find branches that contain this string in their name."),
|
|
548
620
|
}, async ({ repositoryId, top, filterContains }) => {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
+
}
|
|
556
637
|
});
|
|
557
638
|
server.tool(REPO_TOOLS.list_my_branches_by_repo, "Retrieve a list of my branches for a given repository Id.", {
|
|
558
639
|
repositoryId: z.string().describe("The ID of the repository where the branches are located."),
|
|
559
640
|
top: z.number().default(100).describe("The maximum number of branches to return."),
|
|
560
641
|
filterContains: z.string().optional().describe("Filter to find branches that contain this string in their name."),
|
|
561
642
|
}, async ({ repositoryId, top, filterContains }) => {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
+
}
|
|
569
659
|
});
|
|
570
660
|
server.tool(REPO_TOOLS.get_repo_by_name_or_id, "Get the repository by project and repository name or ID.", {
|
|
571
661
|
project: z.string().describe("Project name or ID where the repository is located."),
|
|
572
662
|
repositoryNameOrId: z.string().describe("Repository name or ID."),
|
|
573
663
|
}, async ({ project, repositoryNameOrId }) => {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
+
}
|
|
584
686
|
});
|
|
585
687
|
server.tool(REPO_TOOLS.get_branch_by_name, "Get a branch by its name.", {
|
|
586
688
|
repositoryId: z.string().describe("The ID of the repository where the branch is located."),
|
|
587
689
|
branchName: z.string().describe("The name of the branch to retrieve, e.g., 'main' or 'feature-branch'."),
|
|
588
690
|
}, async ({ repositoryId, branchName }) => {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
+
}
|
|
594
707
|
return {
|
|
595
|
-
content: [
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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}` }],
|
|
601
715
|
isError: true,
|
|
602
716
|
};
|
|
603
717
|
}
|
|
604
|
-
return {
|
|
605
|
-
content: [{ type: "text", text: JSON.stringify(branch, null, 2) }],
|
|
606
|
-
};
|
|
607
718
|
});
|
|
608
719
|
server.tool(REPO_TOOLS.get_pull_request_by_id, "Get a pull request by its ID.", {
|
|
609
720
|
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
610
721
|
pullRequestId: z.number().describe("The ID of the pull request to retrieve."),
|
|
611
722
|
includeWorkItemRefs: z.boolean().optional().default(false).describe("Whether to reference work items associated with the pull request."),
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
+
}
|
|
619
769
|
});
|
|
620
770
|
server.tool(REPO_TOOLS.reply_to_comment, "Replies to a specific comment on a pull request.", {
|
|
621
771
|
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
@@ -625,24 +775,33 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
625
775
|
project: z.string().optional().describe("Project ID or project name (optional)"),
|
|
626
776
|
fullResponse: z.boolean().optional().default(false).describe("Return full comment JSON response instead of a simple confirmation message."),
|
|
627
777
|
}, async ({ repositoryId, pullRequestId, threadId, content, project, fullResponse }) => {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
+
}
|
|
633
794
|
return {
|
|
634
|
-
content: [{ type: "text", text: `
|
|
635
|
-
isError: true,
|
|
795
|
+
content: [{ type: "text", text: `Comment successfully added to thread ${threadId}.` }],
|
|
636
796
|
};
|
|
637
797
|
}
|
|
638
|
-
|
|
798
|
+
catch (error) {
|
|
799
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
639
800
|
return {
|
|
640
|
-
content: [{ type: "text", text:
|
|
801
|
+
content: [{ type: "text", text: `Error replying to comment: ${errorMessage}` }],
|
|
802
|
+
isError: true,
|
|
641
803
|
};
|
|
642
804
|
}
|
|
643
|
-
return {
|
|
644
|
-
content: [{ type: "text", text: `Comment successfully added to thread ${threadId}.` }],
|
|
645
|
-
};
|
|
646
805
|
});
|
|
647
806
|
server.tool(REPO_TOOLS.create_pull_request_thread, "Creates a new comment thread on a pull request.", {
|
|
648
807
|
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
@@ -669,42 +828,66 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
669
828
|
.optional()
|
|
670
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)"),
|
|
671
830
|
}, async ({ repositoryId, pullRequestId, content, project, filePath, status, rightFileStartLine, rightFileStartOffset, rightFileEndLine, rightFileEndOffset }) => {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
if (rightFileStartLine
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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;
|
|
684
852
|
}
|
|
685
|
-
threadContext.rightFileStart.offset = rightFileStartOffset;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
if (rightFileEndLine !== undefined) {
|
|
689
|
-
if (rightFileStartLine === undefined) {
|
|
690
|
-
throw new Error("rightFileEndLine must only be specified if rightFileStartLine is also specified.");
|
|
691
|
-
}
|
|
692
|
-
if (rightFileEndLine < 1) {
|
|
693
|
-
throw new Error("rightFileEndLine must be greater than or equal to 1.");
|
|
694
853
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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;
|
|
699
876
|
}
|
|
700
|
-
threadContext.rightFileEnd.offset = rightFileEndOffset;
|
|
701
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
|
+
};
|
|
702
890
|
}
|
|
703
|
-
const thread = await gitApi.createThread({ comments: [{ content: content }], threadContext: threadContext, status: CommentThreadStatus[status] }, repositoryId, pullRequestId, project);
|
|
704
|
-
const trimmedThread = trimPullRequestThread(thread);
|
|
705
|
-
return {
|
|
706
|
-
content: [{ type: "text", text: JSON.stringify(trimmedThread, null, 2) }],
|
|
707
|
-
};
|
|
708
891
|
});
|
|
709
892
|
server.tool(REPO_TOOLS.resolve_comment, "Resolves a specific comment thread on a pull request.", {
|
|
710
893
|
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
@@ -712,25 +895,34 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
712
895
|
threadId: z.number().describe("The ID of the thread to be resolved."),
|
|
713
896
|
fullResponse: z.boolean().optional().default(false).describe("Return full thread JSON response instead of a simple confirmation message."),
|
|
714
897
|
}, async ({ repositoryId, pullRequestId, threadId, fullResponse }) => {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
898
|
+
try {
|
|
899
|
+
const connection = await connectionProvider();
|
|
900
|
+
const gitApi = await connection.getGitApi();
|
|
901
|
+
const thread = await gitApi.updateThread({ status: 2 }, // 2 corresponds to "Resolved" status
|
|
902
|
+
repositoryId, pullRequestId, threadId);
|
|
903
|
+
// Check if the thread was successfully resolved
|
|
904
|
+
if (!thread) {
|
|
905
|
+
return {
|
|
906
|
+
content: [{ type: "text", text: `Error: Failed to resolve thread ${threadId}. The thread status was not updated successfully.` }],
|
|
907
|
+
isError: true,
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
if (fullResponse) {
|
|
911
|
+
return {
|
|
912
|
+
content: [{ type: "text", text: JSON.stringify(thread, null, 2) }],
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
return {
|
|
916
|
+
content: [{ type: "text", text: `Thread ${threadId} was successfully resolved.` }],
|
|
724
917
|
};
|
|
725
918
|
}
|
|
726
|
-
|
|
919
|
+
catch (error) {
|
|
920
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
727
921
|
return {
|
|
728
|
-
content: [{ type: "text", text:
|
|
922
|
+
content: [{ type: "text", text: `Error resolving comment: ${errorMessage}` }],
|
|
923
|
+
isError: true,
|
|
729
924
|
};
|
|
730
925
|
}
|
|
731
|
-
return {
|
|
732
|
-
content: [{ type: "text", text: `Thread ${threadId} was successfully resolved.` }],
|
|
733
|
-
};
|
|
734
926
|
});
|
|
735
927
|
const gitVersionTypeStrings = Object.values(GitVersionType).filter((value) => typeof value === "string");
|
|
736
928
|
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.", {
|
|
@@ -820,7 +1012,8 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
820
1012
|
if (historySimplificationMode) {
|
|
821
1013
|
// Note: This parameter might not be directly supported by all ADO API versions
|
|
822
1014
|
// but we'll include it in the criteria for forward compatibility
|
|
823
|
-
|
|
1015
|
+
const extendedCriteria = searchCriteria;
|
|
1016
|
+
extendedCriteria.historySimplificationMode = historySimplificationMode;
|
|
824
1017
|
}
|
|
825
1018
|
if (version) {
|
|
826
1019
|
const itemVersion = {
|
|
@@ -892,13 +1085,9 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
892
1085
|
};
|
|
893
1086
|
}
|
|
894
1087
|
catch (error) {
|
|
1088
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
895
1089
|
return {
|
|
896
|
-
content: [
|
|
897
|
-
{
|
|
898
|
-
type: "text",
|
|
899
|
-
text: `Error querying pull requests by commits: ${error instanceof Error ? error.message : String(error)}`,
|
|
900
|
-
},
|
|
901
|
-
],
|
|
1090
|
+
content: [{ type: "text", text: `Error querying pull requests by commits: ${errorMessage}` }],
|
|
902
1091
|
isError: true,
|
|
903
1092
|
};
|
|
904
1093
|
}
|