From d9c8d4d6cc14c673b0be9d54f4697fbd272555e2 Mon Sep 17 00:00:00 2001 From: Siddhant Rathi Date: Fri, 18 Jul 2025 18:27:38 +0530 Subject: [PATCH 1/3] feat: Add Interactive Browser Credential as a fallback - Try all credentials in sequence --> Azure CLI (if tenantID given), Default, then Interactive - Removed ChainedTokenCredential as it fails if Azure CLI is not installed on client's machine - Tested the different auth mechanisms locally --- src/index.ts | 49 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index 79cc9a32..e9ae57fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import * as azdev from "azure-devops-node-api"; -import { AccessToken, AzureCliCredential, ChainedTokenCredential, DefaultAzureCredential, TokenCredential } from "@azure/identity"; +import { AccessToken, AzureCliCredential, DefaultAzureCredential, InteractiveBrowserCredential} from "@azure/identity"; import yargs from "yargs"; import { hideBin } from "yargs/helpers"; @@ -44,18 +44,49 @@ async function getAzureDevOpsToken(): Promise { } else { process.env.AZURE_TOKEN_CREDENTIALS = "dev"; } - let credential: TokenCredential = new DefaultAzureCredential(); // CodeQL [SM05138] resolved by explicitly setting AZURE_TOKEN_CREDENTIALS + + const scope = "499b84ac-1321-427f-aa17-267ca6975798/.default"; + const errors: string[] = []; + + // Try Azure CLI credential if tenantId is provided if (tenantId) { - // Use Azure CLI credential if tenantId is provided for multi-tenant scenarios - const azureCliCredential = new AzureCliCredential({ tenantId }); - credential = new ChainedTokenCredential(azureCliCredential, credential); + try { + const azureCliCredential = new AzureCliCredential({ tenantId }); + const token = await azureCliCredential.getToken(scope); + if (token) { + return token; + } + } catch (error) { + errors.push(`AzureCliCredential failed: ${error}`); + } } - const token = await credential.getToken("499b84ac-1321-427f-aa17-267ca6975798/.default"); - if (!token) { - throw new Error("Failed to obtain Azure DevOps token. Ensure you have Azure CLI logged in or another token source setup correctly."); + // Try DefaultAzureCredential first (includes managed identity, environment variables, etc.) + try { + const defaultCredential = new DefaultAzureCredential(); // CodeQL [SM05138] resolved by explicitly setting AZURE_TOKEN_CREDENTIALS + const token = await defaultCredential.getToken(scope); + if (token) { + return token; + } + } catch (error) { + errors.push(`DefaultAzureCredential failed: ${error}`); } - return token; + + // Try InteractiveBrowserCredential as final fallback + try { + const interactiveBrowserCredential = new InteractiveBrowserCredential({ tenantId }); + const token = await interactiveBrowserCredential.getToken(scope, { + requestOptions: { timeout: 60000 } // 1 minute timeout for interactive authentication + }); + if (token) { + return token; + } + } catch (error) { + errors.push(`InteractiveBrowserCredential failed: ${error}`); + } + + // If all credentials failed, throw an error with details + throw new Error(`Failed to obtain Azure DevOps token. All authentication methods failed:\n${errors.join('\n')}\n\nTroubleshooting steps:\n1. Install Azure CLI and run 'az login'\n2. Set environment variables for service principal authentication\n3. Ensure you have access to Azure DevOps with the specified tenant`); } function getAzureDevOpsClient(userAgentComposer: UserAgentComposer): () => Promise { From bd2ea37c13e980180b153f6920c960b16686c8c5 Mon Sep 17 00:00:00 2001 From: Siddhant Rathi Date: Fri, 18 Jul 2025 18:33:41 +0530 Subject: [PATCH 2/3] chore: Formatting changes --- src/index.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index e9ae57fb..fbdec2d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import * as azdev from "azure-devops-node-api"; -import { AccessToken, AzureCliCredential, DefaultAzureCredential, InteractiveBrowserCredential} from "@azure/identity"; +import { AccessToken, AzureCliCredential, DefaultAzureCredential, InteractiveBrowserCredential } from "@azure/identity"; import yargs from "yargs"; import { hideBin } from "yargs/helpers"; @@ -44,10 +44,10 @@ async function getAzureDevOpsToken(): Promise { } else { process.env.AZURE_TOKEN_CREDENTIALS = "dev"; } - + const scope = "499b84ac-1321-427f-aa17-267ca6975798/.default"; const errors: string[] = []; - + // Try Azure CLI credential if tenantId is provided if (tenantId) { try { @@ -71,12 +71,12 @@ async function getAzureDevOpsToken(): Promise { } catch (error) { errors.push(`DefaultAzureCredential failed: ${error}`); } - + // Try InteractiveBrowserCredential as final fallback try { const interactiveBrowserCredential = new InteractiveBrowserCredential({ tenantId }); const token = await interactiveBrowserCredential.getToken(scope, { - requestOptions: { timeout: 60000 } // 1 minute timeout for interactive authentication + requestOptions: { timeout: 60000 }, // 1 minute timeout for interactive authentication }); if (token) { return token; @@ -86,7 +86,9 @@ async function getAzureDevOpsToken(): Promise { } // If all credentials failed, throw an error with details - throw new Error(`Failed to obtain Azure DevOps token. All authentication methods failed:\n${errors.join('\n')}\n\nTroubleshooting steps:\n1. Install Azure CLI and run 'az login'\n2. Set environment variables for service principal authentication\n3. Ensure you have access to Azure DevOps with the specified tenant`); + throw new Error( + `Failed to obtain Azure DevOps token. All authentication methods failed:\n${errors.join("\n")}\n\nTroubleshooting steps:\n1. Install Azure CLI and run 'az login'\n2. Set environment variables for service principal authentication\n3. Ensure you have access to Azure DevOps with the specified tenant` + ); } function getAzureDevOpsClient(userAgentComposer: UserAgentComposer): () => Promise { From 0aa1dd0143a531cd423772288f84d5c0c7b2bb5a Mon Sep 17 00:00:00 2001 From: Siddhant Rathi Date: Fri, 18 Jul 2025 18:37:19 +0530 Subject: [PATCH 3/3] chore: Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 30e5c553..e9ad6c70 100644 --- a/README.md +++ b/README.md @@ -147,10 +147,10 @@ For the best experience, use Visual Studio Code and GitHub Copilot. 1. Install [VS Code](https://code.visualstudio.com/download) or [VS Code Insiders](https://code.visualstudio.com/insiders) 2. Install [Node.js](https://nodejs.org/en/download) 20+ -3. Install [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) +3. Install [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) (Optional) 4. Open VS Code in an empty folder -### Azure Login +### Azure Login (If using Azure CLI) Ensure you are logged in to Azure DevOps via the Azure CLI: @@ -257,10 +257,10 @@ For the best experience, use Visual Studio Code and GitHub Copilot 👆. ### Prerequisites 1. Install [VS Studio 2022 version 17.14](https://learn.microsoft.com/en-us/visualstudio/releases/2022/release-history) or later -2. Install [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) +2. Install [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) (Optional) 3. Open a project in Visual Studio. -### Azure Login +### Azure Login (If using Azure CLI) Ensure you are logged in to Azure DevOps via the Azure CLI: