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: diff --git a/src/index.ts b/src/index.ts index 79cc9a32..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, 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,51 @@ 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}`); + } + } + + // 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}`); } - 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 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}`); } - return token; + + // 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 {