@azure-devops/mcp 2.2.2 → 2.3.0-nightly.20251204

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,7 @@ const WORKITEM_TOOLS = {
13
13
  update_work_item: "wit_update_work_item",
14
14
  create_work_item: "wit_create_work_item",
15
15
  list_work_item_comments: "wit_list_work_item_comments",
16
+ list_work_item_revisions: "wit_list_work_item_revisions",
16
17
  get_work_items_for_iteration: "wit_get_work_items_for_iteration",
17
18
  add_work_item_comment: "wit_add_work_item_comment",
18
19
  add_child_work_items: "wit_add_child_work_items",
@@ -60,26 +61,44 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
60
61
  project: z.string().describe("The name or ID of the Azure DevOps project."),
61
62
  team: z.string().describe("The name or ID of the Azure DevOps team."),
62
63
  }, async ({ project, team }) => {
63
- const connection = await connectionProvider();
64
- const workApi = await connection.getWorkApi();
65
- const teamContext = { project, team };
66
- const backlogs = await workApi.getBacklogs(teamContext);
67
- return {
68
- content: [{ type: "text", text: JSON.stringify(backlogs, null, 2) }],
69
- };
64
+ try {
65
+ const connection = await connectionProvider();
66
+ const workApi = await connection.getWorkApi();
67
+ const teamContext = { project, team };
68
+ const backlogs = await workApi.getBacklogs(teamContext);
69
+ return {
70
+ content: [{ type: "text", text: JSON.stringify(backlogs, null, 2) }],
71
+ };
72
+ }
73
+ catch (error) {
74
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
75
+ return {
76
+ content: [{ type: "text", text: `Error listing backlogs: ${errorMessage}` }],
77
+ isError: true,
78
+ };
79
+ }
70
80
  });
71
81
  server.tool(WORKITEM_TOOLS.list_backlog_work_items, "Retrieve a list of backlogs of for a given project, team, and backlog category", {
72
82
  project: z.string().describe("The name or ID of the Azure DevOps project."),
73
83
  team: z.string().describe("The name or ID of the Azure DevOps team."),
74
84
  backlogId: z.string().describe("The ID of the backlog category to retrieve work items from."),
75
85
  }, async ({ project, team, backlogId }) => {
76
- const connection = await connectionProvider();
77
- const workApi = await connection.getWorkApi();
78
- const teamContext = { project, team };
79
- const workItems = await workApi.getBacklogLevelWorkItems(teamContext, backlogId);
80
- return {
81
- content: [{ type: "text", text: JSON.stringify(workItems, null, 2) }],
82
- };
86
+ try {
87
+ const connection = await connectionProvider();
88
+ const workApi = await connection.getWorkApi();
89
+ const teamContext = { project, team };
90
+ const workItems = await workApi.getBacklogLevelWorkItems(teamContext, backlogId);
91
+ return {
92
+ content: [{ type: "text", text: JSON.stringify(workItems, null, 2) }],
93
+ };
94
+ }
95
+ catch (error) {
96
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
97
+ return {
98
+ content: [{ type: "text", text: `Error listing backlog work items: ${errorMessage}` }],
99
+ isError: true,
100
+ };
101
+ }
83
102
  });
84
103
  server.tool(WORKITEM_TOOLS.my_work_items, "Retrieve a list of work items relevent to the authenticated user.", {
85
104
  project: z.string().describe("The name or ID of the Azure DevOps project."),
@@ -87,53 +106,71 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
87
106
  top: z.number().default(50).describe("The maximum number of work items to return. Defaults to 50."),
88
107
  includeCompleted: z.boolean().default(false).describe("Whether to include completed work items. Defaults to false."),
89
108
  }, async ({ project, type, top, includeCompleted }) => {
90
- const connection = await connectionProvider();
91
- const workApi = await connection.getWorkApi();
92
- const workItems = await workApi.getPredefinedQueryResults(project, type, top, includeCompleted);
93
- return {
94
- content: [{ type: "text", text: JSON.stringify(workItems, null, 2) }],
95
- };
109
+ try {
110
+ const connection = await connectionProvider();
111
+ const workApi = await connection.getWorkApi();
112
+ const workItems = await workApi.getPredefinedQueryResults(project, type, top, includeCompleted);
113
+ return {
114
+ content: [{ type: "text", text: JSON.stringify(workItems, null, 2) }],
115
+ };
116
+ }
117
+ catch (error) {
118
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
119
+ return {
120
+ content: [{ type: "text", text: `Error retrieving work items: ${errorMessage}` }],
121
+ isError: true,
122
+ };
123
+ }
96
124
  });
