@azure-devops/mcp 2.4.0-nightly.20260315 β†’ 2.4.0-nightly.20260317

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
@@ -1,5 +1,10 @@
1
1
  # ⭐ Azure DevOps MCP Server
2
2
 
3
+ > [!IMPORTANT]
4
+ > The Azure DevOps Remote MCP Server is now available in public preview for all organizations. We recommend migrating to the [Remote MCP Server](https://learn.microsoft.com/en-us/azure/devops/mcp-server/remote-mcp-server) going forward.
5
+ >
6
+ > [Learn more](#-remote-mcp-server)
7
+
3
8
  Easily install the Azure DevOps MCP Server for VS Code or VS Code Insiders:
4
9
 
5
10
  [![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-Install_AzureDevops_MCP_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=ado&config=%7B%20%22type%22%3A%20%22stdio%22%2C%20%22command%22%3A%20%22npx%22%2C%20%22args%22%3A%20%5B%22-y%22%2C%20%22%40azure-devops%2Fmcp%22%2C%20%22%24%7Binput%3Aado_org%7D%22%5D%7D&inputs=%5B%7B%22id%22%3A%20%22ado_org%22%2C%20%22type%22%3A%20%22promptString%22%2C%20%22description%22%3A%20%22Azure%20DevOps%20organization%20name%20%20%28e.g.%20%27contoso%27%29%22%7D%5D)
@@ -11,13 +16,14 @@ This TypeScript project provides a **local** MCP server for Azure DevOps, enabli
11
16
 
12
17
  1. [πŸ“Ί Overview](#-overview)
13
18
  2. [πŸ† Expectations](#-expectations)
14
- 3. [βš™οΈ Supported Tools](#️-supported-tools)
15
- 4. [πŸ”Œ Installation & Getting Started](#-installation--getting-started)
16
- 5. [🌏 Using Domains](#-using-domains)
17
- 6. [πŸ“ Troubleshooting](#-troubleshooting)
18
- 7. [🎩 Examples & Best Practices](#-examples--best-practices)
19
- 8. [πŸ™‹β€β™€οΈ Frequently Asked Questions](#️-frequently-asked-questions)
20
- 9. [πŸ“Œ Contributing](#-contributing)
19
+ 3. [πŸš€ Remote MCP Server](#-remote-mcp-server)
20
+ 4. [βš™οΈ Supported Tools](#️-supported-tools)
21
+ 5. [πŸ”Œ Installation & Getting Started](#-installation--getting-started)
22
+ 6. [🌏 Using Domains](#-using-domains)
23
+ 7. [πŸ“ Troubleshooting](#-troubleshooting)
24
+ 8. [🎩 Examples & Best Practices](#-examples--best-practices)
25
+ 9. [πŸ™‹β€β™€οΈ Frequently Asked Questions](#️-frequently-asked-questions)
26
+ 10. [πŸ“Œ Contributing](#-contributing)
21
27
 
22
28
  ## πŸ“Ί Overview
23
29
 
@@ -40,6 +46,21 @@ The Azure DevOps MCP Server brings Azure DevOps context to your agents. Try prom
40
46
 
41
47
  The Azure DevOps MCP Server is built from tools that are concise, simple, focused, and easy to useβ€”each designed for a specific scenario. We intentionally avoid complex tools that try to do too much. The goal is to provide a thin abstraction layer over the REST APIs, making data access straightforward and letting the language model handle complex reasoning.
42
48
 
49
+ ## πŸš€ Remote MCP Server
50
+
51
+ The Azure DevOps **Remote MCP Server** is now available in [public preview](https://devblogs.microsoft.com/devops/azure-devops-remote-mcp-server-public-preview).
52
+
53
+ Over time, the Remote MCP Server will replace this local MCP Server. We will continue to support the local server for now, but future investments will primarily focus on the remote experience.
54
+
55
+ We encourage all users of the local MCP Server to begin migrating to the Remote MCP Server.
56
+
57
+ If you encounter issues with tools, need support, or have a feature request, you can report an issue using the [Remote MCP Server issue template](https://github.com/microsoft/azure-devops-mcp/issues/new?template=remote-mcp-server-issue.md). During the preview period, we will track Remote MCP Server issues through this repository.
58
+
59
+ > [!WARNING]
60
+ > Internal Microsoft users of the Remote MCP Server should **not** create issues in this repository. Please use the dedicated Teams channel instead.
61
+
62
+ For instructions on how to get started with the Remote MCP Server, see the [onboarding documentation](https://learn.microsoft.com/en-us/azure/devops/mcp-server/remote-mcp-server).
63
+
43
64
  ## βš™οΈ Supported Tools
44
65
 
45
66
  See [TOOLSET.md](./docs/TOOLSET.md) for a comprehensive list.
@@ -0,0 +1,62 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ export async function elicitProject(server, connection, message) {
4
+ const coreApi = await connection.getCoreApi();
5
+ const projects = await coreApi.getProjects("wellFormed", 100, 0, undefined, false);
6
+ if (!projects || projects.length === 0) {
7
+ return { response: { content: [{ type: "text", text: "No projects found to select from." }], isError: true } };
8
+ }
9
+ const result = await server.server.elicitInput({
10
+ mode: "form",
11
+ message: message ?? "Select the Azure DevOps project.",
12
+ requestedSchema: {
13
+ type: "object",
14
+ properties: {
15
+ project: {
16
+ type: "string",
17
+ title: "Project",
18
+ description: "The Azure DevOps project.",
19
+ oneOf: projects.map((p) => ({
20
+ const: p.name ?? p.id ?? "",
21
+ title: p.name ?? p.id ?? "Unknown project",
22
+ })),
23
+ },
24
+ },
25
+ required: ["project"],
26
+ },
27
+ });
28
+ if (result.action !== "accept" || !result.content?.project) {
29
+ return { response: { content: [{ type: "text", text: "Project selection cancelled." }] } };
30
+ }
31
+ return { resolved: String(result.content.project) };
32
+ }
33
+ export async function elicitTeam(server, connection, project, message) {
34
+ const coreApi = await connection.getCoreApi();
35
+ const teams = await coreApi.getTeams(project, undefined, undefined, undefined, false);
36
+ if (!teams || teams.length === 0) {
37
+ return { response: { content: [{ type: "text", text: "No teams found to select from." }], isError: true } };
38
+ }
39
+ const result = await server.server.elicitInput({
40
+ mode: "form",
41
+ message: message ?? "Select the team.",
42
+ requestedSchema: {
43
+ type: "object",
44
+ properties: {
45
+ team: {
46
+ type: "string",
47
+ title: "Team",
48
+ description: "The team from a specific Azure DevOps project.",
49
+ oneOf: teams.map((t) => ({
50
+ const: t.name ?? t.id ?? "",
51
+ title: t.name ?? t.id ?? "Unknown team",
52
+ })),
53
+ },
54
+ },
55
+ required: ["team"],
56
+ },
57
+ });
58
+ if (result.action !== "accept" || !result.content?.team) {
59
+ return { response: { content: [{ type: "text", text: "Team selection cancelled." }] } };
60
+ }
61
+ return { resolved: String(result.content.team) };
62
+ }
@@ -2,6 +2,7 @@
2
2
  // Licensed under the MIT License.
3
3
  import { z } from "zod";
4
4
  import { searchIdentities } from "./auth.js";
5
+ import { elicitProject } from "../shared/elicitations.js";
5
6
  const CORE_TOOLS = {
6
7
  list_project_teams: "core_list_project_teams",
7
8
  list_projects: "core_list_projects",
@@ -13,7 +14,7 @@ function filterProjectsByName(projects, projectNameFilter) {
13
14
  }
14
15
  function configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider) {
15
16
  server.tool(CORE_TOOLS.list_project_teams, "Retrieve a list of teams for an Azure DevOps project. If a project is not specified, you will be prompted to select one.", {
16
- project: z.string().optional().describe("The name or ID of the Azure DevOps project. If not provided, a project selection prompt will be shown."),
17
+ 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."),
17
18
  mine: z.boolean().optional().describe("If true, only return teams that the authenticated user is a member of."),
18
19
  top: z.number().optional().describe("The maximum number of teams to return. Defaults to 100."),
19
20
  skip: z.number().optional().describe("The number of teams to skip for pagination. Defaults to 0."),
@@ -23,33 +24,10 @@ function configureCoreTools(server, tokenProvider, connectionProvider, userAgent
23
24
  const coreApi = await connection.getCoreApi();
24
25
  let resolvedProject = project;
25
26
  if (!resolvedProject) {
26
- const projects = await coreApi.getProjects("wellFormed", 100, 0, undefined, false);
27
- if (!projects || projects.length === 0) {
28
- return { content: [{ type: "text", text: "No projects found to select from." }], isError: true };
29
- }
30
- const result = await server.server.elicitInput({
31
- mode: "form",
32
- message: "Select the Azure DevOps project to list teams for.",
33
- requestedSchema: {
34
- type: "object",
35
- properties: {
36
- project: {
37
- type: "string",
38
- title: "Project",
39
- description: "The Azure DevOps project to list teams for.",
40
- oneOf: projects.map((p) => ({
41
- const: p.name ?? p.id ?? "",
42
- title: p.name ?? p.id ?? "Unknown project",
43
- })),
44
- },
45
- },
46
- required: ["project"],
47
- },
48
- });
49
- if (result.action !== "accept" || !result.content?.project) {
50
- return { content: [{ type: "text", text: "Project selection cancelled." }] };
51
- }
52
- resolvedProject = String(result.content.project);
27
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to list teams for.");
28
+ if ("response" in result)
29
+ return result.response;
30
+ resolvedProject = result.resolved;
53
31
  }
54
32
  const teams = await coreApi.getTeams(resolvedProject, mine, top, skip, false);
55
33
  if (!teams) {
@@ -2,6 +2,7 @@
2
2
  // Licensed under the MIT License.
3
3
  import { z } from "zod";
4
4
  import { TreeStructureGroup, TreeNodeStructureType } from "azure-devops-node-api/interfaces/WorkItemTrackingInterfaces.js";
5
+ import { elicitProject, elicitTeam } from "../shared/elicitations.js";
5
6
  const WORK_TOOLS = {
6
7
  list_team_iterations: "work_list_team_iterations",
7
8
  list_iterations: "work_list_iterations",
@@ -13,20 +14,37 @@ const WORK_TOOLS = {
13
14
  get_team_settings: "work_get_team_settings",
14
15
  };
15
16
  function configureWorkTools(server, _, connectionProvider) {
16
- server.tool(WORK_TOOLS.list_team_iterations, "Retrieve a list of iterations for a specific team in a project.", {
17
- project: z.string().describe("The name or ID of the Azure DevOps project."),
18
- team: z.string().describe("The name or ID of the Azure DevOps team."),
17
+ server.tool(WORK_TOOLS.list_team_iterations, "Retrieve a list of iterations for a specific team in a project. If a project or team is not specified, you will be prompted to select one.", {
18
+ 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."),
19
+ 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."),
19
20
  timeframe: z.enum(["current"]).optional().describe("The timeframe for which to retrieve iterations. Currently, only 'current' is supported."),
20
21
  }, async ({ project, team, timeframe }) => {
21
22
  try {
22
23
  const connection = await connectionProvider();
24
+ let resolvedProject = project;
25
+ if (!resolvedProject) {
26
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to list team iterations for.");
27
+ if ("response" in result)
28
+ return result.response;
29
+ resolvedProject = result.resolved;
30
+ }
31
+ let resolvedTeam = team;
32
+ if (!resolvedTeam) {
33
+ const result = await elicitTeam(server, connection, resolvedProject, "Select the Azure DevOps team to list iterations for.");
34
+ if ("response" in result)
35
+ return result.response;
36
+ resolvedTeam = result.resolved;
37
+ }
23
38
  const workApi = await connection.getWorkApi();
24
- const iterations = await workApi.getTeamIterations({ project, team }, timeframe);
39
+ const iterations = await workApi.getTeamIterations({ project: resolvedProject, team: resolvedTeam }, timeframe);
25
40
  if (!iterations) {
26
41
  return { content: [{ type: "text", text: "No iterations found" }], isError: true };
27
42
  }
28
43
  return {
29
- content: [{ type: "text", text: JSON.stringify(iterations, null, 2) }],
44
+ content: [
45
+ { type: "text", text: `Project: ${resolvedProject}, Team: ${resolvedTeam}` },
46
+ { type: "text", text: JSON.stringify(iterations, null, 2) },
47
+ ],
30
48
  };
31
49
  }
32
50
  catch (error) {
@@ -79,19 +97,26 @@ function configureWorkTools(server, _, connectionProvider) {
79
97
  };
80
98
  }
81
99
  });
82
- server.tool(WORK_TOOLS.list_iterations, "List all iterations in a specified Azure DevOps project.", {
83
- project: z.string().describe("The name or ID of the Azure DevOps project."),
100
+ server.tool(WORK_TOOLS.list_iterations, "List all iterations in a specified Azure DevOps project. If a project 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."),
84
102
  depth: z.number().default(2).describe("Depth of children to fetch."),
85
103
  excludedIds: z.array(z.number()).optional().describe("An optional array of iteration IDs, and thier children, that should not be returned."),
86
104
  }, async ({ project, depth, excludedIds: ids }) => {
87
105
  try {
88
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 iterations for.");
110
+ if ("response" in result)
111
+ return result.response;
112
+ resolvedProject = result.resolved;
113
+ }
89
114
  const workItemTrackingApi = await connection.getWorkItemTrackingApi();
90
115
  let results = [];
91
116
  if (depth === undefined) {
92
117
  depth = 1;
93
118
  }
94
- results = await workItemTrackingApi.getClassificationNodes(project, [], depth);
119
+ results = await workItemTrackingApi.getClassificationNodes(resolvedProject, [], depth);
95
120
  // Handle null or undefined results
96
121
  if (!results) {
97
122
  return { content: [{ type: "text", text: "No iterations were found" }], isError: true };
@@ -166,15 +191,22 @@ function configureWorkTools(server, _, connectionProvider) {
166
191
  };
167
192
  }
168
193
  });
169
- server.tool(WORK_TOOLS.get_team_capacity, "Get the team capacity of a specific team and iteration in a project.", {
170
- project: z.string().describe("The name or Id of the Azure DevOps project."),
171
- team: z.string().describe("The name or Id of the Azure DevOps team."),
194
+ server.tool(WORK_TOOLS.get_team_capacity, "Get the team capacity of a specific team and iteration in a project. If a project is not specified, you will be prompted to select one.", {
195
+ 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."),
196
+ team: z.string().describe("The name or Id of the Azure DevOps team. Reuse from prior context if already known."),
172
197
  iterationId: z.string().describe("The Iteration Id to get capacity for."),
173
198
  }, async ({ project, team, iterationId }) => {
174
199
  try {
175
200
  const connection = await connectionProvider();
201
+ let resolvedProject = project;
202
+ if (!resolvedProject) {
203
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to get team capacity for.");
204
+ if ("response" in result)
205
+ return result.response;
206
+ resolvedProject = result.resolved;
207
+ }
176
208
  const workApi = await connection.getWorkApi();
177
- const teamContext = { project, team };
209
+ const teamContext = { project: resolvedProject, team };
178
210
  const rawResults = await workApi.getCapacitiesWithIdentityRefAndTotals(teamContext, iterationId);
179
211
  if (!rawResults || rawResults.teamMembers?.length === 0) {
180
212
  return { content: [{ type: "text", text: "No team capacity assigned to the team" }], isError: true };
@@ -272,14 +304,21 @@ function configureWorkTools(server, _, connectionProvider) {
272
304
  };
273
305
  }
274
306
  });
275
- server.tool(WORK_TOOLS.get_iteration_capacities, "Get an iteration's capacity for all teams in iteration and project.", {
276
- project: z.string().describe("The name or Id of the Azure DevOps project."),
307
+ server.tool(WORK_TOOLS.get_iteration_capacities, "Get an iteration's capacity for all teams in iteration and project. If a project is not specified, you will be prompted to select one.", {
308
+ 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."),
277
309
  iterationId: z.string().describe("The Iteration Id to get capacity for."),
278
310
  }, async ({ project, iterationId }) => {
279
311
  try {
280
312
  const connection = await connectionProvider();
313
+ let resolvedProject = project;
314
+ if (!resolvedProject) {
315
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to get iteration capacities for.");
316
+ if ("response" in result)
317
+ return result.response;
318
+ resolvedProject = result.resolved;
319
+ }
281
320
  const workApi = await connection.getWorkApi();
282
- const rawResults = await workApi.getTotalIterationCapacities(project, iterationId);
321
+ const rawResults = await workApi.getTotalIterationCapacities(resolvedProject, iterationId);
283
322
  if (!rawResults || !rawResults.teams || rawResults.teams.length === 0) {
284
323
  return { content: [{ type: "text", text: "No iteration capacity assigned to the teams" }], isError: true };
285
324
  }
@@ -295,14 +334,28 @@ function configureWorkTools(server, _, connectionProvider) {
295
334
  };
296
335
  }
297
336
  });
298
- server.tool(WORK_TOOLS.get_team_settings, "Get team settings including default iteration, backlog iteration, and default area path for a team.", {
299
- project: z.string().describe("The name or ID of the Azure DevOps project."),
300
- team: z.string().optional().describe("The name or ID of the Azure DevOps team. If not provided, the default team will be used."),
337
+ server.tool(WORK_TOOLS.get_team_settings, "Get team settings including default iteration, backlog iteration, and default area path for a team. If a project or team is not specified, you will be prompted to select one.", {
338
+ 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."),
339
+ 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."),
301
340
  }, async ({ project, team }) => {
302
341
  try {
303
342
  const connection = await connectionProvider();
343
+ let resolvedProject = project;
344
+ if (!resolvedProject) {
345
+ const result = await elicitProject(server, connection, "Select the Azure DevOps project to get team settings for.");
346
+ if ("response" in result)
347
+ return result.response;
348
+ resolvedProject = result.resolved;
349
+ }
350
+ let resolvedTeam = team;
351
+ if (!resolvedTeam) {
352
+ const result = await elicitTeam(server, connection, resolvedProject, "Select the Azure DevOps team to get settings for.");
353
+ if ("response" in result)
354
+ return result.response;
355
+ resolvedTeam = result.resolved;
356
+ }
304
357
  const workApi = await connection.getWorkApi();
305
- const teamContext = { project, team };
358
+ const teamContext = { project: resolvedProject, team: resolvedTeam };
306
359
  const teamSettings = await workApi.getTeamSettings(teamContext);
307
360
  if (!teamSettings) {
308
361
  return { content: [{ type: "text", text: "No team settings found" }], isError: true };
@@ -320,7 +373,10 @@ function configureWorkTools(server, _, connectionProvider) {
320
373
  areaPaths: teamFieldValues?.values,
321
374
  };
322
375
  return {
323
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
376
+ content: [
377
+ { type: "text", text: `Project: ${resolvedProject}, Team: ${resolvedTeam}` },
378
+ { type: "text", text: JSON.stringify(result, null, 2) },
379
+ ],
324
380
  };
325
381
  }
326
382
  catch (error) {
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const packageVersion = "2.4.0-nightly.20260315";
1
+ export const packageVersion = "2.4.0-nightly.20260317";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-devops/mcp",
3
- "version": "2.4.0-nightly.20260315",
3
+ "version": "2.4.0-nightly.20260317",
4
4
  "mcpName": "microsoft.com/azure-devops",
5
5
  "description": "MCP server for interacting with Azure DevOps",
6
6
  "license": "MIT",