@azure-devops/mcp 1.1.0 → 1.2.0

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.
@@ -2,6 +2,7 @@
2
2
  // Licensed under the MIT License.
3
3
  import { ReleaseDefinitionExpands, ReleaseDefinitionQueryOrder, ReleaseExpands, ReleaseStatus, ReleaseQueryOrder } from "azure-devops-node-api/interfaces/ReleaseInterfaces.js";
4
4
  import { z } from "zod";
5
+ import { getEnumKeys, safeEnumConvert } from "../utils.js";
5
6
  const RELEASE_TOOLS = {
6
7
  get_release_definitions: "release_get_definitions",
7
8
  get_releases: "release_get_releases",
@@ -10,12 +11,18 @@ function configureReleaseTools(server, tokenProvider, connectionProvider) {
10
11
  server.tool(RELEASE_TOOLS.get_release_definitions, "Retrieves list of release definitions for a given project.", {
11
12
  project: z.string().describe("Project ID or name to get release definitions for"),
12
13
  searchText: z.string().optional().describe("Search text to filter release definitions"),
13
- expand: z.nativeEnum(ReleaseDefinitionExpands).default(ReleaseDefinitionExpands.None).describe("Expand options for release definitions"),
14
+ expand: z
15
+ .enum(getEnumKeys(ReleaseDefinitionExpands))
16
+ .default("None")
17
+ .describe("Expand options for release definitions"),
14
18
  artifactType: z.string().optional().describe("Filter by artifact type"),
15
19
  artifactSourceId: z.string().optional().describe("Filter by artifact source ID"),
16
20
  top: z.number().optional().describe("Number of results to return (for pagination)"),
17
21
  continuationToken: z.string().optional().describe("Continuation token for pagination"),
18
- queryOrder: z.nativeEnum(ReleaseDefinitionQueryOrder).default(ReleaseDefinitionQueryOrder.NameAscending).describe("Order of the results"),
22
+ queryOrder: z
23
+ .enum(getEnumKeys(ReleaseDefinitionQueryOrder))
24
+ .default("NameAscending")
25
+ .describe("Order of the results"),
19
26
  path: z.string().optional().describe("Path to filter release definitions"),
20
27
  isExactNameMatch: z.boolean().optional().default(false).describe("Whether to match the exact name of the release definition. Default is false."),
21
28
  tagFilter: z.array(z.string()).optional().describe("Filter by tags associated with the release definitions"),
@@ -26,7 +33,7 @@ function configureReleaseTools(server, tokenProvider, connectionProvider) {
26
33
  }, async ({ project, searchText, expand, artifactType, artifactSourceId, top, continuationToken, queryOrder, path, isExactNameMatch, tagFilter, propertyFilters, definitionIdFilter, isDeleted, searchTextContainsFolderName, }) => {
27
34
  const connection = await connectionProvider();
28
35
  const releaseApi = await connection.getReleaseApi();
29
- const releaseDefinitions = await releaseApi.getReleaseDefinitions(project, searchText, expand, artifactType, artifactSourceId, top, continuationToken, queryOrder, path, isExactNameMatch, tagFilter, propertyFilters, definitionIdFilter, isDeleted, searchTextContainsFolderName);
36
+ const releaseDefinitions = await releaseApi.getReleaseDefinitions(project, searchText, safeEnumConvert(ReleaseDefinitionExpands, expand), artifactType, artifactSourceId, top, continuationToken, safeEnumConvert(ReleaseDefinitionQueryOrder, queryOrder), path, isExactNameMatch, tagFilter, propertyFilters, definitionIdFilter, isDeleted, searchTextContainsFolderName);
30
37
  return {
31
38
  content: [{ type: "text", text: JSON.stringify(releaseDefinitions, null, 2) }],
32
39
  };
@@ -37,7 +44,11 @@ function configureReleaseTools(server, tokenProvider, connectionProvider) {
37
44
  definitionEnvironmentId: z.number().optional().describe("ID of the definition environment to filter releases"),
38
45
  searchText: z.string().optional().describe("Search text to filter releases"),
39
46
  createdBy: z.string().optional().describe("User ID or name who created the release"),
40
- statusFilter: z.nativeEnum(ReleaseStatus).optional().default(ReleaseStatus.Active).describe("Status of the releases to filter (default: Active)"),
47
+ statusFilter: z
48
+ .enum(getEnumKeys(ReleaseStatus))
49
+ .optional()
50
+ .default("Active")
51
+ .describe("Status of the releases to filter (default: Active)"),
41
52
  environmentStatusFilter: z.number().optional().describe("Environment status to filter releases"),
42
53
  minCreatedTime: z.coerce
43
54
  .date()
@@ -53,10 +64,18 @@ function configureReleaseTools(server, tokenProvider, connectionProvider) {
53
64
  .optional()
54
65
  .default(() => new Date())
55
66
  .describe("Maximum created time for releases (default: now)"),
56
- queryOrder: z.nativeEnum(ReleaseQueryOrder).optional().default(ReleaseQueryOrder.Ascending).describe("Order in which to return releases (default: Ascending)"),
67
+ queryOrder: z
68
+ .enum(getEnumKeys(ReleaseQueryOrder))
69
+ .optional()
70
+ .default("Ascending")
71
+ .describe("Order in which to return releases (default: Ascending)"),
57
72
  top: z.number().optional().describe("Number of releases to return"),
58
73
  continuationToken: z.number().optional().describe("Continuation token for pagination"),
59
- expand: z.nativeEnum(ReleaseExpands).optional().default(ReleaseExpands.None).describe("Expand options for releases"),
74
+ expand: z
75
+ .enum(getEnumKeys(ReleaseExpands))
76
+ .optional()
77
+ .default("None")
78
+ .describe("Expand options for releases"),
60
79
  artifactTypeId: z.string().optional().describe("Filter releases by artifact type ID"),
61
80
  sourceId: z.string().optional().describe("Filter releases by artifact source ID"),
62
81
  artifactVersionId: z.string().optional().describe("Filter releases by artifact version ID"),
@@ -69,7 +88,7 @@ function configureReleaseTools(server, tokenProvider, connectionProvider) {
69
88
  }, async ({ project, definitionId, definitionEnvironmentId, searchText, createdBy, statusFilter, environmentStatusFilter, minCreatedTime, maxCreatedTime, queryOrder, top, continuationToken, expand, artifactTypeId, sourceId, artifactVersionId, sourceBranchFilter, isDeleted, tagFilter, propertyFilters, releaseIdFilter, path, }) => {
70
89
  const connection = await connectionProvider();
71
90
  const releaseApi = await connection.getReleaseApi();
72
- const releases = await releaseApi.getReleases(project, definitionId, definitionEnvironmentId, searchText, createdBy, statusFilter, environmentStatusFilter, minCreatedTime, maxCreatedTime, queryOrder, top, continuationToken, expand, artifactTypeId, sourceId, artifactVersionId, sourceBranchFilter, isDeleted, tagFilter, propertyFilters, releaseIdFilter, path);
91
+ const releases = await releaseApi.getReleases(project, definitionId, definitionEnvironmentId, searchText, createdBy, safeEnumConvert(ReleaseStatus, statusFilter), environmentStatusFilter, minCreatedTime, maxCreatedTime, safeEnumConvert(ReleaseQueryOrder, queryOrder), top, continuationToken, safeEnumConvert(ReleaseExpands, expand), artifactTypeId, sourceId, artifactVersionId, sourceBranchFilter, isDeleted, tagFilter, propertyFilters, releaseIdFilter, path);
73
92
  return {
74
93
  content: [{ type: "text", text: JSON.stringify(releases, null, 2) }],
75
94
  };
@@ -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",
@@ -16,6 +17,7 @@ const REPO_TOOLS = {
16
17
  get_pull_request_by_id: "repo_get_pull_request_by_id",
17
18
  create_pull_request: "repo_create_pull_request",
18
19
  update_pull_request_status: "repo_update_pull_request_status",
20
+ update_pull_request_reviewers: "repo_update_pull_request_reviewers",
19
21
  reply_to_comment: "repo_reply_to_comment",
20
22
  resolve_comment: "repo_resolve_comment",
21
23
  search_commits: "repo_search_commits",
@@ -31,15 +33,15 @@ function branchesFilterOutIrrelevantProperties(branches, top) {
31
33
  }
32
34
  function pullRequestStatusStringToInt(status) {
33
35
  switch (status) {
34
- case "abandoned":
36
+ case "Abandoned":
35
37
  return PullRequestStatus.Abandoned.valueOf();
36
- case "active":
38
+ case "Active":
37
39
  return PullRequestStatus.Active.valueOf();
38
- case "all":
40
+ case "All":
39
41
  return PullRequestStatus.All.valueOf();
40
- case "completed":
42
+ case "Completed":
41
43
  return PullRequestStatus.Completed.valueOf();
42
- case "notSet":
44
+ case "NotSet":
43
45
  return PullRequestStatus.NotSet.valueOf();
44
46
  default:
45
47
  throw new Error(`Unknown pull request status: ${status}`);
@@ -78,16 +80,40 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
78
80
  server.tool(REPO_TOOLS.update_pull_request_status, "Update status of an existing pull request to active or abandoned.", {
79
81
  repositoryId: z.string().describe("The ID of the repository where the pull request exists."),
80
82
  pullRequestId: z.number().describe("The ID of the pull request to be published."),
81
- status: z.enum(["active", "abandoned"]).describe("The new status of the pull request. Can be 'active' or 'abandoned'."),
83
+ status: z.enum(["Active", "Abandoned"]).describe("The new status of the pull request. Can be 'Active' or 'Abandoned'."),
82
84
  }, async ({ repositoryId, pullRequestId, status }) => {
83
85
  const connection = await connectionProvider();
84
86
  const gitApi = await connection.getGitApi();
85
- const statusValue = status === "active" ? 3 : 2;
87
+ const statusValue = status === "Active" ? PullRequestStatus.Active.valueOf() : PullRequestStatus.Abandoned.valueOf();
86
88
  const updatedPullRequest = await gitApi.updatePullRequest({ status: statusValue }, repositoryId, pullRequestId);
87
89
  return {
88
90
  content: [{ type: "text", text: JSON.stringify(updatedPullRequest, null, 2) }],
89
91
  };
90
92
  });
93
+ server.tool(REPO_TOOLS.update_pull_request_reviewers, "Add or remove reviewers for an existing pull request.", {
94
+ repositoryId: z.string().describe("The ID of the repository where the pull request exists."),
95
+ pullRequestId: z.number().describe("The ID of the pull request to update."),
96
+ reviewerIds: z.array(z.string()).describe("List of reviewer ids to add or remove from the pull request."),
97
+ action: z.enum(["add", "remove"]).describe("Action to perform on the reviewers. Can be 'add' or 'remove'."),
98
+ }, async ({ repositoryId, pullRequestId, reviewerIds, action }) => {
99
+ const connection = await connectionProvider();
100
+ const gitApi = await connection.getGitApi();
101
+ let updatedPullRequest;
102
+ if (action === "add") {
103
+ updatedPullRequest = await gitApi.createPullRequestReviewers(reviewerIds.map((id) => ({ id: id })), repositoryId, pullRequestId);
104
+ return {
105
+ content: [{ type: "text", text: JSON.stringify(updatedPullRequest, null, 2) }],
106
+ };
107
+ }
108
+ else {
109
+ for (const reviewerId of reviewerIds) {
110
+ await gitApi.deletePullRequestReviewer(repositoryId, pullRequestId, reviewerId);
111
+ }
112
+ return {
113
+ content: [{ type: "text", text: `Reviewers with IDs ${reviewerIds.join(", ")} removed from pull request ${pullRequestId}.` }],
114
+ };
115
+ }
116
+ });
91
117
  server.tool(REPO_TOOLS.list_repos_by_project, "Retrieve a list of repositories for a given project", {
92
118
  project: z.string().describe("The name or ID of the Azure DevOps project."),
93
119
  top: z.number().default(100).describe("The maximum number of repositories to return."),
@@ -119,7 +145,10 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
119
145
  skip: z.number().default(0).describe("The number of pull requests to skip."),
120
146
  created_by_me: z.boolean().default(false).describe("Filter pull requests created by the current user."),
121
147
  i_am_reviewer: z.boolean().default(false).describe("Filter pull requests where the current user is a reviewer."),
122
- status: z.enum(["abandoned", "active", "all", "completed", "notSet"]).default("active").describe("Filter pull requests by status. Defaults to 'active'."),
148
+ status: z
149
+ .enum(getEnumKeys(PullRequestStatus))
150
+ .default("Active")
151
+ .describe("Filter pull requests by status. Defaults to 'Active'."),
123
152
  }, async ({ repositoryId, top, skip, created_by_me, i_am_reviewer, status }) => {
124
153
  const connection = await connectionProvider();
125
154
  const gitApi = await connection.getGitApi();
@@ -164,7 +193,10 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
164
193
  skip: z.number().default(0).describe("The number of pull requests to skip."),
165
194
  created_by_me: z.boolean().default(false).describe("Filter pull requests created by the current user."),
166
195
  i_am_reviewer: z.boolean().default(false).describe("Filter pull requests where the current user is a reviewer."),
167
- status: z.enum(["abandoned", "active", "all", "completed", "notSet"]).default("active").describe("Filter pull requests by status. Defaults to 'active'."),
196
+ status: z
197
+ .enum(getEnumKeys(PullRequestStatus))
198
+ .default("Active")
199
+ .describe("Filter pull requests by status. Defaults to 'Active'."),
168
200
  }, async ({ project, top, skip, created_by_me, i_am_reviewer, status }) => {
169
201
  const connection = await connectionProvider();
170
202
  const gitApi = await connection.getGitApi();
@@ -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
- CODE SEARCH
15
- Get the code search results for a given search text.
16
- */
17
- server.tool(SEARCH_TOOLS.search_code, "Get the code search results for a given search text.", {
18
- searchRequest: z
19
- .object({
20
- searchText: z.string().describe("Search text to find in code"),
21
- $skip: z.number().default(0).describe("Number of results to skip (for pagination)"),
22
- $top: z.number().default(5).describe("Number of results to return (for pagination)"),
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,
30
+ $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(searchRequest),
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(topResults, gitApi);
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
- WIKI SEARCH
68
- Get wiki search results for a given search text.
69
- */
70
- server.tool(SEARCH_TOOLS.search_wiki, "Get wiki search results for a given search text.", {
71
- searchRequest: z
72
- .object({
73
- searchText: z.string().describe("Search text to find in wikis"),
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,
78
+ $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(searchRequest),
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
- WORK ITEM SEARCH
109
- Get work item search results for a given search text.
110
- */
111
- server.tool(SEARCH_TOOLS.search_workitem, "Get work item search results for a given search text.", {
112
- searchRequest: z
113
- .object({
114
- searchText: z.string().describe("Search text to find in work items"),
115
- $skip: z.number().default(0).describe("Number of results to skip for pagination"),
116
- $top: z.number().default(10).describe("Number of results to return"),
117
- filters: z
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,
122
+ $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(searchRequest),
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}`);