@azure-devops/mcp 2.2.2-nightly.20251112 → 2.2.2-nightly.20251113

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -114,6 +114,7 @@ Interact with these Azure DevOps services:
114
114
  - **pipelines_get_build_changes**: Get the changes associated with a specific build.
115
115
  - **pipelines_get_build_status**: Fetch the status of a specific build.
116
116
  - **pipelines_update_build_stage**: Update the stage of a specific build.
117
+ - **pipelines_create_pipeline**: Creates a pipeline definition with YAML configuration for a given project.
117
118
  - **pipelines_get_run**: Gets a run for a particular pipeline.
118
119
  - **pipelines_list_runs**: Gets top 10000 runs for a particular pipeline.
119
120
  - **pipelines_run_pipeline**: Starts a new run of a pipeline.
@@ -4,6 +4,7 @@ import { apiVersion, getEnumKeys, safeEnumConvert } from "../utils.js";
4
4
  import { BuildQueryOrder, DefinitionQueryOrder } from "azure-devops-node-api/interfaces/BuildInterfaces.js";
5
5
  import { z } from "zod";
6
6
  import { StageUpdateType } from "azure-devops-node-api/interfaces/BuildInterfaces.js";
7
+ import { ConfigurationType, RepositoryType } from "azure-devops-node-api/interfaces/PipelinesInterfaces.js";
7
8
  const PIPELINE_TOOLS = {
8
9
  pipelines_get_builds: "pipelines_get_builds",
9
10
  pipelines_get_build_changes: "pipelines_get_build_changes",
@@ -13,6 +14,7 @@ const PIPELINE_TOOLS = {
13
14
  pipelines_get_build_log_by_id: "pipelines_get_build_log_by_id",
14
15
  pipelines_get_build_status: "pipelines_get_build_status",
15
16
  pipelines_update_build_stage: "pipelines_update_build_stage",
17
+ pipelines_create_pipeline: "pipelines_create_pipeline",
16
18
  pipelines_get_run: "pipelines_get_run",
17
19
  pipelines_list_runs: "pipelines_list_runs",
18
20
  pipelines_run_pipeline: "pipelines_run_pipeline",
@@ -47,6 +49,56 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
47
49
  content: [{ type: "text", text: JSON.stringify(buildDefinitions, null, 2) }],
48
50
  };
49
51
  });
52
+ const variableSchema = z.object({
53
+ value: z.string().optional(),
54
+ isSecret: z.boolean().optional(),
55
+ });
56
+ server.tool(PIPELINE_TOOLS.pipelines_create_pipeline, "Creates a pipeline definition with YAML configuration for a given project.", {
57
+ project: z.string().describe("Project ID or name to run the build in."),
58
+ name: z.string().describe("Name of the new pipeline."),
59
+ folder: z.string().optional().describe("Folder path for the new pipeline. Defaults to '\\' if not specified."),
60
+ yamlPath: z.string().describe("The path to the pipeline's YAML file in the repository"),
61
+ repositoryType: z.enum(getEnumKeys(RepositoryType)).describe("The type of repository where the pipeline's YAML file is located."),
62
+ repositoryName: z.string().describe("The name of the repository. In case of GitHub repository, this is the full name (:owner/:repo) - e.g. octocat/Hello-World."),
63
+ repositoryId: z.string().optional().describe("The ID of the repository."),
64
+ repositoryConnectionId: z.string().optional().describe("The service connection ID for GitHub repositories. Not required for Azure Repos Git."),
65
+ }, async ({ project, name, folder, yamlPath, repositoryType, repositoryName, repositoryId, repositoryConnectionId }) => {
66
+ const connection = await connectionProvider();
67
+ const pipelinesApi = await connection.getPipelinesApi();
68
+ let repositoryTypeEnumValue = safeEnumConvert(RepositoryType, repositoryType);
69
+ let repositoryPayload = {
70
+ type: repositoryType,
71
+ };
72
+ if (repositoryTypeEnumValue === RepositoryType.AzureReposGit) {
73
+ repositoryPayload.id = repositoryId;
74
+ repositoryPayload.name = repositoryName;
75
+ }
76
+ else if (repositoryTypeEnumValue === RepositoryType.GitHub) {
77
+ if (!repositoryConnectionId) {
78
+ throw new Error("Parameter 'repositoryConnectionId' is required for GitHub repositories.");
79
+ }
80
+ repositoryPayload.connection = { id: repositoryConnectionId };
81
+ repositoryPayload.fullname = repositoryName;
82
+ }
83
+ else {
84
+ throw new Error("Unsupported repository type");
85
+ }
86
+ const yamlConfigurationType = getEnumKeys(ConfigurationType).find((k) => ConfigurationType[k] === ConfigurationType.Yaml);
87
+ const createPipelineParams = {
88
+ name: name,
89
+ folder: folder || "\\",
90
+ configuration: {
91
+ type: yamlConfigurationType,
92
+ path: yamlPath,
93
+ repository: repositoryPayload,
94
+ variables: undefined,
95
+ },
96
+ };
97
+ const newPipeline = await pipelinesApi.createPipeline(createPipelineParams, project);
98
+ return {
99
+ content: [{ type: "text", text: JSON.stringify(newPipeline, null, 2) }],
100
+ };
101
+ });
50
102
  server.tool(PIPELINE_TOOLS.pipelines_get_build_definition_revisions, "Retrieves a list of revisions for a specific build definition.", {
51
103
  project: z.string().describe("Project ID or name to get the build definition revisions for"),
52
104
  definitionId: z.number().describe("ID of the build definition to get revisions for"),
@@ -154,10 +206,6 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
154
206
  content: [{ type: "text", text: JSON.stringify(pipelineRuns, null, 2) }],
155
207
  };
156
208
  });