97
125
  server.tool(WORKITEM_TOOLS.get_work_items_batch_by_ids, "Retrieve list of work items by IDs in batch.", {
98
126
  project: z.string().describe("The name or ID of the Azure DevOps project."),
99
127
  ids: z.array(z.number()).describe("The IDs of the work items to retrieve."),
100
128
  fields: z.array(z.string()).optional().describe("Optional list of fields to include in the response. If not provided, a hardcoded default set of fields will be used."),
101
129
  }, async ({ project, ids, fields }) => {
102
- const connection = await connectionProvider();
103
- const workItemApi = await connection.getWorkItemTrackingApi();
104
- const defaultFields = ["System.Id", "System.WorkItemType", "System.Title", "System.State", "System.Parent", "System.Tags", "Microsoft.VSTS.Common.StackRank", "System.AssignedTo"];
105
- // If no fields are provided, use the default set of fields
106
- const fieldsToUse = !fields || fields.length === 0 ? defaultFields : fields;
107
- const workitems = await workItemApi.getWorkItemsBatch({ ids, fields: fieldsToUse }, project);
108
- // List of identity fields that need to be transformed from objects to formatted strings
109
- const identityFields = [
110
- "System.AssignedTo",
111
- "System.CreatedBy",
112
- "System.ChangedBy",
113
- "System.AuthorizedAs",
114
- "Microsoft.VSTS.Common.ActivatedBy",
115
- "Microsoft.VSTS.Common.ResolvedBy",
116
- "Microsoft.VSTS.Common.ClosedBy",
117
- ];
118
- // Format identity fields to include displayName and uniqueName
119
- // Removing the identity object as the response. It's too much and not needed
120
- if (workitems && Array.isArray(workitems)) {
121
- workitems.forEach((item) => {
122
- if (item.fields) {
123
- identityFields.forEach((fieldName) => {
124
- if (item.fields && item.fields[fieldName] && typeof item.fields[fieldName] === "object") {
125
- const identityField = item.fields[fieldName];
126
- const name = identityField.displayName || "";
127
- const email = identityField.uniqueName || "";
128
- item.fields[fieldName] = `${name} <${email}>`.trim();
129
- }
130
- });
131
- }
132
- });
130
+ try {
131
+ const connection = await connectionProvider();
132
+ const workItemApi = await connection.getWorkItemTrackingApi();
133
+ const defaultFields = ["System.Id", "System.WorkItemType", "System.Title", "System.State", "System.Parent", "System.Tags", "Microsoft.VSTS.Common.StackRank", "System.AssignedTo"];
134
+ // If no fields are provided, use the default set of fields
135
+ const fieldsToUse = !fields || fields.length === 0 ? defaultFields : fields;
136
+ const workitems = await workItemApi.getWorkItemsBatch({ ids, fields: fieldsToUse }, project);
137
+ // List of identity fields that need to be transformed from objects to formatted strings
138
+ const identityFields = [
139
+ "System.AssignedTo",
140
+ "System.CreatedBy",
141
+ "System.ChangedBy",
142
+ "System.AuthorizedAs",
143
+ "Microsoft.VSTS.Common.ActivatedBy",
144
+ "Microsoft.VSTS.Common.ResolvedBy",
145
+ "Microsoft.VSTS.Common.ClosedBy",
146
+ ];
147
+ // Format identity fields to include displayName and uniqueName
148
+ // Removing the identity object as the response. It's too much and not needed
149
+ if (workitems && Array.isArray(workitems)) {
150
+ workitems.forEach((item) => {
151
+ if (item.fields) {
152
+ identityFields.forEach((fieldName) => {
153
+ if (item.fields && item.fields[fieldName] && typeof item.fields[fieldName] === "object") {
154
+ const identityField = item.fields[fieldName];
155
+ const name = identityField.displayName || "";
156
+ const email = identityField.uniqueName || "";
157
+ item.fields[fieldName] = `${name} <${email}>`.trim();
158
+ }
159
+ });
160
+ }
161
+ });
162
+ }
163
+ return {
164
+ content: [{ type: "text", text: JSON.stringify(workitems, null, 2) }],
165
+ };
166
+ }
167
+ catch (error) {
168
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
169
+ return {
170
+ content: [{ type: "text", text: `Error retrieving work items batch: ${errorMessage}` }],
171
+ isError: true,
172
+ };
133
173
  }
134
- return {
135
- content: [{ type: "text", text: JSON.stringify(workitems, null, 2) }],
136
- };
137
174
  });
138
175
  server.tool(WORKITEM_TOOLS.get_work_item, "Get a single work item by ID.", {
139
176
  id: z.number().describe("The ID of the work item to retrieve."),
@@ -146,24 +183,42 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
146
183
  .optional()
147
184
  .describe("Expand options include 'all', 'fields', 'links', 'none', and 'relations'. Relations can be used to get child workitems. Defaults to 'none'."),
148
185
  }, async ({ id, project, fields, asOf, expand }) => {
149
- const connection = await connectionProvider();
150
- const workItemApi = await connection.getWorkItemTrackingApi();
151
- const workItem = await workItemApi.getWorkItem(id, fields, asOf, expand, project);
152
- return {
153
- content: [{ type: "text", text: JSON.stringify(workItem, null, 2) }],
154
- };
186
+ try {
187
+ const connection = await connectionProvider();
188
+ const workItemApi = await connection.getWorkItemTrackingApi();
189
+ const workItem = await workItemApi.getWorkItem(id, fields, asOf, expand, project);
190
+ return {
191
+ content: [{ type: "text", text: JSON.stringify(workItem, null, 2) }],
192
+ };
193
+ }
194
+ catch (error) {
195
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
196
+ return {
197
+ content: [{ type: "text", text: `Error retrieving work item: ${errorMessage}` }],
198
+ isError: true,
199
+ };
200
+ }
155
201
  });
156
202
  server.tool(WORKITEM_TOOLS.list_work_item_comments, "Retrieve list of comments for a work item by ID.", {
157
203
  project: z.string().describe("The name or ID of the Azure DevOps project."),
158
204
  workItemId: z.number().describe("The ID of the work item to retrieve comments for."),
159
205
  top: z.number().default(50).describe("Optional number of comments to retrieve. Defaults to all comments."),
160
206
  }, async ({ project, workItemId, top }) => {
161
- const connection = await connectionProvider();
162
- const workItemApi = await connection.getWorkItemTrackingApi();
163
- const comments = await workItemApi.getComments(project, workItemId, top);
164
- return {
165
- content: [{ type: "text", text: JSON.stringify(comments, null, 2) }],
166
- };
207
+ try {
208
+ const connection = await connectionProvider();
209
+ const workItemApi = await connection.getWorkItemTrackingApi();
210
+ const comments = await workItemApi.getComments(project, workItemId, top);
211
+ return {
212
+ content: [{ type: "text", text: JSON.stringify(comments, null, 2) }],
213
+ };
214
+ }
215
+ catch (error) {
216
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
217
+ return {
218
+ content: [{ type: "text", text: `Error listing work item comments: ${errorMessage}` }],
219
+ isError: true,
220
+ };
221
+ }
167
222
  });
168
223
  server.tool(WORKITEM_TOOLS.add_work_item_comment, "Add comment to a work item by ID.", {
169
224
  project: z.string().describe("The name or ID of the Azure DevOps project."),
@@ -171,29 +226,90 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
171
226
  comment: z.string().describe("The text of the comment to add to the work item."),
172
227
  format: z.enum(["markdown", "html"]).optional().default("html"),
173
228
  }, async ({ project, workItemId, comment, format }) => {
174
- const connection = await connectionProvider();
175
- const orgUrl = connection.serverUrl;
176
- const accessToken = await tokenProvider();
177
- const body = {
178
- text: comment,
179
- };
180
- const formatParameter = format === "markdown" ? 0 : 1;
181
- const response = await fetch(`${orgUrl}/${project}/_apis/wit/workItems/${workItemId}/comments?format=${formatParameter}&api-version=${markdownCommentsApiVersion}`, {
182
- method: "POST",
183
- headers: {
184
- "Authorization": `Bearer ${accessToken}`,
185
- "Content-Type": "application/json",
186
- "User-Agent": userAgentProvider(),
187
- },
188
- body: JSON.stringify(body),
189
- });
190
- if (!response.ok) {
191
- throw new Error(`Failed to add a work item comment: ${response.statusText}}`);
192
- }
193
- const comments = await response.text();
194
- return {
195
- content: [{ type: "text", text: comments }],
196
- };
229
+ try {
230
+ const connection = await connectionProvider();
231
+ const orgUrl = connection.serverUrl;
232
+ const accessToken = await tokenProvider();
233
+ const body = {
234
+ text: comment,
235
+ };
236
+ const formatParameter = format === "markdown" ? 0 : 1;
237
+ const response = await fetch(`${orgUrl}/${project}/_apis/wit/workItems/${workItemId}/comments?format=${formatParameter}&api-version=${markdownCommentsApiVersion}`, {
238
+ method: "POST",
239
+ headers: {
240
+ "Authorization": `Bearer ${accessToken}`,
241
+ "Content-Type": "application/json",
242
+ "User-Agent": userAgentProvider(),
243
+ },
244
+ body: JSON.stringify(body),
245
+ });
246
+ if (!response.ok) {
247
+ throw new Error(`Failed to add a work item comment: ${response.statusText}}`);
248
+ }
249
+ const comments = await response.text();
250
+ return {
251
+ content: [{ type: "text", text: comments }],
252
+ };
253
+ }
254
+ catch (error) {
255
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
256
+ return {
257
+ content: [{ type: "text", text: `Error adding work item comment: ${errorMessage}` }],
258
+ isError: true,
259
+ };
260
+ }
261
+ });
262
+ server.tool(WORKITEM_TOOLS.list_work_item_revisions, "Retrieve list of revisions for a work item by ID.", {
263
+ project: z.string().describe("The name or ID of the Azure DevOps project."),
264
+ workItemId: z.number().describe("The ID of the work item to retrieve revisions for."),
265
+ top: z.number().default(50).describe("Optional number of revisions to retrieve. If not provided, all revisions will be returned."),
266
+ skip: z.number().optional().describe("Optional number of revisions to skip for pagination. Defaults to 0."),
267
+ expand: z
268
+ .enum(getEnumKeys(WorkItemExpand))
269
+ .default("None")
270
+ .optional()
271
+ .describe("Optional expand parameter to include additional details. Defaults to 'None'."),
272
+ }, async ({ project, workItemId, top, skip, expand }) => {
273
+ try {
274
+ const connection = await connectionProvider();
275
+ const workItemApi = await connection.getWorkItemTrackingApi();
276
+ const revisions = await workItemApi.getRevisions(workItemId, top, skip, safeEnumConvert(WorkItemExpand, expand), project);
277
+ // Dynamically clean up identity objects in revision fields
278
+ // Identity objects typically have properties like displayName, url, _links, id, uniqueName, imageUrl, descriptor
279
+ if (revisions && Array.isArray(revisions)) {
280
+ revisions.forEach((revision) => {
281
+ if (revision.fields) {
282
+ Object.keys(revision.fields).forEach((fieldName) => {
283
+ const fieldValue = revision.fields ? revision.fields[fieldName] : undefined;
284
+ // Check if this is an identity object by looking for common identity properties
285
+ if (fieldValue &&
286
+ typeof fieldValue === "object" &&
287
+ !Array.isArray(fieldValue) &&
288
+ "displayName" in fieldValue &&
289
+ ("url" in fieldValue || "_links" in fieldValue || "uniqueName" in fieldValue)) {
290
+ // Remove unwanted properties from identity objects
291
+ delete fieldValue.url;
292
+ delete fieldValue._links;
293
+ delete fieldValue.id;
294
+ delete fieldValue.uniqueName;
295
+ delete fieldValue.imageUrl;
296
+ delete fieldValue.descriptor;
297
+ }
298
+ });
299
+ }
300
+ });
301
+ }
302
+ return {
303
+ content: [{ type: "text", text: JSON.stringify(revisions, null, 2) }],
304
+ };
305
+ }
306
+ catch (error) {
307
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
308
+ return {
309
+ content: [{ type: "text", text: `Error listing work item revisions: ${errorMessage}` }],
310
+ isError: true,
311
+ };
312
+ }
197
313
  });
198
314
  server.tool(WORKITEM_TOOLS.add_child_work_items, "Create one or many child work items from a parent by work item type and parent id.", {
199
315
  parentId: z.number().describe("The ID of the parent work item to create a child work item under."),
@@ -369,13 +485,22 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
369
485
  team: z.string().optional().describe("The name or ID of the Azure DevOps team. If not provided, the default team will be used."),
370
486
  iterationId: z.string().describe("The ID of the iteration to retrieve work items for."),
371
487
  }, async ({ project, team, iterationId }) => {
372
- const connection = await connectionProvider();
373
- const workApi = await connection.getWorkApi();
374
- //get the work items for the current iteration
375
- const workItems = await workApi.getIterationWorkItems({ project, team }, iterationId);
376
- return {
377
- content: [{ type: "text", text: JSON.stringify(workItems, null, 2) }],
378
- };
488
+ try {
489
+ const connection = await connectionProvider();
490
+ const workApi = await connection.getWorkApi();
491
+ //get the work items for the current iteration
492
+ const workItems = await workApi.getIterationWorkItems({ project, team }, iterationId);
493
+ return {
494
+ content: [{ type: "text", text: JSON.stringify(workItems, null, 2) }],
495
+ };
496
+ }
497
+ catch (error) {
498
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
499
+ return {
500
+ content: [{ type: "text", text: `Error retrieving work items for iteration: ${errorMessage}` }],
501
+ isError: true,
502
+ };
503
+ }
379
504
  });
380
505
  server.tool(WORKITEM_TOOLS.update_work_item, "Update a work item by ID with specified fields.", {
381
506
  id: z.number().describe("The ID of the work item to update."),
@@ -392,28 +517,46 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
392
517
  }))
