@azure-devops/mcp 1.3.1-nightly.20250824 → 2.0.0-nightly.20250825
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 +0 -11
- package/dist/index.js +14 -3
- package/dist/prompts.js +1 -1
- package/dist/shared/domains.js +122 -0
- package/dist/tools/builds.js +0 -15
- package/dist/tools.js +21 -15
- package/dist/version.js +1 -1
- package/package.json +1 -1
- /package/dist/tools/{advsec.js → advanced-security.js} +0 -0
- /package/dist/tools/{repos.js → repositories.js} +0 -0
- /package/dist/tools/{testplans.js → test-plans.js} +0 -0
- /package/dist/tools/{workitems.js → work-items.js} +0 -0
package/README.md
CHANGED
|
@@ -42,16 +42,6 @@ The Azure DevOps MCP Server brings Azure DevOps context to your agents. Try prom
|
|
|
42
42
|
|
|
43
43
|
The Azure DevOps MCP Server is built from tools that are concise, simple, focused, and easy to use—each designed for a specific scenario. We intentionally avoid complex tools that try to do too much. The goal is to provide a thin abstraction layer over the REST APIs, making data access straightforward and letting the language model handle complex reasoning.
|
|
44
44
|
|
|
45
|
-
## ✨ Recent Enhancements
|
|
46
|
-
|
|
47
|
-
### 📖 **Enhanced Wiki Support**
|
|
48
|
-
|
|
49
|
-
- **Full Content Management**: Create and update wiki pages with complete content using the native Azure DevOps REST API
|
|
50
|
-
- **Automatic ETag Handling**: Safe updates with built-in conflict resolution for concurrent edits
|
|
51
|
-
- **Immediate Visibility**: Pages appear instantly in the Azure DevOps wiki interface
|
|
52
|
-
- **Hierarchical Structure**: Support for organized page structures within existing folder hierarchies
|
|
53
|
-
- **Robust Error Handling**: Comprehensive error management for various HTTP status codes and edge cases
|
|
54
|
-
|
|
55
45
|
## ⚙️ Supported Tools
|
|
56
46
|
|
|
57
47
|
Interact with these Azure DevOps services:
|
|
@@ -120,7 +110,6 @@ Interact with these Azure DevOps services:
|
|
|
120
110
|
- **build_get_log**: Retrieve the logs for a specific build.
|
|
121
111
|
- **build_get_log_by_id**: Get a specific build log by log ID.
|
|
122
112
|
- **build_get_changes**: Get the changes associated with a specific build.
|
|
123
|
-
- **build_get_timeline**: Retrieve the timeline for a specific build, showing detailed information about steps and tasks.
|
|
124
113
|
- **build_run_build**: Trigger a new build for a specified definition.
|
|
125
114
|
- **build_get_status**: Fetch the status of a specific build.
|
|
126
115
|
- **build_update_build_stage**: Update the stage of a specific build.
|
package/dist/index.js
CHANGED
|
@@ -11,16 +11,25 @@ import { configurePrompts } from "./prompts.js";
|
|
|
11
11
|
import { configureAllTools } from "./tools.js";
|
|
12
12
|
import { UserAgentComposer } from "./useragent.js";
|
|
13
13
|
import { packageVersion } from "./version.js";
|
|
14
|
+
import { DomainsManager } from "./shared/domains.js";
|
|
14
15
|
// Parse command line arguments using yargs
|
|
15
16
|
const argv = yargs(hideBin(process.argv))
|
|
16
17
|
.scriptName("mcp-server-azuredevops")
|
|
17
18
|
.usage("Usage: $0 <organization> [options]")
|
|
18
19
|
.version(packageVersion)
|
|
19
|
-
.command("$0 <organization>", "Azure DevOps MCP Server", (yargs) => {
|
|
20
|
+
.command("$0 <organization> [options]", "Azure DevOps MCP Server", (yargs) => {
|
|
20
21
|
yargs.positional("organization", {
|
|
21
22
|
describe: "Azure DevOps organization name",
|
|
22
23
|
type: "string",
|
|
24
|
+
demandOption: true,
|
|
23
25
|
});
|
|
26
|
+
})
|
|
27
|
+
.option("domains", {
|
|
28
|
+
alias: "d",
|
|
29
|
+
describe: "Domain(s) to enable: 'all' for everything, or specific domains like 'repositories builds work'. Defaults to 'all'.",
|
|
30
|
+
type: "string",
|
|
31
|
+
array: true,
|
|
32
|
+
default: "all",
|
|
24
33
|
})
|
|
25
34
|
.option("tenant", {
|
|
26
35
|
alias: "t",
|
|
@@ -29,9 +38,11 @@ const argv = yargs(hideBin(process.argv))
|
|
|
29
38
|
})
|
|
30
39
|
.help()
|
|
31
40
|
.parseSync();
|
|
32
|
-
export const orgName = argv.organization;
|
|
33
41
|
const tenantId = argv.tenant;
|
|
42
|
+
export const orgName = argv.organization;
|
|
34
43
|
const orgUrl = "https://dev.azure.com/" + orgName;
|
|
44
|
+
const domainsManager = new DomainsManager(argv.domains);
|
|
45
|
+
export const enabledDomains = domainsManager.getEnabledDomains();
|
|
35
46
|
async function getAzureDevOpsToken() {
|
|
36
47
|
if (process.env.ADO_MCP_AZURE_TOKEN_CREDENTIALS) {
|
|
37
48
|
process.env.AZURE_TOKEN_CREDENTIALS = process.env.ADO_MCP_AZURE_TOKEN_CREDENTIALS;
|
|
@@ -73,7 +84,7 @@ async function main() {
|
|
|
73
84
|
userAgentComposer.appendMcpClientInfo(server.server.getClientVersion());
|
|
74
85
|
};
|
|
75
86
|
configurePrompts(server);
|
|
76
|
-
configureAllTools(server, getAzureDevOpsToken, getAzureDevOpsClient(userAgentComposer), () => userAgentComposer.userAgent);
|
|
87
|
+
configureAllTools(server, getAzureDevOpsToken, getAzureDevOpsClient(userAgentComposer), () => userAgentComposer.userAgent, enabledDomains);
|
|
77
88
|
const transport = new StdioServerTransport();
|
|
78
89
|
await server.connect(transport);
|
|
79
90
|
}
|
package/dist/prompts.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { CORE_TOOLS } from "./tools/core.js";
|
|
5
|
-
import { WORKITEM_TOOLS } from "./tools/
|
|
5
|
+
import { WORKITEM_TOOLS } from "./tools/work-items.js";
|
|
6
6
|
function configurePrompts(server) {
|
|
7
7
|
server.prompt("Projects", "Lists all projects in the Azure DevOps organization.", {}, () => ({
|
|
8
8
|
messages: [
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
/**
|
|
4
|
+
* Available Azure DevOps MCP domains
|
|
5
|
+
*/
|
|
6
|
+
export var Domain;
|
|
7
|
+
(function (Domain) {
|
|
8
|
+
Domain["ADVANCED_SECURITY"] = "advanced-security";
|
|
9
|
+
Domain["BUILDS"] = "builds";
|
|
10
|
+
Domain["CORE"] = "core";
|
|
11
|
+
Domain["RELEASES"] = "releases";
|
|
12
|
+
Domain["REPOSITORIES"] = "repositories";
|
|
13
|
+
Domain["SEARCH"] = "search";
|
|
14
|
+
Domain["TEST_PLANS"] = "test-plans";
|
|
15
|
+
Domain["WIKI"] = "wiki";
|
|
16
|
+
Domain["WORK"] = "work";
|
|
17
|
+
Domain["WORK_ITEMS"] = "work-items";
|
|
18
|
+
})(Domain || (Domain = {}));
|
|
19
|
+
export const ALL_DOMAINS = "all";
|
|
20
|
+
/**
|
|
21
|
+
* Manages domain parsing and validation for Azure DevOps MCP server tools
|
|
22
|
+
*/
|
|
23
|
+
export class DomainsManager {
|
|
24
|
+
static AVAILABLE_DOMAINS = Object.values(Domain);
|
|
25
|
+
enabledDomains;
|
|
26
|
+
constructor(domainsInput) {
|
|
27
|
+
this.enabledDomains = new Set();
|
|
28
|
+
const normalizedInput = DomainsManager.parseDomainsInput(domainsInput);
|
|
29
|
+
this.parseDomains(normalizedInput);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Parse and validate domains from input
|
|
33
|
+
* @param domainsInput - Either "all", single domain name, array of domain names, or undefined (defaults to "all")
|
|
34
|
+
*/
|
|
35
|
+
parseDomains(domainsInput) {
|
|
36
|
+
if (!domainsInput) {
|
|
37
|
+
this.enableAllDomains();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(domainsInput)) {
|
|
41
|
+
this.handleArrayInput(domainsInput);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.handleStringInput(domainsInput);
|
|
45
|
+
}
|
|
46
|
+
handleArrayInput(domainsInput) {
|
|
47
|
+
if (domainsInput.length === 0 || domainsInput.includes(ALL_DOMAINS)) {
|
|
48
|
+
this.enableAllDomains();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (domainsInput.length === 1 && domainsInput[0] === ALL_DOMAINS) {
|
|
52
|
+
this.enableAllDomains();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const domains = domainsInput.map((d) => d.trim().toLowerCase());
|
|
56
|
+
this.validateAndAddDomains(domains);
|
|
57
|
+
}
|
|
58
|
+
handleStringInput(domainsInput) {
|
|
59
|
+
if (domainsInput === ALL_DOMAINS) {
|
|
60
|
+
this.enableAllDomains();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const domains = [domainsInput.trim().toLowerCase()];
|
|
64
|
+
this.validateAndAddDomains(domains);
|
|
65
|
+
}
|
|
66
|
+
validateAndAddDomains(domains) {
|
|
67
|
+
const availableDomainsAsStringArray = Object.values(Domain);
|
|
68
|
+
domains.forEach((domain) => {
|
|
69
|
+
if (availableDomainsAsStringArray.includes(domain)) {
|
|
70
|
+
this.enabledDomains.add(domain);
|
|
71
|
+
}
|
|
72
|
+
else if (domain === ALL_DOMAINS) {
|
|
73
|
+
this.enableAllDomains();
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.error(`Error: Specified invalid domain '${domain}'. Please specify exactly as available domains: ${Object.values(Domain).join(", ")}`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
if (this.enabledDomains.size === 0) {
|
|
80
|
+
this.enableAllDomains();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
enableAllDomains() {
|
|
84
|
+
Object.values(Domain).forEach((domain) => this.enabledDomains.add(domain));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check if a specific domain is enabled
|
|
88
|
+
* @param domain - Domain name to check
|
|
89
|
+
* @returns true if domain is enabled
|
|
90
|
+
*/
|
|
91
|
+
isDomainEnabled(domain) {
|
|
92
|
+
return this.enabledDomains.has(domain);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get all enabled domains
|
|
96
|
+
* @returns Set of enabled domain names
|
|
97
|
+
*/
|
|
98
|
+
getEnabledDomains() {
|
|
99
|
+
return new Set(this.enabledDomains);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get list of all available domains
|
|
103
|
+
* @returns Array of available domain names
|
|
104
|
+
*/
|
|
105
|
+
static getAvailableDomains() {
|
|
106
|
+
return Object.values(Domain);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Parse domains input from string or array to a normalized array of strings
|
|
110
|
+
* @param domainsInput - Domains input to parse
|
|
111
|
+
* @returns Normalized array of domain strings
|
|
112
|
+
*/
|
|
113
|
+
static parseDomainsInput(domainsInput) {
|
|
114
|
+
if (!domainsInput) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
if (typeof domainsInput === "string") {
|
|
118
|
+
return domainsInput.split(",").map((d) => d.trim().toLowerCase());
|
|
119
|
+
}
|
|
120
|
+
return domainsInput.map((d) => d.trim().toLowerCase());
|
|
121
|
+
}
|
|
122
|
+
}
|
package/dist/tools/builds.js
CHANGED
|
@@ -14,7 +14,6 @@ const BUILD_TOOLS = {
|
|
|
14
14
|
run_build: "build_run_build",
|
|
15
15
|
get_status: "build_get_status",
|
|
16
16
|
update_build_stage: "build_update_build_stage",
|
|
17
|
-
get_timeline: "build_get_timeline",
|
|
18
17
|
};
|
|
19
18
|
function configureBuildTools(server, tokenProvider, connectionProvider, userAgentProvider) {
|
|
20
19
|
server.tool(BUILD_TOOLS.get_definitions, "Retrieves a list of build definitions for a given project.", {
|
|
@@ -205,19 +204,5 @@ function configureBuildTools(server, tokenProvider, connectionProvider, userAgen
|
|
|
205
204
|
content: [{ type: "text", text: JSON.stringify(updatedBuild, null, 2) }],
|
|
206
205
|
};
|
|
207
206
|
});
|
|
208
|
-
server.tool(BUILD_TOOLS.get_timeline, "Retrieves the timeline for a specific build, showing detailed information about steps and tasks.", {
|
|
209
|
-
project: z.string().describe("Project ID or name to get the build timeline for"),
|
|
210
|
-
buildId: z.number().describe("ID of the build to get the timeline for"),
|
|
211
|
-
timelineId: z.string().optional().describe("The ID of a specific timeline to retrieve. If not specified, the primary timeline is returned."),
|
|
212
|
-
changeId: z.number().optional().describe("If specified, only includes timeline records that changed after this watermark."),
|
|
213
|
-
planId: z.string().optional().describe("The ID of the plan to retrieve the timeline for."),
|
|
214
|
-
}, async ({ project, buildId, timelineId, changeId, planId }) => {
|
|
215
|
-
const connection = await connectionProvider();
|
|
216
|
-
const buildApi = await connection.getBuildApi();
|
|
217
|
-
const timeline = await buildApi.getBuildTimeline(project, buildId, timelineId, changeId, planId);
|
|
218
|
-
return {
|
|
219
|
-
content: [{ type: "text", text: JSON.stringify(timeline, null, 2) }],
|
|
220
|
-
};
|
|
221
|
-
});
|
|
222
207
|
}
|
|
223
208
|
export { BUILD_TOOLS, configureBuildTools };
|
package/dist/tools.js
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
import {
|
|
3
|
+
import { Domain } from "./shared/domains.js";
|
|
4
|
+
import { configureAdvSecTools } from "./tools/advanced-security.js";
|
|
4
5
|
import { configureBuildTools } from "./tools/builds.js";
|
|
5
6
|
import { configureCoreTools } from "./tools/core.js";
|
|
6
7
|
import { configureReleaseTools } from "./tools/releases.js";
|
|
7
|
-
import { configureRepoTools } from "./tools/
|
|
8
|
+
import { configureRepoTools } from "./tools/repositories.js";
|
|
8
9
|
import { configureSearchTools } from "./tools/search.js";
|
|
9
|
-
import { configureTestPlanTools } from "./tools/
|
|
10
|
+
import { configureTestPlanTools } from "./tools/test-plans.js";
|
|
10
11
|
import { configureWikiTools } from "./tools/wiki.js";
|
|
11
12
|
import { configureWorkTools } from "./tools/work.js";
|
|
12
|
-
import { configureWorkItemTools } from "./tools/
|
|
13
|
-
function configureAllTools(server, tokenProvider, connectionProvider, userAgentProvider) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
import { configureWorkItemTools } from "./tools/work-items.js";
|
|
14
|
+
function configureAllTools(server, tokenProvider, connectionProvider, userAgentProvider, enabledDomains) {
|
|
15
|
+
const configureIfDomainEnabled = (domain, configureFn) => {
|
|
16
|
+
if (enabledDomains.has(domain)) {
|
|
17
|
+
configureFn();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
configureIfDomainEnabled(Domain.CORE, () => configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider));
|
|
21
|
+
configureIfDomainEnabled(Domain.WORK, () => configureWorkTools(server, tokenProvider, connectionProvider));
|
|
22
|
+
configureIfDomainEnabled(Domain.BUILDS, () => configureBuildTools(server, tokenProvider, connectionProvider, userAgentProvider));
|
|
23
|
+
configureIfDomainEnabled(Domain.REPOSITORIES, () => configureRepoTools(server, tokenProvider, connectionProvider, userAgentProvider));
|
|
24
|
+
configureIfDomainEnabled(Domain.WORK_ITEMS, () => configureWorkItemTools(server, tokenProvider, connectionProvider, userAgentProvider));
|
|
25
|
+
configureIfDomainEnabled(Domain.RELEASES, () => configureReleaseTools(server, tokenProvider, connectionProvider));
|
|
26
|
+
configureIfDomainEnabled(Domain.WIKI, () => configureWikiTools(server, tokenProvider, connectionProvider));
|
|
27
|
+
configureIfDomainEnabled(Domain.TEST_PLANS, () => configureTestPlanTools(server, tokenProvider, connectionProvider));
|
|
28
|
+
configureIfDomainEnabled(Domain.SEARCH, () => configureSearchTools(server, tokenProvider, connectionProvider, userAgentProvider));
|
|
29
|
+
configureIfDomainEnabled(Domain.ADVANCED_SECURITY, () => configureAdvSecTools(server, tokenProvider, connectionProvider));
|
|
24
30
|
}
|
|
25
31
|
export { configureAllTools };
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const packageVersion = "
|
|
1
|
+
export const packageVersion = "2.0.0-nightly.20250825";
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|