Azure DevOps MCP Server

#!/bin/bash # Colors for better output GREEN='\033[0;32m' YELLOW='\033[0;33m' RED='\033[0;31m' NC='\033[0m' # No Color echo -e "${GREEN}Azure DevOps MCP Server - Environment Setup${NC}" echo "This script will help you set up your .env file with Azure DevOps credentials." echo # Clean up any existing create_pat.json file if [ -f "create_pat.json" ]; then echo -e "${YELLOW}Cleaning up existing create_pat.json file...${NC}" rm -f create_pat.json fi # Check if Azure CLI is installed if ! command -v az &> /dev/null; then echo -e "${RED}Error: Azure CLI is not installed.${NC}" echo "Please install Azure CLI first: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" exit 1 fi # Check if Azure DevOps extension is installed echo -e "${YELLOW}Checking for Azure DevOps extension...${NC}" az devops &> /dev/null if [ $? -ne 0 ]; then echo "Azure DevOps extension not found. Installing..." az extension add --name azure-devops if [ $? -ne 0 ]; then echo -e "${RED}Failed to install Azure DevOps extension.${NC}" exit 1 else echo -e "${GREEN}Azure DevOps extension installed successfully.${NC}" fi else echo "Azure DevOps extension is already installed." fi # Check if jq is installed if ! command -v jq &> /dev/null; then echo -e "${RED}Error: jq is not installed.${NC}" echo "Please install jq first. On Ubuntu/Debian: sudo apt-get install jq" echo "On macOS: brew install jq" exit 1 fi # Check if already logged in echo -e "\n${YELLOW}Step 1: Checking Azure CLI authentication...${NC}" if ! az account show &> /dev/null; then echo "Not logged in. Initiating login..." az login --allow-no-subscriptions || { echo -e "${RED}Failed to login to Azure CLI.${NC}" exit 1 } else echo -e "${GREEN}Already logged in to Azure CLI.${NC}" fi # Get Azure DevOps Organizations using REST API echo -e "\n${YELLOW}Step 2: Fetching your Azure DevOps organizations...${NC}" echo "This may take a moment..." # First get the user profile echo "Getting user profile..." profile_response=$(az rest --method get --uri "https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=6.0" --resource "499b84ac-1321-427f-aa17-267ca6975798" 2>&1) profile_status=$? if [ $profile_status -ne 0 ]; then echo -e "${RED}Error: Failed to get user profile${NC}" echo -e "${RED}Status code: $profile_status${NC}" echo -e "${RED}Error response:${NC}" echo "$profile_response" echo echo "Manually provide your organization name instead." read -p "Enter your Azure DevOps organization name: " org_name else echo "Profile API response:" echo "$profile_response" echo public_alias=$(echo "$profile_response" | jq -r '.publicAlias') if [ "$public_alias" == "null" ] || [ -z "$public_alias" ]; then echo -e "${RED}Failed to extract publicAlias from response.${NC}" echo "Full response was:" echo "$profile_response" echo echo "Manually provide your organization name instead." read -p "Enter your Azure DevOps organization name: " org_name else # Get organizations using the publicAlias echo "Fetching organizations..." orgs_result=$(az rest --method get --uri "https://app.vssps.visualstudio.com/_apis/accounts?memberId=$public_alias&api-version=6.0" --resource "499b84ac-1321-427f-aa17-267ca6975798") # Extract organization names from the response using jq orgs=$(echo "$orgs_result" | jq -r '.value[].accountName') if [ -z "$orgs" ]; then echo -e "${RED}No organizations found.${NC}" echo "Manually provide your organization name instead." read -p "Enter your Azure DevOps organization name: " org_name else # Display organizations for selection echo -e "\nYour Azure DevOps organizations:" i=1 OLDIFS=$IFS IFS=$'\n' orgs_array=($orgs) IFS=$OLDIFS for org in "${orgs_array[@]}"; do echo "$i) $org" ((i++)) done # Prompt for selection read -p "Select an organization (1-${#orgs_array[@]}): " org_selection if [[ "$org_selection" =~ ^[0-9]+$ ]] && [ "$org_selection" -ge 1 ] && [ "$org_selection" -le "${#orgs_array[@]}" ]; then org_name=${orgs_array[$((org_selection-1))]} else echo -e "${RED}Invalid selection. Please run the script again.${NC}" exit 1 fi fi fi fi org_url="https://dev.azure.com/$org_name" echo -e "${GREEN}Using organization URL: $org_url${NC}" # Get Default Project (Optional) echo -e "\n${YELLOW}Step 3: Would you like to set a default project? (y/n)${NC}" read -p "Select option: " set_default_project default_project="" if [[ "$set_default_project" == "y" || "$set_default_project" == "Y" ]]; then # Configure az devops to use the selected organization az devops configure --defaults organization=$org_url # List projects echo "Fetching projects from $org_name..." projects=$(az devops project list --query "value[].name" -o tsv) if [ $? -ne 0 ] || [ -z "$projects" ]; then echo -e "${YELLOW}No projects found or unable to list projects.${NC}" read -p "Enter a default project name (leave blank to skip): " default_project else # Display projects for selection echo -e "\nAvailable projects in $org_name:" i=1 OLDIFS=$IFS IFS=$'\n' projects_array=($projects) IFS=$OLDIFS for project in "${projects_array[@]}"; do echo "$i) $project" ((i++)) done echo "$i) Skip setting a default project" # Prompt for selection read -p "Select a default project (1-$i): " project_selection if [[ "$project_selection" =~ ^[0-9]+$ ]] && [ "$project_selection" -ge 1 ] && [ "$project_selection" -lt "$i" ]; then default_project=${projects_array[$((project_selection-1))]} echo -e "${GREEN}Using default project: $default_project${NC}" else echo "No default project selected." fi fi fi # Create PAT echo -e "\n${YELLOW}Step 4: Creating a Personal Access Token (PAT)...${NC}" # Two methods to create PAT: REST API or browser echo "Would you like to create a PAT using:" echo "1) REST API (automated, requires appropriate permissions)" echo "2) Web browser (manual, more reliable)" read -p "Select option (1/2): " pat_method pat_token="" if [ "$pat_method" == "1" ]; then # Create temporary JSON file for PAT creation temp_pat_json=$(mktemp) # Calculate date 7 days from now in ISO format expiry_date=$(date -u -d "+7 days" "+%Y-%m-%dT%H:%M:%S.000Z") cat > "$temp_pat_json" << EOF { "displayName": "MCP-Server-Integration", "scope": "vso.code vso.build_execute vso.work vso.project vso.profile", "validTo": "$expiry_date", "allOrgs": false } EOF # Create PAT using REST API echo "Generating PAT for $org_name organization..." pat_response=$(az rest --method post --uri "https://vssps.dev.azure.com/$org_name/_apis/tokens/pats?api-version=7.1-preview.1" --resource "499b84ac-1321-427f-aa17-267ca6975798" --body @"$temp_pat_json" 2>&1) pat_status=$? # Clean up temp file rm -f "$temp_pat_json" if [ $pat_status -ne 0 ]; then echo -e "${RED}Error: Failed to create PAT${NC}" echo -e "${RED}Status code: $pat_status${NC}" echo -e "${RED}Error response:${NC}" echo "$pat_response" echo echo "Switching to manual PAT creation..." pat_method="2" else echo "PAT API response:" echo "$pat_response" echo # Extract token from response using jq pat_token=$(echo "$pat_response" | jq -r '.patToken.token') if [ "$pat_token" == "null" ] || [ -z "$pat_token" ]; then echo -e "${RED}Failed to extract token from response.${NC}" echo "Full response was:" echo "$pat_response" echo echo "Switching to manual PAT creation..." pat_method="2" fi fi fi if [ "$pat_method" == "2" ]; then # Open browser for manual PAT creation pat_url="https://dev.azure.com/$org_name/_usersSettings/tokens" echo -e "${YELLOW}Opening browser to create a PAT manually...${NC}" echo "Please create a PAT with the following scopes:" echo "- Code (read) - vso.code" echo "- Build (read and execute) - vso.build_execute" echo "- Work items (read) - vso.work" echo "- Project and Team (read) - vso.project" echo "- User profile (read) - vso.profile" # Try to open browser using various commands if command -v xdg-open &> /dev/null; then xdg-open "$pat_url" &> /dev/null & elif command -v open &> /dev/null; then open "$pat_url" &> /dev/null & elif command -v start &> /dev/null; then start "$pat_url" &> /dev/null & else echo -e "${YELLOW}Could not open browser automatically.${NC}" echo "Please visit: $pat_url" fi echo -e "\nAfter creating the PAT, copy it and paste it here:" read -p "Enter your PAT: " pat_token fi if [ -z "$pat_token" ]; then echo -e "${RED}No PAT provided. Cannot continue.${NC}" exit 1 fi # Create .env file echo -e "\n${YELLOW}Step 5: Creating .env file...${NC}" cat > .env << EOF # Azure DevOps MCP Server - Environment Variables # Azure DevOps Organization Name (selected from your available organizations) AZURE_DEVOPS_ORG=$org_name # Azure DevOps Organization URL (required) AZURE_DEVOPS_ORG_URL=$org_url # Azure DevOps Personal Access Token (required) # Created via Azure CLI on $(date +"%b %d, %Y") # Scopes: vso.code vso.build_execute vso.work vso.project vso.profile AZURE_DEVOPS_PAT=$pat_token EOF # Add default project if specified if [ ! -z "$default_project" ]; then cat >> .env << EOF # Default Project to use when not specified AZURE_DEVOPS_DEFAULT_PROJECT=$default_project EOF else cat >> .env << EOF # Default Project to use when not specified (optional) # AZURE_DEVOPS_DEFAULT_PROJECT=your-default-project EOF fi # Add remaining configuration cat >> .env << EOF # API Version to use (optional, defaults to latest) # AZURE_DEVOPS_API_VERSION=6.0 # Server Configuration PORT=3000 HOST=localhost # Logging Level (debug, info, warn, error) LOG_LEVEL=info EOF echo -e "\n${GREEN}Environment setup completed successfully!${NC}" echo "Your .env file has been created with the following configuration:" echo "- Organization: $org_name" echo "- Organization URL: $org_url" if [ ! -z "$default_project" ]; then echo "- Default Project: $default_project" fi echo "- PAT: Created with expanded scopes for full integration" echo echo "You can now run your Azure DevOps MCP Server with:" echo " npm run dev" echo echo "You can also run integration tests with:" echo " npm run test:integration"