393
518
  .describe("An array of field updates to apply to the work item."),
394
519
  }, async ({ id, updates }) => {
395
- const connection = await connectionProvider();
396
- const workItemApi = await connection.getWorkItemTrackingApi();
397
- // Convert operation names to lowercase for API
398
- const apiUpdates = updates.map((update) => ({
399
- ...update,
400
- op: update.op,
401
- }));
402
- const updatedWorkItem = await workItemApi.updateWorkItem(null, apiUpdates, id);
403
- return {
404
- content: [{ type: "text", text: JSON.stringify(updatedWorkItem, null, 2) }],
405
- };
520
+ try {
521
+ const connection = await connectionProvider();
522
+ const workItemApi = await connection.getWorkItemTrackingApi();
523
+ // Convert operation names to lowercase for API
524
+ const apiUpdates = updates.map((update) => ({
525
+ ...update,
526
+ op: update.op,
527
+ }));
528
+ const updatedWorkItem = await workItemApi.updateWorkItem(null, apiUpdates, id);
529
+ return {
530
+ content: [{ type: "text", text: JSON.stringify(updatedWorkItem, null, 2) }],
531
+ };
532
+ }
533
+ catch (error) {
534
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
535
+ return {
536
+ content: [{ type: "text", text: `Error updating work item: ${errorMessage}` }],
537
+ isError: true,
538
+ };
539
+ }
406
540
  });
