@azure-devops/mcp 2.0.0 → 2.1.0-nightly.20250910
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 +49 -21
- package/dist/index.js +0 -0
- package/dist/prompts.js +0 -0
- package/dist/shared/domains.js +4 -9
- package/dist/tools/{builds.js → pipelines.js} +99 -36
- package/dist/tools/repositories.js +70 -12
- package/dist/tools.js +2 -4
- package/dist/useragent.js +0 -0
- package/dist/utils.js +0 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/tools/releases.js +0 -97
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. [
|
|
19
|
-
6. [
|
|
20
|
-
7. [
|
|
21
|
-
8. [
|
|
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
|
-
###
|
|
105
|
+
### 🚀 Pipelines
|
|
106
106
|
|
|
107
|
-
- **
|
|
108
|
-
- **
|
|
109
|
-
- **
|
|
110
|
-
- **
|
|
111
|
-
- **
|
|
112
|
-
- **
|
|
113
|
-
- **
|
|
114
|
-
- **
|
|
115
|
-
- **
|
|
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
|
-
###
|
|
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
|
package/dist/shared/domains.js
CHANGED
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
export var Domain;
|
|
7
7
|
(function (Domain) {
|
|
8
8
|
Domain["ADVANCED_SECURITY"] = "advanced-security";
|
|
9
|
-
Domain["
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
19
|
-
server.tool(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
145
|
-
self: {
|
|
146
|
-
refName: sourceBranch || definition.repository?.defaultBranch || "refs/heads/main",
|
|
147
|
-
},
|
|
148
|
-
},
|
|
209
|
+
...resources,
|
|
149
210
|
},
|
|
150
|
-
|
|
211
|
+
stagesToSkip: stagesToSkip,
|
|
212
|
+
templateParameters: templateParameters,
|
|
213
|
+
variables: variables,
|
|
214
|
+
yamlOverride: yamlOverride,
|
|
151
215
|
};
|
|
152
|
-
const pipelineRun = await pipelinesApi.runPipeline(runRequest, project,
|
|
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(
|
|
223
|
+
content: [{ type: "text", text: JSON.stringify(pipelineRun, null, 2) }],
|
|
161
224
|
};
|
|
162
225
|
});
|
|
163
|
-
server.tool(
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
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
|
|
243
|
+
else if (created_by_me) {
|
|
232
244
|
const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
|
|
233
245
|
const userId = data.authenticatedUser.id;
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
238
|
-
|
|
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
|
-
|
|
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
|
|
339
|
+
else if (created_by_me) {
|
|
299
340
|
const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider);
|
|
300
341
|
const userId = data.authenticatedUser.id;
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
305
|
-
|
|
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 {
|
|
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.
|
|
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.
|
|
1
|
+
export const packageVersion = "2.1.0-nightly.20250910";
|
package/package.json
CHANGED
package/dist/tools/releases.js
DELETED
|
@@ -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 };
|