@azure-devops/mcp 2.4.0 β†’ 2.5.0-nightly.20260318

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,13 +46,28 @@ 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.
46
67
 
47
68
  ## πŸ”Œ Installation & Getting Started
48
69
 
49
- For the best experience, use Visual Studio Code and GitHub Copilot. See the [getting started documentation](./docs/GETTINGSTARTED.md) to use our MCP Server with other tools such as Visual Studio 2022, Claude Code, and Cursor.
70
+ For the best experience, use Visual Studio Code and GitHub Copilot. See the [getting started documentation](./docs/GETTINGSTARTED.md) to use our MCP Server with other tools such as Visual Studio 2022, Claude Code, Cursor, Opencode, and Kilocode.
50
71
 
51
72
  ### Prerequisites
52
73
 
package/dist/auth.js CHANGED
File without changes
package/dist/index.js CHANGED
File without changes
package/dist/logger.js CHANGED
File without changes
File without changes
package/dist/prompts.js CHANGED
File without changes
@@ -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",
@@ -12,8 +13,8 @@ function filterProjectsByName(projects, projectNameFilter) {
12
13
  return projects.filter((project) => project.name?.toLowerCase().includes(lowerCaseFilter));
13
14
  }
14
15
  function configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider) {
15
- server.tool(CORE_TOOLS.list_project_teams, "Retrieve a list of teams for the specified Azure DevOps project.", {
16
- project: z.string().describe("The name or ID of the Azure DevOps project."),
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.", {
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."),
@@ -21,7 +22,14 @@ function configureCoreTools(server, tokenProvider, connectionProvider, userAgent
21
22
  try {
22
23
  const connection = await connectionProvider();
23
24
  const coreApi = await connection.getCoreApi();
24
- const teams = await coreApi.getTeams(project, mine, top, skip, false);
25
+ let resolvedProject = project;
26
+ if (!resolvedProject) {
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;
31
+ }
32
+ const teams = await coreApi.getTeams(resolvedProject, mine, top, skip, false);
25
33
  if (!teams) {
26
34
  return { content: [{ type: "text", text: "No teams found" }], isError: true };
27
35
  }
@@ -5,6 +5,8 @@ import { BuildQueryOrder, DefinitionQueryOrder } from "azure-devops-node-api/int
5
5
  import { z } from "zod";
6
6
  import { StageUpdateType } from "azure-devops-node-api/interfaces/BuildInterfaces.js";
7
7
  import { ConfigurationType, RepositoryType } from "azure-devops-node-api/interfaces/PipelinesInterfaces.js";
8
+ import { mkdirSync, createWriteStream } from "fs";
9
+ import { join, resolve } from "path";
8
10
  const PIPELINE_TOOLS = {
9
11
  pipelines_get_builds: "pipelines_get_builds",
10
12
  pipelines_get_build_changes: "pipelines_get_build_changes",
@@ -18,6 +20,8 @@ const PIPELINE_TOOLS = {
18
20
  pipelines_get_run: "pipelines_get_run",
19
21
  pipelines_list_runs: "pipelines_list_runs",
20
22
  pipelines_run_pipeline: "pipelines_run_pipeline",
23
+ pipelines_list_artifacts: "pipelines_list_artifacts",
24
+ pipelines_download_artifact: "pipelines_download_artifact",
21
25
  };
22
26
  function configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider) {
23
27
  server.tool(PIPELINE_TOOLS.pipelines_get_build_definitions, "Retrieves a list of build definitions for a given project.", {
@@ -315,5 +319,68 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
315
319
  content: [{ type: "text", text: JSON.stringify(updatedBuild, null, 2) }],
316
320
  };
317
321
  });
322
+ server.tool(PIPELINE_TOOLS.pipelines_list_artifacts, "Lists artifacts for a given build.", {
323
+ project: z.string().describe("The name or ID of the project."),
324
+ buildId: z.number().describe("The ID of the build."),
325
+ }, async ({ project, buildId }) => {
326
+ const connection = await connectionProvider();
327
+ const buildApi = await connection.getBuildApi();
328
+ const artifacts = await buildApi.getArtifacts(project, buildId);
329
+ return {
330
+ content: [{ type: "text", text: JSON.stringify(artifacts, null, 2) }],
331
+ };
332
+ });
333
+ server.tool(PIPELINE_TOOLS.pipelines_download_artifact, "Downloads a pipeline artifact.", {
334
+ project: z.string().describe("The name or ID of the project."),
335
+ buildId: z.number().describe("The ID of the build."),
336
+ artifactName: z.string().describe("The name of the artifact to download."),
337
+ destinationPath: z.string().optional().describe("The local path to download the artifact to. If not provided, returns binary content as base64."),
338
+ }, async ({ project, buildId, artifactName, destinationPath }) => {
339
+ const connection = await connectionProvider();
340
+ const buildApi = await connection.getBuildApi();
341
+ const artifact = await buildApi.getArtifact(project, buildId, artifactName);
342
+ if (!artifact) {
343
+ return {
344
+ content: [{ type: "text", text: `Artifact ${artifactName} not found in build ${buildId}.` }],
345
+ };
346
+ }
347
+ const fileStream = await buildApi.getArtifactContentZip(project, buildId, artifactName);
348
+ // If destinationPath is provided, save to disk
349
+ if (destinationPath) {
350
+ const fullDestinationPath = resolve(destinationPath);
351
+ mkdirSync(fullDestinationPath, { recursive: true });
352
+ const fileDestinationPath = join(fullDestinationPath, `${artifactName}.zip`);
353
+ const writeStream = createWriteStream(fileDestinationPath);
354
+ await new Promise((resolve, reject) => {
355
+ fileStream.pipe(writeStream);
356
+ fileStream.on("end", () => resolve());
357
+ fileStream.on("error", (err) => reject(err));
358
+ });
359
+ return {
360
+ content: [{ type: "text", text: `Artifact ${artifactName} downloaded to ${destinationPath}.` }],
361
+ };
362
+ }
363
+ // Otherwise, return binary content as base64
364
+ const chunks = [];
365
+ await new Promise((resolve, reject) => {
366
+ fileStream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
367
+ fileStream.on("end", () => resolve());
368
+ fileStream.on("error", (err) => reject(err));
369
+ });
370
+ const buffer = Buffer.concat(chunks);
371
+ const base64Data = buffer.toString("base64");
372
+ return {
373
+ content: [
374
+ {
375
+ type: "resource",
376
+ resource: {
377
+ uri: `data:application/zip;base64,${base64Data}`,
378
+ mimeType: "application/zip",
379
+ text: base64Data,
380
+ },
381
+ },
382
+ ],
383
+ };
384
+ });
318
385
  }
319
386
  export { PIPELINE_TOOLS, configurePipelineTools };