157
- const variableSchema = z.object({
158
- value: z.string().optional(),
159
- isSecret: z.boolean().optional(),
160
- });
161
209
  const resourcesSchema = z.object({
162
210
  builds: z
163
211
  .record(z.string().describe("Name of the build resource."), z.object({
@@ -61,26 +61,44 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
61
61
  project: z.string().describe("The name or ID of the Azure DevOps project."),
62
62
  team: z.string().describe("The name or ID of the Azure DevOps team."),
63
63
  }, async ({ project, team }) => {
64
- const connection = await connectionProvider();
65
- const workApi = await connection.getWorkApi();
66
- const teamContext = { project, team };
67
- const backlogs = await workApi.getBacklogs(teamContext);
68
- return {
69
- content: [{ type: "text", text: JSON.stringify(backlogs, null, 2) }],
70
- };
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
+ }
71
80
  });
72
81
  server.tool(WORKITEM_TOOLS.list_backlog_work_items, "Retrieve a list of backlogs of for a given project, team, and backlog category", {
73
82
  project: z.string().describe("The name or ID of the Azure DevOps project."),
74
83
  team: z.string().describe("The name or ID of the Azure DevOps team."),
75
84
  backlogId: z.string().describe("The ID of the backlog category to retrieve work items from."),
76
85
  }, async ({ project, team, backlogId }) => {
77
- const connection = await connectionProvider();
78
- const workApi = await connection.getWorkApi();
79
- const teamContext = { project, team };
80
- const workItems = await workApi.getBacklogLevelWorkItems(teamContext, backlogId);
81
- return {
82
- content: [{ type: "text", text: JSON.stringify(workItems, null, 2) }],
83
- };
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
+ }
84
102
  });
85
103
  server.tool(WORKITEM_TOOLS.my_work_items, "Retrieve a list of work items relevent to the authenticated user.", {
86
104
  project: z.string().describe("The name or ID of the Azure DevOps project."),
@@ -88,53 +106,71 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
88
106
  top: z.number().default(50).describe("The maximum number of work items to return. Defaults to 50."),
89
107
  includeCompleted: z.boolean().default(false).describe("Whether to include completed work items. Defaults to false."),
90
108
  }, async ({ project, type, top, includeCompleted }) => {
91
- const connection = await connectionProvider();
92
- const workApi = await connection.getWorkApi();
93
- const workItems = await workApi.getPredefinedQueryResults(project, type, top, includeCompleted);
94
- return {
95
- content: [{ type: "text", text: JSON.stringify(workItems, null, 2) }],
96
- };
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
+ }
97
124
  });
