@azure-devops/mcp 2.10.0-nightly.20260920 → 2.10.0-nightly.20260922
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/dist/auth.js +2 -103
- package/dist/oauth.js +130 -0
- package/dist/tools/wiki.js +1 -1
- package/dist/tools/work-items.js +7 -3
- package/dist/version.js +1 -1
- package/package.json +4 -2
package/dist/auth.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
import { AzureCliCredential, ChainedTokenCredential, DefaultAzureCredential } from "@azure/identity";
|
|
4
|
-
import { PublicClientApplication } from "@azure/msal-node";
|
|
5
|
-
import { NativeBrokerPlugin } from "@azure/msal-node-extensions";
|
|
6
|
-
import open from "open";
|
|
7
4
|
import { logger } from "./logger.js";
|
|
8
|
-
|
|
5
|
+
import { azureDevOpsScopes, OAuthAuthenticator } from "./oauth.js";
|
|
9
6
|
const patAllowedHosts = new Set(["dev.azure.com", "vssps.dev.azure.com", "almsearch.dev.azure.com"]);
|
|
10
7
|
function isPatAllowedHost(hostname) {
|
|
11
8
|
const normalizedHostname = hostname.toLowerCase();
|
|
@@ -30,104 +27,6 @@ function installPatFetchInterceptor(basicValue) {
|
|
|
30
27
|
return originalFetch(input, { ...init, headers });
|
|
31
28
|
};
|
|
32
29
|
}
|
|
33
|
-
class OAuthAuthenticator {
|
|
34
|
-
static clientId = "0d50963b-7bb9-4fe7-94c7-a99af00b5136";
|
|
35
|
-
static defaultAuthority = "https://login.microsoftonline.com/common";
|
|
36
|
-
static zeroTenantId = "00000000-0000-0000-0000-000000000000";
|
|
37
|
-
accountId;
|
|
38
|
-
publicClientApp;
|
|
39
|
-
publicClientAppFallback;
|
|
40
|
-
constructor(tenantId) {
|
|
41
|
-
this.accountId = null;
|
|
42
|
-
let authority = OAuthAuthenticator.defaultAuthority;
|
|
43
|
-
if (tenantId && tenantId !== OAuthAuthenticator.zeroTenantId) {
|
|
44
|
-
authority = `https://login.microsoftonline.com/${tenantId}`;
|
|
45
|
-
logger.debug(`OAuthAuthenticator: Using tenant-specific authority for tenantId='${tenantId}'`);
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
logger.debug(`OAuthAuthenticator: Using default common authority`);
|
|
49
|
-
}
|
|
50
|
-
this.publicClientApp = new PublicClientApplication({
|
|
51
|
-
auth: {
|
|
52
|
-
clientId: OAuthAuthenticator.clientId,
|
|
53
|
-
authority,
|
|
54
|
-
},
|
|
55
|
-
broker: {
|
|
56
|
-
nativeBrokerPlugin: new NativeBrokerPlugin(),
|
|
57
|
-
},
|
|
58
|
-
system: {
|
|
59
|
-
loggerOptions: {
|
|
60
|
-
loggerCallback: (level, message) => {
|
|
61
|
-
logger.debug(`MSALClient[${level}]: ${message}`);
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
this.publicClientAppFallback = new PublicClientApplication({
|
|
67
|
-
auth: {
|
|
68
|
-
clientId: OAuthAuthenticator.clientId,
|
|
69
|
-
authority,
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
logger.debug(`OAuthAuthenticator: Initialized with clientId='${OAuthAuthenticator.clientId}'`);
|
|
73
|
-
}
|
|
74
|
-
async getToken() {
|
|
75
|
-
let authResult = null;
|
|
76
|
-
if (this.accountId) {
|
|
77
|
-
logger.debug(`OAuthAuthenticator: Attempting silent token acquisition for cached account`);
|
|
78
|
-
try {
|
|
79
|
-
authResult = await this.publicClientApp.acquireTokenSilent({
|
|
80
|
-
scopes,
|
|
81
|
-
account: this.accountId,
|
|
82
|
-
});
|
|
83
|
-
logger.debug(`OAuthAuthenticator: Successfully acquired token silently`);
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
logger.debug(`OAuthAuthenticator: Silent token acquisition failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
-
authResult = null;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
logger.debug(`OAuthAuthenticator: No cached account available, interactive auth required`);
|
|
92
|
-
}
|
|
93
|
-
if (!authResult) {
|
|
94
|
-
logger.debug(`OAuthAuthenticator: Starting interactive token acquisition`);
|
|
95
|
-
try {
|
|
96
|
-
authResult = await this.publicClientApp.acquireTokenInteractive({
|
|
97
|
-
scopes,
|
|
98
|
-
openBrowser: async (url) => {
|
|
99
|
-
logger.debug(`OAuthAuthenticator: Opening browser for authentication with target URL: ${url}`);
|
|
100
|
-
open(url);
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
this.accountId = authResult.account;
|
|
104
|
-
logger.debug(`OAuthAuthenticator: Successfully acquired token interactively, account cached`);
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
const msalErrorMessage = error.platformBrokerError ? JSON.stringify(error.platformBrokerError) : "";
|
|
108
|
-
logger.debug(`OAuthAuthenticator: Interactive token acquisition failed: ${error instanceof Error ? error.message + msalErrorMessage : String(error)}`);
|
|
109
|
-
authResult = null;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (!authResult) {
|
|
113
|
-
logger.debug(`OAuthAuthenticator: Starting interactive token acquisition without broker`);
|
|
114
|
-
authResult = await this.publicClientAppFallback.acquireTokenInteractive({
|
|
115
|
-
scopes,
|
|
116
|
-
openBrowser: async (url) => {
|
|
117
|
-
logger.debug(`OAuthAuthenticator: Opening browser for authentication with target URL: ${url}`);
|
|
118
|
-
open(url);
|
|
119
|
-
},
|
|
120
|
-
});
|
|
121
|
-
logger.debug(`OAuthAuthenticator: Successfully acquired token interactively without broker`);
|
|
122
|
-
}
|
|
123
|
-
if (!authResult?.accessToken) {
|
|
124
|
-
logger.error(`OAuthAuthenticator: Authentication result contains no access token`);
|
|
125
|
-
throw new Error("Failed to obtain Azure DevOps OAuth token.");
|
|
126
|
-
}
|
|
127
|
-
logger.debug(`OAuthAuthenticator: Token obtained successfully`);
|
|
128
|
-
return authResult.accessToken;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
30
|
function createAuthenticator(type, tenantId) {
|
|
132
31
|
logger.debug(`Creating authenticator of type '${type}' with tenantId='${tenantId ?? "undefined"}'`);
|
|
133
32
|
switch (type) {
|
|
@@ -170,7 +69,7 @@ function createAuthenticator(type, tenantId) {
|
|
|
170
69
|
credential = new ChainedTokenCredential(azureCliCredential, credential);
|
|
171
70
|
}
|
|
172
71
|
return async () => {
|
|
173
|
-
const result = await credential.getToken(
|
|
72
|
+
const result = await credential.getToken(azureDevOpsScopes);
|
|
174
73
|
if (!result) {
|
|
175
74
|
logger.error(`${type}: Failed to obtain token - credential.getToken returned null/undefined`);
|
|
176
75
|
throw new Error("Failed to obtain Azure DevOps token. Ensure you have Azure CLI logged or use interactive type of authentication.");
|
package/dist/oauth.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import { PublicClientApplication } from "@azure/msal-node";
|
|
4
|
+
import open from "open";
|
|
5
|
+
import { logger } from "./logger.js";
|
|
6
|
+
export const azureDevOpsScopes = ["499b84ac-1321-427f-aa17-267ca6975798/.default"];
|
|
7
|
+
const clientId = "0d50963b-7bb9-4fe7-94c7-a99af00b5136";
|
|
8
|
+
const defaultAuthority = "https://login.microsoftonline.com/common";
|
|
9
|
+
const zeroTenantId = "00000000-0000-0000-0000-000000000000";
|
|
10
|
+
class ClientAppWrapper {
|
|
11
|
+
accountId;
|
|
12
|
+
publicClientApp;
|
|
13
|
+
withBroker;
|
|
14
|
+
constructor(authority) {
|
|
15
|
+
this.accountId = null;
|
|
16
|
+
this.publicClientApp = new PublicClientApplication({
|
|
17
|
+
auth: {
|
|
18
|
+
clientId,
|
|
19
|
+
authority,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
this.withBroker = false;
|
|
23
|
+
}
|
|
24
|
+
static async initialize(authority, tryWithBroker) {
|
|
25
|
+
const clientApp = new ClientAppWrapper(authority);
|
|
26
|
+
if (tryWithBroker) {
|
|
27
|
+
try {
|
|
28
|
+
const { NativeBrokerPlugin } = await import("@azure/msal-node-extensions");
|
|
29
|
+
clientApp.publicClientApp = new PublicClientApplication({
|
|
30
|
+
auth: {
|
|
31
|
+
clientId,
|
|
32
|
+
authority,
|
|
33
|
+
},
|
|
34
|
+
broker: {
|
|
35
|
+
nativeBrokerPlugin: new NativeBrokerPlugin(),
|
|
36
|
+
},
|
|
37
|
+
system: {
|
|
38
|
+
loggerOptions: {
|
|
39
|
+
loggerCallback: (level, message) => {
|
|
40
|
+
logger.debug(`MSALClient[${level}]: ${message}`);
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
clientApp.withBroker = true;
|
|
46
|
+
logger.debug(`PublicClientApplication(authority='${authority}') initialized with broker successfully`);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
logger.debug(`PublicClientApplication(authority='${authority}') initialized without broker`);
|
|
50
|
+
logger.error(`Failed to initialize PublicClientApplication with broker: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return clientApp;
|
|
54
|
+
}
|
|
55
|
+
isWithBroker() {
|
|
56
|
+
return this.withBroker;
|
|
57
|
+
}
|
|
58
|
+
async getToken() {
|
|
59
|
+
const logPrefix = `ClientAppWrapper(${this.withBroker ? "withBroker" : "noBroker"})`;
|
|
60
|
+
let authResult = null;
|
|
61
|
+
if (this.accountId) {
|
|
62
|
+
logger.debug(`${logPrefix}: Attempting silent token acquisition for cached account`);
|
|
63
|
+
try {
|
|
64
|
+
authResult = await this.publicClientApp.acquireTokenSilent({
|
|
65
|
+
scopes: azureDevOpsScopes,
|
|
66
|
+
account: this.accountId,
|
|
67
|
+
});
|
|
68
|
+
logger.debug(`${logPrefix}: Successfully acquired token silently`);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
logger.debug(`${logPrefix}: Silent token acquisition failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
72
|
+
authResult = null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
logger.debug(`${logPrefix}: No cached account available, interactive auth required`);
|
|
77
|
+
}
|
|
78
|
+
if (!authResult) {
|
|
79
|
+
logger.debug(`${logPrefix}: Starting interactive token acquisition`);
|
|
80
|
+
try {
|
|
81
|
+
authResult = await this.publicClientApp.acquireTokenInteractive({
|
|
82
|
+
scopes: azureDevOpsScopes,
|
|
83
|
+
openBrowser: async (url) => {
|
|
84
|
+
logger.debug(`${logPrefix}: Opening browser for authentication with target URL: ${url}`);
|
|
85
|
+
open(url);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
this.accountId = authResult.account;
|
|
89
|
+
logger.debug(`${logPrefix}: Successfully acquired token interactively, account cached`);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
const msalErrorMessage = error.platformBrokerError ? JSON.stringify(error.platformBrokerError) : "";
|
|
93
|
+
logger.debug(`${logPrefix}: Interactive token acquisition failed: ${error instanceof Error ? error.message + msalErrorMessage : String(error)}`);
|
|
94
|
+
authResult = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!authResult?.accessToken) {
|
|
98
|
+
throw new Error("Failed to obtain Azure DevOps OAuth token.");
|
|
99
|
+
}
|
|
100
|
+
logger.debug(`${logPrefix}: Token obtained successfully`);
|
|
101
|
+
return authResult.accessToken;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export class OAuthAuthenticator {
|
|
105
|
+
authority;
|
|
106
|
+
clientApp;
|
|
107
|
+
constructor(tenantId) {
|
|
108
|
+
this.authority = defaultAuthority;
|
|
109
|
+
if (tenantId && tenantId !== zeroTenantId) {
|
|
110
|
+
this.authority = `https://login.microsoftonline.com/${tenantId}`;
|
|
111
|
+
}
|
|
112
|
+
this.clientApp = null;
|
|
113
|
+
}
|
|
114
|
+
async getToken() {
|
|
115
|
+
if (!this.clientApp) {
|
|
116
|
+
this.clientApp = await ClientAppWrapper.initialize(this.authority, true);
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const token = await this.clientApp.getToken();
|
|
120
|
+
return token;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
if (this.clientApp.isWithBroker()) {
|
|
124
|
+
logger.debug(`OAuthAuthenticator: Token acquisition failed with broker, retrying without broker`);
|
|
125
|
+
this.clientApp = await ClientAppWrapper.initialize(this.authority, false);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return await this.clientApp.getToken();
|
|
129
|
+
}
|
|
130
|
+
}
|
package/dist/tools/wiki.js
CHANGED
package/dist/tools/work-items.js
CHANGED
|
@@ -108,13 +108,14 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
108
108
|
.optional()
|
|
109
109
|
.describe("Expand options (None, Fields, Relations, Links, All). Used for: get, list_revisions. For get, cannot be combined with fields."),
|
|
110
110
|
top: z.coerce.number().optional().describe("Maximum number of results to return. Used for: list_comments, my, list_revisions. Defaults vary by action."),
|
|
111
|
+
continuationToken: z.string().optional().describe("Token for continuing paged results. Used for: list_comments."),
|
|
111
112
|
includeCompleted: z.boolean().optional().default(false).describe("Include completed work items. Used for: my. Defaults to false."),
|
|
112
113
|
type: z.enum(["assignedtome", "myactivity"]).optional().describe("Type of work items to retrieve. Used for: my. Defaults to 'assignedtome'."),
|
|
113
114
|
skip: z.coerce.number().optional().describe("Number of results to skip for pagination. Used for: list_revisions."),
|
|
114
115
|
team: z.string().optional().describe("Team name or ID. Used for: list_for_iteration."),
|
|
115
116
|
iterationId: z.string().optional().describe("Iteration ID. Required for: list_for_iteration."),
|
|
116
117
|
workItemType: z.string().optional().describe("Work item type name. Required for: get_type."),
|
|
117
|
-
}, async ({ action, project, id, ids, workItemId, fields, asOf, expand, top, includeCompleted, type, skip, team, iterationId, workItemType }) => {
|
|
118
|
+
}, async ({ action, project, id, ids, workItemId, fields, asOf, expand, top, continuationToken, includeCompleted, type, skip, team, iterationId, workItemType }) => {
|
|
118
119
|
try {
|
|
119
120
|
const connection = await connectionProvider();
|
|
120
121
|
let resolvedProject = project;
|
|
@@ -177,7 +178,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
177
178
|
resolvedProject = result.resolved;
|
|
178
179
|
}
|
|
179
180
|
const workItemApi = await connection.getWorkItemTrackingApi();
|
|
180
|
-
const comments = await workItemApi.getComments(resolvedProject, workItemId, top ?? 50);
|
|
181
|
+
const comments = await workItemApi.getComments(resolvedProject, workItemId, top ?? 50, continuationToken);
|
|
181
182
|
return { content: [{ type: "text", text: JSON.stringify(comments, null, 2) }] };
|
|
182
183
|
}
|
|
183
184
|
if (action === "my") {
|
|
@@ -286,7 +287,10 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
286
287
|
team: z.string().optional().describe("Team name or ID. Used for: get_results, wiql."),
|
|
287
288
|
timePrecision: z.boolean().optional().describe("Include time precision in date fields. Used for: get_results, wiql."),
|
|
288
289
|
top: z.coerce.number().default(50).describe("Maximum number of results to return. Used for: get_results, wiql. Defaults to 50."),
|
|
289
|
-
responseType: z
|
|
290
|
+
responseType: z
|
|
291
|
+
.enum(["full", "ids"])
|
|
292
|
+
.default("full")
|
|
293
|
+
.describe("Response type: 'full' returns complete results (default). 'ids' returns only work item IDs and supports flat queries only; any other query type is not supported. Used for: get_results."),
|
|
290
294
|
wiql: z.string().max(32768).optional().describe('The WIQL query string to execute. Required for: wiql. Example: "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @project".'),
|
|
291
295
|
}, async ({ action, project, query, expand, depth, includeDeleted, useIsoDateFormat, id, team, timePrecision, top, responseType, wiql }) => {
|
|
292
296
|
try {
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const packageVersion = "2.10.0-nightly.
|
|
1
|
+
export const packageVersion = "2.10.0-nightly.20260922";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@azure-devops/mcp",
|
|
3
|
-
"version": "2.10.0-nightly.
|
|
3
|
+
"version": "2.10.0-nightly.20260922",
|
|
4
4
|
"mcpName": "microsoft.com/azure-devops",
|
|
5
5
|
"description": "MCP server for interacting with Azure DevOps",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
"@azure/identity": "^4.13.0",
|
|
41
41
|
"@azure/logger": "^1.3.0",
|
|
42
42
|
"@azure/msal-node": "^5.5.0",
|
|
43
|
-
"@azure/msal-node-extensions": "^5.3.5",
|
|
44
43
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
45
44
|
"azure-devops-extension-api": "^5.272.3",
|
|
46
45
|
"azure-devops-extension-sdk": "^4.0.2",
|
|
@@ -51,6 +50,9 @@
|
|
|
51
50
|
"zod": "^3.25.63",
|
|
52
51
|
"zod-to-json-schema": "^3.24.5"
|
|
53
52
|
},
|
|
53
|
+
"optionalDependencies": {
|
|
54
|
+
"@azure/msal-node-extensions": "^5.3.5"
|
|
55
|
+
},
|
|
54
56
|
"devDependencies": {
|
|
55
57
|
"@types/jest": "^30.0.0",
|
|
56
58
|
"@types/node": "^22.19.1",
|