@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 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/mcp", "${input:ado_org}"]
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/mcp@next`. Here is an updated example:
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/mcp@next", "${input:ado_org}"]
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/mcp", "${input:ado_org}", "-d", "core", "work", "work-items"]
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/mcp", "myorg", "--authentication", "azcli"],
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 still required and should match your collection name.
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
- "inputs": [
243
- {
244
- "id": "ado_org",
245
- "type": "promptString",
246
- "description": "Azure DevOps collection name (e.g. 'DefaultCollection')"
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": "<base64 encoded email:pat>"
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/mcp", "MyCollection", "--authentication", "pat"],
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": "<base64 encoded email:pat>"
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/mcp", "MyCollection", "--base-url", "https://azuredo.example.com/tfs/MyCollection", "--authentication", "pat"],
316
+ "args": ["-y", "@radholm/azure-devops-mcp", "--base-url", "https://azuredo.example.com/tfs/MyCollection", "--authentication", "pat"],
308
317
  "env": {
309
- "PERSONAL_ACCESS_TOKEN": "<base64 encoded email:pat>",
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
- [![Star History Chart](https://api.star-history.com/svg?repos=microsoft/azure-devops-mcp&type=Date)](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
- [![Contributors](https://contrib.rocks/image?repo=microsoft/azure-devops-mcp)](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 b64Pat = process.env["PERSONAL_ACCESS_TOKEN"];
79
- if (!b64Pat) {
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 base64-encoded Azure DevOps Personal Access Token.");
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 base64 value as-is caller uses it directly as the Basic auth credential
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 b64Pat;
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("mcp-server-azuredevops")
24
- .usage("Usage: $0 <organization> [options]")
36
+ .scriptName("azure-devops-mcp")
37
+ .usage("Usage: $0 [organization] [options]")
25
38
  .version(packageVersion)
26
- .command("$0 <organization> [options]", "Azure DevOps MCP Server", (yargs) => {
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 orgName = argv.organization;
60
- export const orgUrl = argv.baseUrl ?? process.env.AZURE_DEVOPS_BASE_URL ?? "https://dev.azure.com/" + orgName;
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 base64("{email}:{token}"). Decode to extract the token part,
72
- // since getPersonalAccessTokenHandler prepends ":" internally and just needs the raw token.
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 basicValue = await authenticator();
117
- // basicValue is already base64("{email}:{token}") — use it directly in the Authorization header
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";
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.1",
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
- "mcp-server-azuredevops": "dist/index.js"
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 azure-devops-mcp",
35
- "format-check": "prettier --check azure-devops-mcp",
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": "^4.264.0",
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",