407
541
  server.tool(WORKITEM_TOOLS.get_work_item_type, "Get a specific work item type.", {
408
542
  project: z.string().describe("The name or ID of the Azure DevOps project."),
409
543
  workItemType: z.string().describe("The name of the work item type to retrieve."),
410
544
  }, async ({ project, workItemType }) => {
411
- const connection = await connectionProvider();
412
- const workItemApi = await connection.getWorkItemTrackingApi();
413
- const workItemTypeInfo = await workItemApi.getWorkItemType(project, workItemType);
414
- return {
415
- content: [{ type: "text", text: JSON.stringify(workItemTypeInfo, null, 2) }],
416
- };
545
+ try {
546
+ const connection = await connectionProvider();
547
+ const workItemApi = await connection.getWorkItemTrackingApi();
548
+ const workItemTypeInfo = await workItemApi.getWorkItemType(project, workItemType);
549
+ return {
550
+ content: [{ type: "text", text: JSON.stringify(workItemTypeInfo, null, 2) }],
551
+ };
552
+ }
553
+ catch (error) {
554
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
555
+ return {
556
+ content: [{ type: "text", text: `Error retrieving work item type: ${errorMessage}` }],
557
+ isError: true,
558
+ };
559
+ }
417
560
  });
418
561
  server.tool(WORKITEM_TOOLS.create_work_item, "Create a new work item in a specified project and work item type.", {
419
562
  project: z.string().describe("The name or ID of the Azure DevOps project."),
@@ -473,27 +616,54 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
473
616
  includeDeleted: z.boolean().default(false).describe("Whether to include deleted items in the query results. Defaults to false."),
474
617
  useIsoDateFormat: z.boolean().default(false).describe("Whether to use ISO date format in the response. Defaults to false."),
475
618
  }, async ({ project, query, expand, depth, includeDeleted, useIsoDateFormat }) => {
476
- const connection = await connectionProvider();
477
- const workItemApi = await connection.getWorkItemTrackingApi();
478
- const queryDetails = await workItemApi.getQuery(project, query, safeEnumConvert(QueryExpand, expand), depth, includeDeleted, useIsoDateFormat);
479
- return {
480
- content: [{ type: "text", text: JSON.stringify(queryDetails, null, 2) }],
481
- };
619
+ try {
620
+ const connection = await connectionProvider();
621
+ const workItemApi = await connection.getWorkItemTrackingApi();
622
+ const queryDetails = await workItemApi.getQuery(project, query, safeEnumConvert(QueryExpand, expand), depth, includeDeleted, useIsoDateFormat);
623
+ return {
624
+ content: [{ type: "text", text: JSON.stringify(queryDetails, null, 2) }],
625
+ };
626
+ }
627
+ catch (error) {
628
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
629
+ return {
630
+ content: [{ type: "text", text: `Error retrieving query: ${errorMessage}` }],
631
+ isError: true,
632
+ };
633
+ }
482
634
  });
