@azure-devops/mcp 2.5.0-nightly.20260412 → 2.5.0-nightly.20260413

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.
@@ -12,7 +12,10 @@ const SEARCH_TOOLS = {
12
12
  function configureSearchTools(server, tokenProvider, connectionProvider, userAgentProvider) {
13
13
  server.tool(SEARCH_TOOLS.search_code, "Search Azure DevOps Repositories for a given search text", {
14
14
  searchText: z.string().describe("Keywords to search for in code repositories"),
15
- project: z.array(z.string()).optional().describe("Filter by projects"),
15
+ project: z
16
+ .union([z.string().transform((value) => [value]), z.array(z.string())])
17
+ .optional()
18
+ .describe("Filter by projects"),
16
19
  repository: z.array(z.string()).optional().describe("Filter by repositories"),
17
20
  path: z.array(z.string()).optional().describe("Filter by paths"),
18
21
  branch: z.array(z.string()).optional().describe("Filter by branches"),
@@ -4,7 +4,7 @@ import { WorkItemExpand } from "azure-devops-node-api/interfaces/WorkItemTrackin
4
4
  import { QueryExpand } from "azure-devops-node-api/interfaces/WorkItemTrackingInterfaces.js";
5
5
  import { z } from "zod";
6
6
  import { batchApiVersion, markdownCommentsApiVersion, getEnumKeys, safeEnumConvert, encodeFormattedValue } from "../utils.js";
7
- import { elicitProject } from "../shared/elicitations.js";
7
+ import { elicitProject, elicitTeam } from "../shared/elicitations.js";
8
8
  import { createExternalContentResponse } from "../shared/content-safety.js";
9
9
  const WORKITEM_TOOLS = {
10
10
  my_work_items: "wit_my_work_items",
@@ -62,14 +62,28 @@ function getLinkTypeFromName(name) {
62
62
  }
63
63
  }
64
64
  function configureWorkItemTools(server, tokenProvider, connectionProvider, userAgentProvider) {
65
- server.tool(WORKITEM_TOOLS.list_backlogs, "Receive a list of backlogs for a given project and team.", {
66
- project: z.string().describe("The name or ID of the Azure DevOps project."),
67
- team: z.string().describe("The name or ID of the Azure DevOps team."),
65
+ server.tool(WORKITEM_TOOLS.list_backlogs, "Receive a list of backlogs for a given project and team. If a project or team is not specified, you will be prompted to select one.", {
66
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
67
+ team: z.string().optional().describe("The name or ID of the Azure DevOps team. Reuse from prior context if already known. If not provided, a team selection prompt will be shown."),
68
68
  }, async ({ project, team }) => {
69
69
  try {
70
70
  const connection = await connectionProvider();
71
+ let resolvedProject = project;
72
+ if (!resolvedProject) {
73
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to list backlogs for.");
74
+ if ("response" in result)
75
+ return result.response;
76
+ resolvedProject = result.resolved;
77
+ }
78
+ let resolvedTeam = team;
79
+ if (!resolvedTeam) {
80
+ const result = await elicitTeam(server, connection, resolvedProject, "Select the Azure DevOps team to list backlogs for.");
81
+ if ("response" in result)
82
+ return result.response;
83
+ resolvedTeam = result.resolved;
84
+ }
71
85
  const workApi = await connection.getWorkApi();
72
- const teamContext = { project, team };
86
+ const teamContext = { project: resolvedProject, team: resolvedTeam };
73
87
  const backlogs = await workApi.getBacklogs(teamContext);
74
88
  return {
75
89
  content: [{ type: "text", text: JSON.stringify(backlogs, null, 2) }],
@@ -83,15 +97,29 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
83
97
  };
84
98
  }
85
99
  });
86
- server.tool(WORKITEM_TOOLS.list_backlog_work_items, "Retrieve a list of backlogs of for a given project, team, and backlog category", {
87
- project: z.string().describe("The name or ID of the Azure DevOps project."),
88
- team: z.string().describe("The name or ID of the Azure DevOps team."),
100
+ server.tool(WORKITEM_TOOLS.list_backlog_work_items, "Retrieve a list of backlogs of for a given project, team, and backlog category. If a project or team is not specified, you will be prompted to select one.", {
101
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
102
+ team: z.string().optional().describe("The name or ID of the Azure DevOps team. Reuse from prior context if already known. If not provided, a team selection prompt will be shown."),
89
103
  backlogId: z.string().describe("The ID of the backlog category to retrieve work items from."),
90
104
  }, async ({ project, team, backlogId }) => {
91
105
  try {
92
106
  const connection = await connectionProvider();
107
+ let resolvedProject = project;
108
+ if (!resolvedProject) {
109
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to list backlog work items for.");
110
+ if ("response" in result)
111
+ return result.response;
112
+ resolvedProject = result.resolved;
113
+ }
114
+ let resolvedTeam = team;
115
+ if (!resolvedTeam) {
116
+ const result = await elicitTeam(server, connection, resolvedProject, "Select the Azure DevOps team to list backlog work items for.");
117
+ if ("response" in result)
118
+ return result.response;
119
+ resolvedTeam = result.resolved;
120
+ }
93
121
  const workApi = await connection.getWorkApi();
94
- const teamContext = { project, team };
122
+ const teamContext = { project: resolvedProject, team: resolvedTeam };
95
123
  const workItems = await workApi.getBacklogLevelWorkItems(teamContext, backlogId);
96
124
  return {
97
125
  content: [{ type: "text", text: JSON.stringify(workItems, null, 2) }],
@@ -105,16 +133,23 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
105
133
  };
106
134
  }
107
135
  });
108
- server.tool(WORKITEM_TOOLS.my_work_items, "Retrieve a list of work items relevent to the authenticated user.", {
109
- project: z.string().describe("The name or ID of the Azure DevOps project."),
136
+ server.tool(WORKITEM_TOOLS.my_work_items, "Retrieve a list of work items relevent to the authenticated user. If a project is not specified, you will be prompted to select one.", {
137
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
110
138
  type: z.enum(["assignedtome", "myactivity"]).default("assignedtome").describe("The type of work items to retrieve. Defaults to 'assignedtome'."),
111
139
  top: z.coerce.number().default(50).describe("The maximum number of work items to return. Defaults to 50."),
112
140
  includeCompleted: z.boolean().default(false).describe("Whether to include completed work items. Defaults to false."),
113
141
  }, async ({ project, type, top, includeCompleted }) => {
114
142
  try {
115
143
  const connection = await connectionProvider();
144
+ let resolvedProject = project;
145
+ if (!resolvedProject) {
146
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to retrieve work items for.");
147
+ if ("response" in result)
148
+ return result.response;
149
+ resolvedProject = result.resolved;
150
+ }
116
151
  const workApi = await connection.getWorkApi();
117
- const workItems = await workApi.getPredefinedQueryResults(project, type, top, includeCompleted);
152
+ const workItems = await workApi.getPredefinedQueryResults(resolvedProject, type, top, includeCompleted);
118
153
  return {
119
154
  content: [{ type: "text", text: JSON.stringify(workItems, null, 2) }],
120
155
  };
@@ -127,18 +162,25 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
127
162
  };
128
163
  }
129
164
  });
130
- server.tool(WORKITEM_TOOLS.get_work_items_batch_by_ids, "Retrieve list of work items by IDs in batch.", {
131
- project: z.string().describe("The name or ID of the Azure DevOps project."),
165
+ server.tool(WORKITEM_TOOLS.get_work_items_batch_by_ids, "Retrieve list of work items by IDs in batch. If a project is not specified, you will be prompted to select one.", {
166
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
132
167
  ids: z.array(z.coerce.number().min(1)).describe("The IDs of the work items to retrieve."),
133
168
  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."),
134
169
  }, async ({ project, ids, fields }) => {
135
170
  try {
136
171
  const connection = await connectionProvider();
172
+ let resolvedProject = project;
173
+ if (!resolvedProject) {
174
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to retrieve work items for.");
175
+ if ("response" in result)
176
+ return result.response;
177
+ resolvedProject = result.resolved;
178
+ }
137
179
  const workItemApi = await connection.getWorkItemTrackingApi();
138
180
  const defaultFields = ["System.Id", "System.WorkItemType", "System.Title", "System.State", "System.Parent", "System.Tags", "Microsoft.VSTS.Common.StackRank", "System.AssignedTo"];
139
181
  // If no fields are provided, use the default set of fields
140
182
  const fieldsToUse = !fields || fields.length === 0 ? defaultFields : fields;
141
- const workitems = await workItemApi.getWorkItemsBatch({ ids, fields: fieldsToUse }, project);
183
+ const workitems = await workItemApi.getWorkItemsBatch({ ids, fields: fieldsToUse }, resolvedProject);
142
184
  // List of identity fields that need to be transformed from objects to formatted strings
143
185
  const identityFields = [
144
186
  "System.AssignedTo",
@@ -177,9 +219,9 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
177
219
  };
178
220
  }
179
221
  });
180
- server.tool(WORKITEM_TOOLS.get_work_item, "Get a single work item by ID.", {
222
+ server.tool(WORKITEM_TOOLS.get_work_item, "Get a single work item by ID. If a project is not specified, you will be prompted to select one.", {
181
223
  id: z.coerce.number().min(1).describe("The ID of the work item to retrieve."),
182
- project: z.string().describe("The name or ID of the Azure DevOps project."),
224
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
183
225
  fields: z.array(z.string()).optional().describe("Optional list of fields to include in the response. If not provided, all fields will be returned."),
184
226
  asOf: z.coerce.date().optional().describe("Optional date string to retrieve the work item as of a specific time. If not provided, the current state will be returned."),
185
227
  expand: z
@@ -190,8 +232,15 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
190
232
  }, async ({ id, project, fields, asOf, expand }) => {
191
233
  try {
192
234
  const connection = await connectionProvider();
235
+ let resolvedProject = project;
236
+ if (!resolvedProject) {
237
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to retrieve the work item from.");
238
+ if ("response" in result)
239
+ return result.response;
240
+ resolvedProject = result.resolved;
241
+ }
193
242
  const workItemApi = await connection.getWorkItemTrackingApi();
194
- const workItem = await workItemApi.getWorkItem(id, fields, asOf, expand, project);
243
+ const workItem = await workItemApi.getWorkItem(id, fields, asOf, expand, resolvedProject);
195
244
  return {
196
245
  content: [{ type: "text", text: JSON.stringify(workItem, null, 2) }],
197
246
  };
@@ -204,15 +253,22 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
204
253
  };
205
254
  }
206
255
  });
207
- server.tool(WORKITEM_TOOLS.list_work_item_comments, "Retrieve list of comments for a work item by ID.", {
208
- project: z.string().describe("The name or ID of the Azure DevOps project."),
256
+ server.tool(WORKITEM_TOOLS.list_work_item_comments, "Retrieve list of comments for a work item by ID. If a project is not specified, you will be prompted to select one.", {
257
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
209
258
  workItemId: z.coerce.number().min(1).describe("The ID of the work item to retrieve comments for."),
210
259
  top: z.coerce.number().default(50).describe("Optional number of comments to retrieve. Defaults to all comments."),
211
260
  }, async ({ project, workItemId, top }) => {
212
261
  try {
213
262
  const connection = await connectionProvider();
263
+ let resolvedProject = project;
264
+ if (!resolvedProject) {
265
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to list work item comments for.");
266
+ if ("response" in result)
267
+ return result.response;
268
+ resolvedProject = result.resolved;
269
+ }
214
270
  const workItemApi = await connection.getWorkItemTrackingApi();
215
- const comments = await workItemApi.getComments(project, workItemId, top);
271
+ const comments = await workItemApi.getComments(resolvedProject, workItemId, top);
216
272
  return {
217
273
  content: [{ type: "text", text: JSON.stringify(comments, null, 2) }],
218
274
  };
@@ -225,21 +281,28 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
225
281
  };
226
282
  }
227
283
  });
228
- server.tool(WORKITEM_TOOLS.add_work_item_comment, "Add comment to a work item by ID.", {
229
- project: z.string().describe("The name or ID of the Azure DevOps project."),
284
+ server.tool(WORKITEM_TOOLS.add_work_item_comment, "Add comment to a work item by ID. If a project is not specified, you will be prompted to select one.", {
285
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
230
286
  workItemId: z.coerce.number().min(1).describe("The ID of the work item to add a comment to."),
231
287
  comment: z.string().describe("The text of the comment to add to the work item."),
232
288
  format: z.enum(["markdown", "html"]).optional().default("html"),
233
289
  }, async ({ project, workItemId, comment, format }) => {
234
290
  try {
235
291
  const connection = await connectionProvider();
292
+ let resolvedProject = project;
293
+ if (!resolvedProject) {
294
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to add a work item comment in.");
295
+ if ("response" in result)
296
+ return result.response;
297
+ resolvedProject = result.resolved;
298
+ }
236
299
  const orgUrl = connection.serverUrl;
237
300
  const accessToken = await tokenProvider();
238
301
  const body = {
239
302
  text: comment,
240
303
  };
241
304
  const formatParameter = format === "markdown" ? 0 : 1;
242
- const response = await fetch(`${orgUrl}/${encodeURIComponent(project)}/_apis/wit/workItems/${workItemId}/comments?format=${formatParameter}&api-version=${markdownCommentsApiVersion}`, {
305
+ const response = await fetch(`${orgUrl}/${encodeURIComponent(resolvedProject)}/_apis/wit/workItems/${workItemId}/comments?format=${formatParameter}&api-version=${markdownCommentsApiVersion}`, {
243
306
  method: "POST",
244
307
  headers: {
245
308
  "Authorization": `Bearer ${accessToken}`,
@@ -264,8 +327,8 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
264
327
  };
265
328
  }
266
329
  });
267
- server.tool(WORKITEM_TOOLS.update_work_item_comment, "Update an existing comment on a work item by ID.", {
268
- project: z.string().describe("The name or ID of the Azure DevOps project."),
330
+ server.tool(WORKITEM_TOOLS.update_work_item_comment, "Update an existing comment on a work item by ID. If a project is not specified, you will be prompted to select one.", {
331
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
269
332
  workItemId: z.coerce.number().min(1).describe("The ID of the work item."),
270
333
  commentId: z.coerce.number().min(1).describe("The ID of the comment to update."),
271
334
  text: z.string().describe("The updated comment text."),
@@ -273,11 +336,18 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
273
336
  }, async ({ project, workItemId, commentId, text, format }) => {
274
337
  try {
275
338
  const connection = await connectionProvider();
339
+ let resolvedProject = project;
340
+ if (!resolvedProject) {
341
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to update the work item comment in.");
342
+ if ("response" in result)
343
+ return result.response;
344
+ resolvedProject = result.resolved;
345
+ }
276
346
  const orgUrl = connection.serverUrl;
277
347
  const accessToken = await tokenProvider();
278
348
  const body = { text };
279
349
  const formatParameter = format === "markdown" ? 0 : 1;
280
- const response = await fetch(`${orgUrl}/${encodeURIComponent(project)}/_apis/wit/workItems/${workItemId}/comments/${commentId}?format=${formatParameter}&api-version=${markdownCommentsApiVersion}`, {
350
+ const response = await fetch(`${orgUrl}/${encodeURIComponent(resolvedProject)}/_apis/wit/workItems/${workItemId}/comments/${commentId}?format=${formatParameter}&api-version=${markdownCommentsApiVersion}`, {
281
351
  method: "PATCH",
282
352
  headers: {
283
353
  "Authorization": `Bearer ${accessToken}`,
@@ -302,8 +372,8 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
302
372
  };
303
373
  }
304
374
  });
305
- server.tool(WORKITEM_TOOLS.list_work_item_revisions, "Retrieve list of revisions for a work item by ID.", {
306
- project: z.string().describe("The name or ID of the Azure DevOps project."),
375
+ server.tool(WORKITEM_TOOLS.list_work_item_revisions, "Retrieve list of revisions for a work item by ID. If a project is not specified, you will be prompted to select one.", {
376
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
307
377
  workItemId: z.coerce.number().min(1).describe("The ID of the work item to retrieve revisions for."),
308
378
  top: z.coerce.number().default(50).describe("Optional number of revisions to retrieve. If not provided, all revisions will be returned."),
309
379
  skip: z.coerce.number().optional().describe("Optional number of revisions to skip for pagination. Defaults to 0."),
@@ -315,8 +385,15 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
315
385
  }, async ({ project, workItemId, top, skip, expand }) => {
316
386
  try {
317
387
  const connection = await connectionProvider();
388
+ let resolvedProject = project;
389
+ if (!resolvedProject) {
390
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to list work item revisions for.");
391
+ if ("response" in result)
392
+ return result.response;
393
+ resolvedProject = result.resolved;
394
+ }
318
395
  const workItemApi = await connection.getWorkItemTrackingApi();
319
- const revisions = await workItemApi.getRevisions(workItemId, top, skip, safeEnumConvert(WorkItemExpand, expand), project);
396
+ const revisions = await workItemApi.getRevisions(workItemId, top, skip, safeEnumConvert(WorkItemExpand, expand), resolvedProject);
320
397
  // Dynamically clean up identity objects in revision fields
321
398
  // Identity objects typically have properties like displayName, url, _links, id, uniqueName, imageUrl, descriptor
322
399
  if (revisions && Array.isArray(revisions)) {
@@ -354,9 +431,9 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
354
431
  };
355
432
  }
356
433
  });
357
- 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.", {
434
+ 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. If a project is not specified, you will be prompted to select one.", {
358
435
  parentId: z.coerce.number().min(1).describe("The ID of the parent work item to create a child work item under."),
359
- project: z.string().describe("The name or ID of the Azure DevOps project."),
436
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
360
437
  workItemType: z.string().describe("The type of the child work item to create."),
361
438
  items: z.array(z.object({
362
439
  title: z.string().describe("The title of the child work item."),
@@ -368,6 +445,13 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
368
445
  }, async ({ parentId, project, workItemType, items }) => {
369
446
  try {
370
447
  const connection = await connectionProvider();
448
+ let resolvedProject = project;
449
+ if (!resolvedProject) {
450
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to create child work items in.");
451
+ if ("response" in result)
452
+ return result.response;
453
+ resolvedProject = result.resolved;
454
+ }
371
455
  const orgUrl = connection.serverUrl;
372
456
  const accessToken = await tokenProvider();
373
457
  if (items.length > 50) {
@@ -404,7 +488,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
404
488
  path: "/relations/-",
405
489
  value: {
406
490
  rel: "System.LinkTypes.Hierarchy-Reverse",
407
- url: `${connection.serverUrl}/${project}/_apis/wit/workItems/${parentId}`,
491
+ url: `${connection.serverUrl}/${resolvedProject}/_apis/wit/workItems/${parentId}`,
408
492
  },
409
493
  },
410
494
  ];
@@ -436,7 +520,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
436
520
  }
437
521
  return {
438
522
  method: "PATCH",
439
- uri: `/${encodeURIComponent(project)}/_apis/wit/workitems/$${encodeURIComponent(workItemType)}?api-version=${batchApiVersion}`,
523
+ uri: `/${encodeURIComponent(resolvedProject)}/_apis/wit/workitems/$${encodeURIComponent(workItemType)}?api-version=${batchApiVersion}`,
440
524
  headers: {
441
525
  "Content-Type": "application/json-patch+json",
442
526
  },
@@ -523,16 +607,23 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
523
607
  };
524
608
  }
525
609
  });
526
- server.tool(WORKITEM_TOOLS.get_work_items_for_iteration, "Retrieve a list of work items for a specified iteration.", {
527
- project: z.string().describe("The name or ID of the Azure DevOps project."),
610
+ server.tool(WORKITEM_TOOLS.get_work_items_for_iteration, "Retrieve a list of work items for a specified iteration. If a project is not specified, you will be prompted to select one.", {
611
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
528
612
  team: z.string().optional().describe("The name or ID of the Azure DevOps team. If not provided, the default team will be used."),
529
613
  iterationId: z.string().describe("The ID of the iteration to retrieve work items for."),
530
614
  }, async ({ project, team, iterationId }) => {
531
615
  try {
532
616
  const connection = await connectionProvider();
617
+ let resolvedProject = project;
618
+ if (!resolvedProject) {
619
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to retrieve work items for iteration.");
620
+ if ("response" in result)
621
+ return result.response;
622
+ resolvedProject = result.resolved;
623
+ }
533
624
  const workApi = await connection.getWorkApi();
534
625
  //get the work items for the current iteration
535
- const workItems = await workApi.getIterationWorkItems({ project, team }, iterationId);
626
+ const workItems = await workApi.getIterationWorkItems({ project: resolvedProject, team }, iterationId);
536
627
  return {
537
628
  content: [{ type: "text", text: JSON.stringify(workItems, null, 2) }],
538
629
  };
@@ -581,14 +672,21 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
581
672
  };
582
673
  }
583
674
  });
584
- server.tool(WORKITEM_TOOLS.get_work_item_type, "Get a specific work item type.", {
585
- project: z.string().describe("The name or ID of the Azure DevOps project."),
675
+ server.tool(WORKITEM_TOOLS.get_work_item_type, "Get a specific work item type. If a project is not specified, you will be prompted to select one.", {
676
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
586
677
  workItemType: z.string().describe("The name of the work item type to retrieve."),
587
678
  }, async ({ project, workItemType }) => {
588
679
  try {
589
680
  const connection = await connectionProvider();
681
+ let resolvedProject = project;
682
+ if (!resolvedProject) {
683
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to retrieve the work item type from.");
684
+ if ("response" in result)
685
+ return result.response;
686
+ resolvedProject = result.resolved;
687
+ }
590
688
  const workItemApi = await connection.getWorkItemTrackingApi();
591
- const workItemTypeInfo = await workItemApi.getWorkItemType(project, workItemType);
689
+ const workItemTypeInfo = await workItemApi.getWorkItemType(resolvedProject, workItemType);
592
690
  return {
593
691
  content: [{ type: "text", text: JSON.stringify(workItemTypeInfo, null, 2) }],
594
692
  };
@@ -601,8 +699,8 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
601
699
  };
602
700
  }
603
701
  });
604
- server.tool(WORKITEM_TOOLS.create_work_item, "Create a new work item in a specified project and work item type.", {
605
- project: z.string().describe("The name or ID of the Azure DevOps project."),
702
+ server.tool(WORKITEM_TOOLS.create_work_item, "Create a new work item in a specified project and work item type. If a project is not specified, you will be prompted to select one.", {
703
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
606
704
  workItemType: z.string().describe("The type of work item to create, e.g., 'Task', 'Bug', etc."),
607
705
  fields: z
608
706
  .array(z.object({
@@ -614,6 +712,13 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
614
712
  }, async ({ project, workItemType, fields }) => {
615
713
  try {
616
714
  const connection = await connectionProvider();
715
+ let resolvedProject = project;
716
+ if (!resolvedProject) {
717
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to create the work item in.");
718
+ if ("response" in result)
719
+ return result.response;
720
+ resolvedProject = result.resolved;
721
+ }
617
722
  const workItemApi = await connection.getWorkItemTrackingApi();
618
723
  const document = fields.map(({ name, value, format }) => ({
619
724
  op: "add",
@@ -632,7 +737,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
632
737
  });
633
738
  }
634
739
  });
635
- const newWorkItem = await workItemApi.createWorkItem(null, document, project, workItemType);
740
+ const newWorkItem = await workItemApi.createWorkItem(null, document, resolvedProject, workItemType);
636
741
  if (!newWorkItem) {
637
742
  return { content: [{ type: "text", text: "Work item was not created" }], isError: true };
638
743
  }
@@ -648,8 +753,8 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
648
753
  };
649
754
  }
650
755
  });
651
- server.tool(WORKITEM_TOOLS.get_query, "Get a query by its ID or path.", {
652
- project: z.string().describe("The name or ID of the Azure DevOps project."),
756
+ server.tool(WORKITEM_TOOLS.get_query, "Get a query by its ID or path. If a project is not specified, you will be prompted to select one.", {
757
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
653
758
  query: z.string().describe("The ID or path of the query to retrieve."),
654
759
  expand: z
655
760
  .enum(getEnumKeys(QueryExpand))
@@ -661,8 +766,15 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
661
766
  }, async ({ project, query, expand, depth, includeDeleted, useIsoDateFormat }) => {
662
767
  try {
663
768
  const connection = await connectionProvider();
769
+ let resolvedProject = project;
770
+ if (!resolvedProject) {
771
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to retrieve the query from.");
772
+ if ("response" in result)
773
+ return result.response;
774
+ resolvedProject = result.resolved;
775
+ }
664
776
  const workItemApi = await connection.getWorkItemTrackingApi();
665
- const queryDetails = await workItemApi.getQuery(project, query, safeEnumConvert(QueryExpand, expand), depth, includeDeleted, useIsoDateFormat);
777
+ const queryDetails = await workItemApi.getQuery(resolvedProject, query, safeEnumConvert(QueryExpand, expand), depth, includeDeleted, useIsoDateFormat);
666
778
  return {
667
779
  content: [{ type: "text", text: JSON.stringify(queryDetails, null, 2) }],
668
780
  };
@@ -776,8 +888,8 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
776
888
  };
777
889
  }
778
890
  });
779
- server.tool(WORKITEM_TOOLS.work_items_link, "Link work items together in batch.", {
780
- project: z.string().describe("The name or ID of the Azure DevOps project."),
891
+ server.tool(WORKITEM_TOOLS.work_items_link, "Link work items together in batch. If a project is not specified, you will be prompted to select one.", {
892
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
781
893
  updates: z
782
894
  .array(z.object({
783
895
  id: z.coerce.number().min(1).describe("The ID of the work item to update."),
@@ -792,6 +904,13 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
792
904
  }, async ({ project, updates }) => {
793
905
  try {
794
906
  const connection = await connectionProvider();
907
+ let resolvedProject = project;
908
+ if (!resolvedProject) {
909
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to link work items in.");
910
+ if ("response" in result)
911
+ return result.response;
912
+ resolvedProject = result.resolved;
913
+ }
795
914
  const orgUrl = connection.serverUrl;
796
915
  const accessToken = await tokenProvider();
797
916
  // Extract unique IDs from the updates array
@@ -809,7 +928,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
809
928
  path: "/relations/-",
810
929
  value: {
811
930
  rel: `${getLinkTypeFromName(type)}`,
812
- url: `${orgUrl}/${project}/_apis/wit/workItems/${linkToId}`,
931
+ url: `${orgUrl}/${resolvedProject}/_apis/wit/workItems/${linkToId}`,
813
932
  attributes: {
814
933
  comment: comment || "",
815
934
  },
@@ -841,8 +960,8 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
841
960
  };
842
961
  }
843
962
  });
844
- server.tool(WORKITEM_TOOLS.work_item_unlink, "Remove one or many links from a single work item", {
845
- project: z.string().describe("The name or ID of the Azure DevOps project."),
963
+ server.tool(WORKITEM_TOOLS.work_item_unlink, "Remove one or many links from a single work item. If a project is not specified, you will be prompted to select one.", {
964
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
846
965
  id: z.coerce.number().min(1).describe("The ID of the work item to remove the links from."),
847
966
  type: z
848
967
  .enum(["parent", "child", "duplicate", "duplicate of", "related", "successor", "predecessor", "tested by", "tests", "affects", "affected by", "artifact"])
@@ -852,8 +971,15 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
852
971
  }, async ({ project, id, type, url }) => {
853
972
  try {
854
973
  const connection = await connectionProvider();
974
+ let resolvedProject = project;
975
+ if (!resolvedProject) {
976
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to unlink work items in.");
977
+ if ("response" in result)
978
+ return result.response;
979
+ resolvedProject = result.resolved;
980
+ }
855
981
  const workItemApi = await connection.getWorkItemTrackingApi();
856
- const workItem = await workItemApi.getWorkItem(id, undefined, undefined, WorkItemExpand.Relations, project);
982
+ const workItem = await workItemApi.getWorkItem(id, undefined, undefined, WorkItemExpand.Relations, resolvedProject);
857
983
  const relations = workItem.relations ?? [];
858
984
  const linkType = getLinkTypeFromName(type);
859
985
  let relationIndexes = [];
@@ -879,7 +1005,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
879
1005
  op: "remove",
880
1006
  path: `/relations/${idx}`,
881
1007
  }));
882
- const updatedWorkItem = await workItemApi.updateWorkItem(null, apiUpdates, id, project);
1008
+ const updatedWorkItem = await workItemApi.updateWorkItem(null, apiUpdates, id, resolvedProject);
883
1009
  return {
884
1010
  content: [
885
1011
  {
@@ -905,9 +1031,9 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
905
1031
  };
906
1032
  }
907
1033
  });
908
- server.tool(WORKITEM_TOOLS.add_artifact_link, "Add artifact links (repository, branch, commit, builds) to work items. You can either provide the full vstfs URI or the individual components to build it automatically.", {
1034
+ server.tool(WORKITEM_TOOLS.add_artifact_link, "Add artifact links (repository, branch, commit, builds) to work items. You can either provide the full vstfs URI or the individual components to build it automatically. If a project is not specified, you will be prompted to select one.", {
909
1035
  workItemId: z.coerce.number().min(1).describe("The ID of the work item to add the artifact link to."),
910
- project: z.string().describe("The name or ID of the Azure DevOps project."),
1036
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
911
1037
  // Option 1: Provide full URI directly
912
1038
  artifactUri: z.string().optional().describe("The complete VSTFS URI of the artifact to link. If provided, individual component parameters are ignored."),
913
1039
  // Option 2: Provide individual components to build URI automatically based on linkType
@@ -940,6 +1066,13 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
940
1066
  }, async ({ workItemId, project, artifactUri, projectId, repositoryId, branchName, commitId, pullRequestId, buildId, linkType, comment }) => {
941
1067
  try {
942
1068
  const connection = await connectionProvider();
1069
+ let resolvedProject = project;
1070
+ if (!resolvedProject) {
1071
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to add the artifact link in.");
1072
+ if ("response" in result)
1073
+ return result.response;
1074
+ resolvedProject = result.resolved;
1075
+ }
943
1076
  const workItemTrackingApi = await connection.getWorkItemTrackingApi();
944
1077
  let finalArtifactUri;
945
1078
  if (artifactUri) {
@@ -1010,7 +1143,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
1010
1143
  },
1011
1144
  ];
1012
1145
  // Use the WorkItem API to update the work item with the new relation
1013
- const workItem = await workItemTrackingApi.updateWorkItem({}, patchDocument, workItemId, project);
1146
+ const workItem = await workItemTrackingApi.updateWorkItem({}, patchDocument, workItemId, resolvedProject);
1014
1147
  if (!workItem) {
1015
1148
  return { content: [{ type: "text", text: "Work item update failed" }], isError: true };
1016
1149
  }
@@ -1066,15 +1199,22 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
1066
1199
  };
1067
1200
  }
1068
1201
  });
1069
- server.tool(WORKITEM_TOOLS.get_work_item_attachment, "Download a work item attachment by its ID and return the content as a base64-encoded resource. Useful for viewing images (e.g. screenshots) attached to work items such as bugs.", {
1070
- project: z.string().describe("The name or ID of the Azure DevOps project."),
1202
+ server.tool(WORKITEM_TOOLS.get_work_item_attachment, "Download a work item attachment by its ID and return the content as a base64-encoded resource. Useful for viewing images (e.g. screenshots) attached to work items such as bugs. If a project is not specified, you will be prompted to select one.", {
1203
+ project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
1071
1204
  attachmentId: z.string().describe("The GUID of the attachment. Found in the attachment URL: https://dev.azure.com/{org}/{project}/_apis/wit/attachments/{attachmentId}"),
1072
1205
  fileName: z.string().optional().describe("The file name of the attachment, e.g. 'screenshot.png'. Used to determine the MIME type for the returned resource."),
1073
1206
  }, async ({ project, attachmentId, fileName }) => {
1074
1207
  try {
1075
1208
  const connection = await connectionProvider();
1209
+ let resolvedProject = project;
1210
+ if (!resolvedProject) {
1211
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to retrieve the work item attachment from.");
1212
+ if ("response" in result)
1213
+ return result.response;
1214
+ resolvedProject = result.resolved;
1215
+ }
1076
1216
  const workItemApi = await connection.getWorkItemTrackingApi();
1077
- const stream = await workItemApi.getAttachmentContent(attachmentId, fileName, project);
1217
+ const stream = await workItemApi.getAttachmentContent(attachmentId, fileName, resolvedProject);
1078
1218
  const chunks = [];
1079
1219
  await new Promise((resolve, reject) => {
1080
1220
  stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const packageVersion = "2.5.0-nightly.20260412";
1
+ export const packageVersion = "2.5.0-nightly.20260413";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-devops/mcp",
3
- "version": "2.5.0-nightly.20260412",
3
+ "version": "2.5.0-nightly.20260413",
4
4
  "mcpName": "microsoft.com/azure-devops",
5
5
  "description": "MCP server for interacting with Azure DevOps",
6
6
  "license": "MIT",