@radholm/azure-devops-mcp 1.0.0-beta.1 → 1.0.0-beta.3
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 +34 -37
- package/dist/auth.js +5 -5
- package/dist/index.js +31 -14
- package/dist/version.js +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -124,13 +124,13 @@ In your project, add a `.vscode\mcp.json` file with the following content:
|
|
|
124
124
|
"ado": {
|
|
125
125
|
"type": "stdio",
|
|
126
126
|
"command": "npx",
|
|
127
|
-
"args": ["-y", "@azure-devops
|
|
127
|
+
"args": ["-y", "@radholm/azure-devops-mcp", "${input:ado_org}"]
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
🔥 To stay up to date with the latest features, you can use our nightly builds. Simply update your `mcp.json` configuration to use `@azure-devops
|
|
133
|
+
🔥 To stay up to date with the latest features, you can use our nightly builds. Simply update your `mcp.json` configuration to use `@radholm/azure-devops-mcp@next`. Here is an updated example:
|
|
134
134
|
|
|
135
135
|
```json
|
|
136
136
|
{
|
|
@@ -145,7 +145,7 @@ In your project, add a `.vscode\mcp.json` file with the following content:
|
|
|
145
145
|
"ado": {
|
|
146
146
|
"type": "stdio",
|
|
147
147
|
"command": "npx",
|
|
148
|
-
"args": ["-y", "@azure-devops
|
|
148
|
+
"args": ["-y", "@radholm/azure-devops-mcp@next", "${input:ado_org}"]
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
}
|
|
@@ -187,7 +187,7 @@ For example, use `"-d", "core", "work", "work-items"` to load only Work Item rel
|
|
|
187
187
|
"ado_with_filtered_domains": {
|
|
188
188
|
"type": "stdio",
|
|
189
189
|
"command": "npx",
|
|
190
|
-
"args": ["-y", "@azure-devops
|
|
190
|
+
"args": ["-y", "@radholm/azure-devops-mcp", "${input:ado_org}", "-d", "core", "work", "work-items"]
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
}
|
|
@@ -211,7 +211,7 @@ You can also configure default Azure DevOps project and team values from `.vscod
|
|
|
211
211
|
"ado": {
|
|
212
212
|
"type": "stdio",
|
|
213
213
|
"command": "npx",
|
|
214
|
-
"args": ["-y", "@azure-devops
|
|
214
|
+
"args": ["-y", "@radholm/azure-devops-mcp", "myorg", "--authentication", "azcli"],
|
|
215
215
|
"env": {
|
|
216
216
|
"ado_mcp_project": "Contoso",
|
|
217
217
|
"ado_mcp_team": "Fabrikam Team"
|
|
@@ -231,40 +231,49 @@ The local MCP server supports connecting to on-premises Azure DevOps Server (TFS
|
|
|
231
231
|
|
|
232
232
|
### Configuration
|
|
233
233
|
|
|
234
|
-
Use the `--base-url` (or `-b`) option to point to your on-prem server, or set the `AZURE_DEVOPS_BASE_URL` environment variable. The `organization` positional argument is
|
|
234
|
+
Use the `--base-url` (or `-b`) option to point to your on-prem server, or set the `AZURE_DEVOPS_BASE_URL` environment variable. The `organization` positional argument is **optional** when `--base-url` is provided — the server will automatically derive the organization/collection name from the last path segment of the URL.
|
|
235
235
|
|
|
236
236
|
> **Note:** Only `pat` and `envvar` authentication are supported for on-premises servers.
|
|
237
237
|
|
|
238
|
+
### Authentication
|
|
239
|
+
|
|
240
|
+
Set `PERSONAL_ACCESS_TOKEN` to your **raw** Personal Access Token (the token string as generated by Azure DevOps). The server handles encoding internally.
|
|
241
|
+
|
|
238
242
|
### Example `.vscode/mcp.json`
|
|
239
243
|
|
|
240
244
|
```json
|
|
241
245
|
{
|
|
242
|
-
"
|
|
243
|
-
{
|
|
244
|
-
"
|
|
245
|
-
"
|
|
246
|
-
"
|
|
246
|
+
"servers": {
|
|
247
|
+
"ado-onprem": {
|
|
248
|
+
"type": "stdio",
|
|
249
|
+
"command": "npx",
|
|
250
|
+
"args": ["-y", "@radholm/azure-devops-mcp", "--base-url", "https://azuredo.example.com/tfs/MyCollection", "--authentication", "pat"],
|
|
251
|
+
"env": {
|
|
252
|
+
"PERSONAL_ACCESS_TOKEN": "<your raw PAT>"
|
|
253
|
+
}
|
|
247
254
|
}
|
|
248
|
-
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
You can still provide the organization explicitly if needed:
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
249
263
|
"servers": {
|
|
250
264
|
"ado-onprem": {
|
|
251
265
|
"type": "stdio",
|
|
252
266
|
"command": "npx",
|
|
253
|
-
"args": [
|
|
254
|
-
"-y", "@azure-devops/mcp",
|
|
255
|
-
"${input:ado_org}",
|
|
256
|
-
"--base-url", "https://azuredo.example.com/tfs/MyCollection",
|
|
257
|
-
"--authentication", "pat"
|
|
258
|
-
],
|
|
267
|
+
"args": ["-y", "@radholm/azure-devops-mcp", "MyCollection", "--base-url", "https://azuredo.example.com/tfs/MyCollection", "--authentication", "pat"],
|
|
259
268
|
"env": {
|
|
260
|
-
"PERSONAL_ACCESS_TOKEN": "<
|
|
269
|
+
"PERSONAL_ACCESS_TOKEN": "<your raw PAT>"
|
|
261
270
|
}
|
|
262
271
|
}
|
|
263
272
|
}
|
|
264
273
|
}
|
|
265
274
|
```
|
|
266
275
|
|
|
267
|
-
Alternatively, use the `AZURE_DEVOPS_BASE_URL` environment variable
|
|
276
|
+
Alternatively, use the `AZURE_DEVOPS_BASE_URL` environment variable instead of `--base-url`:
|
|
268
277
|
|
|
269
278
|
```json
|
|
270
279
|
{
|
|
@@ -272,10 +281,10 @@ Alternatively, use the `AZURE_DEVOPS_BASE_URL` environment variable:
|
|
|
272
281
|
"ado-onprem": {
|
|
273
282
|
"type": "stdio",
|
|
274
283
|
"command": "npx",
|
|
275
|
-
"args": ["-y", "@azure-devops
|
|
284
|
+
"args": ["-y", "@radholm/azure-devops-mcp", "--authentication", "pat"],
|
|
276
285
|
"env": {
|
|
277
286
|
"AZURE_DEVOPS_BASE_URL": "https://azuredo.example.com/tfs/MyCollection",
|
|
278
|
-
"PERSONAL_ACCESS_TOKEN": "<
|
|
287
|
+
"PERSONAL_ACCESS_TOKEN": "<your raw PAT>"
|
|
279
288
|
}
|
|
280
289
|
}
|
|
281
290
|
}
|
|
@@ -291,7 +300,7 @@ If your on-prem server uses self-signed SSL certificates, you may need to set th
|
|
|
291
300
|
Azure DevOps Server on-premises may not support the latest API versions used by the cloud service. If you encounter `404` or version-related errors, set the `AZURE_DEVOPS_API_VERSION` environment variable to match your server version:
|
|
292
301
|
|
|
293
302
|
| Azure DevOps Server Version | Supported API Version |
|
|
294
|
-
|
|
303
|
+
| --------------------------- | --------------------- |
|
|
295
304
|
| Azure DevOps Server 2022 | `7.1` |
|
|
296
305
|
| Azure DevOps Server 2020 | `6.0` |
|
|
297
306
|
| Azure DevOps Server 2019 | `5.1` |
|
|
@@ -304,9 +313,9 @@ Example:
|
|
|
304
313
|
"ado-onprem": {
|
|
305
314
|
"type": "stdio",
|
|
306
315
|
"command": "npx",
|
|
307
|
-
"args": ["-y", "@azure-devops
|
|
316
|
+
"args": ["-y", "@radholm/azure-devops-mcp", "--base-url", "https://azuredo.example.com/tfs/MyCollection", "--authentication", "pat"],
|
|
308
317
|
"env": {
|
|
309
|
-
"PERSONAL_ACCESS_TOKEN": "<
|
|
318
|
+
"PERSONAL_ACCESS_TOKEN": "<your raw PAT>",
|
|
310
319
|
"AZURE_DEVOPS_API_VERSION": "7.1"
|
|
311
320
|
}
|
|
312
321
|
}
|
|
@@ -342,18 +351,6 @@ See our [Contributions Guide](CONTRIBUTING.md) for:
|
|
|
342
351
|
This project follows the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
|
343
352
|
For questions, see the [FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [open@microsoft.com](mailto:open@microsoft.com).
|
|
344
353
|
|
|
345
|
-
## 📈 Project Stats
|
|
346
|
-
|
|
347
|
-
[](https://star-history.com/#microsoft/azure-devops-mcp)
|
|
348
|
-
|
|
349
|
-
## 🏆 Hall of Fame
|
|
350
|
-
|
|
351
|
-
Thanks to all contributors who make this project awesome! ❤️
|
|
352
|
-
|
|
353
|
-
[](https://github.com/microsoft/azure-devops-mcp/graphs/contributors)
|
|
354
|
-
|
|
355
|
-
> Generated with [contrib.rocks](https://contrib.rocks)
|
|
356
|
-
|
|
357
354
|
## License
|
|
358
355
|
|
|
359
356
|
Licensed under the [MIT License](LICENSE.md).
|
package/dist/auth.js
CHANGED
|
@@ -75,14 +75,14 @@ function createAuthenticator(type, tenantId) {
|
|
|
75
75
|
logger.debug(`Authenticator: Using PAT authentication (PERSONAL_ACCESS_TOKEN)`);
|
|
76
76
|
return async () => {
|
|
77
77
|
logger.debug(`${type}: Reading token from PERSONAL_ACCESS_TOKEN environment variable`);
|
|
78
|
-
const
|
|
79
|
-
if (!
|
|
78
|
+
const pat = process.env["PERSONAL_ACCESS_TOKEN"];
|
|
79
|
+
if (!pat) {
|
|
80
80
|
logger.error(`${type}: PERSONAL_ACCESS_TOKEN environment variable is not set or empty`);
|
|
81
|
-
throw new Error("Environment variable 'PERSONAL_ACCESS_TOKEN' is not set or empty. Please set it with a valid
|
|
81
|
+
throw new Error("Environment variable 'PERSONAL_ACCESS_TOKEN' is not set or empty. Please set it with a valid Azure DevOps Personal Access Token.");
|
|
82
82
|
}
|
|
83
|
-
// Return
|
|
83
|
+
// Return the raw PAT value as-is. The caller handles encoding for Basic auth.
|
|
84
84
|
logger.debug(`${type}: Successfully retrieved PAT from environment variable`);
|
|
85
|
-
return
|
|
85
|
+
return pat;
|
|
86
86
|
};
|
|
87
87
|
case "envvar":
|
|
88
88
|
logger.debug(`Authenticator: Using environment variable authentication (ADO_MCP_AUTH_TOKEN)`);
|
package/dist/index.js
CHANGED
|
@@ -18,16 +18,28 @@ function isGitHubCodespaceEnv() {
|
|
|
18
18
|
return process.env.CODESPACES === "true" && !!process.env.CODESPACE_NAME;
|
|
19
19
|
}
|
|
20
20
|
const defaultAuthenticationType = isGitHubCodespaceEnv() ? "azcli" : "interactive";
|
|
21
|
+
/**
|
|
22
|
+
* Extract the organization/collection name from a base URL.
|
|
23
|
+
* E.g. "https://azuredo.lfnet.se/tfs/Lansforsakringar" -> "Lansforsakringar"
|
|
24
|
+
* "https://dev.azure.com/MyOrg" -> "MyOrg"
|
|
25
|
+
*/
|
|
26
|
+
function extractOrgFromUrl(url) {
|
|
27
|
+
const pathname = new URL(url).pathname.replace(/\/+$/, "");
|
|
28
|
+
const lastSegment = pathname.split("/").pop();
|
|
29
|
+
if (!lastSegment) {
|
|
30
|
+
throw new Error(`Cannot derive organization name from base URL '${url}'. Please provide the organization positional argument.`);
|
|
31
|
+
}
|
|
32
|
+
return lastSegment;
|
|
33
|
+
}
|
|
21
34
|
// Parse command line arguments using yargs
|
|
22
35
|
const argv = yargs(hideBin(process.argv))
|
|
23
|
-
.scriptName("
|
|
24
|
-
.usage("Usage: $0
|
|
36
|
+
.scriptName("azure-devops-mcp")
|
|
37
|
+
.usage("Usage: $0 [organization] [options]")
|
|
25
38
|
.version(packageVersion)
|
|
26
|
-
.command("$0
|
|
39
|
+
.command("$0 [organization] [options]", "Azure DevOps MCP Server", (yargs) => {
|
|
27
40
|
yargs.positional("organization", {
|
|
28
|
-
describe: "Azure DevOps organization name",
|
|
41
|
+
describe: "Azure DevOps organization name (optional if --base-url is provided)",
|
|
29
42
|
type: "string",
|
|
30
|
-
demandOption: true,
|
|
31
43
|
});
|
|
32
44
|
})
|
|
33
45
|
.option("domains", {
|
|
@@ -53,11 +65,17 @@ const argv = yargs(hideBin(process.argv))
|
|
|
53
65
|
alias: "b",
|
|
54
66
|
describe: "Base URL for Azure DevOps Server (on-prem). E.g. 'https://azuredo.example.com/tfs/MyCollection'. Defaults to 'https://dev.azure.com/{organization}'.",
|
|
55
67
|
type: "string",
|
|
68
|
+
})
|
|
69
|
+
.check((argv) => {
|
|
70
|
+
if (!argv.organization && !argv.baseUrl && !process.env.AZURE_DEVOPS_BASE_URL) {
|
|
71
|
+
throw new Error("Either <organization> or --base-url (or AZURE_DEVOPS_BASE_URL env var) must be provided.");
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
56
74
|
})
|
|
57
75
|
.help()
|
|
58
76
|
.parseSync();
|
|
59
|
-
export const
|
|
60
|
-
export const
|
|
77
|
+
export const orgUrl = argv.baseUrl ?? process.env.AZURE_DEVOPS_BASE_URL ?? "https://dev.azure.com/" + argv.organization;
|
|
78
|
+
export const orgName = argv.organization ?? extractOrgFromUrl(orgUrl);
|
|
61
79
|
/**
|
|
62
80
|
* Whether the server is connecting to an on-premises Azure DevOps Server instance
|
|
63
81
|
* (i.e., not the cloud-hosted dev.azure.com service).
|
|
@@ -68,9 +86,8 @@ export const enabledDomains = domainsManager.getEnabledDomains();
|
|
|
68
86
|
function getAzureDevOpsClient(getAzureDevOpsToken, userAgentComposer, authType) {
|
|
69
87
|
return async () => {
|
|
70
88
|
const accessToken = await getAzureDevOpsToken();
|
|
71
|
-
// For pat, accessToken is
|
|
72
|
-
|
|
73
|
-
const authHandler = authType === "pat" ? getPersonalAccessTokenHandler(Buffer.from(accessToken, "base64").toString("utf8").split(":").slice(1).join(":")) : getBearerHandler(accessToken);
|
|
89
|
+
// For pat, accessToken is the raw PAT token
|
|
90
|
+
const authHandler = authType === "pat" ? getPersonalAccessTokenHandler(accessToken) : getBearerHandler(accessToken);
|
|
74
91
|
const connection = new WebApi(orgUrl, authHandler, undefined, {
|
|
75
92
|
productName: "AzureDevOps.MCP",
|
|
76
93
|
productVersion: packageVersion,
|
|
@@ -94,8 +111,7 @@ async function main() {
|
|
|
94
111
|
// Validate authentication type for on-prem servers
|
|
95
112
|
if (isOnPrem && argv.authentication !== "pat" && argv.authentication !== "envvar") {
|
|
96
113
|
logger.error("On-premises Azure DevOps Server only supports 'pat' or 'envvar' authentication.");
|
|
97
|
-
throw new Error("On-premises Azure DevOps Server only supports 'pat' or 'envvar' authentication. " +
|
|
98
|
-
"Please use '--authentication pat' or '--authentication envvar'.");
|
|
114
|
+
throw new Error("On-premises Azure DevOps Server only supports 'pat' or 'envvar' authentication. " + "Please use '--authentication pat' or '--authentication envvar'.");
|
|
99
115
|
}
|
|
100
116
|
const server = new McpServer({
|
|
101
117
|
name: "Azure DevOps MCP Server",
|
|
@@ -113,8 +129,9 @@ async function main() {
|
|
|
113
129
|
const tenantId = isOnPrem ? argv.tenant : ((await getOrgTenant(orgName)) ?? argv.tenant);
|
|
114
130
|
const authenticator = createAuthenticator(argv.authentication, tenantId);
|
|
115
131
|
if (argv.authentication === "pat") {
|
|
116
|
-
const
|
|
117
|
-
//
|
|
132
|
+
const rawPat = await authenticator();
|
|
133
|
+
// Build the Basic auth value from the raw PAT: base64(":token")
|
|
134
|
+
const basicValue = Buffer.from(`:${rawPat}`).toString("base64");
|
|
118
135
|
const _originalFetch = globalThis.fetch;
|
|
119
136
|
globalThis.fetch = async (input, init) => {
|
|
120
137
|
if (init?.headers) {
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const packageVersion = "1.0.0-beta.
|
|
1
|
+
export const packageVersion = "1.0.0-beta.3";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@radholm/azure-devops-mcp",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.3",
|
|
4
4
|
"description": "MCP server for interacting with Azure DevOps",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Fredrik Radholm",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"type": "module",
|
|
14
14
|
"bin": {
|
|
15
|
-
"
|
|
15
|
+
"azure-devops-mcp": "dist/index.js"
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"dist"
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"start": "node -r tsconfig-paths/register dist/index.js",
|
|
32
32
|
"eslint": "eslint",
|
|
33
33
|
"eslint-fix": "eslint --fix",
|
|
34
|
-
"format": "prettier --write
|
|
35
|
-
"format-check": "prettier --check
|
|
34
|
+
"format": "prettier --write .",
|
|
35
|
+
"format-check": "prettier --check .",
|
|
36
36
|
"clean": "shx rm -rf dist",
|
|
37
37
|
"test": "jest"
|
|
38
38
|
},
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@azure/identity": "^4.10.0",
|
|
41
41
|
"@azure/msal-node": "^5.0.6",
|
|
42
42
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
43
|
-
"azure-devops-extension-api": "^
|
|
43
|
+
"azure-devops-extension-api": "^5.272.3",
|
|
44
44
|
"azure-devops-extension-sdk": "^4.0.2",
|
|
45
45
|
"azure-devops-node-api": "^15.1.2",
|
|
46
46
|
"winston": "^3.18.3",
|