98
125
  server.tool(WORKITEM_TOOLS.get_work_items_batch_by_ids, "Retrieve list of work items by IDs in batch.", {
99
126
  project: z.string().describe("The name or ID of the Azure DevOps project."),
100
127
  ids: z.array(z.number()).describe("The IDs of the work items to retrieve."),
101
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."),
102
129
  }, async ({ project, ids, fields }) => {
103
- const connection = await connectionProvider();
104
- const workItemApi = await connection.getWorkItemTrackingApi();
105
- const defaultFields = ["System.Id", "System.WorkItemType", "System.Title", "System.State", "System.Parent", "System.Tags", "Microsoft.VSTS.Common.StackRank", "System.AssignedTo"];
106
- // If no fields are provided, use the default set of fields
107
- const fieldsToUse = !fields || fields.length === 0 ? defaultFields : fields;
108
- const workitems = await workItemApi.getWorkItemsBatch({ ids, fields: fieldsToUse }, project);
109
- // List of identity fields that need to be transformed from objects to formatted strings
110
- const identityFields = [
111
- "System.AssignedTo",
112
- "System.CreatedBy",
113
- "System.ChangedBy",
114
- "System.AuthorizedAs",
115
- "Microsoft.VSTS.Common.ActivatedBy",
116
- "Microsoft.VSTS.Common.ResolvedBy",
117
- "Microsoft.VSTS.Common.ClosedBy",
118
- ];
119
- // Format identity fields to include displayName and uniqueName
120
- // Removing the identity object as the response. It's too much and not needed
121
- if (workitems && Array.isArray(workitems)) {
122
- workitems.forEach((item) => {
123
- if (item.fields) {
124
- identityFields.forEach((fieldName) => {
125
- if (item.fields && item.fields[fieldName] && typeof item.fields[fieldName] === "object") {
126
- const identityField = item.fields[fieldName];
127
- const name = identityField.displayName || "";
128
- const email = identityField.uniqueName || "";
129
- item.fields[fieldName] = `${name} <${email}>`.trim();
130
- }
131
- });
132
- }
133
- });
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
+ };
134
173
  }
135
- return {
136
- content: [{ type: "text", text: JSON.stringify(workitems, null, 2) }],
137
- };
138
174
  });
139
175
  server.tool(WORKITEM_TOOLS.get_work_item, "Get a single work item by ID.", {
140
176
  id: z.number().describe("The ID of the work item to retrieve."),
@@ -147,24 +183,42 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
147
183
  .optional()
148
184
  .describe("Expand options include 'all', 'fields', 'links', 'none', and 'relations'. Relations can be used to get child workitems. Defaults to 'none'."),
149
185
  }, async ({ id, project, fields, asOf, expand }) => {
150
- const connection = await connectionProvider();
151
- const workItemApi = await connection.getWorkItemTrackingApi();
152
- const workItem = await workItemApi.getWorkItem(id, fields, asOf, expand, project);
153
- return {
154
- content: [{ type: "text", text: JSON.stringify(workItem, null, 2) }],
155
- };
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
+ }
156
201
  });
157
202
  server.tool(WORKITEM_TOOLS.list_work_item_comments, "Retrieve list of comments for a work item by ID.", {
158
203
  project: z.string().describe("The name or ID of the Azure DevOps project."),
159
204
  workItemId: z.number().describe("The ID of the work item to retrieve comments for."),
160
205
  top: z.number().default(50).describe("Optional number of comments to retrieve. Defaults to all comments."),
161
206
  }, async ({ project, workItemId, top }) => {
162
- const connection = await connectionProvider();
163
- const workItemApi = await connection.getWorkItemTrackingApi();
164
- const comments = await workItemApi.getComments(project, workItemId, top);
165
- return {
166
- content: [{ type: "text", text: JSON.stringify(comments, null, 2) }],
167
- };
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
+ }
168
222
  });
169
223
  server.tool(WORKITEM_TOOLS.add_work_item_comment, "Add comment to a work item by ID.", {
170
224
  project: z.string().describe("The name or ID of the Azure DevOps project."),
@@ -172,29 +226,38 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
172
226
  comment: z.string().describe("The text of the comment to add to the work item."),
173
227
  format: z.enum(["markdown", "html"]).optional().default("html"),
174
228
  }, async ({ project, workItemId, comment, format }) => {
175
- const connection = await connectionProvider();
176
- const orgUrl = connection.serverUrl;
177
- const accessToken = await tokenProvider();
178
- const body = {
179
- text: comment,
180
- };
181
- const formatParameter = format === "markdown" ? 0 : 1;
182
- const response = await fetch(`${orgUrl}/${project}/_apis/wit/workItems/${workItemId}/comments?format=${formatParameter}&api-version=${markdownCommentsApiVersion}`, {
183
- method: "POST",
184
- headers: {
185
- "Authorization": `Bearer ${accessToken}`,
186
- "Content-Type": "application/json",
187
- "User-Agent": userAgentProvider(),
188
- },
189
- body: JSON.stringify(body),
190
- });
191
- if (!response.ok) {
192
- throw new Error(`Failed to add a work item comment: ${response.statusText}}`);
193
- }
194
- const comments = await response.text();
195
- return {
196
- content: [{ type: "text", text: comments }],
197
- };
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
+ }
198
261
  });
199
262
  server.tool(WORKITEM_TOOLS.list_work_item_revisions, "Retrieve list of revisions for a work item by ID.", {
200
263
  project: z.string().describe("The name or ID of the Azure DevOps project."),
@@ -207,37 +270,46 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
207
270
  .optional()
208
271
  .describe("Optional expand parameter to include additional details. Defaults to 'None'."),
209
272
  }, async ({ project, workItemId, top, skip, expand }) => {
210
- const connection = await connectionProvider();
211
- const workItemApi = await connection.getWorkItemTrackingApi();
212
- const revisions = await workItemApi.getRevisions(workItemId, top, skip, safeEnumConvert(WorkItemExpand, expand), project);
213
- // Dynamically clean up identity objects in revision fields
214
- // Identity objects typically have properties like displayName, url, _links, id, uniqueName, imageUrl, descriptor
215
- if (revisions && Array.isArray(revisions)) {
216
- revisions.forEach((revision) => {
217
- if (revision.fields) {
218
- Object.keys(revision.fields).forEach((fieldName) => {
219
- const fieldValue = revision.fields ? revision.fields[fieldName] : undefined;
220
- // Check if this is an identity object by looking for common identity properties
221
- if (fieldValue &&
222
- typeof fieldValue === "object" &&
223
- !Array.isArray(fieldValue) &&
224
- "displayName" in fieldValue &&
225
- ("url" in fieldValue || "_links" in fieldValue || "uniqueName" in fieldValue)) {
226
- // Remove unwanted properties from identity objects
227
- delete fieldValue.url;
228
- delete fieldValue._links;
229
- delete fieldValue.id;
230
- delete fieldValue.uniqueName;
231
- delete fieldValue.imageUrl;
232
- delete fieldValue.descriptor;
233
- }
234
- });
235
- }
236
- });
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
+ };
237
312
  }
238
- return {
239
- content: [{ type: "text", text: JSON.stringify(revisions, null, 2) }],
240
- };
241
313
  });
242
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.", {
243
315
  parentId: z.number().describe("The ID of the parent work item to create a child work item under."),
@@ -413,13 +485,22 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
413
485
  team: z.string().optional().describe("The name or ID of the Azure DevOps team. If not provided, the default team will be used."),
414
486
  iterationId: z.string().describe("The ID of the iteration to retrieve work items for."),
415
487
  }, async ({ project, team, iterationId }) => {
416
- const connection = await connectionProvider();
417
- const workApi = await connection.getWorkApi();
418
- //get the work items for the current iteration
419
- const workItems = await workApi.getIterationWorkItems({ project, team }, iterationId);
420
- return {
421
- content: [{ type: "text", text: JSON.stringify(workItems, null, 2) }],
422
- };
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
+ }
423
504
  });
424
505
  server.tool(WORKITEM_TOOLS.update_work_item, "Update a work item by ID with specified fields.", {
425
506
  id: z.number().describe("The ID of the work item to update."),
@@ -436,28 +517,46 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
436
517
  }))
437
518
  .describe("An array of field updates to apply to the work item."),
438
519
  }, async ({ id, updates }) => {
439
- const connection = await connectionProvider();
440
- const workItemApi = await connection.getWorkItemTrackingApi();
441
- // Convert operation names to lowercase for API
442
- const apiUpdates = updates.map((update) => ({
443
- ...update,
444
- op: update.op,
445
- }));
446
- const updatedWorkItem = await workItemApi.updateWorkItem(null, apiUpdates, id);
447
- return {
448
- content: [{ type: "text", text: JSON.stringify(updatedWorkItem, null, 2) }],
449
- };
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
+ }
450
540
  });
451
541
  server.tool(WORKITEM_TOOLS.get_work_item_type, "Get a specific work item type.", {
452
542
  project: z.string().describe("The name or ID of the Azure DevOps project."),
453
543
  workItemType: z.string().describe("The name of the work item type to retrieve."),
454
544
  }, async ({ project, workItemType }) => {
455
- const connection = await connectionProvider();
456
- const workItemApi = await connection.getWorkItemTrackingApi();
457
- const workItemTypeInfo = await workItemApi.getWorkItemType(project, workItemType);
458
- return {
459
- content: [{ type: "text", text: JSON.stringify(workItemTypeInfo, null, 2) }],
460
- };
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
+ }
461
560
  });
462
561
  server.tool(WORKITEM_TOOLS.create_work_item, "Create a new work item in a specified project and work item type.", {
463
562
  project: z.string().describe("The name or ID of the Azure DevOps project."),
@@ -517,12 +616,21 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
517
616
  includeDeleted: z.boolean().default(false).describe("Whether to include deleted items in the query results. Defaults to false."),
518
617
  useIsoDateFormat: z.boolean().default(false).describe("Whether to use ISO date format in the response. Defaults to false."),
519
618
  }, async ({ project, query, expand, depth, includeDeleted, useIsoDateFormat }) => {
520
- const connection = await connectionProvider();
521
- const workItemApi = await connection.getWorkItemTrackingApi();
522
- const queryDetails = await workItemApi.getQuery(project, query, safeEnumConvert(QueryExpand, expand), depth, includeDeleted, useIsoDateFormat);
523
- return {
524
- content: [{ type: "text", text: JSON.stringify(queryDetails, null, 2) }],
525
- };
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
+ }
526
634
  });
527
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.", {
528
636
  id: z.string().describe("The ID of the query to retrieve results for."),
@@ -532,21 +640,30 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
532
640
  top: z.number().default(50).describe("The maximum number of results to return. Defaults to 50."),
533
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."),
534
642
  }, async ({ id, project, team, timePrecision, top, responseType }) => {
535
- const connection = await connectionProvider();
536
- const workItemApi = await connection.getWorkItemTrackingApi();
537
- const teamContext = { project, team };
538
- const queryResult = await workItemApi.queryById(id, teamContext, timePrecision, top);
539
- // If ids mode, extract and return only the IDs
540
- if (responseType === "ids") {
541
- const ids = queryResult.workItems?.map((workItem) => workItem.id).filter((id) => id !== undefined) || [];
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
542
656
  return {
543
- content: [{ type: "text", text: JSON.stringify({ ids, count: ids.length }, null, 2) }],
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,
544
665
  };
545
666
  }
546
- // Default: return full query results
547
- return {
548
- content: [{ type: "text", text: JSON.stringify(queryResult, null, 2) }],
549
- };
550
667
  });
551
668
  server.tool(WORKITEM_TOOLS.update_work_items_batch, "Update work items in batch", {
552
669
  updates: z
@@ -559,53 +676,62 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
559
676
  }))
560
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)."),
561
678
  }, async ({ updates }) => {
562
- const connection = await connectionProvider();
563
- const orgUrl = connection.serverUrl;
564
- const accessToken = await tokenProvider();
565
- // Extract unique IDs from the updates array
566
- const uniqueIds = Array.from(new Set(updates.map((update) => update.id)));
567
- const body = uniqueIds.map((id) => {
568
- const workItemUpdates = updates.filter((update) => update.id === id);
569
- const operations = workItemUpdates.map(({ op, path, value, format }) => ({
570
- op: op,
571
- path: path,
572
- value: encodeFormattedValue(value, format),
573
- }));
574
- // Add format operations for Markdown fields
575
- workItemUpdates.forEach(({ path, value, format }) => {
576
- if (format === "Markdown" && value && value.length > 50) {
577
- operations.push({
578
- op: "Add",
579
- path: `/multilineFieldsFormat${path.replace("/fields", "")}`,
580
- value: "Markdown",
581
- });
582
- }
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
+ };
583
710
  });
584
- return {
711
+ const response = await fetch(`${orgUrl}/_apis/wit/$batch?api-version=${batchApiVersion}`, {
585
712
  method: "PATCH",
586
- uri: `/_apis/wit/workitems/${id}?api-version=${batchApiVersion}`,
587
713
  headers: {
588
- "Content-Type": "application/json-patch+json",
714
+ "Authorization": `Bearer ${accessToken}`,
715
+ "Content-Type": "application/json",
716
+ "User-Agent": userAgentProvider(),
589
717
  },
590
- body: operations,
591
- };
592
- });
593
- const response = await fetch(`${orgUrl}/_apis/wit/$batch?api-version=${batchApiVersion}`, {
594
- method: "PATCH",
595
- headers: {
596
- "Authorization": `Bearer ${accessToken}`,
597
- "Content-Type": "application/json",
598
- "User-Agent": userAgentProvider(),
599
- },
600
- body: JSON.stringify(body),
601
- });
602
- if (!response.ok) {
603
- throw new Error(`Failed to update work items in batch: ${response.statusText}`);
604
- }
605
- const result = await response.json();
606
- return {
607
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
608
- };
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
+ }
609
735
  });
610
736
  server.tool(WORKITEM_TOOLS.work_items_link, "Link work items together in batch.", {
611
737
  project: z.string().describe("The name or ID of the Azure DevOps project."),
@@ -621,47 +747,56 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
621
747
  }))
622
748
  .describe(""),
623
749
  }, async ({ project, updates }) => {
624
- const connection = await connectionProvider();
625
- const orgUrl = connection.serverUrl;
626
- const accessToken = await tokenProvider();
627
- // Extract unique IDs from the updates array
628
- const uniqueIds = Array.from(new Set(updates.map((update) => update.id)));
629
- const body = uniqueIds.map((id) => ({
630
- method: "PATCH",
631
- uri: `/_apis/wit/workitems/${id}?api-version=${batchApiVersion}`,
632
- headers: {
633
- "Content-Type": "application/json-patch+json",
634
- },
635
- body: updates
636
- .filter((update) => update.id === id)
637
- .map(({ linkToId, type, comment }) => ({
638
- op: "add",
639
- path: "/relations/-",
640
- value: {
641
- rel: `${getLinkTypeFromName(type)}`,
642
- url: `${orgUrl}/${project}/_apis/wit/workItems/${linkToId}`,
643
- attributes: {
644
- 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
+ },
645
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(),
646
782
  },
647
- })),
648
- }));
649
- const response = await fetch(`${orgUrl}/_apis/wit/$batch?api-version=${batchApiVersion}`, {
650
- method: "PATCH",
651
- headers: {
652
- "Authorization": `Bearer ${accessToken}`,
653
- "Content-Type": "application/json",
654
- "User-Agent": userAgentProvider(),
655
- },
656
- body: JSON.stringify(body),
657
- });
658
- if (!response.ok) {
659
- throw new Error(`Failed to update work items in batch: ${response.statusText}`);
660
- }
661
- const result = await response.json();
662
- return {
663
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
664
- };
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
+ }
665
800
  });
666
801
  server.tool(WORKITEM_TOOLS.work_item_unlink, "Remove one or many links from a single work item", {
667
802
  project: z.string().describe("The name or ID of the Azure DevOps project."),
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const packageVersion = "2.2.2-nightly.20251112";
1
+ export const packageVersion = "2.2.2-nightly.20251113";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-devops/mcp",
3
- "version": "2.2.2-nightly.20251112",
3
+ "version": "2.2.2-nightly.20251113",
4
4
  "description": "MCP server for interacting with Azure DevOps",
5
5
  "license": "MIT",
6
6
  "author": "Microsoft Corporation",