@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 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. [๐Ÿ”ฆ Usage](#-usage)
19
- 6. [๐Ÿ“ Troubleshooting](#-troubleshooting)
20
- 7. [๐ŸŽฉ Samples & Best Practices](#-samples--best-practices)
21
- 8. [๐Ÿ™‹โ€โ™€๏ธ Frequently Asked Questions](#๏ธ-frequently-asked-questions)
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
- Clone the repository, install dependencies, and add it to your MCP client configuration.
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
- 1. In your project, add a `.vscode\mcp.json` file with the following content:
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
- 2. Save the file.
302
- 3. Add your organization name by clicking on the `input` option.
199
+ Save the file, then click 'Start'.
303
200
 
304
- <img src="./docs/media/start-mcp-server-from-vs.png" alt="start mcp server from visual studio 2022" width="250"/>
201
+ <img src="./docs/media/start-mcp-server.gif" alt="start mcp server" width="250"/>
305
202
 
306
- 4. Open Copilot chat and switch to [Agent Mode](https://learn.microsoft.com/en-us/visualstudio/ide/copilot-agent-mode?view=vs-2022).
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
- <img src="./docs/media/set-tools-from-vs.png" alt="set tools to use in visual studio 2022" width="250"/>
205
+ Click "Select Tools" and choose the available tools.
310
206
 
311
- 6. 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
+ <img src="./docs/media/configure-mcp-server-tools.gif" alt="configure mcp server tools" width="300"/>
312
208
 
313
- ## ๐Ÿ”ฆ Usage
209
+ Open GitHub Copilot Chat and try a prompt like `List ADO projects`.
314
210
 
315
- ### Visual Studio Code + GitHub Copilot
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
- 1. Open GitHub Copilot in VS Code and switch to Agent mode.
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
- ## ๐ŸŽฉ Samples & Best Practices
219
+ ## ๐ŸŽฉ Examples & Best Practices
220
+
221
+ Explore example prompts in our [Examples documentation](./docs/EXAMPLES.md).
338
222
 
339
- Find sample prompts and best practices in our [How-to Guide](./docs/HOWTO.md).
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 {};
@@ -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
- update_pull_request_status: "repo_update_pull_request_status",
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
- }, async ({ repositoryId, sourceRefName, targetRefName, title, description, isDraft, workItems }) => {
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.update_pull_request_status, "Update status of an existing pull request to active or abandoned.", {
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 be published."),
104
- status: z.enum(["Active", "Abandoned"]).describe("The new status of the pull request. Can be 'Active' or 'Abandoned'."),
105
- }, async ({ repositoryId, pullRequestId, status }) => {
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
- const statusValue = status === "Active" ? PullRequestStatus.Active.valueOf() : PullRequestStatus.Abandoned.valueOf();
109
- const updatedPullRequest = await gitApi.updatePullRequest({ status: statusValue }, repositoryId, pullRequestId);
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
- }, async ({ repositoryId, pullRequestId }) => {
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
  };