@azure-devops/mcp 2.0.0 → 2.1.0-nightly.20250909

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
@@ -15,10 +15,11 @@ This TypeScript project provides a **local** MCP server for Azure DevOps, enabli
15
15
  2. [🏆 Expectations](#-expectations)
16
16
  3. [⚙️ Supported Tools](#️-supported-tools)
17
17
  4. [🔌 Installation & Getting Started](#-installation--getting-started)
18
- 5. [📝 Troubleshooting](#-troubleshooting)
19
- 6. [🎩 Examples & Best Practices](#-examples--best-practices)
20
- 7. [🙋‍♀️ Frequently Asked Questions](#️-frequently-asked-questions)
21
- 8. [📌 Contributing](#-contributing)
18
+ 5. [🌏 Using Domains](#-using-domains)
19
+ 6. [📝 Troubleshooting](#-troubleshooting)
20
+ 7. [🎩 Examples & Best Practices](#-examples--best-practices)
21
+ 8. [🙋‍♀️ Frequently Asked Questions](#️-frequently-asked-questions)
22
+ 9. [📌 Contributing](#-contributing)
22
23
 
23
24
  ## 📺 Overview
24
25
 
@@ -26,7 +27,6 @@ The Azure DevOps MCP Server brings Azure DevOps context to your agents. Try prom
26
27
 
27
28
  - "List my ADO projects"
28
29
  - "List ADO Builds for 'Contoso'"
29
- - "List ADO Releases for 'Contoso'"
30
30
  - "List ADO Repos for 'Contoso'"
31
31
  - "List test plans for 'Contoso'"
32
32
  - "List teams for project 'Contoso'"
@@ -102,24 +102,21 @@ Interact with these Azure DevOps services:
102
102
  - **repo_search_commits**: Searches for commits.
103
103
  - **repo_create_pull_request_thread**: Creates a new comment thread on a pull request.
104
104
 
105
- ### 🛰️ Builds
105
+ ### 🚀 Pipelines
106
106
 
107
- - **build_get_definitions**: Retrieve a list of build definitions for a given project.
108
- - **build_get_definition_revisions**: Retrieve a list of revisions for a specific build definition.
109
- - **build_get_builds**: Retrieve a list of builds for a given project.
110
- - **build_get_log**: Retrieve the logs for a specific build.
111
- - **build_get_log_by_id**: Get a specific build log by log ID.
112
- - **build_get_changes**: Get the changes associated with a specific build.
113
- - **build_run_build**: Trigger a new build for a specified definition.
114
- - **build_get_status**: Fetch the status of a specific build.
115
- - **build_update_build_stage**: Update the stage of a specific build.
107
+ - **pipelines_get_build_definitions**: Retrieve a list of build definitions for a given project.
108
+ - **pipelines_get_build_definition_revisions**: Retrieve a list of revisions for a specific build definition.
109
+ - **pipelines_get_builds**: Retrieve a list of builds for a given project.
110
+ - **pipelines_get_build_log**: Retrieve the logs for a specific build.
111
+ - **pipelines_get_build_log_by_id**: Get a specific build log by log ID.
112
+ - **pipelines_get_build_changes**: Get the changes associated with a specific build.
113
+ - **pipelines_get_build_status**: Fetch the status of a specific build.
114
+ - **pipelines_update_build_stage**: Update the stage of a specific build.
115
+ - **pipelines_get_run**: Gets a run for a particular pipeline.
116
+ - **pipelines_list_runs**: Gets top 10000 runs for a particular pipeline.
117
+ - **pipelines_run_pipeline**: Starts a new run of a pipeline.
116
118
 
117
- ### 🚀 Releases
118
-
119
- - **release_get_definitions**: Retrieve a list of release definitions for a given project.
120
- - **release_get_releases**: Retrieve a list of releases for a given project.
121
-
122
- ### 🔒 Advanced Security
119
+ ### Advanced Security
123
120
 
124
121
  - **advsec_get_alerts**: Retrieve Advanced Security alerts for a repository.
125
122
  - **advsec_get_alert_details**: Get detailed information about a specific Advanced Security alert.
@@ -242,6 +239,37 @@ Open GitHub Copilot Chat and try a prompt like `List ADO projects`.
242
239
 
243
240
  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.
244
241
 
242
+ ## 🌏 Using Domains
243
+
244
+ Azure DevOps exposes a large surface area. As a result, our Azure DevOps MCP Server includes many tools. To keep the toolset manageable, avoid confusing the model, and respect client limits on loaded tools, use Domains to load only the areas you need. Domains are named groups of related tools (for example: core, work, work-items, repositories, wiki). Add the `-d` argument and the domain names to the server args in your `mcp.json` to list the domains to enable.
245
+
246
+ For example, use `"-d", "core", "work", "work-items"` to load only Work Item related tools (see the example below).
247
+
248
+ ```json
249
+ {
250
+ "inputs": [
251
+ {
252
+ "id": "ado_org",
253
+ "type": "promptString",
254
+ "description": "Azure DevOps organization name (e.g. 'contoso')"
255
+ }
256
+ ],
257
+ "servers": {
258
+ "ado": {
259
+ "type": "stdio",
260
+ "command": "mcp-server-azuredevops",
261
+ "args": ["${input:ado_org}", "-d", "core", "work", "work-items"]
262
+ }
263
+ }
264
+ }
265
+ ```
266
+
267
+ Domains that are available are: `core`, `work`, `work-items`, `search`, `test-plans`, `repositories`, `wiki`, `pipelines`, `advanced-security`
268
+
269
+ We recommend that you always enable `core` tools so that you can fetch project level information.
270
+
271
+ > By default all domains are loaded
272
+
245
273
  ## 📝 Troubleshooting
246
274
 
247
275
  See the [Troubleshooting guide](./docs/TROUBLESHOOTING.md) for help with common issues and logging.
package/dist/index.js CHANGED
File without changes
package/dist/prompts.js CHANGED
File without changes
@@ -6,9 +6,8 @@
6
6
  export var Domain;
7
7
  (function (Domain) {
8
8
  Domain["ADVANCED_SECURITY"] = "advanced-security";
9
- Domain["BUILDS"] = "builds";
9
+ Domain["PIPELINES"] = "pipelines";
10
10
  Domain["CORE"] = "core";
11
- Domain["RELEASES"] = "releases";
12
11
  Domain["REPOSITORIES"] = "repositories";
13
12
  Domain["SEARCH"] = "search";
14
13
  Domain["TEST_PLANS"] = "test-plans";
@@ -25,8 +24,7 @@ export class DomainsManager {
25
24
  enabledDomains;
26
25
  constructor(domainsInput) {
27
26
  this.enabledDomains = new Set();
28
- const normalizedInput = DomainsManager.parseDomainsInput(domainsInput);
29
- this.parseDomains(normalizedInput);
27
+ this.parseDomains(domainsInput);
30
28
  }
31
29
  /**
32
30
  * Parse and validate domains from input
@@ -48,10 +46,6 @@ export class DomainsManager {
48
46
  this.enableAllDomains();
49
47
  return;
50
48
  }
51
- if (domainsInput.length === 1 && domainsInput[0] === ALL_DOMAINS) {
52
- this.enableAllDomains();
53
- return;
54
- }
55
49
  const domains = domainsInput.map((d) => d.trim().toLowerCase());
56
50
  this.validateAndAddDomains(domains);
57
51
  }
@@ -60,7 +54,8 @@ export class DomainsManager {
60
54
  this.enableAllDomains();
61
55
  return;
62
56
  }
63
- const domains = [domainsInput.trim().toLowerCase()];
57
+ // Handle comma-separated domains
58
+ const domains = domainsInput.split(",").map((d) => d.trim().toLowerCase());
64
59
  this.validateAndAddDomains(domains);
65
60
  }
66
61
  validateAndAddDomains(domains) {
@@ -4,19 +4,21 @@ 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
- const BUILD_TOOLS = {
8
- get_definitions: "build_get_definitions",
9
- get_definition_revisions: "build_get_definition_revisions",
10
- get_builds: "build_get_builds",
11
- get_log: "build_get_log",
12
- get_log_by_id: "build_get_log_by_id",
13
- get_changes: "build_get_changes",
14
- run_build: "build_run_build",
15
- get_status: "build_get_status",
16
- update_build_stage: "build_update_build_stage",
7
+ const PIPELINE_TOOLS = {
8
+ pipelines_get_builds: "pipelines_get_builds",
9
+ pipelines_get_build_changes: "pipelines_get_build_changes",
10
+ pipelines_get_build_definitions: "pipelines_get_build_definitions",
11
+ pipelines_get_build_definition_revisions: "pipelines_get_build_definition_revisions",
12
+ pipelines_get_build_log: "pipelines_get_build_log",
13
+ pipelines_get_build_log_by_id: "pipelines_get_build_log_by_id",
14
+ pipelines_get_build_status: "pipelines_get_build_status",
15
+ pipelines_update_build_stage: "pipelines_update_build_stage",
16
+ pipelines_get_run: "pipelines_get_run",
17
+ pipelines_list_runs: "pipelines_list_runs",
18
+ pipelines_run_pipeline: "pipelines_run_pipeline",
17
19
  };
18
- function configureBuildTools(server, tokenProvider, connectionProvider, userAgentProvider) {
19
- server.tool(BUILD_TOOLS.get_definitions, "Retrieves a list of build definitions for a given project.", {
20
+ function configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider) {
21
+ server.tool(PIPELINE_TOOLS.pipelines_get_build_definitions, "Retrieves a list of build definitions for a given project.", {
20
22
  project: z.string().describe("Project ID or name to get build definitions for"),
21
23
  repositoryId: z.string().optional().describe("Repository ID to filter build definitions"),
22
24
  repositoryType: z.enum(["TfsGit", "GitHub", "BitbucketCloud"]).optional().describe("Type of repository to filter build definitions"),
@@ -45,7 +47,7 @@ function configureBuildTools(server, tokenProvider, connectionProvider, userAgen
45
47
  content: [{ type: "text", text: JSON.stringify(buildDefinitions, null, 2) }],
46
48
  };
47
49
  });
48
- server.tool(BUILD_TOOLS.get_definition_revisions, "Retrieves a list of revisions for a specific build definition.", {
50
+ server.tool(PIPELINE_TOOLS.pipelines_get_build_definition_revisions, "Retrieves a list of revisions for a specific build definition.", {
49
51
  project: z.string().describe("Project ID or name to get the build definition revisions for"),
50
52
  definitionId: z.number().describe("ID of the build definition to get revisions for"),
51
53
  }, async ({ project, definitionId }) => {
@@ -56,7 +58,7 @@ function configureBuildTools(server, tokenProvider, connectionProvider, userAgen
56
58
  content: [{ type: "text", text: JSON.stringify(revisions, null, 2) }],
57
59
  };
58
60
  });
59
- server.tool(BUILD_TOOLS.get_builds, "Retrieves a list of builds for a given project.", {
61
+ server.tool(PIPELINE_TOOLS.pipelines_get_builds, "Retrieves a list of builds for a given project.", {
60
62
  project: z.string().describe("Project ID or name to get builds for"),
61
63
  definitions: z.array(z.number()).optional().describe("Array of build definition IDs to filter builds"),
62
64
  queues: z.array(z.number()).optional().describe("Array of queue IDs to filter builds"),
@@ -90,7 +92,7 @@ function configureBuildTools(server, tokenProvider, connectionProvider, userAgen
90
92
  content: [{ type: "text", text: JSON.stringify(builds, null, 2) }],
91
93
  };
92
94
  });
93
- server.tool(BUILD_TOOLS.get_log, "Retrieves the logs for a specific build.", {
95
+ server.tool(PIPELINE_TOOLS.pipelines_get_build_log, "Retrieves the logs for a specific build.", {
94
96
  project: z.string().describe("Project ID or name to get the build log for"),
95
97
  buildId: z.number().describe("ID of the build to get the log for"),
96
98
  }, async ({ project, buildId }) => {
@@ -101,7 +103,7 @@ function configureBuildTools(server, tokenProvider, connectionProvider, userAgen
101
103
  content: [{ type: "text", text: JSON.stringify(logs, null, 2) }],
102
104
  };
103
105
  });
104
- server.tool(BUILD_TOOLS.get_log_by_id, "Get a specific build log by log ID.", {
106
+ server.tool(PIPELINE_TOOLS.pipelines_get_build_log_by_id, "Get a specific build log by log ID.", {
105
107
  project: z.string().describe("Project ID or name to get the build log for"),
106
108
  buildId: z.number().describe("ID of the build to get the log for"),
107
109
  logId: z.number().describe("ID of the log to retrieve"),
@@ -115,7 +117,7 @@ function configureBuildTools(server, tokenProvider, connectionProvider, userAgen
115
117
  content: [{ type: "text", text: JSON.stringify(logLines, null, 2) }],
116
118
  };
117
119
  });
118
- server.tool(BUILD_TOOLS.get_changes, "Get the changes associated with a specific build.", {
120
+ server.tool(PIPELINE_TOOLS.pipelines_get_build_changes, "Get the changes associated with a specific build.", {
119
121
  project: z.string().describe("Project ID or name to get the build changes for"),
120
122
  buildId: z.number().describe("ID of the build to get changes for"),
121
123
  continuationToken: z.string().optional().describe("Continuation token for pagination"),
@@ -129,38 +131,99 @@ function configureBuildTools(server, tokenProvider, connectionProvider, userAgen
129
131
  content: [{ type: "text", text: JSON.stringify(changes, null, 2) }],
130
132
  };
131
133
  });
132
- server.tool(BUILD_TOOLS.run_build, "Triggers a new build for a specified definition.", {
134
+ server.tool(PIPELINE_TOOLS.pipelines_get_run, "Gets a run for a particular pipeline.", {
133
135
  project: z.string().describe("Project ID or name to run the build in"),
134
- definitionId: z.number().describe("ID of the build definition to run"),
135
- sourceBranch: z.string().optional().describe("Source branch to run the build from. If not provided, the default branch will be used."),
136
- parameters: z.record(z.string(), z.string()).optional().describe("Custom build parameters as key-value pairs"),
137
- }, async ({ project, definitionId, sourceBranch, parameters }) => {
136
+ pipelineId: z.number().describe("ID of the pipeline to run"),
137
+ runId: z.number().describe("ID of the run to get"),
138
+ }, async ({ project, pipelineId, runId }) => {
139
+ const connection = await connectionProvider();
140
+ const pipelinesApi = await connection.getPipelinesApi();
141
+ const pipelineRun = await pipelinesApi.getRun(project, pipelineId, runId);
142
+ return {
143
+ content: [{ type: "text", text: JSON.stringify(pipelineRun, null, 2) }],
144
+ };
145
+ });
146
+ server.tool(PIPELINE_TOOLS.pipelines_list_runs, "Gets top 10000 runs for a particular pipeline.", {
147
+ project: z.string().describe("Project ID or name to run the build in"),
148
+ pipelineId: z.number().describe("ID of the pipeline to run"),
149
+ }, async ({ project, pipelineId }) => {
150
+ const connection = await connectionProvider();
151
+ const pipelinesApi = await connection.getPipelinesApi();
152
+ const pipelineRuns = await pipelinesApi.listRuns(project, pipelineId);
153
+ return {
154
+ content: [{ type: "text", text: JSON.stringify(pipelineRuns, null, 2) }],
155
+ };
156
+ });
157
+ const variableSchema = z.object({
158
+ value: z.string().optional(),
159
+ isSecret: z.boolean().optional(),
160
+ });
161
+ const resourcesSchema = z.object({
162
+ builds: z
163
+ .record(z.string().describe("Name of the build resource."), z.object({
164
+ version: z.string().optional().describe("Version of the build resource."),
165
+ }))
166
+ .optional(),
167
+ containers: z
168
+ .record(z.string().describe("Name of the container resource."), z.object({
169
+ version: z.string().optional().describe("Version of the container resource."),
170
+ }))
171
+ .optional(),
172
+ packages: z
173
+ .record(z.string().describe("Name of the package resource."), z.object({
174
+ version: z.string().optional().describe("Version of the package resource."),
175
+ }))
176
+ .optional(),
177
+ pipelines: z.record(z.string().describe("Name of the pipeline resource."), z.object({
178
+ runId: z.number().describe("Id of the source pipeline run that triggered or is referenced by this pipeline run."),
179
+ version: z.string().optional().describe("Version of the source pipeline run."),
180
+ })),
181
+ repositories: z
182
+ .record(z.string().describe("Name of the repository resource."), z.object({
183
+ refName: z.string().describe("Reference name, e.g., refs/heads/main."),
184
+ token: z.string().optional(),
185
+ tokenType: z.string().optional(),
186
+ version: z.string().optional().describe("Version of the repository resource, git commit sha."),
187
+ }))
188
+ .optional(),
189
+ });
190
+ server.tool(PIPELINE_TOOLS.pipelines_run_pipeline, "Starts a new run of a pipeline.", {
191
+ project: z.string().describe("Project ID or name to run the build in"),
192
+ pipelineId: z.number().describe("ID of the pipeline to run"),
193
+ pipelineVersion: z.number().optional().describe("Version of the pipeline to run. If not provided, the latest version will be used."),
194
+ previewRun: z.boolean().optional().describe("If true, returns the final YAML document after parsing templates without creating a new run."),
195
+ resources: resourcesSchema.optional().describe("A dictionary of resources to pass to the pipeline."),
196
+ stagesToSkip: z.array(z.string()).optional().describe("A list of stages to skip."),
197
+ templateParameters: z.record(z.string(), z.string()).optional().describe("Custom build parameters as key-value pairs"),
198
+ variables: z.record(z.string(), variableSchema).optional().describe("A dictionary of variables to pass to the pipeline."),
199
+ yamlOverride: z.string().optional().describe("YAML override for the pipeline run."),
200
+ }, async ({ project, pipelineId, pipelineVersion, previewRun, resources, stagesToSkip, templateParameters, variables, yamlOverride }) => {
201
+ if (!previewRun && yamlOverride) {
202
+ throw new Error("Parameter 'yamlOverride' can only be specified together with parameter 'previewRun'.");
203
+ }
138
204
  const connection = await connectionProvider();
139
- const buildApi = await connection.getBuildApi();
140
205
  const pipelinesApi = await connection.getPipelinesApi();
141
- const definition = await buildApi.getDefinition(project, definitionId);
142
206
  const runRequest = {
207
+ previewRun: previewRun,
143
208
  resources: {
144
- repositories: {
145
- self: {
146
- refName: sourceBranch || definition.repository?.defaultBranch || "refs/heads/main",
147
- },
148
- },
209
+ ...resources,
149
210
  },
150
- templateParameters: parameters,
211
+ stagesToSkip: stagesToSkip,
212
+ templateParameters: templateParameters,
213
+ variables: variables,
214
+ yamlOverride: yamlOverride,
151
215
  };
152
- const pipelineRun = await pipelinesApi.runPipeline(runRequest, project, definitionId);
216
+ const pipelineRun = await pipelinesApi.runPipeline(runRequest, project, pipelineId, pipelineVersion);
153
217
  const queuedBuild = { id: pipelineRun.id };
154
218
  const buildId = queuedBuild.id;
155
219
  if (buildId === undefined) {
156
220
  throw new Error("Failed to get build ID from pipeline run");
157
221
  }
158
- const buildReport = await buildApi.getBuildReport(project, buildId);
159
222
  return {
160
- content: [{ type: "text", text: JSON.stringify(buildReport, null, 2) }],
223
+ content: [{ type: "text", text: JSON.stringify(pipelineRun, null, 2) }],
161
224
  };
162
225
  });
163
- server.tool(BUILD_TOOLS.get_status, "Fetches the status of a specific build.", {
226
+ server.tool(PIPELINE_TOOLS.pipelines_get_build_status, "Fetches the status of a specific build.", {
164
227
  project: z.string().describe("Project ID or name to get the build status for"),
165
228
  buildId: z.number().describe("ID of the build to get the status for"),
166
229
  }, async ({ project, buildId }) => {
@@ -171,7 +234,7 @@ function configureBuildTools(server, tokenProvider, connectionProvider, userAgen
171
234
  content: [{ type: "text", text: JSON.stringify(build, null, 2) }],
172
235
  };
173
236
  });
174
- server.tool(BUILD_TOOLS.update_build_stage, "Updates the stage of a specific build.", {
237
+ server.tool(PIPELINE_TOOLS.pipelines_update_build_stage, "Updates the stage of a specific build.", {
175
238
  project: z.string().describe("Project ID or name to update the build stage for"),
176
239
  buildId: z.number().describe("ID of the build to update"),
177
240
  stageName: z.string().describe("Name of the stage to update"),
@@ -205,4 +268,4 @@ function configureBuildTools(server, tokenProvider, connectionProvider, userAgen
205
268
  };
206
269
  });
207
270
  }
208
- export { BUILD_TOOLS, configureBuildTools };
271
+ export { PIPELINE_TOOLS, configurePipelineTools };
@@ -199,11 +199,17 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
199
199
  created_by_me: z.boolean().default(false).describe("Filter pull requests created by the current user."),
200
200
  created_by_user: z.string().optional().describe("Filter pull requests created by a specific user (provide email or unique name). Takes precedence over created_by_me if both are provided."),
201
201
  i_am_reviewer: z.boolean().default(false).describe("Filter pull requests where the current user is a reviewer."),
202
+ user_is_reviewer: z
203
+ .string()
204
+ .optional()
205
+ .describe("Filter pull requests where a specific user is a reviewer (provide email or unique name). Takes precedence over i_am_reviewer if both are provided."),
202
206
  status: z
203
207
  .enum(getEnumKeys(PullRequestStatus))
204
208
  .default("Active")
205
209
  .describe("Filter pull requests by status. Defaults to 'Active'."),
206
- }, async ({ repositoryId, top, skip, created_by_me, created_by_user, i_am_reviewer, status }) => {
210
+ sourceRefName: z.string().optional().describe("Filter pull requests from this source branch (e.g., 'refs/heads/feature-branch')."),
211
+ targetRefName: z.string().optional().describe("Filter pull requests into this target branch (e.g., 'refs/heads/main')."),
212
+ }, async ({ repositoryId, top, skip, created_by_me, created_by_user, i_am_reviewer, user_is_reviewer, status, sourceRefName, targetRefName }) => {
207
213
  const connection = await connectionProvider();
208
214
  const gitApi = await connection.getGitApi();
209
215
  // Build the search criteria
@@ -211,6 +217,12 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
211
217
  status: pullRequestStatusStringToInt(status),
212
218
  repositoryId: repositoryId,
213
219
  };
220
+ if (sourceRefName) {
221
+ searchCriteria.sourceRefName = sourceRefName;
222
+ }
223
+ if (targetRefName) {
224
+ searchCriteria.targetRefName = targetRefName;
225
+ }
214
226
  if (created_by_user) {
215
227
  try {
216
228
  const userId = await getUserIdFromEmail(created_by_user, tokenProvider, connectionProvider, userAgentProvider);
@@ -228,16 +240,33 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
228
240
  };
229
241
  }
230
242
  }
231
- else if (created_by_me || i_am_reviewer) {
243
+ else if (created_by_me) {
232
244
  const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
233
245
  const userId = data.authenticatedUser.id;
234
- if (created_by_me) {
235
- searchCriteria.creatorId = userId;
246
+ searchCriteria.creatorId = userId;
247
+ }
248
+ if (user_is_reviewer) {
249
+ try {
250
+ const reviewerUserId = await getUserIdFromEmail(user_is_reviewer, tokenProvider, connectionProvider, userAgentProvider);
251
+ searchCriteria.reviewerId = reviewerUserId;
236
252
  }
237
- if (i_am_reviewer) {
238
- searchCriteria.reviewerId = userId;
253
+ catch (error) {
254
+ return {
255
+ content: [
256
+ {
257
+ type: "text",
258
+ text: `Error finding reviewer with email ${user_is_reviewer}: ${error instanceof Error ? error.message : String(error)}`,
259
+ },
260
+ ],
261
+ isError: true,
262
+ };
239
263
  }
240
264
  }
265
+ else if (i_am_reviewer) {
266
+ const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
267
+ const userId = data.authenticatedUser.id;
268
+ searchCriteria.reviewerId = userId;
269
+ }
241
270
  const pullRequests = await gitApi.getPullRequests(repositoryId, searchCriteria, undefined, // project
242
271
  undefined, // maxCommentLength
243
272
  skip, top);
@@ -267,17 +296,29 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
267
296
  created_by_me: z.boolean().default(false).describe("Filter pull requests created by the current user."),
268
297
  created_by_user: z.string().optional().describe("Filter pull requests created by a specific user (provide email or unique name). Takes precedence over created_by_me if both are provided."),
269
298
  i_am_reviewer: z.boolean().default(false).describe("Filter pull requests where the current user is a reviewer."),
299
+ user_is_reviewer: z
300
+ .string()
301
+ .optional()
302
+ .describe("Filter pull requests where a specific user is a reviewer (provide email or unique name). Takes precedence over i_am_reviewer if both are provided."),
270
303
  status: z
271
304
  .enum(getEnumKeys(PullRequestStatus))
272
305
  .default("Active")
273
306
  .describe("Filter pull requests by status. Defaults to 'Active'."),
274
- }, async ({ project, top, skip, created_by_me, created_by_user, i_am_reviewer, status }) => {
307
+ sourceRefName: z.string().optional().describe("Filter pull requests from this source branch (e.g., 'refs/heads/feature-branch')."),
308
+ targetRefName: z.string().optional().describe("Filter pull requests into this target branch (e.g., 'refs/heads/main')."),
309
+ }, async ({ project, top, skip, created_by_me, created_by_user, i_am_reviewer, user_is_reviewer, status, sourceRefName, targetRefName }) => {
275
310
  const connection = await connectionProvider();
276
311
  const gitApi = await connection.getGitApi();
277
312
  // Build the search criteria
278
313
  const gitPullRequestSearchCriteria = {
279
314
  status: pullRequestStatusStringToInt(status),
280
315
  };
316
+ if (sourceRefName) {
317
+ gitPullRequestSearchCriteria.sourceRefName = sourceRefName;
318
+ }
319
+ if (targetRefName) {
320
+ gitPullRequestSearchCriteria.targetRefName = targetRefName;
321
+ }
281
322
  if (created_by_user) {
282
323
  try {
283
324
  const userId = await getUserIdFromEmail(created_by_user, tokenProvider, connectionProvider, userAgentProvider);
@@ -295,16 +336,33 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
295
336
  };
296
337
  }
297
338
  }
298
- else if (created_by_me || i_am_reviewer) {
339
+ else if (created_by_me) {
299
340
  const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
300
341
  const userId = data.authenticatedUser.id;
301
- if (created_by_me) {
302
- gitPullRequestSearchCriteria.creatorId = userId;
342
+ gitPullRequestSearchCriteria.creatorId = userId;
343
+ }
344
+ if (user_is_reviewer) {
345
+ try {
346
+ const reviewerUserId = await getUserIdFromEmail(user_is_reviewer, tokenProvider, connectionProvider, userAgentProvider);
347
+ gitPullRequestSearchCriteria.reviewerId = reviewerUserId;
303
348
  }
304
- if (i_am_reviewer) {
305
- gitPullRequestSearchCriteria.reviewerId = userId;
349
+ catch (error) {
350
+ return {
351
+ content: [
352
+ {
353
+ type: "text",
354
+ text: `Error finding reviewer with email ${user_is_reviewer}: ${error instanceof Error ? error.message : String(error)}`,
355
+ },
356
+ ],
357
+ isError: true,
358
+ };
306
359
  }
307
360
  }
361
+ else if (i_am_reviewer) {
362
+ const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
363
+ const userId = data.authenticatedUser.id;
364
+ gitPullRequestSearchCriteria.reviewerId = userId;
365
+ }
308
366
  const pullRequests = await gitApi.getPullRequestsByProject(project, gitPullRequestSearchCriteria, undefined, // maxCommentLength
309
367
  skip, top);
310
368
  // Filter out the irrelevant properties
package/dist/tools.js CHANGED
@@ -2,9 +2,8 @@
2
2
  // Licensed under the MIT License.
3
3
  import { Domain } from "./shared/domains.js";
4
4
  import { configureAdvSecTools } from "./tools/advanced-security.js";
5
- import { configureBuildTools } from "./tools/builds.js";
5
+ import { configurePipelineTools } from "./tools/pipelines.js";
6
6
  import { configureCoreTools } from "./tools/core.js";
7
- import { configureReleaseTools } from "./tools/releases.js";
8
7
  import { configureRepoTools } from "./tools/repositories.js";
9
8
  import { configureSearchTools } from "./tools/search.js";
10
9
  import { configureTestPlanTools } from "./tools/test-plans.js";
@@ -19,10 +18,9 @@ function configureAllTools(server, tokenProvider, connectionProvider, userAgentP
19
18
  };
20
19
  configureIfDomainEnabled(Domain.CORE, () => configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider));
21
20
  configureIfDomainEnabled(Domain.WORK, () => configureWorkTools(server, tokenProvider, connectionProvider));
22
- configureIfDomainEnabled(Domain.BUILDS, () => configureBuildTools(server, tokenProvider, connectionProvider, userAgentProvider));
21
+ configureIfDomainEnabled(Domain.PIPELINES, () => configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider));
23
22
  configureIfDomainEnabled(Domain.REPOSITORIES, () => configureRepoTools(server, tokenProvider, connectionProvider, userAgentProvider));
24
23
  configureIfDomainEnabled(Domain.WORK_ITEMS, () => configureWorkItemTools(server, tokenProvider, connectionProvider, userAgentProvider));
25
- configureIfDomainEnabled(Domain.RELEASES, () => configureReleaseTools(server, tokenProvider, connectionProvider));
26
24
  configureIfDomainEnabled(Domain.WIKI, () => configureWikiTools(server, tokenProvider, connectionProvider));
27
25
  configureIfDomainEnabled(Domain.TEST_PLANS, () => configureTestPlanTools(server, tokenProvider, connectionProvider));
28
26
  configureIfDomainEnabled(Domain.SEARCH, () => configureSearchTools(server, tokenProvider, connectionProvider, userAgentProvider));
package/dist/useragent.js CHANGED
File without changes
package/dist/utils.js CHANGED
File without changes
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const packageVersion = "2.0.0";
1
+ export const packageVersion = "2.1.0-nightly.20250909";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-devops/mcp",
3
- "version": "2.0.0",
3
+ "version": "2.1.0-nightly.20250909",
4
4
  "description": "MCP server for interacting with Azure DevOps",
5
5
  "license": "MIT",
6
6
  "author": "Microsoft Corporation",
@@ -1,97 +0,0 @@
1
- // Copyright (c) Microsoft Corporation.
2
- // Licensed under the MIT License.
3
- import { ReleaseDefinitionExpands, ReleaseDefinitionQueryOrder, ReleaseExpands, ReleaseStatus, ReleaseQueryOrder } from "azure-devops-node-api/interfaces/ReleaseInterfaces.js";
4
- import { z } from "zod";
5
- import { getEnumKeys, safeEnumConvert } from "../utils.js";
6
- const RELEASE_TOOLS = {
7
- get_release_definitions: "release_get_definitions",
8
- get_releases: "release_get_releases",
9
- };
10
- function configureReleaseTools(server, tokenProvider, connectionProvider) {
11
- server.tool(RELEASE_TOOLS.get_release_definitions, "Retrieves list of release definitions for a given project.", {
12
- project: z.string().describe("Project ID or name to get release definitions for"),
13
- searchText: z.string().optional().describe("Search text to filter release definitions"),
14
- expand: z
15
- .enum(getEnumKeys(ReleaseDefinitionExpands))
16
- .default("None")
17
- .describe("Expand options for release definitions"),
18
- artifactType: z.string().optional().describe("Filter by artifact type"),
19
- artifactSourceId: z.string().optional().describe("Filter by artifact source ID"),
20
- top: z.number().optional().describe("Number of results to return (for pagination)"),
21
- continuationToken: z.string().optional().describe("Continuation token for pagination"),
22
- queryOrder: z
23
- .enum(getEnumKeys(ReleaseDefinitionQueryOrder))
24
- .default("NameAscending")
25
- .describe("Order of the results"),
26
- path: z.string().optional().describe("Path to filter release definitions"),
27
- isExactNameMatch: z.boolean().optional().default(false).describe("Whether to match the exact name of the release definition. Default is false."),
28
- tagFilter: z.array(z.string()).optional().describe("Filter by tags associated with the release definitions"),
29
- propertyFilters: z.array(z.string()).optional().describe("Filter by properties associated with the release definitions"),
30
- definitionIdFilter: z.array(z.string()).optional().describe("Filter by specific release definition IDs"),
31
- isDeleted: z.boolean().default(false).describe("Whether to include deleted release definitions. Default is false."),
32
- searchTextContainsFolderName: z.boolean().optional().describe("Whether to include folder names in the search text"),
33
- }, async ({ project, searchText, expand, artifactType, artifactSourceId, top, continuationToken, queryOrder, path, isExactNameMatch, tagFilter, propertyFilters, definitionIdFilter, isDeleted, searchTextContainsFolderName, }) => {
34
- const connection = await connectionProvider();
35
- const releaseApi = await connection.getReleaseApi();
36
- const releaseDefinitions = await releaseApi.getReleaseDefinitions(project, searchText, safeEnumConvert(ReleaseDefinitionExpands, expand), artifactType, artifactSourceId, top, continuationToken, safeEnumConvert(ReleaseDefinitionQueryOrder, queryOrder), path, isExactNameMatch, tagFilter, propertyFilters, definitionIdFilter, isDeleted, searchTextContainsFolderName);
37
- return {
38
- content: [{ type: "text", text: JSON.stringify(releaseDefinitions, null, 2) }],
39
- };
40
- });
41
- server.tool(RELEASE_TOOLS.get_releases, "Retrieves a list of releases for a given project.", {
42
- project: z.string().optional().describe("Project ID or name to get releases for"),
43
- definitionId: z.number().optional().describe("ID of the release definition to filter releases"),
44
- definitionEnvironmentId: z.number().optional().describe("ID of the definition environment to filter releases"),
45
- searchText: z.string().optional().describe("Search text to filter releases"),
46
- createdBy: z.string().optional().describe("User ID or name who created the release"),
47
- statusFilter: z
48
- .enum(getEnumKeys(ReleaseStatus))
49
- .optional()
50
- .default("Active")
51
- .describe("Status of the releases to filter (default: Active)"),
52
- environmentStatusFilter: z.number().optional().describe("Environment status to filter releases"),
53
- minCreatedTime: z.coerce
54
- .date()
55
- .optional()
56
- .default(() => {
57
- const sevenDaysAgo = new Date();
58
- sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
59
- return sevenDaysAgo;
60
- })
61
- .describe("Minimum created time for releases (default: 7 days ago)"),
62
- maxCreatedTime: z.coerce
63
- .date()
64
- .optional()
65
- .default(() => new Date())
66
- .describe("Maximum created time for releases (default: now)"),
67
- queryOrder: z
68
- .enum(getEnumKeys(ReleaseQueryOrder))
69
- .optional()
70
- .default("Ascending")
71
- .describe("Order in which to return releases (default: Ascending)"),
72
- top: z.number().optional().describe("Number of releases to return"),
73
- continuationToken: z.number().optional().describe("Continuation token for pagination"),
74
- expand: z
75
- .enum(getEnumKeys(ReleaseExpands))
76
- .optional()
77
- .default("None")
78
- .describe("Expand options for releases"),
79
- artifactTypeId: z.string().optional().describe("Filter releases by artifact type ID"),
80
- sourceId: z.string().optional().describe("Filter releases by artifact source ID"),
81
- artifactVersionId: z.string().optional().describe("Filter releases by artifact version ID"),
82
- sourceBranchFilter: z.string().optional().describe("Filter releases by source branch"),
83
- isDeleted: z.boolean().optional().default(false).describe("Whether to include deleted releases (default: false)"),
84
- tagFilter: z.array(z.string()).optional().describe("Filter releases by tags"),
85
- propertyFilters: z.array(z.string()).optional().describe("Filter releases by properties"),
86
- releaseIdFilter: z.array(z.number()).optional().describe("Filter by specific release IDs"),
87
- path: z.string().optional().describe("Path to filter releases"),
88
- }, async ({ project, definitionId, definitionEnvironmentId, searchText, createdBy, statusFilter, environmentStatusFilter, minCreatedTime, maxCreatedTime, queryOrder, top, continuationToken, expand, artifactTypeId, sourceId, artifactVersionId, sourceBranchFilter, isDeleted, tagFilter, propertyFilters, releaseIdFilter, path, }) => {
89
- const connection = await connectionProvider();
90
- const releaseApi = await connection.getReleaseApi();
91
- const releases = await releaseApi.getReleases(project, definitionId, definitionEnvironmentId, searchText, createdBy, safeEnumConvert(ReleaseStatus, statusFilter), environmentStatusFilter, minCreatedTime, maxCreatedTime, safeEnumConvert(ReleaseQueryOrder, queryOrder), top, continuationToken, safeEnumConvert(ReleaseExpands, expand), artifactTypeId, sourceId, artifactVersionId, sourceBranchFilter, isDeleted, tagFilter, propertyFilters, releaseIdFilter, path);
92
- return {
93
- content: [{ type: "text", text: JSON.stringify(releases, null, 2) }],
94
- };
95
- });
96
- }
97
- export { RELEASE_TOOLS, configureReleaseTools };