@azure-devops/mcp 2.5.0-nightly.20260323 → 2.5.0-nightly.20260325

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.
@@ -37,7 +37,7 @@ function configureAdvSecTools(server, _, connectionProvider) {
37
37
  .array(z.enum(getEnumKeys(AlertValidityStatus)))
38
38
  .optional()
39
39
  .describe("Filter alerts by validity status. Only applicable for secret alerts."),
40
- top: z.number().optional().default(100).describe("Maximum number of alerts to return. Defaults to 100."),
40
+ top: z.coerce.number().optional().default(100).describe("Maximum number of alerts to return. Defaults to 100."),
41
41
  orderBy: z.enum(["id", "firstSeen", "lastSeen", "fixedOn", "severity"]).optional().default("severity").describe("Order results by specified field. Defaults to 'severity'."),
42
42
  continuationToken: z.string().optional().describe("Continuation token for pagination."),
43
43
  }, async ({ project, repository, alertType, states, severities, ruleId, ruleName, toolName, ref, onlyDefaultBranch, confidenceLevels, validity, top, orderBy, continuationToken }) => {
@@ -79,7 +79,7 @@ function configureAdvSecTools(server, _, connectionProvider) {
79
79
  server.tool(ADVSEC_TOOLS.get_alert_details, "Get detailed information about a specific Advanced Security alert.", {
80
80
  project: z.string().describe("The name or ID of the Azure DevOps project."),
81
81
  repository: z.string().describe("The name or ID of the repository containing the alert."),
82
- alertId: z.number().describe("The ID of the alert to retrieve details for."),
82
+ alertId: z.coerce.number().min(1).describe("The ID of the alert to retrieve details for."),
83
83
  ref: z.string().optional().describe("Git reference (branch) to filter the alert."),
84
84
  }, async ({ project, repository, alertId, ref }) => {
85
85
  try {
@@ -16,8 +16,8 @@ function configureCoreTools(server, tokenProvider, connectionProvider, userAgent
16
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
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."),
18
18
  mine: z.boolean().optional().describe("If true, only return teams that the authenticated user is a member of."),
19
- top: z.number().optional().describe("The maximum number of teams to return. Defaults to 100."),
20
- skip: z.number().optional().describe("The number of teams to skip for pagination. Defaults to 0."),
19
+ top: z.coerce.number().optional().describe("The maximum number of teams to return. Defaults to 100."),
20
+ skip: z.coerce.number().optional().describe("The number of teams to skip for pagination. Defaults to 0."),
21
21
  }, async ({ project, mine, top, skip }) => {
22
22
  try {
23
23
  const connection = await connectionProvider();
@@ -47,9 +47,9 @@ function configureCoreTools(server, tokenProvider, connectionProvider, userAgent
47
47
  });
48
48
  server.tool(CORE_TOOLS.list_projects, "Retrieve a list of projects in your Azure DevOps organization.", {
49
49
  stateFilter: z.enum(["all", "wellFormed", "createPending", "deleted"]).default("wellFormed").describe("Filter projects by their state. Defaults to 'wellFormed'."),
50
- top: z.number().optional().describe("The maximum number of projects to return. Defaults to 100."),
51
- skip: z.number().optional().describe("The number of projects to skip for pagination. Defaults to 0."),
52
- continuationToken: z.number().optional().describe("Continuation token for pagination. Used to fetch the next set of results if available."),
50
+ top: z.coerce.number().optional().describe("The maximum number of projects to return. Defaults to 100."),
51
+ skip: z.coerce.number().optional().describe("The number of projects to skip for pagination. Defaults to 0."),
52
+ continuationToken: z.coerce.number().optional().describe("Continuation token for pagination. Used to fetch the next set of results if available."),
53
53
  projectNameFilter: z.string().optional().describe("Filter projects by name. Supports partial matches."),
54
54
  }, async ({ stateFilter, top, skip, continuationToken, projectNameFilter }) => {
55
55
  try {
@@ -37,7 +37,7 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
37
37
  top: z.number().optional().describe("Maximum number of build definitions to return"),
38
38
  continuationToken: z.string().optional().describe("Token for continuing paged results"),
39
39
  minMetricsTime: z.coerce.date().optional().describe("Minimum metrics time to filter build definitions"),
40
- definitionIds: z.array(z.number()).optional().describe("Array of build definition IDs to filter"),
40
+ definitionIds: z.array(z.coerce.number().min(1)).optional().describe("Array of build definition IDs to filter"),
41
41
  builtAfter: z.coerce.date().optional().describe("Return definitions that have builds after this date"),
42
42
  notBuiltAfter: z.coerce.date().optional().describe("Return definitions that do not have builds after this date"),
43
43
  includeAllProperties: z.boolean().optional().describe("Whether to include all properties in the results"),
@@ -105,7 +105,7 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
105
105
  });
106
106
  server.tool(PIPELINE_TOOLS.pipelines_get_build_definition_revisions, "Retrieves a list of revisions for a specific build definition.", {
107
107
  project: z.string().describe("Project ID or name to get the build definition revisions for"),
108
- definitionId: z.number().describe("ID of the build definition to get revisions for"),
108
+ definitionId: z.coerce.number().min(1).describe("ID of the build definition to get revisions for"),
109
109
  }, async ({ project, definitionId }) => {
110
110
  const connection = await connectionProvider();
111
111
  const buildApi = await connection.getBuildApi();
@@ -116,8 +116,8 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
116
116
  });
117
117
  server.tool(PIPELINE_TOOLS.pipelines_get_builds, "Retrieves a list of builds for a given project.", {
118
118
  project: z.string().describe("Project ID or name to get builds for"),
119
- definitions: z.array(z.number()).optional().describe("Array of build definition IDs to filter builds"),
120
- queues: z.array(z.number()).optional().describe("Array of queue IDs to filter builds"),
119
+ definitions: z.array(z.coerce.number().min(1)).optional().describe("Array of build definition IDs to filter builds"),
120
+ queues: z.array(z.coerce.number().min(1)).optional().describe("Array of queue IDs to filter builds"),
121
121
  buildNumber: z.string().optional().describe("Build number to filter builds"),
122
122
  minTime: z.coerce.date().optional().describe("Minimum finish time to filter builds"),
123
123
  maxTime: z.coerce.date().optional().describe("Maximum finish time to filter builds"),
@@ -137,7 +137,7 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
137
137
  .optional()
138
138
  .describe("Order in which builds are returned"),
139
139
  branchName: z.string().optional().describe("Branch name to filter builds"),
140
- buildIds: z.array(z.number()).optional().describe("Array of build IDs to retrieve"),
140
+ buildIds: z.array(z.coerce.number().min(1)).optional().describe("Array of build IDs to retrieve"),
141
141
  repositoryId: z.string().optional().describe("Repository ID to filter builds"),
142
142
  repositoryType: z.enum(["TfsGit", "GitHub", "BitbucketCloud"]).optional().describe("Type of repository to filter builds"),
143
143
  }, async ({ project, definitions, queues, buildNumber, minTime, maxTime, requestedFor, reasonFilter, statusFilter, resultFilter, tagFilters, properties, top, continuationToken, maxBuildsPerDefinition, deletedFilter, queryOrder, branchName, buildIds, repositoryId, repositoryType, }) => {
@@ -150,7 +150,7 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
150
150
  });
151
151
  server.tool(PIPELINE_TOOLS.pipelines_get_build_log, "Retrieves the logs for a specific build.", {
152
152
  project: z.string().describe("Project ID or name to get the build log for"),
153
- buildId: z.number().describe("ID of the build to get the log for"),
153
+ buildId: z.coerce.number().min(1).describe("ID of the build to get the log for"),
154
154
  }, async ({ project, buildId }) => {
155
155
  const connection = await connectionProvider();
156
156
  const buildApi = await connection.getBuildApi();
@@ -161,10 +161,10 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
161
161
  });
162
162
  server.tool(PIPELINE_TOOLS.pipelines_get_build_log_by_id, "Get a specific build log by log ID.", {
163
163
  project: z.string().describe("Project ID or name to get the build log for"),
164
- buildId: z.number().describe("ID of the build to get the log for"),
165
- logId: z.number().describe("ID of the log to retrieve"),
166
- startLine: z.number().optional().describe("Starting line number for the log content, defaults to 0"),
167
- endLine: z.number().optional().describe("Ending line number for the log content, defaults to the end of the log"),
164
+ buildId: z.coerce.number().min(1).describe("ID of the build to get the log for"),
165
+ logId: z.coerce.number().min(1).describe("ID of the log to retrieve"),
166
+ startLine: z.coerce.number().optional().describe("Starting line number for the log content, defaults to 0"),
167
+ endLine: z.coerce.number().optional().describe("Ending line number for the log content, defaults to the end of the log"),
168
168
  }, async ({ project, buildId, logId, startLine, endLine }) => {
169
169
  const connection = await connectionProvider();
170
170
  const buildApi = await connection.getBuildApi();
@@ -175,7 +175,7 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
175
175
  });
176
176
  server.tool(PIPELINE_TOOLS.pipelines_get_build_changes, "Get the changes associated with a specific build.", {
177
177
  project: z.string().describe("Project ID or name to get the build changes for"),
178
- buildId: z.number().describe("ID of the build to get changes for"),
178
+ buildId: z.coerce.number().min(1).describe("ID of the build to get changes for"),
179
179
  continuationToken: z.string().optional().describe("Continuation token for pagination"),
180
180
  top: z.number().default(100).describe("Number of changes to retrieve, defaults to 100"),
181
181
  includeSourceChange: z.boolean().optional().describe("Whether to include source changes in the results, defaults to false"),
@@ -189,8 +189,8 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
189
189
  });
190
190
  server.tool(PIPELINE_TOOLS.pipelines_get_run, "Gets a run for a particular pipeline.", {
191
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
- runId: z.number().describe("ID of the run to get"),
192
+ pipelineId: z.coerce.number().min(1).describe("ID of the pipeline to run"),
193
+ runId: z.coerce.number().min(1).describe("ID of the run to get"),
194
194
  }, async ({ project, pipelineId, runId }) => {
195
195
  const connection = await connectionProvider();
196
196
  const pipelinesApi = await connection.getPipelinesApi();
@@ -201,7 +201,7 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
201
201
  });
202
202
  server.tool(PIPELINE_TOOLS.pipelines_list_runs, "Gets top 10000 runs for a particular pipeline.", {
203
203
  project: z.string().describe("Project ID or name to run the build in"),
204
- pipelineId: z.number().describe("ID of the pipeline to run"),
204
+ pipelineId: z.coerce.number().min(1).describe("ID of the pipeline to run"),
205
205
  }, async ({ project, pipelineId }) => {
206
206
  const connection = await connectionProvider();
207
207
  const pipelinesApi = await connection.getPipelinesApi();
@@ -227,7 +227,7 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
227
227
  }))
228
228
  .optional(),
229
229
  pipelines: z.record(z.string().describe("Name of the pipeline resource."), z.object({
230
- runId: z.number().describe("Id of the source pipeline run that triggered or is referenced by this pipeline run."),
230
+ runId: z.coerce.number().min(1).describe("Id of the source pipeline run that triggered or is referenced by this pipeline run."),
231
231
  version: z.string().optional().describe("Version of the source pipeline run."),
232
232
  })),
233
233
  repositories: z
@@ -241,8 +241,8 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
241
241
  });
242
242
  server.tool(PIPELINE_TOOLS.pipelines_run_pipeline, "Starts a new run of a pipeline.", {
243
243
  project: z.string().describe("Project ID or name to run the build in"),
244
- pipelineId: z.number().describe("ID of the pipeline to run"),
245
- pipelineVersion: z.number().optional().describe("Version of the pipeline to run. If not provided, the latest version will be used."),
244
+ pipelineId: z.coerce.number().min(1).describe("ID of the pipeline to run"),
245
+ pipelineVersion: z.coerce.number().min(1).optional().describe("Version of the pipeline to run. If not provided, the latest version will be used."),
246
246
  previewRun: z.boolean().optional().describe("If true, returns the final YAML document after parsing templates without creating a new run."),
247
247
  resources: resourcesSchema.optional().describe("A dictionary of resources to pass to the pipeline."),
248
248
  stagesToSkip: z.array(z.string()).optional().describe("A list of stages to skip."),
@@ -277,7 +277,7 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
277
277
  });
278
278
  server.tool(PIPELINE_TOOLS.pipelines_get_build_status, "Fetches the status of a specific build.", {
279
279
  project: z.string().describe("Project ID or name to get the build status for"),
280
- buildId: z.number().describe("ID of the build to get the status for"),
280
+ buildId: z.coerce.number().min(1).describe("ID of the build to get the status for"),
281
281
  }, async ({ project, buildId }) => {
282
282
  const connection = await connectionProvider();
283
283
  const buildApi = await connection.getBuildApi();
@@ -288,14 +288,14 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
288
288
  });
289
289
  server.tool(PIPELINE_TOOLS.pipelines_update_build_stage, "Updates the stage of a specific build.", {
290
290
  project: z.string().describe("Project ID or name to update the build stage for"),
291
- buildId: z.number().describe("ID of the build to update"),
291
+ buildId: z.coerce.number().min(1).describe("ID of the build to update"),
292
292
  stageName: z.string().describe("Name of the stage to update"),
293
293
  status: z.enum(getEnumKeys(StageUpdateType)).describe("New status for the stage"),
294
294
  forceRetryAllJobs: z.boolean().default(false).describe("Whether to force retry all jobs in the stage."),
295
295
  }, async ({ project, buildId, stageName, status, forceRetryAllJobs }) => {
296
296
  const connection = await connectionProvider();
297
297
  const orgUrl = connection.serverUrl;
298
- const endpoint = `${orgUrl}/${project}/_apis/build/builds/${buildId}/stages/${stageName}?api-version=${apiVersion}`;
298
+ const endpoint = `${orgUrl}/${encodeURIComponent(project)}/_apis/build/builds/${buildId}/stages/${encodeURIComponent(stageName)}?api-version=${apiVersion}`;
299
299
  const token = await tokenProvider();
300
300
  const body = {
301
301
  forceRetryAllJobs: forceRetryAllJobs,
@@ -321,7 +321,7 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
321
321
  });
322
322
  server.tool(PIPELINE_TOOLS.pipelines_list_artifacts, "Lists artifacts for a given build.", {
323
323
  project: z.string().describe("The name or ID of the project."),
324
- buildId: z.number().describe("The ID of the build."),
324
+ buildId: z.coerce.number().min(1).describe("The ID of the build."),
325
325
  }, async ({ project, buildId }) => {
326
326
  const connection = await connectionProvider();
327
327
  const buildApi = await connection.getBuildApi();
@@ -332,16 +332,17 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
332
332
  });
333
333
  server.tool(PIPELINE_TOOLS.pipelines_download_artifact, "Downloads a pipeline artifact.", {
334
334
  project: z.string().describe("The name or ID of the project."),
335
- buildId: z.number().describe("The ID of the build."),
335
+ buildId: z.coerce.number().min(1).describe("The ID of the build."),
336
336
  artifactName: z.string().describe("The name of the artifact to download."),
337
337
  destinationPath: z.string().optional().describe("The local path to download the artifact to. If not provided, returns binary content as base64."),
338
338
  }, async ({ project, buildId, artifactName, destinationPath }) => {
339
339
  const isAbsolutePath = (value) => posix.isAbsolute(value) || win32.isAbsolute(value);
340
+ const hasDriveLetter = (value) => /^[a-zA-Z]:/.test(value);
340
341
  if (artifactName.includes("..")) {
341
342
  throw new Error("Invalid artifactName: path traversal is not allowed.");
342
343
  }
343
- if (destinationPath && (destinationPath.includes("..") || isAbsolutePath(destinationPath))) {
344
- throw new Error("Invalid destinationPath: absolute paths and paths traversals are not allowed.");
344
+ if (destinationPath && (destinationPath.includes("..") || isAbsolutePath(destinationPath) || hasDriveLetter(destinationPath))) {
345
+ throw new Error("Invalid destinationPath: absolute paths and path traversals are not allowed.");
345
346
  }
346
347
  const connection = await connectionProvider();
347
348
  const buildApi = await connection.getBuildApi();