@azure-devops/mcp 1.2.1 โ 1.3.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/LICENSE.md +21 -21
- package/README.md +25 -141
- package/dist/shared/tool-validation.js +92 -0
- package/dist/tools/advsec.js +108 -0
- package/dist/tools/core.test.js +1 -0
- package/dist/tools/repos.js +51 -13
- package/dist/tools/testplan.test.js +125 -0
- package/dist/tools/testplans.js +8 -4
- package/dist/tools/utils.js +6 -0
- package/dist/tools/wiki.test.js +87 -0
- package/dist/tools/workitem.test.js +101 -0
- package/dist/tools/workitems.js +85 -3
- package/dist/tools/workitems.test.js +530 -0
- package/dist/tools.js +8 -6
- package/dist/utils.js +26 -0
- package/dist/version.js +1 -1
- package/package.json +4 -2
- package/dist/http.js +0 -52
- package/dist/server.js +0 -36
package/LICENSE.md
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) Microsoft Corporation.
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Microsoft Corporation.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE
|
package/README.md
CHANGED
|
@@ -15,11 +15,10 @@ 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. [
|
|
22
|
-
9. [๐ Contributing](#-contributing)
|
|
18
|
+
5. [๐ Troubleshooting](#-troubleshooting)
|
|
19
|
+
6. [๐ฉ Examples & Best Practices](#-samples--best-practices)
|
|
20
|
+
7. [๐โโ๏ธ Frequently Asked Questions](#๏ธ-frequently-asked-questions)
|
|
21
|
+
8. [๐ Contributing](#-contributing)
|
|
23
22
|
|
|
24
23
|
## ๐บ Overview
|
|
25
24
|
|
|
@@ -74,6 +73,7 @@ Interact with these Azure DevOps services:
|
|
|
74
73
|
- **wit_get_query_results_by_id**: Retrieve the results of a work item query given the query ID.
|
|
75
74
|
- **wit_update_work_items_batch**: Update work items in batch.
|
|
76
75
|
- **wit_work_items_link**: Link work items together in batch.
|
|
76
|
+
- **wit_work_item_unlink**: Unlink one or many links from a work item.
|
|
77
77
|
|
|
78
78
|
#### Deprecated Tools
|
|
79
79
|
|
|
@@ -95,6 +95,7 @@ Interact with these Azure DevOps services:
|
|
|
95
95
|
- **repo_get_pull_request_by_id**: Get a pull request by its ID.
|
|
96
96
|
- **repo_create_pull_request**: Create a new pull request.
|
|
97
97
|
- **repo_update_pull_request_status**: Update the status of an existing pull request to active or abandoned.
|
|
98
|
+
- **repo_update_pull_request**: Update various fields of an existing pull request (title, description, draft status, target branch).
|
|
98
99
|
- **repo_update_pull_request_reviewers**: Add or remove reviewers for an existing pull request.
|
|
99
100
|
- **repo_reply_to_comment**: Replies to a specific comment on a pull request.
|
|
100
101
|
- **repo_resolve_comment**: Resolves a specific comment thread on a pull request.
|
|
@@ -118,6 +119,11 @@ Interact with these Azure DevOps services:
|
|
|
118
119
|
- **release_get_definitions**: Retrieve a list of release definitions for a given project.
|
|
119
120
|
- **release_get_releases**: Retrieve a list of releases for a given project.
|
|
120
121
|
|
|
122
|
+
### ๐ Advanced Security
|
|
123
|
+
|
|
124
|
+
- **advsec_get_alerts**: Retrieve Advanced Security alerts for a repository.
|
|
125
|
+
- **advsec_get_alert_details**: Get detailed information about a specific Advanced Security alert.
|
|
126
|
+
|
|
121
127
|
### ๐งช Test Plans
|
|
122
128
|
|
|
123
129
|
- **testplan_create_test_plan**: Create a new test plan in the project.
|
|
@@ -135,14 +141,7 @@ Interact with these Azure DevOps services:
|
|
|
135
141
|
|
|
136
142
|
## ๐ Installation & Getting Started
|
|
137
143
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
[VS Code and GitHub Copilot](#%EF%B8%8F-visual-studio-code--github-copilot)<br/>
|
|
141
|
-
[Visual Studio 2022 and GitHub Copilot](#%EF%B8%8F-visual-studio-2022--github-copilot)
|
|
142
|
-
|
|
143
|
-
### โก๏ธ Visual Studio Code & GitHub Copilot
|
|
144
|
-
|
|
145
|
-
For the best experience, use Visual Studio Code and GitHub Copilot.
|
|
144
|
+
For the best experience, use Visual Studio Code and GitHub Copilot. See the [getting started documentation](./docs/GETTINGSTARTED.md) to use our MCP Server with other tools such as Visual Studio 2022, Claude Code, and Cursor.
|
|
146
145
|
|
|
147
146
|
### Prerequisites
|
|
148
147
|
|
|
@@ -176,108 +175,7 @@ This installation method is the easiest for all users of Visual Studio Code.
|
|
|
176
175
|
|
|
177
176
|
##### Steps
|
|
178
177
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
```json
|
|
182
|
-
{
|
|
183
|
-
"inputs": [
|
|
184
|
-
{
|
|
185
|
-
"id": "ado_org",
|
|
186
|
-
"type": "promptString",
|
|
187
|
-
"description": "Azure DevOps organization name (e.g. 'contoso')"
|
|
188
|
-
}
|
|
189
|
-
],
|
|
190
|
-
"servers": {
|
|
191
|
-
"ado": {
|
|
192
|
-
"type": "stdio",
|
|
193
|
-
"command": "npx",
|
|
194
|
-
"args": ["-y", "@azure-devops/mcp", "${input:ado_org}"]
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
2. Save the file, then click 'Start'.
|
|
201
|
-
|
|
202
|
-
<img src="./docs/media/start-mcp-server.gif" alt="start mcp server" width="250"/>
|
|
203
|
-
|
|
204
|
-
3. In chat, switch to [Agent Mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode).
|
|
205
|
-
4. Click "Select Tools" and choose the available tools.
|
|
206
|
-
5. We strongly recommend creating a `.github\copilot-instructions.md` in your project and copying the contents from this [copilot-instructions.md](./.github/copilot-instructions.md) file. This will enhance your experience using the Azure DevOps MCP Server with GitHub Copilot Chat.
|
|
207
|
-
|
|
208
|
-
#### ๐ ๏ธ Install from Source (Dev Mode)
|
|
209
|
-
|
|
210
|
-
This installation method is recommended for advanced users and contributors who want immediate access to the latest updates from the main branch. It is ideal if you are developing new tools, enhancing existing features, or maintaining a custom fork.
|
|
211
|
-
|
|
212
|
-
> **Note:** For most users, installing from the public feed is simpler and preferred. Use the source installation only if you need the latest changes or are actively contributing to the project.
|
|
213
|
-
|
|
214
|
-
##### Steps
|
|
215
|
-
|
|
216
|
-
1. Clone the repository.
|
|
217
|
-
2. Install dependencies:
|
|
218
|
-
|
|
219
|
-
```sh
|
|
220
|
-
npm install
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
3. Edit or add `.vscode/mcp.json`:
|
|
224
|
-
|
|
225
|
-
```json
|
|
226
|
-
{
|
|
227
|
-
"inputs": [
|
|
228
|
-
{
|
|
229
|
-
"id": "ado_org",
|
|
230
|
-
"type": "promptString",
|
|
231
|
-
"description": "Azure DevOps organization's name (e.g. 'contoso')"
|
|
232
|
-
}
|
|
233
|
-
],
|
|
234
|
-
"servers": {
|
|
235
|
-
"ado": {
|
|
236
|
-
"type": "stdio",
|
|
237
|
-
"command": "mcp-server-azuredevops",
|
|
238
|
-
"args": ["${input:ado_org}"]
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
4. Start the Azure DevOps MCP Server.
|
|
245
|
-
|
|
246
|
-
<img src="./docs/media/start-mcp-server.gif" alt="start mcp server" width="250"/>
|
|
247
|
-
|
|
248
|
-
5. In chat, switch to [Agent Mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode).
|
|
249
|
-
6. Click "Select Tools" and choose the available tools.
|
|
250
|
-
7. We strongly recommend creating a `.github\copilot-instructions.md` in your project and copying the contents from this [copilot-instructions.md](./.github/copilot-instructions.md) file. This will help you get the best experience using the Azure DevOps MCP Server in GitHub Copilot Chat.
|
|
251
|
-
|
|
252
|
-
See the [How To](./docs/HOWTO.md) section for details.
|
|
253
|
-
|
|
254
|
-
### โก๏ธ Visual Studio 2022 & GitHub Copilot
|
|
255
|
-
|
|
256
|
-
For the best experience, use Visual Studio Code and GitHub Copilot ๐.
|
|
257
|
-
|
|
258
|
-
### Prerequisites
|
|
259
|
-
|
|
260
|
-
1. Install [VS Studio 2022 version 17.14](https://learn.microsoft.com/en-us/visualstudio/releases/2022/release-history) or later
|
|
261
|
-
2. Install [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest)
|
|
262
|
-
3. Open a project in Visual Studio.
|
|
263
|
-
|
|
264
|
-
### Azure Login
|
|
265
|
-
|
|
266
|
-
Ensure you are logged in to Azure DevOps via the Azure CLI:
|
|
267
|
-
|
|
268
|
-
```sh
|
|
269
|
-
az login
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
#### ๐งจ Install from Public Feed (Recommended)
|
|
273
|
-
|
|
274
|
-
This installation method is the easiest for all users of Visual Studio 2022.
|
|
275
|
-
|
|
276
|
-
๐ฅ [Watch this quick start video to get up and running in under two minutes!](https://youtu.be/nz_Gn-WL7j0)
|
|
277
|
-
|
|
278
|
-
##### Steps
|
|
279
|
-
|
|
280
|
-
1. Add a `.mcp.json` file to the solution folder with the following content:
|
|
178
|
+
In your project, add a `.vscode\mcp.json` file with the following content:
|
|
281
179
|
|
|
282
180
|
```json
|
|
283
181
|
{
|
|
@@ -298,45 +196,31 @@ This installation method is the easiest for all users of Visual Studio 2022.
|
|
|
298
196
|
}
|
|
299
197
|
```
|
|
300
198
|
|
|
301
|
-
|
|
302
|
-
3. Add your organization name by clicking on the `input` option.
|
|
199
|
+
Save the file, then click 'Start'.
|
|
303
200
|
|
|
304
|
-
|
|
201
|
+
<img src="./docs/media/start-mcp-server.gif" alt="start mcp server" width="250"/>
|
|
305
202
|
|
|
306
|
-
|
|
307
|
-
5. Click the "Tools" icon and choose the available tools.
|
|
203
|
+
In chat, switch to [Agent Mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode).
|
|
308
204
|
|
|
309
|
-
|
|
205
|
+
Click "Select Tools" and choose the available tools.
|
|
310
206
|
|
|
311
|
-
|
|
207
|
+
<img src="./docs/media/configure-mcp-server-tools.gif" alt="configure mcp server tools" width="300"/>
|
|
312
208
|
|
|
313
|
-
|
|
209
|
+
Open GitHub Copilot Chat and try a prompt like `List ADO projects`.
|
|
314
210
|
|
|
315
|
-
|
|
211
|
+
> ๐ฅ We strongly recommend creating a `.github\copilot-instructions.md` in your project and copying the contents from this [copilot-instructions.md](./.github/copilot-instructions.md) file. This will enhance your experience using the Azure DevOps MCP Server with GitHub Copilot Chat.
|
|
316
212
|
|
|
317
|
-
|
|
318
|
-
2. Start the Azure DevOps MCP Server.
|
|
319
|
-
3. The server appears in the tools list.
|
|
320
|
-
4. Try prompts like "List ADO projects".
|
|
321
|
-
|
|
322
|
-
### Visual Studio + GitHub Copilot
|
|
323
|
-
|
|
324
|
-
> _Prerequisites:_ Visual Studio 2022 v17.14+, Agent mode enabled in Tools > Options > GitHub > Copilot > Copilot Chat.
|
|
325
|
-
|
|
326
|
-
1. Switch to Agent mode in the Copilot Chat window.
|
|
327
|
-
2. Enter your Azure DevOps organization name.
|
|
328
|
-
3. Select desired `ado` tools.
|
|
329
|
-
4. Try prompts like "List ADO projects".
|
|
330
|
-
|
|
331
|
-
For more details, see [Visual Studio MCP Servers documentation](https://learn.microsoft.com/en-us/visualstudio/ide/mcp-servers?view=vs-2022) and the [Getting Started Video](https://www.youtube.com/watch?v=oPFecZHBCkg).
|
|
213
|
+
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.
|
|
332
214
|
|
|
333
215
|
## ๐ Troubleshooting
|
|
334
216
|
|
|
335
217
|
See the [Troubleshooting guide](./docs/TROUBLESHOOTING.md) for help with common issues and logging.
|
|
336
218
|
|
|
337
|
-
## ๐ฉ
|
|
219
|
+
## ๐ฉ Examples & Best Practices
|
|
220
|
+
|
|
221
|
+
Explore example prompts in our [Examples documentation](./docs/EXAMPLES.md).
|
|
338
222
|
|
|
339
|
-
|
|
223
|
+
For best practices and tips to enhance your experience with the MCP Server, refer to the [How-To guide](./docs/HOWTO.md).
|
|
340
224
|
|
|
341
225
|
## ๐โโ๏ธ Frequently Asked Questions
|
|
342
226
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
/**
|
|
4
|
+
* Validates that a name conforms to Claude API requirements.
|
|
5
|
+
* Names must match pattern: ^[a-zA-Z0-9_.-]{1,64}$
|
|
6
|
+
* @param name The name to validate
|
|
7
|
+
* @returns Object with isValid boolean and error/reason message if invalid
|
|
8
|
+
*/
|
|
9
|
+
export function validateName(name) {
|
|
10
|
+
// Check length
|
|
11
|
+
if (name.length === 0) {
|
|
12
|
+
return { isValid: false, error: "Name cannot be empty", reason: "name cannot be empty" };
|
|
13
|
+
}
|
|
14
|
+
if (name.length > 64) {
|
|
15
|
+
return {
|
|
16
|
+
isValid: false,
|
|
17
|
+
error: `Name '${name}' is ${name.length} characters long, maximum allowed is 64`,
|
|
18
|
+
reason: `name is ${name.length} characters long, maximum allowed is 64`,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// Check pattern: only alphanumeric, underscore, dot, and hyphen allowed
|
|
22
|
+
const validPattern = /^[a-zA-Z0-9_.-]+$/;
|
|
23
|
+
if (!validPattern.test(name)) {
|
|
24
|
+
return {
|
|
25
|
+
isValid: false,
|
|
26
|
+
error: `Name '${name}' contains invalid characters. Only alphanumeric characters, underscores, dots, and hyphens are allowed`,
|
|
27
|
+
reason: "name contains invalid characters. Only alphanumeric characters, underscores, dots, and hyphens are allowed",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return { isValid: true };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Validates that a tool name conforms to Claude API requirements.
|
|
34
|
+
* @param toolName The tool name to validate
|
|
35
|
+
* @returns Object with isValid boolean and error message if invalid
|
|
36
|
+
*/
|
|
37
|
+
export function validateToolName(toolName) {
|
|
38
|
+
const result = validateName(toolName);
|
|
39
|
+
if (!result.isValid) {
|
|
40
|
+
return { isValid: false, error: result.error?.replace("Name", "Tool name") };
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Validates that a parameter name conforms to Claude API requirements.
|
|
46
|
+
* @param paramName The parameter name to validate
|
|
47
|
+
* @returns Object with isValid boolean and error message if invalid
|
|
48
|
+
*/
|
|
49
|
+
export function validateParameterName(paramName) {
|
|
50
|
+
const result = validateName(paramName);
|
|
51
|
+
if (!result.isValid) {
|
|
52
|
+
return { isValid: false, error: result.error?.replace("Name", "Parameter name") };
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Extracts tool names from tool constant definitions
|
|
58
|
+
* @param fileContent - The content of a TypeScript file
|
|
59
|
+
* @returns Array of tool names found
|
|
60
|
+
*/
|
|
61
|
+
export function extractToolNames(fileContent) {
|
|
62
|
+
const toolNames = [];
|
|
63
|
+
// Pattern to match tool constant definitions in tool objects
|
|
64
|
+
// This looks for patterns like: const SOMETHING_TOOLS = { ... } or const Test_Plan_Tools = { ... }
|
|
65
|
+
const toolsObjectPattern = /const\s+\w*[Tt][Oo][Oo][Ll][Ss]?\s*=\s*\{([^}]+)\}/g;
|
|
66
|
+
let toolsMatch;
|
|
67
|
+
while ((toolsMatch = toolsObjectPattern.exec(fileContent)) !== null) {
|
|
68
|
+
const objectContent = toolsMatch[1];
|
|
69
|
+
// Now extract individual tool definitions from within the object
|
|
70
|
+
const toolPattern = /^\s*[a-zA-Z_][a-zA-Z0-9_]*:\s*"([^"]+)"/gm;
|
|
71
|
+
let match;
|
|
72
|
+
while ((match = toolPattern.exec(objectContent)) !== null) {
|
|
73
|
+
toolNames.push(match[1]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return toolNames;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Extracts parameter names from Zod schema definitions
|
|
80
|
+
* @param fileContent - The content of a TypeScript file
|
|
81
|
+
* @returns Array of parameter names found
|
|
82
|
+
*/
|
|
83
|
+
export function extractParameterNames(fileContent) {
|
|
84
|
+
const paramNames = [];
|
|
85
|
+
// Pattern to match parameter definitions like: paramName: z.string()
|
|
86
|
+
const paramPattern = /^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*z\./gm;
|
|
87
|
+
let match;
|
|
88
|
+
while ((match = paramPattern.exec(fileContent)) !== null) {
|
|
89
|
+
paramNames.push(match[1]);
|
|
90
|
+
}
|
|
91
|
+
return paramNames;
|
|
92
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import { AlertType, AlertValidityStatus, Confidence, Severity, State } from "azure-devops-node-api/interfaces/AlertInterfaces.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { getEnumKeys, mapStringArrayToEnum, mapStringToEnum } from "../utils.js";
|
|
6
|
+
const ADVSEC_TOOLS = {
|
|
7
|
+
get_alerts: "advsec_get_alerts",
|
|
8
|
+
get_alert_details: "advsec_get_alert_details",
|
|
9
|
+
};
|
|
10
|
+
function configureAdvSecTools(server, tokenProvider, connectionProvider) {
|
|
11
|
+
server.tool(ADVSEC_TOOLS.get_alerts, "Retrieve Advanced Security alerts for a repository.", {
|
|
12
|
+
project: z.string().describe("The name or ID of the Azure DevOps project."),
|
|
13
|
+
repository: z.string().describe("The name or ID of the repository to get alerts for."),
|
|
14
|
+
alertType: z
|
|
15
|
+
.enum(getEnumKeys(AlertType))
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Filter alerts by type. If not specified, returns all alert types."),
|
|
18
|
+
states: z
|
|
19
|
+
.array(z.enum(getEnumKeys(State)))
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Filter alerts by state. If not specified, returns alerts in any state."),
|
|
22
|
+
severities: z
|
|
23
|
+
.array(z.enum(getEnumKeys(Severity)))
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Filter alerts by severity level. If not specified, returns alerts at any severity."),
|
|
26
|
+
ruleId: z.string().optional().describe("Filter alerts by rule ID."),
|
|
27
|
+
ruleName: z.string().optional().describe("Filter alerts by rule name."),
|
|
28
|
+
toolName: z.string().optional().describe("Filter alerts by tool name."),
|
|
29
|
+
ref: z.string().optional().describe("Filter alerts by git reference (branch). If not provided and onlyDefaultBranch is true, only includes alerts from default branch."),
|
|
30
|
+
onlyDefaultBranch: z.boolean().optional().default(true).describe("If true, only return alerts found on the default branch. Defaults to true."),
|
|
31
|
+
confidenceLevels: z
|
|
32
|
+
.array(z.enum(getEnumKeys(Confidence)))
|
|
33
|
+
.optional()
|
|
34
|
+
.default(["high", "other"])
|
|
35
|
+
.describe("Filter alerts by confidence levels. Only applicable for secret alerts. Defaults to both 'high' and 'other'."),
|
|
36
|
+
validity: z
|
|
37
|
+
.array(z.enum(getEnumKeys(AlertValidityStatus)))
|
|
38
|
+
.optional()
|
|
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."),
|
|
41
|
+
orderBy: z.enum(["id", "firstSeen", "lastSeen", "fixedOn", "severity"]).optional().default("severity").describe("Order results by specified field. Defaults to 'severity'."),
|
|
42
|
+
continuationToken: z.string().optional().describe("Continuation token for pagination."),
|
|
43
|
+
}, async ({ project, repository, alertType, states, severities, ruleId, ruleName, toolName, ref, onlyDefaultBranch, confidenceLevels, validity, top, orderBy, continuationToken }) => {
|
|
44
|
+
try {
|
|
45
|
+
const connection = await connectionProvider();
|
|
46
|
+
const alertApi = await connection.getAlertApi();
|
|
47
|
+
const isSecretAlert = !alertType || alertType.toLowerCase() === "secret";
|
|
48
|
+
const criteria = {
|
|
49
|
+
...(alertType && { alertType: mapStringToEnum(alertType, AlertType) }),
|
|
50
|
+
...(states && { states: mapStringArrayToEnum(states, State) }),
|
|
51
|
+
...(severities && { severities: mapStringArrayToEnum(severities, Severity) }),
|
|
52
|
+
...(ruleId && { ruleId }),
|
|
53
|
+
...(ruleName && { ruleName }),
|
|
54
|
+
...(toolName && { toolName }),
|
|
55
|
+
...(ref && { ref }),
|
|
56
|
+
...(onlyDefaultBranch !== undefined && { onlyDefaultBranch }),
|
|
57
|
+
...(isSecretAlert && confidenceLevels && { confidenceLevels: mapStringArrayToEnum(confidenceLevels, Confidence) }),
|
|
58
|
+
...(isSecretAlert && validity && { validity: mapStringArrayToEnum(validity, AlertValidityStatus) }),
|
|
59
|
+
};
|
|
60
|
+
const result = await alertApi.getAlerts(project, repository, top, orderBy, criteria, undefined, // expand parameter
|
|
61
|
+
continuationToken);
|
|
62
|
+
return {
|
|
63
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `Error fetching Advanced Security alerts: ${errorMessage}`,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
isError: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
server.tool(ADVSEC_TOOLS.get_alert_details, "Get detailed information about a specific Advanced Security alert.", {
|
|
80
|
+
project: z.string().describe("The name or ID of the Azure DevOps project."),
|
|
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."),
|
|
83
|
+
ref: z.string().optional().describe("Git reference (branch) to filter the alert."),
|
|
84
|
+
}, async ({ project, repository, alertId, ref }) => {
|
|
85
|
+
try {
|
|
86
|
+
const connection = await connectionProvider();
|
|
87
|
+
const alertApi = await connection.getAlertApi();
|
|
88
|
+
const result = await alertApi.getAlert(project, alertId, repository, ref, undefined // expand parameter
|
|
89
|
+
);
|
|
90
|
+
return {
|
|
91
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: `Error fetching alert details: ${errorMessage}`,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
isError: true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
export { ADVSEC_TOOLS, configureAdvSecTools };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/tools/repos.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
import { PullRequestStatus, GitVersionType, GitPullRequestQueryType, } from "azure-devops-node-api/interfaces/GitInterfaces.js";
|
|
3
|
+
import { PullRequestStatus, GitVersionType, GitPullRequestQueryType, CommentThreadStatus, } from "azure-devops-node-api/interfaces/GitInterfaces.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { getCurrentUserDetails } from "./auth.js";
|
|
6
6
|
import { getEnumKeys } from "../utils.js";
|
|
@@ -16,7 +16,7 @@ const REPO_TOOLS = {
|
|
|
16
16
|
get_branch_by_name: "repo_get_branch_by_name",
|
|
17
17
|
get_pull_request_by_id: "repo_get_pull_request_by_id",
|
|
18
18
|
create_pull_request: "repo_create_pull_request",
|
|
19
|
-
|
|
19
|
+
update_pull_request: "repo_update_pull_request",
|
|
20
20
|
update_pull_request_reviewers: "repo_update_pull_request_reviewers",
|
|
21
21
|
reply_to_comment: "repo_reply_to_comment",
|
|
22
22
|
create_pull_request_thread: "repo_create_pull_request_thread",
|
|
@@ -82,10 +82,18 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
|
|
|
82
82
|
description: z.string().optional().describe("The description of the pull request. Optional."),
|
|
83
83
|
isDraft: z.boolean().optional().default(false).describe("Indicates whether the pull request is a draft. Defaults to false."),
|
|
84
84
|
workItems: z.string().optional().describe("Work item IDs to associate with the pull request, space-separated."),
|
|
85
|
-
|
|
85
|
+
forkSourceRepositoryId: z.string().optional().describe("The ID of the fork repository that the pull request originates from. Optional, used when creating a pull request from a fork."),
|
|
86
|
+
}, async ({ repositoryId, sourceRefName, targetRefName, title, description, isDraft, workItems, forkSourceRepositoryId }) => {
|
|
86
87
|
const connection = await connectionProvider();
|
|
87
88
|
const gitApi = await connection.getGitApi();
|
|
88
89
|
const workItemRefs = workItems ? workItems.split(" ").map((id) => ({ id: id.trim() })) : [];
|
|
90
|
+
const forkSource = forkSourceRepositoryId
|
|
91
|
+
? {
|
|
92
|
+
repository: {
|
|
93
|
+
id: forkSourceRepositoryId,
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
: undefined;
|
|
89
97
|
const pullRequest = await gitApi.createPullRequest({
|
|
90
98
|
sourceRefName,
|
|
91
99
|
targetRefName,
|
|
@@ -93,20 +101,44 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
|
|
|
93
101
|
description,
|
|
94
102
|
isDraft,
|
|
95
103
|
workItemRefs: workItemRefs,
|
|
104
|
+
forkSource,
|
|
96
105
|
}, repositoryId);
|
|
97
106
|
return {
|
|
98
107
|
content: [{ type: "text", text: JSON.stringify(pullRequest, null, 2) }],
|
|
99
108
|
};
|
|
100
109
|
});
|
|
101
|
-
server.tool(REPO_TOOLS.
|
|
110
|
+
server.tool(REPO_TOOLS.update_pull_request, "Update a Pull Request by ID with specified fields.", {
|
|
102
111
|
repositoryId: z.string().describe("The ID of the repository where the pull request exists."),
|
|
103
|
-
pullRequestId: z.number().describe("The ID of the pull request to
|
|
104
|
-
|
|
105
|
-
|
|
112
|
+
pullRequestId: z.number().describe("The ID of the pull request to update."),
|
|
113
|
+
title: z.string().optional().describe("The new title for the pull request."),
|
|
114
|
+
description: z.string().optional().describe("The new description for the pull request."),
|
|
115
|
+
isDraft: z.boolean().optional().describe("Whether the pull request should be a draft."),
|
|
116
|
+
targetRefName: z.string().optional().describe("The new target branch name (e.g., 'refs/heads/main')."),
|
|
117
|
+
status: z.enum(["Active", "Abandoned"]).optional().describe("The new status of the pull request. Can be 'Active' or 'Abandoned'."),
|
|
118
|
+
}, async ({ repositoryId, pullRequestId, title, description, isDraft, targetRefName, status }) => {
|
|
106
119
|
const connection = await connectionProvider();
|
|
107
120
|
const gitApi = await connection.getGitApi();
|
|
108
|
-
|
|
109
|
-
const
|
|
121
|
+
// Build update object with only provided fields
|
|
122
|
+
const updateRequest = {};
|
|
123
|
+
if (title !== undefined)
|
|
124
|
+
updateRequest.title = title;
|
|
125
|
+
if (description !== undefined)
|
|
126
|
+
updateRequest.description = description;
|
|
127
|
+
if (isDraft !== undefined)
|
|
128
|
+
updateRequest.isDraft = isDraft;
|
|
129
|
+
if (targetRefName !== undefined)
|
|
130
|
+
updateRequest.targetRefName = targetRefName;
|
|
131
|
+
if (status !== undefined) {
|
|
132
|
+
updateRequest.status = status === "Active" ? PullRequestStatus.Active.valueOf() : PullRequestStatus.Abandoned.valueOf();
|
|
133
|
+
}
|
|
134
|
+
// Validate that at least one field is provided for update
|
|
135
|
+
if (Object.keys(updateRequest).length === 0) {
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: "text", text: "Error: At least one field (title, description, isDraft, targetRefName, or status) must be provided for update." }],
|
|
138
|
+
isError: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const updatedPullRequest = await gitApi.updatePullRequest(updateRequest, repositoryId, pullRequestId);
|
|
110
142
|
return {
|
|
111
143
|
content: [{ type: "text", text: JSON.stringify(updatedPullRequest, null, 2) }],
|
|
112
144
|
};
|
|
@@ -375,10 +407,11 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
|
|
|
375
407
|
server.tool(REPO_TOOLS.get_pull_request_by_id, "Get a pull request by its ID.", {
|
|
376
408
|
repositoryId: z.string().describe("The ID of the repository where the pull request is located."),
|
|
377
409
|
pullRequestId: z.number().describe("The ID of the pull request to retrieve."),
|
|
378
|
-
|
|
410
|
+
includeWorkItemRefs: z.boolean().optional().default(false).describe("Whether to reference work items associated with the pull request."),
|
|
411
|
+
}, async ({ repositoryId, pullRequestId, includeWorkItemRefs }) => {
|
|
379
412
|
const connection = await connectionProvider();
|
|
380
413
|
const gitApi = await connection.getGitApi();
|
|
381
|
-
const pullRequest = await gitApi.getPullRequest(repositoryId, pullRequestId);
|
|
414
|
+
const pullRequest = await gitApi.getPullRequest(repositoryId, pullRequestId, undefined, undefined, undefined, undefined, undefined, includeWorkItemRefs);
|
|
382
415
|
return {
|
|
383
416
|
content: [{ type: "text", text: JSON.stringify(pullRequest, null, 2) }],
|
|
384
417
|
};
|
|
@@ -416,6 +449,11 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
|
|
|
416
449
|
content: z.string().describe("The content of the comment to be added."),
|
|
417
450
|
project: z.string().optional().describe("Project ID or project name (optional)"),
|
|
418
451
|
filePath: z.string().optional().describe("The path of the file where the comment thread will be created. (optional)"),
|
|
452
|
+
status: z
|
|
453
|
+
.enum(getEnumKeys(CommentThreadStatus))
|
|
454
|
+
.optional()
|
|
455
|
+
.default(CommentThreadStatus[CommentThreadStatus.Active])
|
|
456
|
+
.describe("The status of the comment thread. Defaults to 'Active'."),
|
|
419
457
|
rightFileStartLine: z.number().optional().describe("Position of first character of the thread's span in right file. The line number of a thread's position. Starts at 1. (optional)"),
|
|
420
458
|
rightFileStartOffset: z
|
|
421
459
|
.number()
|
|
@@ -429,7 +467,7 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
|
|
|
429
467
|
.number()
|
|
430
468
|
.optional()
|
|
431
469
|
.describe("Position of last character of the thread's span in right file. The character offset of a thread's position inside of a line. Must only be set if rightFileEndLine is also specified. (optional)"),
|
|
432
|
-
}, async ({ repositoryId, pullRequestId, content, project, filePath, rightFileStartLine, rightFileStartOffset, rightFileEndLine, rightFileEndOffset }) => {
|
|
470
|
+
}, async ({ repositoryId, pullRequestId, content, project, filePath, status, rightFileStartLine, rightFileStartOffset, rightFileEndLine, rightFileEndOffset }) => {
|
|
433
471
|
const connection = await connectionProvider();
|
|
434
472
|
const gitApi = await connection.getGitApi();
|
|
435
473
|
const threadContext = { filePath: filePath };
|
|
@@ -460,7 +498,7 @@ function configureRepoTools(server, tokenProvider, connectionProvider) {
|
|
|
460
498
|
threadContext.rightFileEnd.offset = rightFileEndOffset;
|
|
461
499
|
}
|
|
462
500
|
}
|
|
463
|
-
const thread = await gitApi.createThread({ comments: [{ content: content }], threadContext: threadContext }, repositoryId, pullRequestId, project);
|
|
501
|
+
const thread = await gitApi.createThread({ comments: [{ content: content }], threadContext: threadContext, status: CommentThreadStatus[status] }, repositoryId, pullRequestId, project);
|
|
464
502
|
return {
|
|
465
503
|
content: [{ type: "text", text: JSON.stringify(thread, null, 2) }],
|
|
466
504
|
};
|