483
- server.tool(WORKITEM_TOOLS.get_query_results_by_id, "Retrieve the results of a work item query given the query ID.", {
635
+ server.tool(WORKITEM_TOOLS.get_query_results_by_id, "Retrieve the results of a work item query given the query ID. Supports full or IDs-only response types.", {
484
636
  id: z.string().describe("The ID of the query to retrieve results for."),
485
637
  project: z.string().optional().describe("The name or ID of the Azure DevOps project. If not provided, the default project will be used."),
486
638
  team: z.string().optional().describe("The name or ID of the Azure DevOps team. If not provided, the default team will be used."),
487
639
  timePrecision: z.boolean().optional().describe("Whether to include time precision in the results. Defaults to false."),
488
640
  top: z.number().default(50).describe("The maximum number of results to return. Defaults to 50."),
489
- }, async ({ id, project, team, timePrecision, top }) => {
490
- const connection = await connectionProvider();
491
- const workItemApi = await connection.getWorkItemTrackingApi();
492
- const teamContext = { project, team };
493
- const queryResult = await workItemApi.queryById(id, teamContext, timePrecision, top);
494
- return {
495
- content: [{ type: "text", text: JSON.stringify(queryResult, null, 2) }],
496
- };
641
+ responseType: z.enum(["full", "ids"]).default("full").describe("Response type: 'full' returns complete query results (default), 'ids' returns only work item IDs for reduced payload size."),
642
+ }, async ({ id, project, team, timePrecision, top, responseType }) => {
643
+ try {
644
+ const connection = await connectionProvider();
645
+ const workItemApi = await connection.getWorkItemTrackingApi();
646
+ const teamContext = { project, team };
647
+ const queryResult = await workItemApi.queryById(id, teamContext, timePrecision, top);
648
+ // If ids mode, extract and return only the IDs
649
+ if (responseType === "ids") {
650
+ const ids = queryResult.workItems?.map((workItem) => workItem.id).filter((id) => id !== undefined) || [];
651
+ return {
652
+ content: [{ type: "text", text: JSON.stringify({ ids, count: ids.length }, null, 2) }],
653
+ };
654
+ }
655
+ // Default: return full query results
656
+ return {
657
+ content: [{ type: "text", text: JSON.stringify(queryResult, null, 2) }],
658
+ };
659
+ }
660
+ catch (error) {
661
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
662
+ return {
663
+ content: [{ type: "text", text: `Error retrieving query results: ${errorMessage}` }],
664
+ isError: true,
665
+ };
666
+ }
497
667
  });
498
668
  server.tool(WORKITEM_TOOLS.update_work_items_batch, "Update work items in batch", {
499
669
  updates: z
@@ -506,53 +676,62 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
506
676
  }))
507
677
  .describe("An array of updates to apply to work items. Each update should include the operation (op), work item ID (id), field path (path), and new value (value)."),
508
678
  }, async ({ updates }) => {
509
- const connection = await connectionProvider();
510
- const orgUrl = connection.serverUrl;
511
- const accessToken = await tokenProvider();
512
- // Extract unique IDs from the updates array
513
- const uniqueIds = Array.from(new Set(updates.map((update) => update.id)));
514
- const body = uniqueIds.map((id) => {
515
- const workItemUpdates = updates.filter((update) => update.id === id);
516
- const operations = workItemUpdates.map(({ op, path, value, format }) => ({
517
- op: op,
518
- path: path,
519
- value: encodeFormattedValue(value, format),
520
- }));
521
- // Add format operations for Markdown fields
522
- workItemUpdates.forEach(({ path, value, format }) => {
523
- if (format === "Markdown" && value && value.length > 50) {
524
- operations.push({
525
- op: "Add",
526
- path: `/multilineFieldsFormat${path.replace("/fields", "")}`,
527
- value: "Markdown",
528
- });
529
- }
679
+ try {
680
+ const connection = await connectionProvider();
681
+ const orgUrl = connection.serverUrl;
682
+ const accessToken = await tokenProvider();
683
+ // Extract unique IDs from the updates array
684
+ const uniqueIds = Array.from(new Set(updates.map((update) => update.id)));
685
+ const body = uniqueIds.map((id) => {
686
+ const workItemUpdates = updates.filter((update) => update.id === id);
687
+ const operations = workItemUpdates.map(({ op, path, value, format }) => ({
688
+ op: op,
689
+ path: path,
690
+ value: encodeFormattedValue(value, format),
691
+ }));
692
+ // Add format operations for Markdown fields
693
+ workItemUpdates.forEach(({ path, value, format }) => {
694
+ if (format === "Markdown" && value && value.length > 50) {
695
+ operations.push({
696
+ op: "Add",
697
+ path: `/multilineFieldsFormat${path.replace("/fields", "")}`,
698
+ value: "Markdown",
699
+ });
700
+ }
701
+ });
702
+ return {
703
+ method: "PATCH",
704
+ uri: `/_apis/wit/workitems/${id}?api-version=${batchApiVersion}`,
705
+ headers: {
706
+ "Content-Type": "application/json-patch+json",
707
+ },
708
+ body: operations,
709
+ };
530
710
  });
531
- return {
711
+ const response = await fetch(`${orgUrl}/_apis/wit/$batch?api-version=${batchApiVersion}`, {
532
712
  method: "PATCH",
533
- uri: `/_apis/wit/workitems/${id}?api-version=${batchApiVersion}`,
534
713
  headers: {
535
- "Content-Type": "application/json-patch+json",
714
+ "Authorization": `Bearer ${accessToken}`,
715
+ "Content-Type": "application/json",
716
+ "User-Agent": userAgentProvider(),
536
717
  },
537
- body: operations,
538
- };
539
- });
540
- const response = await fetch(`${orgUrl}/_apis/wit/$batch?api-version=${batchApiVersion}`, {
541
- method: "PATCH",
542
- headers: {
543
- "Authorization": `Bearer ${accessToken}`,
544
- "Content-Type": "application/json",
545
- "User-Agent": userAgentProvider(),
546
- },
547
- body: JSON.stringify(body),
548
- });
549
- if (!response.ok) {
550
- throw new Error(`Failed to update work items in batch: ${response.statusText}`);
551
- }
552
- const result = await response.json();
553
- return {
554
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
555
- };
718
+ body: JSON.stringify(body),
719
+ });
720
+ if (!response.ok) {
721
+ throw new Error(`Failed to update work items in batch: ${response.statusText}`);
722
+ }
723
+ const result = await response.json();
724
+ return {
725
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
726
+ };
727
+ }
728
+ catch (error) {
729
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
730
+ return {
731
+ content: [{ type: "text", text: `Error updating work items in batch: ${errorMessage}` }],
732
+ isError: true,
733
+ };
734
+ }
556
735
  });
557
736
  server.tool(WORKITEM_TOOLS.work_items_link, "Link work items together in batch.", {
558
737
  project: z.string().describe("The name or ID of the Azure DevOps project."),
@@ -568,47 +747,56 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
568
747
  }))
