@azure-devops/mcp 0.1.0 โ†’ 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  Easily install the Azure DevOps MCP Server for VS Code or VS Code Insiders:
4
4
 
5
- [![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-Install_AzureDevops_MCP_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=ado&config=%7B%20%22type%22%3A%20%22stdio%22%2C%20%22command%22%3A%20%22npx%22%2C%20%22args%22%3A%20%5B%22-y%22%2C%20%22%40ado%2Fazure-devops-mcp%22%2C%20%22%24%7Binput%3Aado_org%7D%22%5D%7D&inputs=%5B%7B%22id%22%3A%20%22ado_org%22%2C%20%22type%22%3A%20%22promptString%22%2C%20%22description%22%3A%20%22Azure%20DevOps%20organization%20name%20%20%28e.g.%20%27contoso%27%29%22%7D%5D)
6
- [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_AzureDevops_MCP_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=ado&quality=insiders&config=%7B%20%22type%22%3A%20%22stdio%22%2C%20%22command%22%3A%20%22npx%22%2C%20%22args%22%3A%20%5B%22-y%22%2C%20%22%40ado%2Fazure-devops-mcp%22%2C%20%22%24%7Binput%3Aado_org%7D%22%5D%7D&inputs=%5B%7B%22id%22%3A%20%22ado_org%22%2C%20%22type%22%3A%20%22promptString%22%2C%20%22description%22%3A%20%22Azure%20DevOps%20organization%20name%20%20%28e.g.%20%27contoso%27%29%22%7D%5D)
5
+ [![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-Install_AzureDevops_MCP_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=ado&config=%7B%20%22type%22%3A%20%22stdio%22%2C%20%22command%22%3A%20%22npx%22%2C%20%22args%22%3A%20%5B%22-y%22%2C%20%22%40azure-devops%2Fmcp%22%2C%20%22%24%7Binput%3Aado_org%7D%22%5D%7D&inputs=%5B%7B%22id%22%3A%20%22ado_org%22%2C%20%22type%22%3A%20%22promptString%22%2C%20%22description%22%3A%20%22Azure%20DevOps%20organization%20name%20%20%28e.g.%20%27contoso%27%29%22%7D%5D)
6
+ [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_AzureDevops_MCP_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=ado&quality=insiders&config=%7B%20%22type%22%3A%20%22stdio%22%2C%20%22command%22%3A%20%22npx%22%2C%20%22args%22%3A%20%5B%22-y%22%2C%20%22%40azure-devops%2Fmcp%22%2C%20%22%24%7Binput%3Aado_org%7D%22%5D%7D&inputs=%5B%7B%22id%22%3A%20%22ado_org%22%2C%20%22type%22%3A%20%22promptString%22%2C%20%22description%22%3A%20%22Azure%20DevOps%20organization%20name%20%20%28e.g.%20%27contoso%27%29%22%7D%5D)
7
7
 
8
8
  This TypeScript project defines the **local** MCP server for Azure DevOps, enabling you to perform a wide range of Azure DevOps tasks directly from your code editor.
9
9
 
@@ -17,7 +17,8 @@ This TypeScript project defines the **local** MCP server for Azure DevOps, enabl
17
17
  4. [๐Ÿ”ฆ Usage](#-usage)
18
18
  5. [๐Ÿ“ Troubleshooting](#-troubleshooting)
19
19
  6. [๐ŸŽฉ Samples & best practices](#-samples--best-practices)
20
- 7. [๐Ÿ“Œ Contributing](#๏ธ-contributing)
20
+ 7. [๐Ÿ™‹โ€โ™€๏ธ Frequently asked questions](#๏ธ-frequently-asked-questions)
21
+ 8. [๐Ÿ“Œ Contributing](#๏ธ-contributing)
21
22
 
22
23
  ## ๐Ÿ“บ Overview
23
24
 
@@ -129,7 +130,8 @@ For the best experience, use Visual Studio Code and GitHub Copilot.
129
130
 
130
131
  1. Install [VS Code](https://code.visualstudio.com/download) or [VS Code Insiders](https://code.visualstudio.com/insiders)
131
132
  2. Install [Node.js](https://nodejs.org/en/download) 20+
132
- 3. Open VS Code in an empty folder
133
+ 3. Install [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest)
134
+ 4. Open VS Code in an empty folder
133
135
 
134
136
  ### Azure Login
135
137
 
@@ -143,11 +145,51 @@ az login
143
145
 
144
146
  #### โœจ One-Click install
145
147
 
146
- [![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-Install_AzureDevops_MCP_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=ado&config=%7B%20%22type%22%3A%20%22stdio%22%2C%20%22command%22%3A%20%22npx%22%2C%20%22args%22%3A%20%5B%22-y%22%2C%20%22%40ado%2Fazure-devops-mcp%22%2C%20%22%24%7Binput%3Aado_org%7D%22%5D%7D&inputs=%5B%7B%22id%22%3A%20%22ado_org%22%2C%20%22type%22%3A%20%22promptString%22%2C%20%22description%22%3A%20%22Azure%20DevOps%20organization%20name%20%20%28e.g.%20%27contoso%27%29%22%7D%5D)
147
- [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_AzureDevops_MCP_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=ado&quality=insiders&config=%7B%20%22type%22%3A%20%22stdio%22%2C%20%22command%22%3A%20%22npx%22%2C%20%22args%22%3A%20%5B%22-y%22%2C%20%22%40ado%2Fazure-devops-mcp%22%2C%20%22%24%7Binput%3Aado_org%7D%22%5D%7D&inputs=%5B%7B%22id%22%3A%20%22ado_org%22%2C%20%22type%22%3A%20%22promptString%22%2C%20%22description%22%3A%20%22Azure%20DevOps%20organization%20name%20%20%28e.g.%20%27contoso%27%29%22%7D%5D)
148
+ [![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-Install_AzureDevops_MCP_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=ado&config=%7B%20%22type%22%3A%20%22stdio%22%2C%20%22command%22%3A%20%22npx%22%2C%20%22args%22%3A%20%5B%22-y%22%2C%20%22%40azure-devops%2Fmcp%22%2C%20%22%24%7Binput%3Aado_org%7D%22%5D%7D&inputs=%5B%7B%22id%22%3A%20%22ado_org%22%2C%20%22type%22%3A%20%22promptString%22%2C%20%22description%22%3A%20%22Azure%20DevOps%20organization%20name%20%20%28e.g.%20%27contoso%27%29%22%7D%5D)
149
+ [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_AzureDevops_MCP_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=ado&quality=insiders&config=%7B%20%22type%22%3A%20%22stdio%22%2C%20%22command%22%3A%20%22npx%22%2C%20%22args%22%3A%20%5B%22-y%22%2C%20%22%40azure-devops%2Fmcp%22%2C%20%22%24%7Binput%3Aado_org%7D%22%5D%7D&inputs=%5B%7B%22id%22%3A%20%22ado_org%22%2C%20%22type%22%3A%20%22promptString%22%2C%20%22description%22%3A%20%22Azure%20DevOps%20organization%20name%20%20%28e.g.%20%27contoso%27%29%22%7D%5D)
148
150
 
149
151
  After installation, select GitHub Copilot Agent Mode and refresh the tools list. Learn more about Agent Mode in the [VS Code Documentation](https://code.visualstudio.com/docs/copilot/chat/chat-agent-mode).
150
152
 
153
+ #### ๐Ÿงจ Installing from public feed (recommended)
154
+
155
+ This installation method is the easiest for all users using Visual Studio Code.
156
+
157
+ ๐ŸŽฅ [Watch this quick start video to get up and running in under two minutes!](https://youtu.be/EUmFM6qXoYk)
158
+
159
+ ##### Steps
160
+
161
+ 1. In your project, add a `.vscode\mcp.json` file and add the following:
162
+
163
+ ``` json
164
+ {
165
+ "inputs": [
166
+ {
167
+ "id": "ado_org",
168
+ "type": "promptString",
169
+ "description": "Azure DevOps organization name (e.g. 'contoso')"
170
+ }
171
+ ],
172
+ "servers": {
173
+ "ado": {
174
+ "type": "stdio",
175
+ "command": "npx",
176
+ "args": [
177
+ "-y",
178
+ "@azure-devops/mcp",
179
+ "${input:ado_org}"
180
+ ]
181
+ }
182
+ }
183
+ }
184
+ ```
185
+ 2. Save the file and click 'Start`
186
+
187
+ <img src="./docs/media/start-mcp-server.gif" alt="start mcp server" width="250"/>
188
+
189
+ 3. In chat, switch to [Agent Mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode).
190
+ 4. Click "Select Tools" and choose the available tools.
191
+ 5. We strongly recommend that you create a `.github\copilot-instructions.md` in your project and copy and paste the contents from this [copilot-instructions.md](./.github/copilot-instructions.md) file. This will help your experience when it comes to using the Azure DevOps MCP Server in GitHub Copilot Chat.
192
+
151
193
  #### ๐Ÿ› ๏ธ Installing from source (dev mode)
152
194
 
153
195
  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.
@@ -182,16 +224,16 @@ This installation method is recommended for advanced users and contributors who
182
224
  }
183
225
  ```
184
226
 
185
- 4. Start the Azure DevOps MCP Server:
227
+ 4. Start the Azure DevOps MCP Server
228
+
229
+ <img src="./docs/media/start-mcp-server.gif" alt="start mcp server" width="250"/>
230
+
186
231
  5. In chat, switch to [Agent Mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode).
187
- 6. Click "Select Tools" and choose the available `ado_` tools.
232
+ 6. Click "Select Tools" and choose the available tools.
233
+ 7. We strongly recommend that you create a `.github\copilot-instructions.md` in your project and copy and paste the contents from this [copilot-instructions.md](./.github/copilot-instructions.md) file. This will help your experience when it comes to using the Azure DevOps MCP Server in GitHub Copilot Chat.
188
234
 
189
235
  See [How To](./docs/HOWTO.md) section for details
190
236
 
191
- #### Placeholder for public feed
192
-
193
- Update for Public Feed
194
-
195
237
  ## ๐Ÿ”ฆ Usage
196
238
 
197
239
  ### Visual Studio Code + GitHub Copilot
@@ -220,6 +262,10 @@ See the [Troubleshooting guide](./docs/TROUBLESHOOTING.md) for help with common
220
262
 
221
263
  Find sample prompts and best practices in our [How-to Guide](./docs/HOWTO.md).
222
264
 
265
+ ## ๐Ÿ™‹โ€โ™€๏ธ Frequently asked questions
266
+
267
+ For answers to common questions about the Azure DevOps MCP Server, see the [Frequently Asked Questions](./docs/FAQ.md).
268
+
223
269
  ## ๐Ÿ“Œ Contributing
224
270
 
225
271
  We welcome contributions! During preview, please file Issues for bugs, enhancements, or documentation improvements.
package/dist/http.js ADDED
@@ -0,0 +1,52 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import express from "express";
4
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5
+ import { serverBuildAndConnect } from "./server.js";
6
+ import { packageVersion } from "./version.js";
7
+ const app = express();
8
+ app.use(express.json());
9
+ app.post('/mcp/:orgName', async (req, res) => {
10
+ // In stateless mode, create a new instance of transport and server for each request
11
+ // to ensure complete isolation. A single instance would cause request ID collisions
12
+ // when multiple clients connect concurrently.
13
+ try {
14
+ const transport = new StreamableHTTPServerTransport({
15
+ sessionIdGenerator: undefined,
16
+ });
17
+ const server = await serverBuildAndConnect(req.params.orgName, transport);
18
+ res.on('close', () => {
19
+ transport.close();
20
+ server.close();
21
+ });
22
+ await transport.handleRequest(req, res, req.body);
23
+ }
24
+ catch (error) {
25
+ console.error('Error handling MCP request:', error);
26
+ if (!res.headersSent) {
27
+ res.status(500).json({
28
+ jsonrpc: '2.0',
29
+ error: {
30
+ code: -32603,
31
+ message: 'Internal server error',
32
+ },
33
+ id: null,
34
+ });
35
+ }
36
+ }
37
+ });
38
+ app.get('/mcp/:orgName', async (req, res) => {
39
+ console.log('Received GET MCP request');
40
+ res.writeHead(405).end(JSON.stringify({
41
+ jsonrpc: "2.0",
42
+ error: {
43
+ code: -32000,
44
+ message: "Method not allowed."
45
+ },
46
+ id: null
47
+ }));
48
+ });
49
+ const PORT = 3000;
50
+ app.listen(PORT, () => {
51
+ console.log(`Azure DevOps MCP Server with http transport listening on port ${PORT}. Version: ${packageVersion}`);
52
+ });
package/dist/index.js CHANGED
@@ -33,17 +33,15 @@ async function getAzureDevOpsClient() {
33
33
  return connection;
34
34
  }
35
35
  async function main() {
36
- console.error("Starting Azure DevOps MCP Server...");
37
36
  const server = new McpServer({
38
37
  name: "Azure DevOps MCP Server",
39
- version: "1.0.0",
38
+ version: packageVersion,
40
39
  });
41
40
  configurePrompts(server);
42
41
  configureAllTools(server, getAzureDevOpsToken, getAzureDevOpsClient);
43
42
  const transport = new StdioServerTransport();
44
- console.error("Connecting server to transport...");
43
+ console.log("Azure DevOps MCP Server version : " + packageVersion);
45
44
  await server.connect(transport);
46
- console.error("Azure DevOps MCP Server running on stdio");
47
45
  }
48
46
  main().catch((error) => {
49
47
  console.error("Fatal error in main():", error);
package/dist/server.js ADDED
@@ -0,0 +1,36 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import * as azdev from "azure-devops-node-api";
5
+ import { DefaultAzureCredential } from "@azure/identity";
6
+ import { configurePrompts } from "./prompts.js";
7
+ import { configureAllTools } from "./tools.js";
8
+ import { userAgent } from "./utils.js";
9
+ import { packageVersion } from "./version.js";
10
+ async function getAzureDevOpsToken() {
11
+ process.env.AZURE_TOKEN_CREDENTIALS = "dev";
12
+ const credential = new DefaultAzureCredential(); // CodeQL [SM05138] resolved by explicitly setting AZURE_TOKEN_CREDENTIALS
13
+ const token = await credential.getToken("499b84ac-1321-427f-aa17-267ca6975798/.default");
14
+ return token;
15
+ }
16
+ async function getAzureDevOpsClient(orgUrl) {
17
+ const token = await getAzureDevOpsToken();
18
+ const authHandler = azdev.getBearerHandler(token.token);
19
+ const connection = new azdev.WebApi(orgUrl, authHandler, undefined, {
20
+ productName: "AzureDevOps.MCP",
21
+ productVersion: packageVersion,
22
+ userAgent: userAgent
23
+ });
24
+ return connection;
25
+ }
26
+ export async function serverBuildAndConnect(orgName, transport) {
27
+ const server = new McpServer({
28
+ name: "Azure DevOps MCP Server",
29
+ version: packageVersion,
30
+ });
31
+ const orgUrl = "https://dev.azure.com/" + orgName;
32
+ configurePrompts(server);
33
+ configureAllTools(server, () => orgName, getAzureDevOpsToken, () => getAzureDevOpsClient(orgUrl));
34
+ await server.connect(transport);
35
+ return server;
36
+ }
@@ -12,12 +12,24 @@ function configureCoreTools(server, tokenProvider, connectionProvider) {
12
12
  top: z.number().optional().describe("The maximum number of teams to return. Defaults to 100."),
13
13
  skip: z.number().optional().describe("The number of teams to skip for pagination. Defaults to 0."),
14
14
  }, async ({ project, mine, top, skip }) => {
15
- const connection = await connectionProvider();
16
- const coreApi = await connection.getCoreApi();
17
- const teams = await coreApi.getTeams(project, mine, top, skip, false);
18
- return {
19
- content: [{ type: "text", text: JSON.stringify(teams, null, 2) }],
20
- };
15
+ try {
16
+ const connection = await connectionProvider();
17
+ const coreApi = await connection.getCoreApi();
18
+ const teams = await coreApi.getTeams(project, mine, top, skip, false);
19
+ if (!teams) {
20
+ return { content: [{ type: "text", text: "No teams found" }], isError: true };
21
+ }
22
+ return {
23
+ content: [{ type: "text", text: JSON.stringify(teams, null, 2) }],
24
+ };
25
+ }
26
+ catch (error) {
27
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
28
+ return {
29
+ content: [{ type: "text", text: `Error fetching project teams: ${errorMessage}` }],
30
+ isError: true
31
+ };
32
+ }
21
33
  });
22
34
  server.tool(CORE_TOOLS.list_projects, "Retrieve a list of projects in your Azure DevOps organization.", {
23
35
  stateFilter: z.enum(["all", "wellFormed", "createPending", "deleted"]).default("wellFormed").describe("Filter projects by their state. Defaults to 'wellFormed'."),
@@ -25,12 +37,24 @@ function configureCoreTools(server, tokenProvider, connectionProvider) {
25
37
  skip: z.number().optional().describe("The number of projects to skip for pagination. Defaults to 0."),
26
38
  continuationToken: z.number().optional().describe("Continuation token for pagination. Used to fetch the next set of results if available."),
27
39
  }, async ({ stateFilter, top, skip, continuationToken }) => {
28
- const connection = await connectionProvider();
29
- const coreApi = await connection.getCoreApi();
30
- const projects = await coreApi.getProjects(stateFilter, top, skip, continuationToken, false);
31
- return {
32
- content: [{ type: "text", text: JSON.stringify(projects, null, 2) }],
33
- };
40
+ try {
41
+ const connection = await connectionProvider();
42
+ const coreApi = await connection.getCoreApi();
43
+ const projects = await coreApi.getProjects(stateFilter, top, skip, continuationToken, false);
44
+ if (!projects) {
45
+ return { content: [{ type: "text", text: "No projects found" }], isError: true };
46
+ }
47
+ return {
48
+ content: [{ type: "text", text: JSON.stringify(projects, null, 2) }],
49
+ };
50
+ }
51
+ catch (error) {
52
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
53
+ return {
54
+ content: [{ type: "text", text: `Error fetching projects: ${errorMessage}` }],
55
+ isError: true
56
+ };
57
+ }
34
58
  });
35
59
  }
36
60
  export { CORE_TOOLS, configureCoreTools };
@@ -13,12 +13,24 @@ function configureWorkTools(server, tokenProvider, connectionProvider) {
13
13
  team: z.string().describe("The name or ID of the Azure DevOps team."),
14
14
  timeframe: z.enum(["current"]).optional().describe("The timeframe for which to retrieve iterations. Currently, only 'current' is supported."),
15
15
  }, async ({ project, team, timeframe }) => {
16
- const connection = await connectionProvider();
17
- const workApi = await connection.getWorkApi();
18
- const iterations = await workApi.getTeamIterations({ project, team }, timeframe);
19
- return {
20
- content: [{ type: "text", text: JSON.stringify(iterations, null, 2) }],
21
- };
16
+ try {
17
+ const connection = await connectionProvider();
18
+ const workApi = await connection.getWorkApi();
19
+ const iterations = await workApi.getTeamIterations({ project, team }, timeframe);
20
+ if (!iterations) {
21
+ return { content: [{ type: "text", text: "No iterations found" }], isError: true };
22
+ }
23
+ return {
24
+ content: [{ type: "text", text: JSON.stringify(iterations, null, 2) }],
25
+ };
26
+ }
27
+ catch (error) {
28
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
29
+ return {
30
+ content: [{ type: "text", text: `Error fetching team iterations: ${errorMessage}` }],
31
+ isError: true
32
+ };
33
+ }
22
34
  });
23
35
  server.tool(WORK_TOOLS.create_iterations, "Create new iterations in a specified Azure DevOps project.", {
24
36
  project: z.string().describe("The name or ID of the Azure DevOps project."),
@@ -28,23 +40,37 @@ function configureWorkTools(server, tokenProvider, connectionProvider) {
28
40
  finishDate: z.string().optional().describe("The finish date of the iteration in ISO format (e.g., '2023-01-31T23:59:59Z'). Optional.")
29
41
  })).describe("An array of iterations to create. Each iteration must have a name and can optionally have start and finish dates in ISO format.")
30
42
  }, async ({ project, iterations }) => {
31
- const connection = await connectionProvider();
32
- const workItemTrackingApi = await connection.getWorkItemTrackingApi();
33
- const results = [];
34
- for (const { iterationName, startDate, finishDate } of iterations) {
35
- // Step 1: Create the iteration
36
- const iteration = await workItemTrackingApi.createOrUpdateClassificationNode({
37
- name: iterationName,
38
- attributes: {
39
- startDate: startDate ? new Date(startDate) : undefined,
40
- finishDate: finishDate ? new Date(finishDate) : undefined,
41
- },
42
- }, project, TreeStructureGroup.Iterations);
43
- results.push(iteration);
43
+ try {
44
+ const connection = await connectionProvider();
45
+ const workItemTrackingApi = await connection.getWorkItemTrackingApi();
46
+ const results = [];
47
+ for (const { iterationName, startDate, finishDate } of iterations) {
48
+ // Step 1: Create the iteration
49
+ const iteration = await workItemTrackingApi.createOrUpdateClassificationNode({
50
+ name: iterationName,
51
+ attributes: {
52
+ startDate: startDate ? new Date(startDate) : undefined,
53
+ finishDate: finishDate ? new Date(finishDate) : undefined,
54
+ },
55
+ }, project, TreeStructureGroup.Iterations);
56
+ if (iteration) {
57
+ results.push(iteration);
58
+ }
59
+ }
60
+ if (results.length === 0) {
61
+ return { content: [{ type: "text", text: "No iterations were created" }], isError: true };
62
+ }
63
+ return {
64
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
65
+ };
66
+ }
67
+ catch (error) {
68
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
69
+ return {
70
+ content: [{ type: "text", text: `Error creating iterations: ${errorMessage}` }],
71
+ isError: true
72
+ };
44
73
  }
45
- return {
46
- content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
47
- };
48
74
  });
49
75
  server.tool(WORK_TOOLS.assign_iterations, "Assign existing iterations to a specific team in a project.", {
50
76
  project: z.string().describe("The name or ID of the Azure DevOps project."),
@@ -54,17 +80,31 @@ function configureWorkTools(server, tokenProvider, connectionProvider) {
54
80
  path: z.string().describe("The path of the iteration to assign, e.g., 'Project/Iteration'.")
55
81
  })).describe("An array of iterations to assign. Each iteration must have an identifier and a path."),
56
82
  }, async ({ project, team, iterations }) => {
57
- const connection = await connectionProvider();
58
- const workApi = await connection.getWorkApi();
59
- const teamContext = { project, team };
60
- const results = [];
61
- for (const { identifier, path } of iterations) {
62
- const assignment = await workApi.postTeamIteration({ path: path, id: identifier }, teamContext);
63
- results.push(assignment);
83
+ try {
84
+ const connection = await connectionProvider();
85
+ const workApi = await connection.getWorkApi();
86
+ const teamContext = { project, team };
87
+ const results = [];
88
+ for (const { identifier, path } of iterations) {
89
+ const assignment = await workApi.postTeamIteration({ path: path, id: identifier }, teamContext);
90
+ if (assignment) {
91
+ results.push(assignment);
92
+ }
93
+ }
94
+ if (results.length === 0) {
95
+ return { content: [{ type: "text", text: "No iterations were assigned to the team" }], isError: true };
96
+ }
97
+ return {
98
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
99
+ };
100
+ }
101
+ catch (error) {
102
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
103
+ return {
104
+ content: [{ type: "text", text: `Error assigning iterations: ${errorMessage}` }],
105
+ isError: true
106
+ };
64
107
  }
65
- return {
66
- content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
67
- };
68
108
  });
69
109
  }
70
110
  export { WORK_TOOLS, configureWorkTools };
@@ -194,14 +194,14 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider) {
194
194
  };
195
195
  });
196
196
  server.tool(WORKITEM_TOOLS.link_work_item_to_pull_request, "Link a single work item to an existing pull request.", {
197
- project: z.string().describe,
197
+ project: z.string().describe("The name or ID of the Azure DevOps project."),
198
198
  repositoryId: z.string().describe("The ID of the repository containing the pull request. Do not use the repository name here, use the ID instead."),
199
199
  pullRequestId: z.number().describe("The ID of the pull request to link to."),
200
200
  workItemId: z.number().describe("The ID of the work item to link to the pull request."),
201
201
  }, async ({ project, repositoryId, pullRequestId, workItemId }) => {
202
- const connection = await connectionProvider();
203
- const workItemTrackingApi = await connection.getWorkItemTrackingApi();
204
202
  try {
203
+ const connection = await connectionProvider();
204
+ const workItemTrackingApi = await connection.getWorkItemTrackingApi();
205
205
  // Create artifact link relation using vstfs format
206
206
  // Format: vstfs:///Git/PullRequestId/{project}/{repositoryId}/{pullRequestId}
207
207
  const artifactPathValue = `${project}/${repositoryId}/${pullRequestId}`;
@@ -221,7 +221,10 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider) {
221
221
  },
222
222
  ];
223
223
  // Use the WorkItem API to update the work item with the new relation
224
- await workItemTrackingApi.updateWorkItem({}, patchDocument, workItemId, project);
224
+ const workItem = await workItemTrackingApi.updateWorkItem({}, patchDocument, workItemId, project);
225
+ if (!workItem) {
226
+ return { content: [{ type: "text", text: "Work item update failed" }], isError: true };
227
+ }
225
228
  return {
226
229
  content: [
227
230
  {
@@ -236,18 +239,10 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider) {
236
239
  };
237
240
  }
238
241
  catch (error) {
239
- console.error(`Error linking work item ${workItemId} to PR ${pullRequestId}:`, error);
242
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
240
243
  return {
241
- content: [
242
- {
243
- type: "text",
244
- text: JSON.stringify({
245
- workItemId,
246
- pullRequestId,
247
- success: false,
248
- }, null, 2),
249
- },
250
- ],
244
+ content: [{ type: "text", text: `Error linking work item to pull request: ${errorMessage}` }],
245
+ isError: true
251
246
  };
252
247
  }
253
248
  });
@@ -299,17 +294,29 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider) {
299
294
  workItemType: z.string().describe("The type of work item to create, e.g., 'Task', 'Bug', etc."),
300
295
  fields: z.record(z.string(), z.string()).describe("A record of field names and values to set on the new work item. Each key is a field name, and each value is the corresponding value to set for that field."),
301
296
  }, async ({ project, workItemType, fields }) => {
302
- const connection = await connectionProvider();
303
- const workItemApi = await connection.getWorkItemTrackingApi();
304
- const document = Object.entries(fields).map(([key, value]) => ({
305
- op: "add",
306
- path: `/fields/${key}`,
307
- value,
308
- }));
309
- const newWorkItem = await workItemApi.createWorkItem(null, document, project, workItemType);
310
- return {
311
- content: [{ type: "text", text: JSON.stringify(newWorkItem, null, 2) }],
312
- };
297
+ try {
298
+ const connection = await connectionProvider();
299
+ const workItemApi = await connection.getWorkItemTrackingApi();
300
+ const document = Object.entries(fields).map(([key, value]) => ({
301
+ op: "add",
302
+ path: `/fields/${key}`,
303
+ value,
304
+ }));
305
+ const newWorkItem = await workItemApi.createWorkItem(null, document, project, workItemType);
306
+ if (!newWorkItem) {
307
+ return { content: [{ type: "text", text: "Work item was not created" }], isError: true };
308
+ }
309
+ return {
310
+ content: [{ type: "text", text: JSON.stringify(newWorkItem, null, 2) }],
311
+ };
312
+ }
313
+ catch (error) {
314
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
315
+ return {
316
+ content: [{ type: "text", text: `Error creating work item: ${errorMessage}` }],
317
+ isError: true
318
+ };
319
+ }
313
320
  });
314
321
  server.tool(WORKITEM_TOOLS.get_query, "Get a query by its ID or path.", {
315
322
  project: z.string().describe("The name or ID of the Azure DevOps project."),
package/dist/tools.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
3
  import { configureCoreTools } from "./tools/core.js";
4
+ import { configureWorkTools } from "./tools/work.js";
4
5
  import { configureBuildTools } from "./tools/builds.js";
5
6
  import { configureRepoTools } from "./tools/repos.js";
6
7
  import { configureWorkItemTools } from "./tools/workitems.js";
@@ -10,6 +11,7 @@ import { configureTestPlanTools } from "./tools/testplans.js";
10
11
  import { configureSearchTools } from "./tools/search.js";
11
12
  function configureAllTools(server, tokenProvider, connectionProvider) {
12
13
  configureCoreTools(server, tokenProvider, connectionProvider);
14
+ configureWorkTools(server, tokenProvider, connectionProvider);
13
15
  configureBuildTools(server, tokenProvider, connectionProvider);
14
16
  configureRepoTools(server, tokenProvider, connectionProvider);
15
17
  configureWorkItemTools(server, tokenProvider, connectionProvider);
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const packageVersion = "0.1.0";
1
+ export const packageVersion = "1.0.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-devops/mcp",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "description": "MCP server for interacting with Azure DevOps",
5
5
  "license": "MIT",
6
6
  "author": "Microsoft Corporation",
@@ -31,23 +31,22 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@azure/identity": "^4.10.0",
34
- "@modelcontextprotocol/sdk": "1.12.1",
34
+ "@modelcontextprotocol/sdk": "1.13.0",
35
35
  "azure-devops-extension-api": "^4.252.0",
36
36
  "azure-devops-extension-sdk": "^4.0.2",
37
37
  "azure-devops-node-api": "^15.1.0",
38
- "save-dev": "^0.0.1-security",
39
- "zod": "^3.25.55",
38
+ "zod": "^3.25.63",
40
39
  "zod-to-json-schema": "^3.24.5"
41
40
  },
42
41
  "devDependencies": {
43
42
  "@modelcontextprotocol/inspector": "^0.14.0",
44
43
  "@types/node": "^22",
45
- "@types/jest": "^29.5.14",
44
+ "@types/jest": "^30.0.0",
46
45
  "eslint-plugin-header": "^3.1.1",
47
- "jest": "^29.7.0",
46
+ "jest": "^30.0.2",
48
47
  "jest-extended": "^6.0.0",
49
48
  "shx": "^0.4.0",
50
- "ts-jest": "^29.3.4",
49
+ "ts-jest": "^29.4.0",
51
50
  "tsconfig-paths": "^4.2.0",
52
51
  "typescript": "^5.8.3",
53
52
  "typescript-eslint": "^8.32.1"