@azure-devops/mcp 2.8.1 → 2.9.0
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 +2 -7
- package/dist/shared/command.js +34 -0
- package/dist/tools/advanced-security.js +31 -11
- package/dist/tools/pipelines.dto.js +95 -0
- package/dist/tools/pipelines.js +350 -373
- package/dist/tools/repositories.js +695 -1485
- package/dist/tools/test-plans.js +297 -396
- package/dist/tools/wiki.js +242 -273
- package/dist/tools/work-items.js +851 -1174
- package/dist/tools/work.js +239 -285
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ⭐ Azure DevOps MCP Server
|
|
2
2
|
|
|
3
3
|
> [!IMPORTANT]
|
|
4
|
-
>
|
|
4
|
+
> We recommend using the [Remote MCP Server](https://learn.microsoft.com/en-us/azure/devops/mcp-server/remote-mcp-server) instead of this local server. It requires no installation and gets new features first.
|
|
5
5
|
>
|
|
6
6
|
> [Learn more](#-remote-mcp-server-recommended)
|
|
7
7
|
|
|
@@ -44,7 +44,7 @@ The Azure DevOps MCP Server is built around tools that are concise, simple, focu
|
|
|
44
44
|
|
|
45
45
|
## 🚀 Remote MCP Server (Recommended)
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
For complete instructions, see the [Remote MCP Server onboarding documentation](https://learn.microsoft.com/en-us/azure/devops/mcp-server/remote-mcp-server?view=azure-devops).
|
|
48
48
|
|
|
49
49
|
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.
|
|
50
50
|
|
|
@@ -52,11 +52,6 @@ We encourage all users of the local MCP Server to begin migrating to the Remote
|
|
|
52
52
|
|
|
53
53
|
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.
|
|
54
54
|
|
|
55
|
-
> [!WARNING]
|
|
56
|
-
> Internal Microsoft users of the Remote MCP Server should **not** create issues in this repository. Please use the dedicated Teams channel instead.
|
|
57
|
-
|
|
58
|
-
For complete instructions, see the [Remote MCP Server onboarding documentation](https://learn.microsoft.com/en-us/azure/devops/mcp-server/remote-mcp-server?view=azure-devops).
|
|
59
|
-
|
|
60
55
|
### Quick start with `.vscode/mcp.json`
|
|
61
56
|
|
|
62
57
|
Use this configuration to connect directly to the Azure DevOps-hosted endpoint using streamable HTTP transport:
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
/** Builds an error `CallToolResult`. */
|
|
4
|
+
export const errorResult = (text) => ({ content: [{ type: "text", text }], isError: true });
|
|
5
|
+
/**
|
|
6
|
+
* Routes a validated, action-carrying args object to the matching command.
|
|
7
|
+
*
|
|
8
|
+
* This is what removes long positional parameter lists from grouped ("action")
|
|
9
|
+
* tools: instead of destructuring every possible field, the whole typed args
|
|
10
|
+
* object is forwarded to the single command keyed by `args.action`, coupling
|
|
11
|
+
* each action to exactly one command.
|
|
12
|
+
*
|
|
13
|
+
* - Unknown actions short-circuit with an "Unknown action" error and never
|
|
14
|
+
* touch the context (so no connection is opened).
|
|
15
|
+
* - Errors thrown by a command are caught and formatted using the optional
|
|
16
|
+
* per-action `errorPrefixes` map (falling back to a generic message).
|
|
17
|
+
* - Errors returned by a command (e.g. validation `errorResult`s) pass through
|
|
18
|
+
* unchanged.
|
|
19
|
+
*/
|
|
20
|
+
export async function dispatchAction(commands, context, args, errorPrefixes) {
|
|
21
|
+
const command = commands[args.action];
|
|
22
|
+
if (!command) {
|
|
23
|
+
const supportedActions = Object.keys(commands).sort().join(", ");
|
|
24
|
+
return errorResult(`Unknown action: ${args.action}. Supported actions: ${supportedActions}`);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return await command.execute(context, args);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
31
|
+
const prefix = errorPrefixes?.[args.action];
|
|
32
|
+
return errorResult(prefix ? `${prefix}${message}` : `Error: ${message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -8,7 +8,7 @@ const ADVSEC_TOOLS = {
|
|
|
8
8
|
get_alert_details: "advsec_get_alert_details",
|
|
9
9
|
};
|
|
10
10
|
function configureAdvSecTools(server, _, connectionProvider) {
|
|
11
|
-
server.tool(ADVSEC_TOOLS.get_alerts, "Retrieve Advanced Security alerts for a repository.", {
|
|
11
|
+
server.tool(ADVSEC_TOOLS.get_alerts, "Retrieve Advanced Security alerts for a repository. Results are scoped to the specified project and repository. Branch filters (onlyDefaultBranch, ref) apply only to code, dependency, and license alerts; they are not applicable to secret alerts and are ignored by the service, so they neither include nor exclude secrets. To narrow secret alerts by confidence, pass a single 'confidenceLevels' value ('High' or 'Other'); selecting every level is treated as no confidence filter.", {
|
|
12
12
|
project: z.string().describe("The name or ID of the Azure DevOps project."),
|
|
13
13
|
repository: z.string().describe("The name or ID of the repository to get alerts for."),
|
|
14
14
|
alertType: z
|
|
@@ -26,17 +26,22 @@ function configureAdvSecTools(server, _, connectionProvider) {
|
|
|
26
26
|
ruleId: z.string().optional().describe("Filter alerts by rule ID."),
|
|
27
27
|
ruleName: z.string().optional().describe("Filter alerts by rule name."),
|
|
28
28
|
toolName: z.string().optional().describe("Filter alerts by tool name."),
|
|
29
|
-
ref: z
|
|
30
|
-
|
|
29
|
+
ref: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Filter non-secret alerts by git reference (branch), e.g. 'refs/heads/main'. When omitted and onlyDefaultBranch is true, only alerts on the default branch are returned. Not applicable to secret alerts and ignored by this tool when alertType is 'Secret'. When alertType is unspecified, this filter is still sent and may exclude secret alerts from the results; query alertType 'Secret' separately to retrieve all secrets."),
|
|
33
|
+
onlyDefaultBranch: z
|
|
34
|
+
.boolean()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("For non-secret alerts: if true (the service default when omitted) only return alerts found on the default branch; if false, return alerts from all branches. Ignored when 'ref' is provided. Not applicable to secret alerts and ignored by this tool when alertType is 'Secret'. When alertType is unspecified, this filter is still sent and may exclude secret alerts from the results; query alertType 'Secret' separately to retrieve all secrets."),
|
|
31
37
|
confidenceLevels: z
|
|
32
38
|
.array(z.enum(getEnumKeys(Confidence)))
|
|
33
39
|
.optional()
|
|
34
|
-
.
|
|
35
|
-
.describe("Filter alerts by confidence levels. Only applicable for secret alerts. Defaults to both 'high' and 'other'."),
|
|
40
|
+
.describe("Only applicable to secret alerts. Accepted values are 'High' and 'Other'. Pass a single value (e.g. ['High']) to narrow secrets to that confidence level. Leave unset to return secrets without a confidence filter. Do not select both levels to widen results: the Alerts service does not accept a multi-value confidence filter and would return no alerts, so this tool treats an all-levels selection as no filter and omits it."),
|
|
36
41
|
validity: z
|
|
37
42
|
.array(z.enum(getEnumKeys(AlertValidityStatus)))
|
|
38
43
|
.optional()
|
|
39
|
-
.describe("
|
|
44
|
+
.describe("Only applicable to secret alerts. If omitted, alerts of all validity statuses are returned (no validity filter is applied). Filtering by validity may return fewer alerts than 'top'; use the continuation token to fetch any remaining alerts."),
|
|
40
45
|
top: z.coerce.number().optional().default(100).describe("Maximum number of alerts to return. Defaults to 100."),
|
|
41
46
|
orderBy: z.enum(["id", "firstSeen", "lastSeen", "fixedOn", "severity"]).optional().default("severity").describe("Order results by specified field. Defaults to 'severity'."),
|
|
42
47
|
continuationToken: z.string().optional().describe("Continuation token for pagination."),
|
|
@@ -44,7 +49,22 @@ function configureAdvSecTools(server, _, connectionProvider) {
|
|
|
44
49
|
try {
|
|
45
50
|
const connection = await connectionProvider();
|
|
46
51
|
const alertApi = await connection.getAlertApi();
|
|
47
|
-
const
|
|
52
|
+
const normalizedAlertType = alertType?.toLowerCase();
|
|
53
|
+
// "onlyDefaultBranch" and "ref" are not applicable to secret alerts (secrets are not
|
|
54
|
+
// branch-scoped and carry a null gitRef). Forwarding them for a secret-only query diverges
|
|
55
|
+
// from the REST API / Advanced Security UI and can incorrectly return no alerts, so only
|
|
56
|
+
// include them when the query is not restricted to secret alerts.
|
|
57
|
+
const isSecretOnly = normalizedAlertType === "secret";
|
|
58
|
+
// "confidenceLevels" and "validity" only apply to secret alerts, so include them whenever
|
|
59
|
+
// the result set can contain secrets (an explicit "secret" type or no type filter at all).
|
|
60
|
+
const canIncludeSecrets = !alertType || isSecretOnly;
|
|
61
|
+
// The Alerts service does not accept the multi-value (comma-serialized) confidence filter
|
|
62
|
+
// that the SDK emits: selecting every level (e.g. both "High" and "Other") returns zero
|
|
63
|
+
// alerts, and it is a no-op filter regardless. Only forward confidenceLevels when it
|
|
64
|
+
// narrows the result to a proper subset (a single level); otherwise omit it so secrets are
|
|
65
|
+
// returned without a confidence filter instead of an empty set.
|
|
66
|
+
const confidenceLevelValues = confidenceLevels ? mapStringArrayToEnum(confidenceLevels, Confidence) : [];
|
|
67
|
+
const narrowsByConfidence = confidenceLevelValues.length > 0 && confidenceLevelValues.length < getEnumKeys(Confidence).length;
|
|
48
68
|
const criteria = {
|
|
49
69
|
...(alertType && { alertType: mapStringToEnum(alertType, AlertType) }),
|
|
50
70
|
...(states && { states: mapStringArrayToEnum(states, State) }),
|
|
@@ -52,10 +72,10 @@ function configureAdvSecTools(server, _, connectionProvider) {
|
|
|
52
72
|
...(ruleId && { ruleId }),
|
|
53
73
|
...(ruleName && { ruleName }),
|
|
54
74
|
...(toolName && { toolName }),
|
|
55
|
-
...(ref && { ref }),
|
|
56
|
-
...(onlyDefaultBranch !== undefined && { onlyDefaultBranch }),
|
|
57
|
-
...(
|
|
58
|
-
...(
|
|
75
|
+
...(!isSecretOnly && ref && { ref }),
|
|
76
|
+
...(!isSecretOnly && onlyDefaultBranch !== undefined && { onlyDefaultBranch }),
|
|
77
|
+
...(canIncludeSecrets && narrowsByConfidence && { confidenceLevels: confidenceLevelValues }),
|
|
78
|
+
...(canIncludeSecrets && validity && { validity: mapStringArrayToEnum(validity, AlertValidityStatus) }),
|
|
59
79
|
};
|
|
60
80
|
const result = await alertApi.getAlerts(project, repository, top, orderBy, criteria, undefined, // expand parameter
|
|
61
81
|
continuationToken);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { getEnumKeys } from "../utils.js";
|
|
5
|
+
import { RepositoryType } from "azure-devops-node-api/interfaces/PipelinesInterfaces.js";
|
|
6
|
+
import { StageUpdateType } from "azure-devops-node-api/interfaces/BuildInterfaces.js";
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
// DTOs for the pipelines_write tool.
|
|
9
|
+
//
|
|
10
|
+
// Each action's inputs are declared once as a Zod "raw shape". The shapes are
|
|
11
|
+
// the single source of truth: the tool's input schema is composed from them,
|
|
12
|
+
// and the TypeScript argument types are derived via `z.infer` (no hand-written,
|
|
13
|
+
// drift-prone duplicate types). These types are safe to export — they are
|
|
14
|
+
// compile-time only and erased at runtime, so they have no effect on the MCP
|
|
15
|
+
// protocol or a local server.
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
export const variableSchema = z.object({
|
|
18
|
+
value: z.string().optional(),
|
|
19
|
+
isSecret: z.boolean().optional(),
|
|
20
|
+
});
|
|
21
|
+
export const resourcesSchema = z.object({
|
|
22
|
+
builds: z.record(z.string(), z.object({ version: z.string().optional() })).optional(),
|
|
23
|
+
containers: z.record(z.string(), z.object({ version: z.string().optional() })).optional(),
|
|
24
|
+
packages: z.record(z.string(), z.object({ version: z.string().optional() })).optional(),
|
|
25
|
+
pipelines: z
|
|
26
|
+
.record(z.string(), z.object({
|
|
27
|
+
runId: z.coerce.number().min(1).optional().describe("Id of the source pipeline run."),
|
|
28
|
+
version: z.string().optional(),
|
|
29
|
+
}))
|
|
30
|
+
.optional(),
|
|
31
|
+
repositories: z
|
|
32
|
+
.record(z.string(), z.object({
|
|
33
|
+
refName: z.string().describe("Reference name, e.g., refs/heads/main."),
|
|
34
|
+
token: z.string().optional(),
|
|
35
|
+
tokenType: z.string().optional(),
|
|
36
|
+
version: z.string().optional(),
|
|
37
|
+
}))
|
|
38
|
+
.optional(),
|
|
39
|
+
});
|
|
40
|
+
/** Fields shared by every write action. */
|
|
41
|
+
const projectShape = {
|
|
42
|
+
project: z.string().describe("Project ID or name."),
|
|
43
|
+
};
|
|
44
|
+
/** run_pipeline inputs. */
|
|
45
|
+
export const runPipelineShape = {
|
|
46
|
+
...projectShape,
|
|
47
|
+
pipelineId: z.coerce.number().min(1).optional().describe("ID of the pipeline to run. Required for: run_pipeline."),
|
|
48
|
+
pipelineVersion: z.coerce.number().min(1).optional().describe("Version of the pipeline to run. Used for: run_pipeline."),
|
|
49
|
+
previewRun: z.boolean().optional().describe("If true, returns the final YAML without creating a run. Used for: run_pipeline."),
|
|
50
|
+
resources: resourcesSchema.optional().describe("Resources to pass to the pipeline. Used for: run_pipeline."),
|
|
51
|
+
stagesToSkip: z.array(z.string()).optional().describe("Stages to skip. Used for: run_pipeline."),
|
|
52
|
+
templateParameters: z.record(z.string(), z.string()).optional().describe("Custom build parameters as key-value pairs. Used for: run_pipeline."),
|
|
53
|
+
variables: z.record(z.string(), variableSchema).optional().describe("Variables to pass to the pipeline. Used for: run_pipeline."),
|
|
54
|
+
yamlOverride: z.string().optional().describe("YAML override (only valid with previewRun). Used for: run_pipeline."),
|
|
55
|
+
};
|
|
56
|
+
/** create_pipeline inputs. */
|
|
57
|
+
export const createPipelineShape = {
|
|
58
|
+
...projectShape,
|
|
59
|
+
name: z.string().optional().describe("Name of the new pipeline. Required for: create_pipeline."),
|
|
60
|
+
folder: z.string().optional().describe("Folder path for the new pipeline. Used for: create_pipeline."),
|
|
61
|
+
yamlPath: z.string().optional().describe("Path to the YAML file in the repository. Required for: create_pipeline."),
|
|
62
|
+
repositoryType: z
|
|
63
|
+
.enum(getEnumKeys(RepositoryType))
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("Type of the repository. Required for: create_pipeline."),
|
|
66
|
+
repositoryName: z.string().optional().describe("Name of the repository (for GitHub: owner/repo). Required for: create_pipeline."),
|
|
67
|
+
repositoryId: z.string().optional().describe("ID of the repository. Used for: create_pipeline."),
|
|
68
|
+
repositoryConnectionId: z.string().optional().describe("Service connection ID for GitHub repositories. Used for: create_pipeline."),
|
|
69
|
+
};
|
|
70
|
+
/** update_build_stage inputs. */
|
|
71
|
+
export const updateBuildStageShape = {
|
|
72
|
+
...projectShape,
|
|
73
|
+
buildId: z.coerce.number().min(1).optional().describe("ID of the build to update. Required for: update_build_stage."),
|
|
74
|
+
stageName: z.string().optional().describe("Name of the stage to update. Required for: update_build_stage."),
|
|
75
|
+
status: z
|
|
76
|
+
.enum(getEnumKeys(StageUpdateType))
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("New status for the stage. Required for: update_build_stage."),
|
|
79
|
+
forceRetryAllJobs: z.boolean().default(false).describe("Whether to force retry all jobs in the stage. Used for: update_build_stage."),
|
|
80
|
+
};
|
|
81
|
+
/** The composed input shape for the grouped `pipelines_write` tool. */
|
|
82
|
+
export const pipelinesWriteShape = {
|
|
83
|
+
action: z
|
|
84
|
+
.enum(["run_pipeline", "create_pipeline", "update_build_stage"])
|
|
85
|
+
.describe("The action to perform. Options: run_pipeline (queue a new pipeline run), create_pipeline (create a new YAML pipeline definition), update_build_stage (cancel, retry, or run a stage on an in-flight build)."),
|
|
86
|
+
...runPipelineShape,
|
|
87
|
+
...createPipelineShape,
|
|
88
|
+
...updateBuildStageShape,
|
|
89
|
+
};
|
|
90
|
+
// Per-action schemas + inferred argument DTOs. `z.infer` keeps these types in
|
|
91
|
+
// lockstep with the schemas above.
|
|
92
|
+
export const runPipelineSchema = z.object(runPipelineShape);
|
|
93
|
+
export const createPipelineSchema = z.object(createPipelineShape);
|
|
94
|
+
export const updateBuildStageSchema = z.object(updateBuildStageShape);
|
|
95
|
+
export const pipelinesWriteSchema = z.object(pipelinesWriteShape);
|