569
748
  .describe(""),
570
749
  }, async ({ project, updates }) => {
571
- const connection = await connectionProvider();
572
- const orgUrl = connection.serverUrl;
573
- const accessToken = await tokenProvider();
574
- // Extract unique IDs from the updates array
575
- const uniqueIds = Array.from(new Set(updates.map((update) => update.id)));
576
- const body = uniqueIds.map((id) => ({
577
- method: "PATCH",
578
- uri: `/_apis/wit/workitems/${id}?api-version=${batchApiVersion}`,
579
- headers: {
580
- "Content-Type": "application/json-patch+json",
581
- },
582
- body: updates
583
- .filter((update) => update.id === id)
584
- .map(({ linkToId, type, comment }) => ({
585
- op: "add",
586
- path: "/relations/-",
587
- value: {
588
- rel: `${getLinkTypeFromName(type)}`,
589
- url: `${orgUrl}/${project}/_apis/wit/workItems/${linkToId}`,
590
- attributes: {
591
- comment: comment || "",
750
+ try {
751
+ const connection = await connectionProvider();
752
+ const orgUrl = connection.serverUrl;
753
+ const accessToken = await tokenProvider();
754
+ // Extract unique IDs from the updates array
755
+ const uniqueIds = Array.from(new Set(updates.map((update) => update.id)));
756
+ const body = uniqueIds.map((id) => ({
757
+ method: "PATCH",
758
+ uri: `/_apis/wit/workitems/${id}?api-version=${batchApiVersion}`,
759
+ headers: {
760
+ "Content-Type": "application/json-patch+json",
761
+ },
762
+ body: updates
763
+ .filter((update) => update.id === id)
764
+ .map(({ linkToId, type, comment }) => ({
765
+ op: "add",
766
+ path: "/relations/-",
767
+ value: {
768
+ rel: `${getLinkTypeFromName(type)}`,
769
+ url: `${orgUrl}/${project}/_apis/wit/workItems/${linkToId}`,
770
+ attributes: {
771
+ comment: comment || "",
772
+ },
592
773
  },
774
+ })),
775
+ }));
776
+ const response = await fetch(`${orgUrl}/_apis/wit/$batch?api-version=${batchApiVersion}`, {
777
+ method: "PATCH",
778
+ headers: {
779
+ "Authorization": `Bearer ${accessToken}`,
780
+ "Content-Type": "application/json",
781
+ "User-Agent": userAgentProvider(),
593
782
  },
594
- })),
595
- }));
596
- const response = await fetch(`${orgUrl}/_apis/wit/$batch?api-version=${batchApiVersion}`, {
597
- method: "PATCH",
598
- headers: {
599
- "Authorization": `Bearer ${accessToken}`,
600
- "Content-Type": "application/json",
601
- "User-Agent": userAgentProvider(),
602
- },
603
- body: JSON.stringify(body),
604
- });
605
- if (!response.ok) {
606
- throw new Error(`Failed to update work items in batch: ${response.statusText}`);
607
- }
608
- const result = await response.json();
609
- return {
610
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
611
- };
783
+ body: JSON.stringify(body),
784
+ });
785
+ if (!response.ok) {
786
+ throw new Error(`Failed to update work items in batch: ${response.statusText}`);
787
+ }
788
+ const result = await response.json();
789
+ return {
790
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
791
+ };
792
+ }
793
+ catch (error) {
794
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
795
+ return {
796
+ content: [{ type: "text", text: `Error linking work items: ${errorMessage}` }],
797
+ isError: true,
798
+ };
799
+ }
612
800
  });
613
801
  server.tool(WORKITEM_TOOLS.work_item_unlink, "Remove one or many links from a single work item", {
614
802
  project: z.string().describe("The name or ID of the Azure DevOps project."),