@azure-devops/mcp 1.2.0-daily.20250715 → 1.2.1
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 +163 -98
- package/dist/index.js +32 -8
- package/dist/tools/builds.js +14 -7
- package/dist/tools/releases.js +26 -7
- package/dist/tools/repos.js +148 -17
- package/dist/tools/search.js +85 -80
- package/dist/tools/workitems.js +105 -89
- package/dist/utils.js +26 -0
- package/dist/version.js +1 -1
- package/package.json +6 -5
package/dist/tools/repos.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { PullRequestStatus, GitVersionType, GitPullRequestQueryType, } from "azure-devops-node-api/interfaces/GitInterfaces.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { getCurrentUserDetails } from "./auth.js";
|
|
6
|
+
import { getEnumKeys } from "../utils.js";
|
|
6
7
|
const REPO_TOOLS = {
|
|
7
8
|
list_repos_by_project: "repo_list_repos_by_project",
|
|
8
9
|
list_pull_requests_by_repo: "repo_list_pull_requests_by_repo",
|
|
@@ -18,6 +19,7 @@ const REPO_TOOLS = {
|
|
|
18
19
|
update_pull_request_status: "repo_update_pull_request_status",
|
|
19
20
|
update_pull_request_reviewers: "repo_update_pull_request_reviewers",
|
|
20
21
|
reply_to_comment: "repo_reply_to_comment",
|
|
22
|
+
create_pull_request_thread: "repo_create_pull_request_thread",
|
|
21
23
|
resolve_comment: "repo_resolve_comment",
|
|
22
24
|
search_commits: "repo_search_commits",
|
|
23
25
|
list_pull_requests_by_commits: "repo_list_pull_requests_by_commits",
|
|
@@ -30,17 +32,37 @@ function branchesFilterOutIrrelevantProperties(branches, top) {
|
|
|
30
32
|
.sort((a, b) => b.localeCompare(a))
|
|
31
33
|
.slice(0, top);
|
|
32
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Trims comment data to essential properties, filtering out deleted comments
|
|
37
|
+
* @param comments Array of comments to trim (can be undefined/null)
|
|
38
|
+
* @returns Array of trimmed comment objects with essential properties only
|
|
39
|
+
*/
|
|
40
|
+
function trimComments(comments) {
|
|
41
|
+
return comments
|
|
42
|
+
?.filter((comment) => !comment.isDeleted) // Exclude deleted comments
|
|
43
|
+
?.map((comment) => ({
|
|
44
|
+
id: comment.id,
|
|
45
|
+
author: {
|
|
46
|
+
displayName: comment.author?.displayName,
|
|
47
|
+
uniqueName: comment.author?.uniqueName,
|
|
48
|
+
},
|
|
49
|
+
content: comment.content,
|
|
50
|
+
publishedDate: comment.publishedDate,
|
|
51
|
+
lastUpdatedDate: comment.lastUpdatedDate,
|
|
52
|
+
lastContentUpdatedDate: comment.lastContentUpdatedDate,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
33
55
|
function pullRequestStatusStringToInt(status) {
|
|
34
56
|
switch (status) {
|
|
35
|
-
case "
|
|
57
|
+
case "Abandoned":
|
|
36
58
|
return PullRequestStatus.Abandoned.valueOf();
|
|
37
|
-
case "
|
|
59
|
+
case "Active":
|
|
38
60
|
return PullRequestStatus.Active.valueOf();
|
|
39
|
-
case "
|
|
61
|
+
case "All":
|
|
40
62
|
return PullRequestStatus.All.valueOf();
|
|
41
|
-
case "
|
|
63
|
+
case "Completed":
|
|
42
64
|
return PullRequestStatus.Completed.valueOf();
|
|
43
|
-
case "
|
|
65
|
+
case "NotSet":
|
|
44
66
|
return PullRequestStatus.NotSet.valueOf();
|
|
45
67
|
default:
|
|
46
68
|
throw new Error(`Unknown pull request status: ${status}`);
|
|
@@ -79,11 +101,11 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
|
|
|
79
101
|
server.tool(REPO_TOOLS.update_pull_request_status, "Update status of an existing pull request to active or abandoned.", {
|
|
80
102
|
repositoryId: z.string().describe("The ID of the repository where the pull request exists."),
|
|
81
103
|
pullRequestId: z.number().describe("The ID of the pull request to be published."),
|
|
82
|
-
status: z.enum(["
|
|
104
|
+
status: z.enum(["Active", "Abandoned"]).describe("The new status of the pull request. Can be 'Active' or 'Abandoned'."),
|
|
83
105
|
}, async ({ repositoryId, pullRequestId, status }) => {
|
|
84
106
|
const connection = await connectionProvider();
|
|
85
107
|
const gitApi = await connection.getGitApi();
|
|
86
|
-
const statusValue = status === "
|
|
108
|
+
const statusValue = status === "Active" ? PullRequestStatus.Active.valueOf() : PullRequestStatus.Abandoned.valueOf();
|
|
87
109
|
const updatedPullRequest = await gitApi.updatePullRequest({ status: statusValue }, repositoryId, pullRequestId);
|
|
88
110
|
return {
|
|
89
111
|
content: [{ type: "text", text: JSON.stringify(updatedPullRequest, null, 2) }],
|
|
@@ -144,7 +166,10 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
|
|
|
144
166
|
skip: z.number().default(0).describe("The number of pull requests to skip."),
|
|
145
167
|
created_by_me: z.boolean().default(false).describe("Filter pull requests created by the current user."),
|
|
146
168
|
i_am_reviewer: z.boolean().default(false).describe("Filter pull requests where the current user is a reviewer."),
|
|
147
|
-
status: z
|
|
169
|
+
status: z
|
|
170
|
+
.enum(getEnumKeys(PullRequestStatus))
|
|
171
|
+
.default("Active")
|
|
172
|
+
.describe("Filter pull requests by status. Defaults to 'Active'."),
|
|
148
173
|
}, async ({ repositoryId, top, skip, created_by_me, i_am_reviewer, status }) => {
|
|
149
174
|
const connection = await connectionProvider();
|
|
150
175
|
const gitApi = await connection.getGitApi();
|
|
@@ -189,7 +214,10 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
|
|
|
189
214
|
skip: z.number().default(0).describe("The number of pull requests to skip."),
|
|
190
215
|
created_by_me: z.boolean().default(false).describe("Filter pull requests created by the current user."),
|
|
191
216
|
i_am_reviewer: z.boolean().default(false).describe("Filter pull requests where the current user is a reviewer."),
|
|
192
|
-
status: z
|
|
217
|
+
status: z
|
|
218
|
+
.enum(getEnumKeys(PullRequestStatus))
|
|
219
|
+
.default("Active")
|
|
220
|
+
.describe("Filter pull requests by status. Defaults to 'Active'."),
|
|
193
221
|
}, async ({ project, top, skip, created_by_me, i_am_reviewer, status }) => {
|
|
194
222
|
const connection = await connectionProvider();
|
|
195
223
|
const gitApi = await connection.getGitApi();
|
|
@@ -235,13 +263,27 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
|
|
|
235
263
|
baseIteration: z.number().optional().describe("The base iteration ID for which to retrieve threads. Optional, defaults to the latest base iteration."),
|
|
236
264
|
top: z.number().default(100).describe("The maximum number of threads to return."),
|
|
237
265
|
skip: z.number().default(0).describe("The number of threads to skip."),
|
|
238
|
-
|
|
266
|
+
fullResponse: z.boolean().optional().default(false).describe("Return full thread JSON response instead of trimmed data."),
|
|
267
|
+
}, async ({ repositoryId, pullRequestId, project, iteration, baseIteration, top, skip, fullResponse }) => {
|
|
239
268
|
const connection = await connectionProvider();
|
|
240
269
|
const gitApi = await connection.getGitApi();
|
|
241
270
|
const threads = await gitApi.getThreads(repositoryId, pullRequestId, project, iteration, baseIteration);
|
|
242
271
|
const paginatedThreads = threads?.sort((a, b) => (a.id ?? 0) - (b.id ?? 0)).slice(skip, skip + top);
|
|
272
|
+
if (fullResponse) {
|
|
273
|
+
return {
|
|
274
|
+
content: [{ type: "text", text: JSON.stringify(paginatedThreads, null, 2) }],
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
// Return trimmed thread data focusing on essential information
|
|
278
|
+
const trimmedThreads = paginatedThreads?.map((thread) => ({
|
|
279
|
+
id: thread.id,
|
|
280
|
+
publishedDate: thread.publishedDate,
|
|
281
|
+
lastUpdatedDate: thread.lastUpdatedDate,
|
|
282
|
+
status: thread.status,
|
|
283
|
+
comments: trimComments(thread.comments),
|
|
284
|
+
}));
|
|
243
285
|
return {
|
|
244
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
286
|
+
content: [{ type: "text", text: JSON.stringify(trimmedThreads, null, 2) }],
|
|
245
287
|
};
|
|
246
288
|
});
|
|
247
289
|
server.tool(REPO_TOOLS.list_pull_request_thread_comments, "Retrieve a list of comments in a pull request thread.", {
|
|
@@ -251,14 +293,22 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
|
|
|
251
293
|
project: z.string().optional().describe("Project ID or project name (optional)"),
|
|
252
294
|
top: z.number().default(100).describe("The maximum number of comments to return."),
|
|
253
295
|
skip: z.number().default(0).describe("The number of comments to skip."),
|
|
254
|
-
|
|
296
|
+
fullResponse: z.boolean().optional().default(false).describe("Return full comment JSON response instead of trimmed data."),
|
|
297
|
+
}, async ({ repositoryId, pullRequestId, threadId, project, top, skip, fullResponse }) => {
|
|
255
298
|
const connection = await connectionProvider();
|
|
256
299
|
const gitApi = await connection.getGitApi();
|
|
257
300
|
// Get thread comments - GitApi uses getComments for retrieving comments from a specific thread
|
|
258
301
|
const comments = await gitApi.getComments(repositoryId, pullRequestId, threadId, project);
|
|
259
302
|
const paginatedComments = comments?.sort((a, b) => (a.id ?? 0) - (b.id ?? 0)).slice(skip, skip + top);
|
|
303
|
+
if (fullResponse) {
|
|
304
|
+
return {
|
|
305
|
+
content: [{ type: "text", text: JSON.stringify(paginatedComments, null, 2) }],
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
// Return trimmed comment data focusing on essential information
|
|
309
|
+
const trimmedComments = trimComments(paginatedComments);
|
|
260
310
|
return {
|
|
261
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
311
|
+
content: [{ type: "text", text: JSON.stringify(trimmedComments, null, 2) }],
|
|
262
312
|
};
|
|
263
313
|
});
|
|
264
314
|
server.tool(REPO_TOOLS.list_branches_by_repo, "Retrieve a list of branches for a given repository.", {
|
|
@@ -339,25 +389,106 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
|
|
|
339
389
|
threadId: z.number().describe("The ID of the thread to which the comment will be added."),
|
|
340
390
|
content: z.string().describe("The content of the comment to be added."),
|
|
341
391
|
project: z.string().optional().describe("Project ID or project name (optional)"),
|
|
342
|
-
|
|
392
|
+
fullResponse: z.boolean().optional().default(false).describe("Return full comment JSON response instead of a simple confirmation message."),
|
|
393
|
+
}, async ({ repositoryId, pullRequestId, threadId, content, project, fullResponse }) => {
|
|
343
394
|
const connection = await connectionProvider();
|
|
344
395
|
const gitApi = await connection.getGitApi();
|
|
345
396
|
const comment = await gitApi.createComment({ content }, repositoryId, pullRequestId, threadId, project);
|
|
397
|
+
// Check if the comment was successfully created
|
|
398
|
+
if (!comment) {
|
|
399
|
+
return {
|
|
400
|
+
content: [{ type: "text", text: `Error: Failed to add comment to thread ${threadId}. The comment was not created successfully.` }],
|
|
401
|
+
isError: true,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
if (fullResponse) {
|
|
405
|
+
return {
|
|
406
|
+
content: [{ type: "text", text: JSON.stringify(comment, null, 2) }],
|
|
407
|
+
};
|
|
408
|
+
}
|
|
346
409
|
return {
|
|
347
|
-
content: [{ type: "text", text:
|
|
410
|
+
content: [{ type: "text", text: `Comment successfully added to thread ${threadId}.` }],
|
|
411
|
+
};
|
|
412
|
+
});
|
|
413
|
+
server.tool(REPO_TOOLS.create_pull_request_thread, "Creates a new comment thread on a pull request.", {
|
|
414
|
+
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
415
|
+
pullRequestId: z.number().describe("The ID of the pull request where the comment thread exists."),
|
|
416
|
+
content: z.string().describe("The content of the comment to be added."),
|
|
417
|
+
project: z.string().optional().describe("Project ID or project name (optional)"),
|
|
418
|
+
filePath: z.string().optional().describe("The path of the file where the comment thread will be created. (optional)"),
|
|
419
|
+
rightFileStartLine: z.number().optional().describe("Position of first character of the thread's span in right file. The line number of a thread's position. Starts at 1. (optional)"),
|
|
420
|
+
rightFileStartOffset: z
|
|
421
|
+
.number()
|
|
422
|
+
.optional()
|
|
423
|
+
.describe("Position of first character of the thread's span in right file. The line number of a thread's position. The character offset of a thread's position inside of a line. Starts at 1. Must only be set if rightFileStartLine is also specified. (optional)"),
|
|
424
|
+
rightFileEndLine: z
|
|
425
|
+
.number()
|
|
426
|
+
.optional()
|
|
427
|
+
.describe("Position of last character of the thread's span in right file. The line number of a thread's position. Starts at 1. Must only be set if rightFileStartLine is also specified. (optional)"),
|
|
428
|
+
rightFileEndOffset: z
|
|
429
|
+
.number()
|
|
430
|
+
.optional()
|
|
431
|
+
.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)"),
|
|
432
|
+
}, async ({ repositoryId, pullRequestId, content, project, filePath, rightFileStartLine, rightFileStartOffset, rightFileEndLine, rightFileEndOffset }) => {
|
|
433
|
+
const connection = await connectionProvider();
|
|
434
|
+
const gitApi = await connection.getGitApi();
|
|
435
|
+
const threadContext = { filePath: filePath };
|
|
436
|
+
if (rightFileStartLine !== undefined) {
|
|
437
|
+
if (rightFileStartLine < 1) {
|
|
438
|
+
throw new Error("rightFileStartLine must be greater than or equal to 1.");
|
|
439
|
+
}
|
|
440
|
+
threadContext.rightFileStart = { line: rightFileStartLine };
|
|
441
|
+
if (rightFileStartOffset !== undefined) {
|
|
442
|
+
if (rightFileStartOffset < 1) {
|
|
443
|
+
throw new Error("rightFileStartOffset must be greater than or equal to 1.");
|
|
444
|
+
}
|
|
445
|
+
threadContext.rightFileStart.offset = rightFileStartOffset;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (rightFileEndLine !== undefined) {
|
|
449
|
+
if (rightFileStartLine === undefined) {
|
|
450
|
+
throw new Error("rightFileEndLine must only be specified if rightFileStartLine is also specified.");
|
|
451
|
+
}
|
|
452
|
+
if (rightFileEndLine < 1) {
|
|
453
|
+
throw new Error("rightFileEndLine must be greater than or equal to 1.");
|
|
454
|
+
}
|
|
455
|
+
threadContext.rightFileEnd = { line: rightFileEndLine };
|
|
456
|
+
if (rightFileEndOffset !== undefined) {
|
|
457
|
+
if (rightFileEndOffset < 1) {
|
|
458
|
+
throw new Error("rightFileEndOffset must be greater than or equal to 1.");
|
|
459
|
+
}
|
|
460
|
+
threadContext.rightFileEnd.offset = rightFileEndOffset;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
const thread = await gitApi.createThread({ comments: [{ content: content }], threadContext: threadContext }, repositoryId, pullRequestId, project);
|
|
464
|
+
return {
|
|
465
|
+
content: [{ type: "text", text: JSON.stringify(thread, null, 2) }],
|
|
348
466
|
};
|
|
349
467
|
});
|
|
350
468
|
server.tool(REPO_TOOLS.resolve_comment, "Resolves a specific comment thread on a pull request.", {
|
|
351
469
|
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
352
470
|
pullRequestId: z.number().describe("The ID of the pull request where the comment thread exists."),
|
|
353
471
|
threadId: z.number().describe("The ID of the thread to be resolved."),
|
|
354
|
-
|
|
472
|
+
fullResponse: z.boolean().optional().default(false).describe("Return full thread JSON response instead of a simple confirmation message."),
|
|
473
|
+
}, async ({ repositoryId, pullRequestId, threadId, fullResponse }) => {
|
|
355
474
|
const connection = await connectionProvider();
|
|
356
475
|
const gitApi = await connection.getGitApi();
|
|
357
476
|
const thread = await gitApi.updateThread({ status: 2 }, // 2 corresponds to "Resolved" status
|
|
358
477
|
repositoryId, pullRequestId, threadId);
|
|
478
|
+
// Check if the thread was successfully resolved
|
|
479
|
+
if (!thread) {
|
|
480
|
+
return {
|
|
481
|
+
content: [{ type: "text", text: `Error: Failed to resolve thread ${threadId}. The thread status was not updated successfully.` }],
|
|
482
|
+
isError: true,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
if (fullResponse) {
|
|
486
|
+
return {
|
|
487
|
+
content: [{ type: "text", text: JSON.stringify(thread, null, 2) }],
|
|
488
|
+
};
|
|
489
|
+
}
|
|
359
490
|
return {
|
|
360
|
-
content: [{ type: "text", text:
|
|
491
|
+
content: [{ type: "text", text: `Thread ${threadId} was successfully resolved.` }],
|
|
361
492
|
};
|
|
362
493
|
});
|
|
363
494
|
const gitVersionTypeStrings = Object.values(GitVersionType).filter((value) => typeof value === "string");
|
package/dist/tools/search.js
CHANGED
|
@@ -10,38 +10,37 @@ const SEARCH_TOOLS = {
|
|
|
10
10
|
search_workitem: "search_workitem",
|
|
11
11
|
};
|
|
12
12
|
function configureSearchTools(server, tokenProvider, connectionProvider, userAgentProvider) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
filters: z
|
|
24
|
-
.object({
|
|
25
|
-
Project: z.array(z.string()).optional().describe("Filter in these projects"),
|
|
26
|
-
Repository: z.array(z.string()).optional().describe("Filter in these repositories"),
|
|
27
|
-
Path: z.array(z.string()).optional().describe("Filter in these paths"),
|
|
28
|
-
Branch: z.array(z.string()).optional().describe("Filter in these branches"),
|
|
29
|
-
CodeElement: z.array(z.string()).optional().describe("Filter for these code elements (e.g., classes, functions, symbols)"),
|
|
30
|
-
// Note: CodeElement is optional and can be used to filter results by specific code elements.
|
|
31
|
-
// It can be a string or an array of strings.
|
|
32
|
-
// If provided, the search will only return results that match the specified code elements.
|
|
33
|
-
// This is useful for narrowing down the search to specific classes, functions, definitions, or symbols.
|
|
34
|
-
// Example: CodeElement: ["MyClass", "MyFunction"]
|
|
35
|
-
})
|
|
36
|
-
.partial()
|
|
37
|
-
.optional(),
|
|
38
|
-
includeFacets: z.boolean().optional(),
|
|
39
|
-
})
|
|
40
|
-
.strict(),
|
|
41
|
-
}, async ({ searchRequest }) => {
|
|
13
|
+
server.tool(SEARCH_TOOLS.search_code, "Search Azure DevOps Repositories for a given search text", {
|
|
14
|
+
searchText: z.string().describe("Keywords to search for in code repositories"),
|
|
15
|
+
project: z.array(z.string()).optional().describe("Filter by projects"),
|
|
16
|
+
repository: z.array(z.string()).optional().describe("Filter by repositories"),
|
|
17
|
+
path: z.array(z.string()).optional().describe("Filter by paths"),
|
|
18
|
+
branch: z.array(z.string()).optional().describe("Filter by branches"),
|
|
19
|
+
includeFacets: z.boolean().default(false).describe("Include facets in the search results"),
|
|
20
|
+
skip: z.number().default(0).describe("Number of results to skip"),
|
|
21
|
+
top: z.number().default(5).describe("Maximum number of results to return"),
|
|
22
|
+
}, async ({ searchText, project, repository, path, branch, includeFacets, skip, top }) => {
|
|
42
23
|
const accessToken = await tokenProvider();
|
|
43
24
|
const connection = await connectionProvider();
|
|
44
25
|
const url = `https://almsearch.dev.azure.com/${orgName}/_apis/search/codesearchresults?api-version=${apiVersion}`;
|
|
26
|
+
const requestBody = {
|
|
27
|
+
searchText,
|
|
28
|
+
includeFacets,
|
|
29
|
+
$skip: skip,
|
|
30
|
+
$top: top,
|
|
31
|
+
};
|
|
32
|
+
const filters = {};
|
|
33
|
+
if (project && project.length > 0)
|
|
34
|
+
filters.Project = project;
|
|
35
|
+
if (repository && repository.length > 0)
|
|
36
|
+
filters.Repository = repository;
|
|
37
|
+
if (path && path.length > 0)
|
|
38
|
+
filters.Path = path;
|
|
39
|
+
if (branch && branch.length > 0)
|
|
40
|
+
filters.Branch = branch;
|
|
41
|
+
if (Object.keys(filters).length > 0) {
|
|
42
|
+
requestBody.filters = filters;
|
|
43
|
+
}
|
|
45
44
|
const response = await fetch(url, {
|
|
46
45
|
method: "POST",
|
|
47
46
|
headers: {
|
|
@@ -49,44 +48,43 @@ function configureSearchTools(server, tokenProvider, connectionProvider, userAge
|
|
|
49
48
|
"Authorization": `Bearer ${accessToken.token}`,
|
|
50
49
|
"User-Agent": userAgentProvider(),
|
|
51
50
|
},
|
|
52
|
-
body: JSON.stringify(
|
|
51
|
+
body: JSON.stringify(requestBody),
|
|
53
52
|
});
|
|
54
53
|
if (!response.ok) {
|
|
55
54
|
throw new Error(`Azure DevOps Code Search API error: ${response.status} ${response.statusText}`);
|
|
56
55
|
}
|
|
57
56
|
const resultText = await response.text();
|
|
58
57
|
const resultJson = JSON.parse(resultText);
|
|
59
|
-
const topResults = Array.isArray(resultJson.results) ? resultJson.results.slice(0, Math.min(searchRequest.$top, resultJson.results.length)) : [];
|
|
60
58
|
const gitApi = await connection.getGitApi();
|
|
61
|
-
const combinedResults = await fetchCombinedResults(
|
|
59
|
+
const combinedResults = await fetchCombinedResults(resultJson.results ?? [], gitApi);
|
|
62
60
|
return {
|
|
63
61
|
content: [{ type: "text", text: resultText + JSON.stringify(combinedResults) }],
|
|
64
62
|
};
|
|
65
63
|
});
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
$skip: z.number().default(0).describe("Number of results to skip (for pagination)"),
|
|
75
|
-
$top: z.number().default(10).describe("Number of results to return (for pagination)"),
|
|
76
|
-
filters: z
|
|
77
|
-
.object({
|
|
78
|
-
Project: z.array(z.string()).optional().describe("Filter in these projects"),
|
|
79
|
-
Wiki: z.array(z.string()).optional().describe("Filter in these wiki names"),
|
|
80
|
-
})
|
|
81
|
-
.partial()
|
|
82
|
-
.optional()
|
|
83
|
-
.describe("Filters to apply to the search text"),
|
|
84
|
-
includeFacets: z.boolean().optional(),
|
|
85
|
-
})
|
|
86
|
-
.strict(),
|
|
87
|
-
}, async ({ searchRequest }) => {
|
|
64
|
+
server.tool(SEARCH_TOOLS.search_wiki, "Search Azure DevOps Wiki for a given search text", {
|
|
65
|
+
searchText: z.string().describe("Keywords to search for wiki pages"),
|
|
66
|
+
project: z.array(z.string()).optional().describe("Filter by projects"),
|
|
67
|
+
wiki: z.array(z.string()).optional().describe("Filter by wiki names"),
|
|
68
|
+
includeFacets: z.boolean().default(false).describe("Include facets in the search results"),
|
|
69
|
+
skip: z.number().default(0).describe("Number of results to skip"),
|
|
70
|
+
top: z.number().default(10).describe("Maximum number of results to return"),
|
|
71
|
+
}, async ({ searchText, project, wiki, includeFacets, skip, top }) => {
|
|
88
72
|
const accessToken = await tokenProvider();
|
|
89
73
|
const url = `https://almsearch.dev.azure.com/${orgName}/_apis/search/wikisearchresults?api-version=${apiVersion}`;
|
|
74
|
+
const requestBody = {
|
|
75
|
+
searchText,
|
|
76
|
+
includeFacets,
|
|
77
|
+
$skip: skip,
|
|
78
|
+
$top: top,
|
|
79
|
+
};
|
|
80
|
+
const filters = {};
|
|
81
|
+
if (project && project.length > 0)
|
|
82
|
+
filters.Project = project;
|
|
83
|
+
if (wiki && wiki.length > 0)
|
|
84
|
+
filters.Wiki = wiki;
|
|
85
|
+
if (Object.keys(filters).length > 0) {
|
|
86
|
+
requestBody.filters = filters;
|
|
87
|
+
}
|
|
90
88
|
const response = await fetch(url, {
|
|
91
89
|
method: "POST",
|
|
92
90
|
headers: {
|
|
@@ -94,7 +92,7 @@ function configureSearchTools(server, tokenProvider, connectionProvider, userAge
|
|
|
94
92
|
"Authorization": `Bearer ${accessToken.token}`,
|
|
95
93
|
"User-Agent": userAgentProvider(),
|
|
96
94
|
},
|
|
97
|
-
body: JSON.stringify(
|
|
95
|
+
body: JSON.stringify(requestBody),
|
|
98
96
|
});
|
|
99
97
|
if (!response.ok) {
|
|
100
98
|
throw new Error(`Azure DevOps Wiki Search API error: ${response.status} ${response.statusText}`);
|
|
@@ -104,32 +102,39 @@ function configureSearchTools(server, tokenProvider, connectionProvider, userAge
|
|
|
104
102
|
content: [{ type: "text", text: result }],
|
|
105
103
|
};
|
|
106
104
|
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
.object({
|
|
119
|
-
"System.TeamProject": z.array(z.string()).optional().describe("Filter by team project"),
|
|
120
|
-
"System.AreaPath": z.array(z.string()).optional().describe("Filter by area path"),
|
|
121
|
-
"System.WorkItemType": z.array(z.string()).optional().describe("Filter by work item type like Bug, Task, User Story"),
|
|
122
|
-
"System.State": z.array(z.string()).optional().describe("Filter by state"),
|
|
123
|
-
"System.AssignedTo": z.array(z.string()).optional().describe("Filter by assigned to"),
|
|
124
|
-
})
|
|
125
|
-
.partial()
|
|
126
|
-
.optional(),
|
|
127
|
-
includeFacets: z.boolean().optional(),
|
|
128
|
-
})
|
|
129
|
-
.strict(),
|
|
130
|
-
}, async ({ searchRequest }) => {
|
|
105
|
+
server.tool(SEARCH_TOOLS.search_workitem, "Get Azure DevOps Work Item search results for a given search text", {
|
|
106
|
+
searchText: z.string().describe("Search text to find in work items"),
|
|
107
|
+
project: z.array(z.string()).optional().describe("Filter by projects"),
|
|
108
|
+
areaPath: z.array(z.string()).optional().describe("Filter by area paths"),
|
|
109
|
+
workItemType: z.array(z.string()).optional().describe("Filter by work item types"),
|
|
110
|
+
state: z.array(z.string()).optional().describe("Filter by work item states"),
|
|
111
|
+
assignedTo: z.array(z.string()).optional().describe("Filter by assigned to users"),
|
|
112
|
+
includeFacets: z.boolean().default(false).describe("Include facets in the search results"),
|
|
113
|
+
skip: z.number().default(0).describe("Number of results to skip for pagination"),
|
|
114
|
+
top: z.number().default(10).describe("Number of results to return"),
|
|
115
|
+
}, async ({ searchText, project, areaPath, workItemType, state, assignedTo, includeFacets, skip, top }) => {
|
|
131
116
|
const accessToken = await tokenProvider();
|
|
132
117
|
const url = `https://almsearch.dev.azure.com/${orgName}/_apis/search/workitemsearchresults?api-version=${apiVersion}`;
|
|
118
|
+
const requestBody = {
|
|
119
|
+
searchText,
|
|
120
|
+
includeFacets,
|
|
121
|
+
$skip: skip,
|
|
122
|
+
$top: top,
|
|
123
|
+
};
|
|
124
|
+
const filters = {};
|
|
125
|
+
if (project && project.length > 0)
|
|
126
|
+
filters["System.TeamProject"] = project;
|
|
127
|
+
if (areaPath && areaPath.length > 0)
|
|
128
|
+
filters["System.AreaPath"] = areaPath;
|
|
129
|
+
if (workItemType && workItemType.length > 0)
|
|
130
|
+
filters["System.WorkItemType"] = workItemType;
|
|
131
|
+
if (state && state.length > 0)
|
|
132
|
+
filters["System.State"] = state;
|
|
133
|
+
if (assignedTo && assignedTo.length > 0)
|
|
134
|
+
filters["System.AssignedTo"] = assignedTo;
|
|
135
|
+
if (Object.keys(filters).length > 0) {
|
|
136
|
+
requestBody.filters = filters;
|
|
137
|
+
}
|
|
133
138
|
const response = await fetch(url, {
|
|
134
139
|
method: "POST",
|
|
135
140
|
headers: {
|
|
@@ -137,7 +142,7 @@ function configureSearchTools(server, tokenProvider, connectionProvider, userAge
|
|
|
137
142
|
"Authorization": `Bearer ${accessToken.token}`,
|
|
138
143
|
"User-Agent": userAgentProvider(),
|
|
139
144
|
},
|
|
140
|
-
body: JSON.stringify(
|
|
145
|
+
body: JSON.stringify(requestBody),
|
|
141
146
|
});
|
|
142
147
|
if (!response.ok) {
|
|
143
148
|
throw new Error(`Azure DevOps Work Item Search API error: ${response.status} ${response.statusText}`);
|