From 42e0a92ecad1c1548fd83ce0584768b161818f1b Mon Sep 17 00:00:00 2001 From: abdulanu0 Date: Mon, 2 Feb 2026 17:58:49 -0800 Subject: [PATCH 01/13] Adding new samples to the test suite - Add Python Agent Framework E2E workflow - Add Python Google ADK E2E workflow - Add Node.js Copilot Studio E2E workflow - Add health endpoints to Google ADK and Copilot Studio samples - Update orchestrator with new samples - Update README with new E2E status badges --- .../workflows/e2e-nodejs-copilot-studio.yml | 180 ++++++++++++++++ .github/workflows/e2e-orchestrator.yml | 43 +++- .../workflows/e2e-python-agent-framework.yml | 192 ++++++++++++++++++ .github/workflows/e2e-python-google-adk.yml | 189 +++++++++++++++++ README.md | 3 + .../copilot-studio/sample-agent/src/index.ts | 6 + python/google-adk/sample-agent/main.py | 5 + 7 files changed, 616 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/e2e-nodejs-copilot-studio.yml create mode 100644 .github/workflows/e2e-python-agent-framework.yml create mode 100644 .github/workflows/e2e-python-google-adk.yml diff --git a/.github/workflows/e2e-nodejs-copilot-studio.yml b/.github/workflows/e2e-nodejs-copilot-studio.yml new file mode 100644 index 00000000..5c2fce2f --- /dev/null +++ b/.github/workflows/e2e-nodejs-copilot-studio.yml @@ -0,0 +1,180 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +name: E2E - Node.js Copilot Studio + +on: + workflow_call: # Allow orchestrator to call this workflow + workflow_dispatch: # Allow manual triggering + push: + branches: [main] + paths: + - 'nodejs/copilot-studio/**' + - 'scripts/e2e/**' + - '.github/workflows/e2e-nodejs-copilot-studio.yml' + pull_request: + branches: [main] + paths: + - 'nodejs/copilot-studio/**' + - 'scripts/e2e/**' + +permissions: + contents: read + +env: + SAMPLE_PATH: nodejs/copilot-studio/sample-agent + AGENT_PORT: 3978 + SCRIPTS_PATH: scripts/e2e + E2E_TESTS_PATH: tests/e2e + +jobs: + nodejs-copilot-studio: + name: Node.js Copilot Studio Agent + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + working-directory: ${{ env.SAMPLE_PATH }} + run: | + if (Test-Path "package-lock.json") { + npm ci + } else { + npm install + } + if (Test-Path "tsconfig.json") { + npm run build + } + shell: pwsh + + - name: Log SDK Versions + shell: pwsh + run: | + & "${{ env.SCRIPTS_PATH }}/Get-SDKVersions.ps1" ` + -Runtime "nodejs" ` + -WorkingDirectory "${{ env.SAMPLE_PATH }}" + + - name: Acquire Bearer Token (ROPC) + id: token + shell: pwsh + run: | + $token = & "${{ env.SCRIPTS_PATH }}/Acquire-BearerToken.ps1" ` + -ClientId "${{ secrets.MCP_CLIENT_ID }}" ` + -TenantId "${{ secrets.MCP_TENANT_ID }}" ` + -Username "${{ secrets.MCP_TEST_USERNAME }}" ` + -Password "${{ secrets.MCP_TEST_PASSWORD }}" + echo "BEARER_TOKEN=$token" >> $env:GITHUB_OUTPUT + echo "::add-mask::$token" + + - name: Generate .env configuration + shell: pwsh + run: | + $configMappings = @{ + "NODE_ENV" = "development" + "directConnectUrl" = "${{ secrets.NODEJS_COPILOT_STUDIO_DIRECT_CONNECT_URL }}" + "environmentId" = "${{ secrets.NODEJS_COPILOT_STUDIO_ENVIRONMENT_ID }}" + "agentIdentifier" = "${{ secrets.NODEJS_COPILOT_STUDIO_AGENT_IDENTIFIER }}" + "connections__service_connection__settings__authType" = "ClientSecret" + "connections__service_connection__settings__clientId" = "${{ secrets.NODEJS_COPILOT_STUDIO_AGENT_ID }}" + "connections__service_connection__settings__clientSecret" = "${{ secrets.NODEJS_COPILOT_STUDIO_CLIENT_SECRET }}" + "connections__service_connection__settings__tenantId" = "${{ secrets.TENANT_ID }}" + "connectionsMap__0__serviceUrl" = "*" + "connectionsMap__0__connection" = "service_connection" + "agentic_scopes" = "https://api.powerplatform.com/.default" + } + & "${{ env.SCRIPTS_PATH }}/Generate-EnvConfig.ps1" ` + -OutputPath "${{ env.SAMPLE_PATH }}/.env" ` + -BearerToken "${{ steps.token.outputs.BEARER_TOKEN }}" ` + -Port ${{ env.AGENT_PORT }} ` + -ConfigMappings $configMappings + + - name: Start Agent + id: start-agent + shell: pwsh + run: | + $agentPid = & "${{ env.SCRIPTS_PATH }}/Start-Agent.ps1" ` + -AgentPath "${{ env.SAMPLE_PATH }}" ` + -StartCommand "node dist/index.js" ` + -Port ${{ env.AGENT_PORT }} ` + -BearerToken "${{ steps.token.outputs.BEARER_TOKEN }}" ` + -Environment "Development" ` + -Runtime "nodejs" + echo "AGENT_PID=$agentPid" >> $env:GITHUB_OUTPUT + + - name: Verify Agent Running + shell: pwsh + run: | + $agentPid = "${{ steps.start-agent.outputs.AGENT_PID }}" + if ($agentPid) { + try { + $proc = Get-Process -Id $agentPid -ErrorAction Stop + Write-Host "Agent process (PID: $agentPid) is running: $($proc.ProcessName)" -ForegroundColor Green + } catch { + Write-Host "ERROR: Agent process (PID: $agentPid) is NOT running!" -ForegroundColor Red + throw "Agent process has stopped" + } + } + $agentUrl = "http://localhost:${{ env.AGENT_PORT }}" + $healthResponse = Invoke-WebRequest -Uri "$agentUrl/api/health" -Method GET -UseBasicParsing -ErrorAction SilentlyContinue + Write-Host "Health check: $($healthResponse.StatusCode)" -ForegroundColor Green + + - name: Restore E2E Test Dependencies + run: dotnet restore "${{ env.E2E_TESTS_PATH }}/Agent365.E2E.Tests.csproj" + + - name: Run .NET E2E Tests + shell: pwsh + run: | + dotnet test "${{ env.E2E_TESTS_PATH }}/Agent365.E2E.Tests.csproj" ` + --no-restore ` + --verbosity normal ` + --logger "console;verbosity=detailed" ` + --logger "trx;LogFileName=test-results-nodejs-copilot-studio.trx" ` + --filter "Category=HTTP" + env: + AGENT_URL: http://localhost:${{ env.AGENT_PORT }} + TEST_RESULTS_DIR: ${{ runner.temp }}/TestConversations + + - name: Emit Test Conversations + if: always() + shell: pwsh + run: | + & "${{ env.SCRIPTS_PATH }}/Emit-TestConversations.ps1" ` + -TestResultsDir "${{ runner.temp }}/TestConversations" + + - name: Capture Agent Logs + if: always() + shell: pwsh + run: | + & "${{ env.SCRIPTS_PATH }}/Capture-AgentLogs.ps1" ` + -AgentPath "${{ env.SAMPLE_PATH }}" + + - name: Stop Agent Process + if: always() + shell: pwsh + run: | + $agentPid = "${{ steps.start-agent.outputs.AGENT_PID }}" + if ($agentPid) { + & "${{ env.SCRIPTS_PATH }}/Stop-AgentProcess.ps1" -AgentPID $agentPid + } + + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-nodejs-copilot-studio + path: | + ${{ env.E2E_TESTS_PATH }}/TestResults/**/*.trx + ${{ env.E2E_TESTS_PATH }}/TestResults/**/*-logs.txt + retention-days: 7 diff --git a/.github/workflows/e2e-orchestrator.yml b/.github/workflows/e2e-orchestrator.yml index ec6eeccd..3039535a 100644 --- a/.github/workflows/e2e-orchestrator.yml +++ b/.github/workflows/e2e-orchestrator.yml @@ -22,8 +22,11 @@ on: options: - '' - 'python-openai' + - 'python-af' + - 'python-google-adk' - 'nodejs-openai' - 'nodejs-langchain' + - 'nodejs-copilot-studio' - 'dotnet-sk' - 'dotnet-af' @@ -37,6 +40,18 @@ jobs: if: ${{ github.event.inputs.sample == '' || github.event.inputs.sample == 'python-openai' }} uses: ./.github/workflows/e2e-python-openai.yml secrets: inherit + + python-af: + name: Python Agent Framework E2E + if: ${{ github.event.inputs.sample == '' || github.event.inputs.sample == 'python-af' }} + uses: ./.github/workflows/e2e-python-agent-framework.yml + secrets: inherit + + python-google-adk: + name: Python Google ADK E2E + if: ${{ github.event.inputs.sample == '' || github.event.inputs.sample == 'python-google-adk' }} + uses: ./.github/workflows/e2e-python-google-adk.yml + secrets: inherit nodejs-openai: name: Node.js OpenAI E2E @@ -49,6 +64,12 @@ jobs: if: ${{ github.event.inputs.sample == '' || github.event.inputs.sample == 'nodejs-langchain' }} uses: ./.github/workflows/e2e-nodejs-langchain.yml secrets: inherit + + nodejs-copilot-studio: + name: Node.js Copilot Studio E2E + if: ${{ github.event.inputs.sample == '' || github.event.inputs.sample == 'nodejs-copilot-studio' }} + uses: ./.github/workflows/e2e-nodejs-copilot-studio.yml + secrets: inherit dotnet-sk: name: .NET Semantic Kernel E2E @@ -69,7 +90,7 @@ jobs: e2e-status: name: E2E Status runs-on: ubuntu-latest - needs: [python-openai, nodejs-openai, nodejs-langchain, dotnet-sk, dotnet-af] + needs: [python-openai, python-af, python-google-adk, nodejs-openai, nodejs-langchain, nodejs-copilot-studio, dotnet-sk, dotnet-af] if: always() steps: @@ -78,7 +99,7 @@ jobs: echo "Checking E2E test results..." # Get all job results (skipped jobs are fine, failed jobs are not) - results="${{ needs.python-openai.result }} ${{ needs.nodejs-openai.result }} ${{ needs.nodejs-langchain.result }} ${{ needs.dotnet-sk.result }} ${{ needs.dotnet-af.result }}" + results="${{ needs.python-openai.result }} ${{ needs.python-af.result }} ${{ needs.python-google-adk.result }} ${{ needs.nodejs-openai.result }} ${{ needs.nodejs-langchain.result }} ${{ needs.nodejs-copilot-studio.result }} ${{ needs.dotnet-sk.result }} ${{ needs.dotnet-af.result }}" echo "Job results: $results" @@ -97,8 +118,11 @@ jobs: # Determine overall status OVERALL_STATUS="✅ All Tests Passed" if [[ "${{ needs.python-openai.result }}" != "success" ]] || \ + [[ "${{ needs.python-af.result }}" != "success" ]] || \ + [[ "${{ needs.python-google-adk.result }}" != "success" ]] || \ [[ "${{ needs.nodejs-openai.result }}" != "success" ]] || \ [[ "${{ needs.nodejs-langchain.result }}" != "success" ]] || \ + [[ "${{ needs.nodejs-copilot-studio.result }}" != "success" ]] || \ [[ "${{ needs.dotnet-sk.result }}" != "success" ]] || \ [[ "${{ needs.dotnet-af.result }}" != "success" ]]; then OVERALL_STATUS="⚠️ Some Tests Failed" @@ -106,8 +130,11 @@ jobs: # Generate status icons using if-else for reliability if [[ "${{ needs.python-openai.result }}" == "success" ]]; then PYTHON_OPENAI_ICON="✅"; else PYTHON_OPENAI_ICON="❌"; fi + if [[ "${{ needs.python-af.result }}" == "success" ]]; then PYTHON_AF_ICON="✅"; else PYTHON_AF_ICON="❌"; fi + if [[ "${{ needs.python-google-adk.result }}" == "success" ]]; then PYTHON_GOOGLE_ADK_ICON="✅"; else PYTHON_GOOGLE_ADK_ICON="❌"; fi if [[ "${{ needs.nodejs-openai.result }}" == "success" ]]; then NODEJS_OPENAI_ICON="✅"; else NODEJS_OPENAI_ICON="❌"; fi if [[ "${{ needs.nodejs-langchain.result }}" == "success" ]]; then NODEJS_LANGCHAIN_ICON="✅"; else NODEJS_LANGCHAIN_ICON="❌"; fi + if [[ "${{ needs.nodejs-copilot-studio.result }}" == "success" ]]; then NODEJS_COPILOT_STUDIO_ICON="✅"; else NODEJS_COPILOT_STUDIO_ICON="❌"; fi if [[ "${{ needs.dotnet-sk.result }}" == "success" ]]; then DOTNET_SK_ICON="✅"; else DOTNET_SK_ICON="❌"; fi if [[ "${{ needs.dotnet-af.result }}" == "success" ]]; then DOTNET_AF_ICON="✅"; else DOTNET_AF_ICON="❌"; fi @@ -118,8 +145,11 @@ jobs: echo "| Sample | Status | Result |" >> $GITHUB_STEP_SUMMARY echo "|--------|--------|--------|" >> $GITHUB_STEP_SUMMARY echo "| Python OpenAI | $PYTHON_OPENAI_ICON | ${{ needs.python-openai.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Python Agent Framework | $PYTHON_AF_ICON | ${{ needs.python-af.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Python Google ADK | $PYTHON_GOOGLE_ADK_ICON | ${{ needs.python-google-adk.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Node.js OpenAI | $NODEJS_OPENAI_ICON | ${{ needs.nodejs-openai.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Node.js LangChain | $NODEJS_LANGCHAIN_ICON | ${{ needs.nodejs-langchain.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Node.js Copilot Studio | $NODEJS_COPILOT_STUDIO_ICON | ${{ needs.nodejs-copilot-studio.result }} |" >> $GITHUB_STEP_SUMMARY echo "| .NET Semantic Kernel | $DOTNET_SK_ICON | ${{ needs.dotnet-sk.result }} |" >> $GITHUB_STEP_SUMMARY echo "| .NET Agent Framework | $DOTNET_AF_ICON | ${{ needs.dotnet-af.result }} |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY @@ -131,14 +161,20 @@ jobs: with: script: | const pythonOpenaiIcon = '${{ needs.python-openai.result }}' === 'success' ? '✅' : '❌'; + const pythonAfIcon = '${{ needs.python-af.result }}' === 'success' ? '✅' : '❌'; + const pythonGoogleAdkIcon = '${{ needs.python-google-adk.result }}' === 'success' ? '✅' : '❌'; const nodejsOpenaiIcon = '${{ needs.nodejs-openai.result }}' === 'success' ? '✅' : '❌'; const nodejsLangchainIcon = '${{ needs.nodejs-langchain.result }}' === 'success' ? '✅' : '❌'; + const nodejsCopilotStudioIcon = '${{ needs.nodejs-copilot-studio.result }}' === 'success' ? '✅' : '❌'; const dotnetSkIcon = '${{ needs.dotnet-sk.result }}' === 'success' ? '✅' : '❌'; const dotnetAfIcon = '${{ needs.dotnet-af.result }}' === 'success' ? '✅' : '❌'; const allPassed = '${{ needs.python-openai.result }}' === 'success' && + '${{ needs.python-af.result }}' === 'success' && + '${{ needs.python-google-adk.result }}' === 'success' && '${{ needs.nodejs-openai.result }}' === 'success' && '${{ needs.nodejs-langchain.result }}' === 'success' && + '${{ needs.nodejs-copilot-studio.result }}' === 'success' && '${{ needs.dotnet-sk.result }}' === 'success' && '${{ needs.dotnet-af.result }}' === 'success'; @@ -150,8 +186,11 @@ jobs: '| Sample | Status | Result |', '|--------|--------|--------|', `| Python OpenAI | ${pythonOpenaiIcon} | ${{ needs.python-openai.result }} |`, + `| Python Agent Framework | ${pythonAfIcon} | ${{ needs.python-af.result }} |`, + `| Python Google ADK | ${pythonGoogleAdkIcon} | ${{ needs.python-google-adk.result }} |`, `| Node.js OpenAI | ${nodejsOpenaiIcon} | ${{ needs.nodejs-openai.result }} |`, `| Node.js LangChain | ${nodejsLangchainIcon} | ${{ needs.nodejs-langchain.result }} |`, + `| Node.js Copilot Studio | ${nodejsCopilotStudioIcon} | ${{ needs.nodejs-copilot-studio.result }} |`, `| .NET Semantic Kernel | ${dotnetSkIcon} | ${{ needs.dotnet-sk.result }} |`, `| .NET Agent Framework | ${dotnetAfIcon} | ${{ needs.dotnet-af.result }} |`, '', diff --git a/.github/workflows/e2e-python-agent-framework.yml b/.github/workflows/e2e-python-agent-framework.yml new file mode 100644 index 00000000..8770b1da --- /dev/null +++ b/.github/workflows/e2e-python-agent-framework.yml @@ -0,0 +1,192 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +name: E2E - Python Agent Framework + +on: + workflow_call: # Allow orchestrator to call this workflow + workflow_dispatch: # Allow manual triggering + push: + branches: [main] + paths: + - 'python/agent-framework/**' + - 'scripts/e2e/**' + - '.github/workflows/e2e-python-agent-framework.yml' + pull_request: + branches: [main] + paths: + - 'python/agent-framework/**' + - 'scripts/e2e/**' + +permissions: + contents: read + +env: + SAMPLE_PATH: python/agent-framework/sample-agent + AGENT_PORT: 3979 + SCRIPTS_PATH: scripts/e2e + E2E_TESTS_PATH: tests/e2e + +jobs: + python-agent-framework: + name: Python Agent Framework Agent + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install uv package manager + run: pip install uv + + - name: Install dependencies + working-directory: ${{ env.SAMPLE_PATH }} + run: uv sync + + - name: Log SDK Versions + shell: pwsh + run: | + & "${{ env.SCRIPTS_PATH }}/Get-SDKVersions.ps1" ` + -Runtime "python" ` + -WorkingDirectory "${{ env.SAMPLE_PATH }}" + + - name: Acquire Bearer Token (ROPC) + id: token + shell: pwsh + run: | + $token = & "${{ env.SCRIPTS_PATH }}/Acquire-BearerToken.ps1" ` + -ClientId "${{ secrets.MCP_CLIENT_ID }}" ` + -TenantId "${{ secrets.MCP_TENANT_ID }}" ` + -Username "${{ secrets.MCP_TEST_USERNAME }}" ` + -Password "${{ secrets.MCP_TEST_PASSWORD }}" + echo "BEARER_TOKEN=$token" >> $env:GITHUB_OUTPUT + echo "::add-mask::$token" + + - name: Copy ToolingManifest.json + shell: pwsh + run: | + & "${{ env.SCRIPTS_PATH }}/Copy-ToolingManifest.ps1" -TargetPath "${{ env.SAMPLE_PATH }}" + + - name: Generate .env configuration + shell: pwsh + run: | + $configMappings = @{ + "AZURE_OPENAI_API_KEY" = "${{ secrets.PYTHON_AF_AZURE_OPENAI_API_KEY }}" + "AZURE_OPENAI_ENDPOINT" = "${{ secrets.PYTHON_AF_AZURE_OPENAI_ENDPOINT }}" + "AZURE_OPENAI_DEPLOYMENT" = "${{ secrets.PYTHON_AF_AZURE_OPENAI_DEPLOYMENT }}" + "AZURE_OPENAI_API_VERSION" = "2024-12-01-preview" + "USE_AGENTIC_AUTH" = "false" + "MCP_PLATFORM_ENDPOINT" = "https://agent365.svc.cloud.microsoft" + "AGENT_ID" = "${{ secrets.PYTHON_AF_AGENT_ID }}" + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__AUTHTYPE" = "ClientSecret" + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID" = "${{ secrets.PYTHON_AF_AGENT_ID }}" + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET" = "${{ secrets.PYTHON_AF_CLIENT_SECRET }}" + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID" = "${{ secrets.TENANT_ID }}" + "CONNECTIONSMAP__0__SERVICEURL" = "*" + "CONNECTIONSMAP__0__CONNECTION" = "SERVICE_CONNECTION" + "ENABLE_OBSERVABILITY" = "true" + } + & "${{ env.SCRIPTS_PATH }}/Generate-EnvConfig.ps1" ` + -OutputPath "${{ env.SAMPLE_PATH }}/.env" ` + -BearerToken "${{ steps.token.outputs.BEARER_TOKEN }}" ` + -Port ${{ env.AGENT_PORT }} ` + -ConfigMappings $configMappings + + - name: Start Agent + id: start-agent + shell: pwsh + run: | + $agentPid = & "${{ env.SCRIPTS_PATH }}/Start-Agent.ps1" ` + -AgentPath "${{ env.SAMPLE_PATH }}" ` + -StartCommand "uv run python start_with_generic_host.py" ` + -Port ${{ env.AGENT_PORT }} ` + -BearerToken "${{ steps.token.outputs.BEARER_TOKEN }}" ` + -Environment "Development" ` + -Runtime "python" + echo "AGENT_PID=$agentPid" >> $env:GITHUB_OUTPUT + + - name: Verify Agent Running + shell: pwsh + run: | + Write-Host "=== Verifying Agent ===" -ForegroundColor Cyan + + $agentPid = "${{ steps.start-agent.outputs.AGENT_PID }}" + Write-Host "Checking agent process (PID: $agentPid)..." -ForegroundColor Gray + + if ($agentPid) { + try { + $proc = Get-Process -Id $agentPid -ErrorAction Stop + Write-Host "Agent process (PID: $agentPid) is running: $($proc.ProcessName)" -ForegroundColor Green + } catch { + Write-Host "ERROR: Agent process (PID: $agentPid) is NOT running!" -ForegroundColor Red + + $logFile = "${{ env.SAMPLE_PATH }}/agent.log" + if (Test-Path $logFile) { + Write-Host "Agent logs:" -ForegroundColor Yellow + Get-Content $logFile + } + throw "Agent process has stopped" + } + } + + $agentUrl = "http://localhost:${{ env.AGENT_PORT }}" + Write-Host "Verifying agent at $agentUrl..." -ForegroundColor Gray + $healthResponse = Invoke-WebRequest -Uri "$agentUrl/api/health" -Method GET -UseBasicParsing -ErrorAction SilentlyContinue + Write-Host "Health check: $($healthResponse.StatusCode)" -ForegroundColor Green + + - name: Restore E2E Test Dependencies + shell: pwsh + run: | + dotnet restore "${{ env.E2E_TESTS_PATH }}/Agent365.E2E.Tests.csproj" + + - name: Run .NET E2E Tests + shell: pwsh + run: | + dotnet test "${{ env.E2E_TESTS_PATH }}/Agent365.E2E.Tests.csproj" ` + --no-restore ` + --filter "Category=HTTP" ` + --logger "trx;LogFileName=test-results.trx" ` + --results-directory "${{ runner.temp }}/TestResults" + env: + AGENT_PORT: ${{ env.AGENT_PORT }} + AGENT_URL: http://localhost:${{ env.AGENT_PORT }} + TEST_RESULTS_DIR: ${{ runner.temp }}/TestConversations + + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-python-agent-framework + path: ${{ runner.temp }}/TestResults/ + retention-days: 30 + + - name: Emit Test Conversations + if: always() + shell: pwsh + run: | + & "${{ env.SCRIPTS_PATH }}/Emit-TestConversations.ps1" ` + -TestResultsDir "${{ runner.temp }}/TestConversations" + + - name: Capture Agent Logs + if: always() + shell: pwsh + run: | + & "${{ env.SCRIPTS_PATH }}/Capture-AgentLogs.ps1" -AgentPath "${{ env.SAMPLE_PATH }}" + + - name: Cleanup Agent Process + if: always() + shell: pwsh + run: | + & "${{ env.SCRIPTS_PATH }}/Stop-AgentProcess.ps1" ` + -AgentPID "${{ steps.start-agent.outputs.AGENT_PID }}" ` + -Port ${{ env.AGENT_PORT }} diff --git a/.github/workflows/e2e-python-google-adk.yml b/.github/workflows/e2e-python-google-adk.yml new file mode 100644 index 00000000..d72ba29d --- /dev/null +++ b/.github/workflows/e2e-python-google-adk.yml @@ -0,0 +1,189 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +name: E2E - Python Google ADK + +on: + workflow_call: # Allow orchestrator to call this workflow + workflow_dispatch: # Allow manual triggering + push: + branches: [main] + paths: + - 'python/google-adk/**' + - 'scripts/e2e/**' + - '.github/workflows/e2e-python-google-adk.yml' + pull_request: + branches: [main] + paths: + - 'python/google-adk/**' + - 'scripts/e2e/**' + +permissions: + contents: read + +env: + SAMPLE_PATH: python/google-adk/sample-agent + AGENT_PORT: 3978 + SCRIPTS_PATH: scripts/e2e + E2E_TESTS_PATH: tests/e2e + +jobs: + python-google-adk: + name: Python Google ADK Agent + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install uv package manager + run: pip install uv + + - name: Install dependencies + working-directory: ${{ env.SAMPLE_PATH }} + run: uv sync + + - name: Log SDK Versions + shell: pwsh + run: | + & "${{ env.SCRIPTS_PATH }}/Get-SDKVersions.ps1" ` + -Runtime "python" ` + -WorkingDirectory "${{ env.SAMPLE_PATH }}" + + - name: Acquire Bearer Token (ROPC) + id: token + shell: pwsh + run: | + $token = & "${{ env.SCRIPTS_PATH }}/Acquire-BearerToken.ps1" ` + -ClientId "${{ secrets.MCP_CLIENT_ID }}" ` + -TenantId "${{ secrets.MCP_TENANT_ID }}" ` + -Username "${{ secrets.MCP_TEST_USERNAME }}" ` + -Password "${{ secrets.MCP_TEST_PASSWORD }}" + echo "BEARER_TOKEN=$token" >> $env:GITHUB_OUTPUT + echo "::add-mask::$token" + + - name: Copy ToolingManifest.json + shell: pwsh + run: | + & "${{ env.SCRIPTS_PATH }}/Copy-ToolingManifest.ps1" -TargetPath "${{ env.SAMPLE_PATH }}" + + - name: Generate .env configuration + shell: pwsh + run: | + $configMappings = @{ + "GOOGLE_API_KEY" = "${{ secrets.PYTHON_GOOGLE_ADK_API_KEY }}" + "GOOGLE_GENAI_USE_VERTEXAI" = "FALSE" + "USE_AGENTIC_AUTH" = "false" + "MCP_PLATFORM_ENDPOINT" = "https://agent365.svc.cloud.microsoft" + "AGENT_ID" = "${{ secrets.PYTHON_GOOGLE_ADK_AGENT_ID }}" + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__AUTHTYPE" = "ClientSecret" + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID" = "${{ secrets.PYTHON_GOOGLE_ADK_AGENT_ID }}" + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET" = "${{ secrets.PYTHON_GOOGLE_ADK_CLIENT_SECRET }}" + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID" = "${{ secrets.TENANT_ID }}" + "CONNECTIONSMAP__0__SERVICEURL" = "*" + "CONNECTIONSMAP__0__CONNECTION" = "SERVICE_CONNECTION" + } + & "${{ env.SCRIPTS_PATH }}/Generate-EnvConfig.ps1" ` + -OutputPath "${{ env.SAMPLE_PATH }}/.env" ` + -BearerToken "${{ steps.token.outputs.BEARER_TOKEN }}" ` + -Port ${{ env.AGENT_PORT }} ` + -ConfigMappings $configMappings + + - name: Start Agent + id: start-agent + shell: pwsh + run: | + $agentPid = & "${{ env.SCRIPTS_PATH }}/Start-Agent.ps1" ` + -AgentPath "${{ env.SAMPLE_PATH }}" ` + -StartCommand "uv run python main.py" ` + -Port ${{ env.AGENT_PORT }} ` + -BearerToken "${{ steps.token.outputs.BEARER_TOKEN }}" ` + -Environment "Development" ` + -Runtime "python" + echo "AGENT_PID=$agentPid" >> $env:GITHUB_OUTPUT + + - name: Verify Agent Running + shell: pwsh + run: | + Write-Host "=== Verifying Agent ===" -ForegroundColor Cyan + + $agentPid = "${{ steps.start-agent.outputs.AGENT_PID }}" + Write-Host "Checking agent process (PID: $agentPid)..." -ForegroundColor Gray + + if ($agentPid) { + try { + $proc = Get-Process -Id $agentPid -ErrorAction Stop + Write-Host "Agent process (PID: $agentPid) is running: $($proc.ProcessName)" -ForegroundColor Green + } catch { + Write-Host "ERROR: Agent process (PID: $agentPid) is NOT running!" -ForegroundColor Red + + $logFile = "${{ env.SAMPLE_PATH }}/agent.log" + if (Test-Path $logFile) { + Write-Host "Agent logs:" -ForegroundColor Yellow + Get-Content $logFile + } + throw "Agent process has stopped" + } + } + + $agentUrl = "http://localhost:${{ env.AGENT_PORT }}" + Write-Host "Verifying agent at $agentUrl..." -ForegroundColor Gray + $healthResponse = Invoke-WebRequest -Uri "$agentUrl/api/health" -Method GET -UseBasicParsing -ErrorAction SilentlyContinue + Write-Host "Health check: $($healthResponse.StatusCode)" -ForegroundColor Green + + - name: Restore E2E Test Dependencies + shell: pwsh + run: | + dotnet restore "${{ env.E2E_TESTS_PATH }}/Agent365.E2E.Tests.csproj" + + - name: Run .NET E2E Tests + shell: pwsh + run: | + dotnet test "${{ env.E2E_TESTS_PATH }}/Agent365.E2E.Tests.csproj" ` + --no-restore ` + --filter "Category=HTTP" ` + --logger "trx;LogFileName=test-results.trx" ` + --results-directory "${{ runner.temp }}/TestResults" + env: + AGENT_PORT: ${{ env.AGENT_PORT }} + AGENT_URL: http://localhost:${{ env.AGENT_PORT }} + TEST_RESULTS_DIR: ${{ runner.temp }}/TestConversations + + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-python-google-adk + path: ${{ runner.temp }}/TestResults/ + retention-days: 30 + + - name: Emit Test Conversations + if: always() + shell: pwsh + run: | + & "${{ env.SCRIPTS_PATH }}/Emit-TestConversations.ps1" ` + -TestResultsDir "${{ runner.temp }}/TestConversations" + + - name: Capture Agent Logs + if: always() + shell: pwsh + run: | + & "${{ env.SCRIPTS_PATH }}/Capture-AgentLogs.ps1" -AgentPath "${{ env.SAMPLE_PATH }}" + + - name: Cleanup Agent Process + if: always() + shell: pwsh + run: | + & "${{ env.SCRIPTS_PATH }}/Stop-AgentProcess.ps1" ` + -AgentPID "${{ steps.start-agent.outputs.AGENT_PID }}" ` + -Port ${{ env.AGENT_PORT }} diff --git a/README.md b/README.md index 26005e68..26c5a2a5 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,11 @@ This repository contains sample agents and prompts for building with the Microso | Sample | Status | |--------|--------| | Python OpenAI | [![Python OpenAI](https://img.shields.io/github/actions/workflow/status/microsoft/Agent365-Samples/e2e-python-openai.yml?branch=main&label=E2E)](https://github.com/microsoft/Agent365-Samples/actions/workflows/e2e-python-openai.yml) | +| Python Agent Framework | [![Python AF](https://img.shields.io/github/actions/workflow/status/microsoft/Agent365-Samples/e2e-python-agent-framework.yml?branch=main&label=E2E)](https://github.com/microsoft/Agent365-Samples/actions/workflows/e2e-python-agent-framework.yml) | +| Python Google ADK | [![Python Google ADK](https://img.shields.io/github/actions/workflow/status/microsoft/Agent365-Samples/e2e-python-google-adk.yml?branch=main&label=E2E)](https://github.com/microsoft/Agent365-Samples/actions/workflows/e2e-python-google-adk.yml) | | Node.js OpenAI | [![Node.js OpenAI](https://img.shields.io/github/actions/workflow/status/microsoft/Agent365-Samples/e2e-nodejs-openai.yml?branch=main&label=E2E)](https://github.com/microsoft/Agent365-Samples/actions/workflows/e2e-nodejs-openai.yml) | | Node.js LangChain | [![Node.js LangChain](https://img.shields.io/github/actions/workflow/status/microsoft/Agent365-Samples/e2e-nodejs-langchain.yml?branch=main&label=E2E)](https://github.com/microsoft/Agent365-Samples/actions/workflows/e2e-nodejs-langchain.yml) | +| Node.js Copilot Studio | [![Node.js Copilot Studio](https://img.shields.io/github/actions/workflow/status/microsoft/Agent365-Samples/e2e-nodejs-copilot-studio.yml?branch=main&label=E2E)](https://github.com/microsoft/Agent365-Samples/actions/workflows/e2e-nodejs-copilot-studio.yml) | | .NET Semantic Kernel | [![.NET SK](https://img.shields.io/github/actions/workflow/status/microsoft/Agent365-Samples/e2e-dotnet-semantic-kernel.yml?branch=main&label=E2E)](https://github.com/microsoft/Agent365-Samples/actions/workflows/e2e-dotnet-semantic-kernel.yml) | | .NET Agent Framework | [![.NET AF](https://img.shields.io/github/actions/workflow/status/microsoft/Agent365-Samples/e2e-dotnet-agent-framework.yml?branch=main&label=E2E)](https://github.com/microsoft/Agent365-Samples/actions/workflows/e2e-dotnet-agent-framework.yml) | diff --git a/nodejs/copilot-studio/sample-agent/src/index.ts b/nodejs/copilot-studio/sample-agent/src/index.ts index c5ca76ea..3e93cde7 100644 --- a/nodejs/copilot-studio/sample-agent/src/index.ts +++ b/nodejs/copilot-studio/sample-agent/src/index.ts @@ -16,6 +16,12 @@ const authConfig: AuthConfiguration = isProduction ? loadAuthConfigFromEnv() : { const server: Express = express() server.use(express.json()) + +// Health endpoint - before auth middleware so it's always accessible +server.get('/api/health', (_req, res: Response) => { + res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() }); +}); + server.use(authorizeJWT(authConfig)) server.post('/api/messages', (req: Request, res: Response) => { diff --git a/python/google-adk/sample-agent/main.py b/python/google-adk/sample-agent/main.py index f1e85977..de482869 100644 --- a/python/google-adk/sample-agent/main.py +++ b/python/google-adk/sample-agent/main.py @@ -52,8 +52,13 @@ async def anonymous_claims(request, handler): if (auth_config and isProduction): middlewares.append(jwt_authorization_middleware) + # Health endpoint + async def health(_req: Request) -> Response: + return Response(text='{"status": "healthy"}', content_type='application/json') + # Configure App app = Application(middlewares=middlewares) + app.router.add_get("/api/health", health) app.router.add_post("/api/messages", entry_point) app["agent_configuration"] = auth_config From 043fcd4574ab0845e729189a4f88f215a5f9b1d4 Mon Sep 17 00:00:00 2001 From: abdulanu0 Date: Mon, 2 Feb 2026 18:12:22 -0800 Subject: [PATCH 02/13] fix: Use existing Python OpenAI secrets for Agent Framework sample --- .github/workflows/e2e-python-agent-framework.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e-python-agent-framework.yml b/.github/workflows/e2e-python-agent-framework.yml index 8770b1da..a4f3c05f 100644 --- a/.github/workflows/e2e-python-agent-framework.yml +++ b/.github/workflows/e2e-python-agent-framework.yml @@ -81,16 +81,16 @@ jobs: shell: pwsh run: | $configMappings = @{ - "AZURE_OPENAI_API_KEY" = "${{ secrets.PYTHON_AF_AZURE_OPENAI_API_KEY }}" - "AZURE_OPENAI_ENDPOINT" = "${{ secrets.PYTHON_AF_AZURE_OPENAI_ENDPOINT }}" - "AZURE_OPENAI_DEPLOYMENT" = "${{ secrets.PYTHON_AF_AZURE_OPENAI_DEPLOYMENT }}" + "AZURE_OPENAI_API_KEY" = "${{ secrets.PYTHON_OPENAI_AZURE_OPENAI_API_KEY }}" + "AZURE_OPENAI_ENDPOINT" = "${{ secrets.PYTHON_OPENAI_AZURE_OPENAI_ENDPOINT }}" + "AZURE_OPENAI_DEPLOYMENT" = "${{ secrets.PYTHON_OPENAI_AZURE_OPENAI_DEPLOYMENT }}" "AZURE_OPENAI_API_VERSION" = "2024-12-01-preview" "USE_AGENTIC_AUTH" = "false" "MCP_PLATFORM_ENDPOINT" = "https://agent365.svc.cloud.microsoft" - "AGENT_ID" = "${{ secrets.PYTHON_AF_AGENT_ID }}" + "AGENT_ID" = "${{ secrets.PYTHON_OPENAI_AGENT_ID }}" "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__AUTHTYPE" = "ClientSecret" - "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID" = "${{ secrets.PYTHON_AF_AGENT_ID }}" - "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET" = "${{ secrets.PYTHON_AF_CLIENT_SECRET }}" + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID" = "${{ secrets.PYTHON_OPENAI_AGENT_ID }}" + "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET" = "${{ secrets.PYTHON_OPENAI_CLIENT_SECRET }}" "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID" = "${{ secrets.TENANT_ID }}" "CONNECTIONSMAP__0__SERVICEURL" = "*" "CONNECTIONSMAP__0__CONNECTION" = "SERVICE_CONNECTION" From 1e0240918c420e111c62e871798f3bf5de521683 Mon Sep 17 00:00:00 2001 From: abdulanu0 Date: Mon, 2 Feb 2026 18:22:18 -0800 Subject: [PATCH 03/13] fix: Improve PR comment posting in e2e-orchestrator --- .github/workflows/e2e-orchestrator.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e-orchestrator.yml b/.github/workflows/e2e-orchestrator.yml index 3039535a..41cdf007 100644 --- a/.github/workflows/e2e-orchestrator.yml +++ b/.github/workflows/e2e-orchestrator.yml @@ -156,7 +156,7 @@ jobs: echo "> 📦 **SDK Versions**: Click on each sample workflow above to view detailed SDK version information in the step summary." >> $GITHUB_STEP_SUMMARY - name: Post PR Comment - if: github.event_name == 'pull_request' + if: always() && github.event_name == 'pull_request' && github.event.pull_request.number uses: actions/github-script@v7 with: script: | @@ -199,14 +199,18 @@ jobs: `[View full test details](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})` ].join('\n'); + // Get PR number from event context + const prNumber = ${{ github.event.pull_request.number }}; + console.log(`PR number: ${prNumber}`); + // Find existing comment const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, - issue_number: context.issue.number, + issue_number: prNumber, }); - console.log(`Found ${comments.length} comments on PR #${context.issue.number}`); + console.log(`Found ${comments.length} comments on PR #${prNumber}`); const botComment = comments.find(comment => comment.user.login === 'github-actions[bot]' && @@ -231,7 +235,7 @@ jobs: const result = await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, - issue_number: context.issue.number, + issue_number: prNumber, body: body }); console.log(`Create result: ${result.status}, comment ID: ${result.data.id}`); From c64a3afd942597fa5e783889cdd93129b295de1e Mon Sep 17 00:00:00 2001 From: abdulanu0 Date: Thu, 5 Feb 2026 11:30:42 -0800 Subject: [PATCH 04/13] fix: Remove Node.js Copilot Studio from E2E tests --- .../workflows/e2e-nodejs-copilot-studio.yml | 180 ------------------ .github/workflows/e2e-orchestrator.yml | 17 +- README.md | 1 - 3 files changed, 2 insertions(+), 196 deletions(-) delete mode 100644 .github/workflows/e2e-nodejs-copilot-studio.yml diff --git a/.github/workflows/e2e-nodejs-copilot-studio.yml b/.github/workflows/e2e-nodejs-copilot-studio.yml deleted file mode 100644 index 5c2fce2f..00000000 --- a/.github/workflows/e2e-nodejs-copilot-studio.yml +++ /dev/null @@ -1,180 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -name: E2E - Node.js Copilot Studio - -on: - workflow_call: # Allow orchestrator to call this workflow - workflow_dispatch: # Allow manual triggering - push: - branches: [main] - paths: - - 'nodejs/copilot-studio/**' - - 'scripts/e2e/**' - - '.github/workflows/e2e-nodejs-copilot-studio.yml' - pull_request: - branches: [main] - paths: - - 'nodejs/copilot-studio/**' - - 'scripts/e2e/**' - -permissions: - contents: read - -env: - SAMPLE_PATH: nodejs/copilot-studio/sample-agent - AGENT_PORT: 3978 - SCRIPTS_PATH: scripts/e2e - E2E_TESTS_PATH: tests/e2e - -jobs: - nodejs-copilot-studio: - name: Node.js Copilot Studio Agent - runs-on: windows-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '8.0.x' - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install dependencies - working-directory: ${{ env.SAMPLE_PATH }} - run: | - if (Test-Path "package-lock.json") { - npm ci - } else { - npm install - } - if (Test-Path "tsconfig.json") { - npm run build - } - shell: pwsh - - - name: Log SDK Versions - shell: pwsh - run: | - & "${{ env.SCRIPTS_PATH }}/Get-SDKVersions.ps1" ` - -Runtime "nodejs" ` - -WorkingDirectory "${{ env.SAMPLE_PATH }}" - - - name: Acquire Bearer Token (ROPC) - id: token - shell: pwsh - run: | - $token = & "${{ env.SCRIPTS_PATH }}/Acquire-BearerToken.ps1" ` - -ClientId "${{ secrets.MCP_CLIENT_ID }}" ` - -TenantId "${{ secrets.MCP_TENANT_ID }}" ` - -Username "${{ secrets.MCP_TEST_USERNAME }}" ` - -Password "${{ secrets.MCP_TEST_PASSWORD }}" - echo "BEARER_TOKEN=$token" >> $env:GITHUB_OUTPUT - echo "::add-mask::$token" - - - name: Generate .env configuration - shell: pwsh - run: | - $configMappings = @{ - "NODE_ENV" = "development" - "directConnectUrl" = "${{ secrets.NODEJS_COPILOT_STUDIO_DIRECT_CONNECT_URL }}" - "environmentId" = "${{ secrets.NODEJS_COPILOT_STUDIO_ENVIRONMENT_ID }}" - "agentIdentifier" = "${{ secrets.NODEJS_COPILOT_STUDIO_AGENT_IDENTIFIER }}" - "connections__service_connection__settings__authType" = "ClientSecret" - "connections__service_connection__settings__clientId" = "${{ secrets.NODEJS_COPILOT_STUDIO_AGENT_ID }}" - "connections__service_connection__settings__clientSecret" = "${{ secrets.NODEJS_COPILOT_STUDIO_CLIENT_SECRET }}" - "connections__service_connection__settings__tenantId" = "${{ secrets.TENANT_ID }}" - "connectionsMap__0__serviceUrl" = "*" - "connectionsMap__0__connection" = "service_connection" - "agentic_scopes" = "https://api.powerplatform.com/.default" - } - & "${{ env.SCRIPTS_PATH }}/Generate-EnvConfig.ps1" ` - -OutputPath "${{ env.SAMPLE_PATH }}/.env" ` - -BearerToken "${{ steps.token.outputs.BEARER_TOKEN }}" ` - -Port ${{ env.AGENT_PORT }} ` - -ConfigMappings $configMappings - - - name: Start Agent - id: start-agent - shell: pwsh - run: | - $agentPid = & "${{ env.SCRIPTS_PATH }}/Start-Agent.ps1" ` - -AgentPath "${{ env.SAMPLE_PATH }}" ` - -StartCommand "node dist/index.js" ` - -Port ${{ env.AGENT_PORT }} ` - -BearerToken "${{ steps.token.outputs.BEARER_TOKEN }}" ` - -Environment "Development" ` - -Runtime "nodejs" - echo "AGENT_PID=$agentPid" >> $env:GITHUB_OUTPUT - - - name: Verify Agent Running - shell: pwsh - run: | - $agentPid = "${{ steps.start-agent.outputs.AGENT_PID }}" - if ($agentPid) { - try { - $proc = Get-Process -Id $agentPid -ErrorAction Stop - Write-Host "Agent process (PID: $agentPid) is running: $($proc.ProcessName)" -ForegroundColor Green - } catch { - Write-Host "ERROR: Agent process (PID: $agentPid) is NOT running!" -ForegroundColor Red - throw "Agent process has stopped" - } - } - $agentUrl = "http://localhost:${{ env.AGENT_PORT }}" - $healthResponse = Invoke-WebRequest -Uri "$agentUrl/api/health" -Method GET -UseBasicParsing -ErrorAction SilentlyContinue - Write-Host "Health check: $($healthResponse.StatusCode)" -ForegroundColor Green - - - name: Restore E2E Test Dependencies - run: dotnet restore "${{ env.E2E_TESTS_PATH }}/Agent365.E2E.Tests.csproj" - - - name: Run .NET E2E Tests - shell: pwsh - run: | - dotnet test "${{ env.E2E_TESTS_PATH }}/Agent365.E2E.Tests.csproj" ` - --no-restore ` - --verbosity normal ` - --logger "console;verbosity=detailed" ` - --logger "trx;LogFileName=test-results-nodejs-copilot-studio.trx" ` - --filter "Category=HTTP" - env: - AGENT_URL: http://localhost:${{ env.AGENT_PORT }} - TEST_RESULTS_DIR: ${{ runner.temp }}/TestConversations - - - name: Emit Test Conversations - if: always() - shell: pwsh - run: | - & "${{ env.SCRIPTS_PATH }}/Emit-TestConversations.ps1" ` - -TestResultsDir "${{ runner.temp }}/TestConversations" - - - name: Capture Agent Logs - if: always() - shell: pwsh - run: | - & "${{ env.SCRIPTS_PATH }}/Capture-AgentLogs.ps1" ` - -AgentPath "${{ env.SAMPLE_PATH }}" - - - name: Stop Agent Process - if: always() - shell: pwsh - run: | - $agentPid = "${{ steps.start-agent.outputs.AGENT_PID }}" - if ($agentPid) { - & "${{ env.SCRIPTS_PATH }}/Stop-AgentProcess.ps1" -AgentPID $agentPid - } - - - name: Upload Test Results - if: always() - uses: actions/upload-artifact@v4 - with: - name: test-results-nodejs-copilot-studio - path: | - ${{ env.E2E_TESTS_PATH }}/TestResults/**/*.trx - ${{ env.E2E_TESTS_PATH }}/TestResults/**/*-logs.txt - retention-days: 7 diff --git a/.github/workflows/e2e-orchestrator.yml b/.github/workflows/e2e-orchestrator.yml index 41cdf007..fa98e620 100644 --- a/.github/workflows/e2e-orchestrator.yml +++ b/.github/workflows/e2e-orchestrator.yml @@ -26,7 +26,6 @@ on: - 'python-google-adk' - 'nodejs-openai' - 'nodejs-langchain' - - 'nodejs-copilot-studio' - 'dotnet-sk' - 'dotnet-af' @@ -65,12 +64,6 @@ jobs: uses: ./.github/workflows/e2e-nodejs-langchain.yml secrets: inherit - nodejs-copilot-studio: - name: Node.js Copilot Studio E2E - if: ${{ github.event.inputs.sample == '' || github.event.inputs.sample == 'nodejs-copilot-studio' }} - uses: ./.github/workflows/e2e-nodejs-copilot-studio.yml - secrets: inherit - dotnet-sk: name: .NET Semantic Kernel E2E if: ${{ github.event.inputs.sample == '' || github.event.inputs.sample == 'dotnet-sk' }} @@ -90,7 +83,7 @@ jobs: e2e-status: name: E2E Status runs-on: ubuntu-latest - needs: [python-openai, python-af, python-google-adk, nodejs-openai, nodejs-langchain, nodejs-copilot-studio, dotnet-sk, dotnet-af] + needs: [python-openai, python-af, python-google-adk, nodejs-openai, nodejs-langchain, dotnet-sk, dotnet-af] if: always() steps: @@ -99,7 +92,7 @@ jobs: echo "Checking E2E test results..." # Get all job results (skipped jobs are fine, failed jobs are not) - results="${{ needs.python-openai.result }} ${{ needs.python-af.result }} ${{ needs.python-google-adk.result }} ${{ needs.nodejs-openai.result }} ${{ needs.nodejs-langchain.result }} ${{ needs.nodejs-copilot-studio.result }} ${{ needs.dotnet-sk.result }} ${{ needs.dotnet-af.result }}" + results="${{ needs.python-openai.result }} ${{ needs.python-af.result }} ${{ needs.python-google-adk.result }} ${{ needs.nodejs-openai.result }} ${{ needs.nodejs-langchain.result }} ${{ needs.dotnet-sk.result }} ${{ needs.dotnet-af.result }}" echo "Job results: $results" @@ -122,7 +115,6 @@ jobs: [[ "${{ needs.python-google-adk.result }}" != "success" ]] || \ [[ "${{ needs.nodejs-openai.result }}" != "success" ]] || \ [[ "${{ needs.nodejs-langchain.result }}" != "success" ]] || \ - [[ "${{ needs.nodejs-copilot-studio.result }}" != "success" ]] || \ [[ "${{ needs.dotnet-sk.result }}" != "success" ]] || \ [[ "${{ needs.dotnet-af.result }}" != "success" ]]; then OVERALL_STATUS="⚠️ Some Tests Failed" @@ -134,7 +126,6 @@ jobs: if [[ "${{ needs.python-google-adk.result }}" == "success" ]]; then PYTHON_GOOGLE_ADK_ICON="✅"; else PYTHON_GOOGLE_ADK_ICON="❌"; fi if [[ "${{ needs.nodejs-openai.result }}" == "success" ]]; then NODEJS_OPENAI_ICON="✅"; else NODEJS_OPENAI_ICON="❌"; fi if [[ "${{ needs.nodejs-langchain.result }}" == "success" ]]; then NODEJS_LANGCHAIN_ICON="✅"; else NODEJS_LANGCHAIN_ICON="❌"; fi - if [[ "${{ needs.nodejs-copilot-studio.result }}" == "success" ]]; then NODEJS_COPILOT_STUDIO_ICON="✅"; else NODEJS_COPILOT_STUDIO_ICON="❌"; fi if [[ "${{ needs.dotnet-sk.result }}" == "success" ]]; then DOTNET_SK_ICON="✅"; else DOTNET_SK_ICON="❌"; fi if [[ "${{ needs.dotnet-af.result }}" == "success" ]]; then DOTNET_AF_ICON="✅"; else DOTNET_AF_ICON="❌"; fi @@ -149,7 +140,6 @@ jobs: echo "| Python Google ADK | $PYTHON_GOOGLE_ADK_ICON | ${{ needs.python-google-adk.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Node.js OpenAI | $NODEJS_OPENAI_ICON | ${{ needs.nodejs-openai.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Node.js LangChain | $NODEJS_LANGCHAIN_ICON | ${{ needs.nodejs-langchain.result }} |" >> $GITHUB_STEP_SUMMARY - echo "| Node.js Copilot Studio | $NODEJS_COPILOT_STUDIO_ICON | ${{ needs.nodejs-copilot-studio.result }} |" >> $GITHUB_STEP_SUMMARY echo "| .NET Semantic Kernel | $DOTNET_SK_ICON | ${{ needs.dotnet-sk.result }} |" >> $GITHUB_STEP_SUMMARY echo "| .NET Agent Framework | $DOTNET_AF_ICON | ${{ needs.dotnet-af.result }} |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY @@ -165,7 +155,6 @@ jobs: const pythonGoogleAdkIcon = '${{ needs.python-google-adk.result }}' === 'success' ? '✅' : '❌'; const nodejsOpenaiIcon = '${{ needs.nodejs-openai.result }}' === 'success' ? '✅' : '❌'; const nodejsLangchainIcon = '${{ needs.nodejs-langchain.result }}' === 'success' ? '✅' : '❌'; - const nodejsCopilotStudioIcon = '${{ needs.nodejs-copilot-studio.result }}' === 'success' ? '✅' : '❌'; const dotnetSkIcon = '${{ needs.dotnet-sk.result }}' === 'success' ? '✅' : '❌'; const dotnetAfIcon = '${{ needs.dotnet-af.result }}' === 'success' ? '✅' : '❌'; @@ -174,7 +163,6 @@ jobs: '${{ needs.python-google-adk.result }}' === 'success' && '${{ needs.nodejs-openai.result }}' === 'success' && '${{ needs.nodejs-langchain.result }}' === 'success' && - '${{ needs.nodejs-copilot-studio.result }}' === 'success' && '${{ needs.dotnet-sk.result }}' === 'success' && '${{ needs.dotnet-af.result }}' === 'success'; @@ -190,7 +178,6 @@ jobs: `| Python Google ADK | ${pythonGoogleAdkIcon} | ${{ needs.python-google-adk.result }} |`, `| Node.js OpenAI | ${nodejsOpenaiIcon} | ${{ needs.nodejs-openai.result }} |`, `| Node.js LangChain | ${nodejsLangchainIcon} | ${{ needs.nodejs-langchain.result }} |`, - `| Node.js Copilot Studio | ${nodejsCopilotStudioIcon} | ${{ needs.nodejs-copilot-studio.result }} |`, `| .NET Semantic Kernel | ${dotnetSkIcon} | ${{ needs.dotnet-sk.result }} |`, `| .NET Agent Framework | ${dotnetAfIcon} | ${{ needs.dotnet-af.result }} |`, '', diff --git a/README.md b/README.md index 6999b02b..ad91f096 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ This repository contains sample agents and prompts for building with the Microso | Python Google ADK | [![Python Google ADK](https://img.shields.io/github/actions/workflow/status/microsoft/Agent365-Samples/e2e-python-google-adk.yml?branch=main&label=E2E)](https://github.com/microsoft/Agent365-Samples/actions/workflows/e2e-python-google-adk.yml) | | Node.js OpenAI | [![Node.js OpenAI](https://img.shields.io/github/actions/workflow/status/microsoft/Agent365-Samples/e2e-nodejs-openai.yml?branch=main&label=E2E)](https://github.com/microsoft/Agent365-Samples/actions/workflows/e2e-nodejs-openai.yml) | | Node.js LangChain | [![Node.js LangChain](https://img.shields.io/github/actions/workflow/status/microsoft/Agent365-Samples/e2e-nodejs-langchain.yml?branch=main&label=E2E)](https://github.com/microsoft/Agent365-Samples/actions/workflows/e2e-nodejs-langchain.yml) | -| Node.js Copilot Studio | [![Node.js Copilot Studio](https://img.shields.io/github/actions/workflow/status/microsoft/Agent365-Samples/e2e-nodejs-copilot-studio.yml?branch=main&label=E2E)](https://github.com/microsoft/Agent365-Samples/actions/workflows/e2e-nodejs-copilot-studio.yml) | | .NET Semantic Kernel | [![.NET SK](https://img.shields.io/github/actions/workflow/status/microsoft/Agent365-Samples/e2e-dotnet-semantic-kernel.yml?branch=main&label=E2E)](https://github.com/microsoft/Agent365-Samples/actions/workflows/e2e-dotnet-semantic-kernel.yml) | | .NET Agent Framework | [![.NET AF](https://img.shields.io/github/actions/workflow/status/microsoft/Agent365-Samples/e2e-dotnet-agent-framework.yml?branch=main&label=E2E)](https://github.com/microsoft/Agent365-Samples/actions/workflows/e2e-dotnet-agent-framework.yml) | From d633f8c23d779a14048200752080ed52005e58e1 Mon Sep 17 00:00:00 2001 From: abdulanu0 Date: Thu, 5 Feb 2026 11:42:04 -0800 Subject: [PATCH 05/13] fix: Extract Python module name from StartCommand for pre-flight check --- scripts/e2e/Start-Agent.ps1 | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/e2e/Start-Agent.ps1 b/scripts/e2e/Start-Agent.ps1 index d0af3863..1f7d575e 100644 --- a/scripts/e2e/Start-Agent.ps1 +++ b/scripts/e2e/Start-Agent.ps1 @@ -172,6 +172,12 @@ $scriptLines = @( # Add runtime-specific pre-flight checks if ($Runtime -eq "python") { + # Extract the Python module name from the start command (e.g., "uv run python main.py" -> "main") + $pythonModule = "main" + if ($StartCommand -match "python\s+(\w+)\.py") { + $pythonModule = $matches[1] + } + $scriptLines += @( "Write-Host 'Checking Python environment...'" "uv run python --version" @@ -179,9 +185,9 @@ if ($Runtime -eq "python") { "uv run pip list | Select-Object -First 20" "Write-Host ''" "Write-Host 'Testing Python can import main script...'" - "uv run python -c `"import start_with_generic_host; print('Import OK')`"" + "uv run python -c `"import $pythonModule; print('Import OK')`"" "if (`$LASTEXITCODE -ne 0) {" - " Write-Host 'ERROR: Failed to import start_with_generic_host.py' -ForegroundColor Red" + " Write-Host 'ERROR: Failed to import $pythonModule.py' -ForegroundColor Red" " exit 1" "}" ) From 766b013c03802b28676cb6e7024843804511b200 Mon Sep 17 00:00:00 2001 From: abdulanu0 Date: Thu, 5 Feb 2026 11:46:42 -0800 Subject: [PATCH 06/13] fix: Add missing wrapt dependency to Google ADK sample --- python/google-adk/sample-agent/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/python/google-adk/sample-agent/pyproject.toml b/python/google-adk/sample-agent/pyproject.toml index 286cc813..42267443 100644 --- a/python/google-adk/sample-agent/pyproject.toml +++ b/python/google-adk/sample-agent/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ # Core dependencies "python-dotenv", "aiohttp", + "wrapt", # HTTP server support for MCP servers "uvicorn[standard]>=0.20.0", From 182d973fc7513210f4a420bbca49ce6a892a9a1f Mon Sep 17 00:00:00 2001 From: abdulanu0 Date: Thu, 5 Feb 2026 11:52:34 -0800 Subject: [PATCH 07/13] fix: Make auth_handler_name configurable in Google ADK sample --- python/google-adk/sample-agent/.env.template | 4 ++++ python/google-adk/sample-agent/hosting.py | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/python/google-adk/sample-agent/.env.template b/python/google-adk/sample-agent/.env.template index 80dd31c5..90e3884e 100644 --- a/python/google-adk/sample-agent/.env.template +++ b/python/google-adk/sample-agent/.env.template @@ -1,6 +1,10 @@ GOOGLE_GENAI_USE_VERTEXAI=FALSE GOOGLE_API_KEY= +# Auth Handler Configuration +# Set to AGENTIC for production agentic authentication, leave empty for no auth +AUTH_HANDLER_NAME=AGENTIC + # Agent365 Agentic Authentication Configuration CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID= CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET= diff --git a/python/google-adk/sample-agent/hosting.py b/python/google-adk/sample-agent/hosting.py index 505aae1b..7e18442e 100644 --- a/python/google-adk/sample-agent/hosting.py +++ b/python/google-adk/sample-agent/hosting.py @@ -71,14 +71,16 @@ def __init__(self, agent: AgentInterface): ) self.agent = agent - self.auth_handler_name = "AGENTIC" + # Auth handler name - defaults to empty (no auth handler) for E2E tests + # Set AUTH_HANDLER_NAME=AGENTIC for production agentic auth + self.auth_handler_name = os.getenv("AUTH_HANDLER_NAME", "") or None self.agent_notification = AgentNotification(self) self._setup_handlers() def _setup_handlers(self): """Set up activity handlers for the agent.""" - auth_handlers = [self.auth_handler_name] + auth_handlers = [self.auth_handler_name] if self.auth_handler_name else None @self.conversation_update("membersAdded") async def help_handler(context: TurnContext, _: TurnState): From faf7695790e800c9ec26f17d9fb787aa5ef062e4 Mon Sep 17 00:00:00 2001 From: abdulanu0 Date: Thu, 5 Feb 2026 13:21:17 -0800 Subject: [PATCH 08/13] fix: Add graceful fallback for MCP session errors in Google ADK sample --- python/google-adk/sample-agent/agent.py | 51 ++++++++++++++++++------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/python/google-adk/sample-agent/agent.py b/python/google-adk/sample-agent/agent.py index d8a252f6..7c249f69 100644 --- a/python/google-adk/sample-agent/agent.py +++ b/python/google-adk/sample-agent/agent.py @@ -92,24 +92,47 @@ async def invoke_agent( ) responses = [] - result = await runner.run_debug( - user_messages=[message] - ) + try: + result = await runner.run_debug( + user_messages=[message] + ) - # Extract text responses from the result - if not hasattr(result, '__iter__'): - return "I couldn't get a response from the agent. :(" + # Extract text responses from the result + if not hasattr(result, '__iter__'): + return "I couldn't get a response from the agent. :(" - for event in result: - if not (hasattr(event, 'content') and event.content): - continue + for event in result: + if not (hasattr(event, 'content') and event.content): + continue - if not hasattr(event.content, 'parts'): - continue + if not hasattr(event.content, 'parts'): + continue - for part in event.content.parts: - if hasattr(part, 'text') and part.text: - responses.append(part.text) + for part in event.content.parts: + if hasattr(part, 'text') and part.text: + responses.append(part.text) + except Exception as e: + logger.error(f"Error during agent invocation: {e}") + # If MCP tools fail, try again with base agent (no MCP tools) + if "MCP" in str(e) or "cancel scope" in str(e): + logger.info("Retrying with base agent (no MCP tools)...") + base_runner = Runner( + app_name="agents", + agent=self.agent, # Use base agent without MCP tools + session_service=InMemorySessionService(), + ) + try: + result = await base_runner.run_debug(user_messages=[message]) + for event in result: + if hasattr(event, 'content') and event.content and hasattr(event.content, 'parts'): + for part in event.content.parts: + if hasattr(part, 'text') and part.text: + responses.append(part.text) + except Exception as retry_error: + logger.error(f"Retry also failed: {retry_error}") + return f"I encountered an error processing your request." + else: + return f"I encountered an error processing your request." await self._cleanup_agent(agent) From 0629a84e3f3496c09973471020baa3ad8e4e0d79 Mon Sep 17 00:00:00 2001 From: abdulanu0 Date: Thu, 5 Feb 2026 13:36:11 -0800 Subject: [PATCH 09/13] Fix MCP tool URL - use server_config.url instead of mcp_server_unique_name --- python/google-adk/sample-agent/mcp_tool_registration_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/google-adk/sample-agent/mcp_tool_registration_service.py b/python/google-adk/sample-agent/mcp_tool_registration_service.py index ab167173..460693ab 100644 --- a/python/google-adk/sample-agent/mcp_tool_registration_service.py +++ b/python/google-adk/sample-agent/mcp_tool_registration_service.py @@ -76,7 +76,7 @@ async def add_tool_servers_to_agent( for server_config in mcp_server_configs: server_info = McpToolset( connection_params=StreamableHTTPConnectionParams( - url=server_config.mcp_server_unique_name, + url=server_config.url, headers=mcp_server_headers ) ) From 0c87d050d6bd0320046809a1ba043cd5fe94f525 Mon Sep 17 00:00:00 2001 From: abdulanu0 Date: Thu, 5 Feb 2026 13:45:14 -0800 Subject: [PATCH 10/13] Improve MCP error handling with fallback and better logging --- python/google-adk/sample-agent/agent.py | 90 +++++++++++++------ .../mcp_tool_registration_service.py | 29 ++++-- 2 files changed, 83 insertions(+), 36 deletions(-) diff --git a/python/google-adk/sample-agent/agent.py b/python/google-adk/sample-agent/agent.py index 7c249f69..6bcfb941 100644 --- a/python/google-adk/sample-agent/agent.py +++ b/python/google-adk/sample-agent/agent.py @@ -113,25 +113,22 @@ async def invoke_agent( responses.append(part.text) except Exception as e: logger.error(f"Error during agent invocation: {e}") - # If MCP tools fail, try again with base agent (no MCP tools) - if "MCP" in str(e) or "cancel scope" in str(e): - logger.info("Retrying with base agent (no MCP tools)...") - base_runner = Runner( - app_name="agents", - agent=self.agent, # Use base agent without MCP tools - session_service=InMemorySessionService(), - ) - try: - result = await base_runner.run_debug(user_messages=[message]) - for event in result: - if hasattr(event, 'content') and event.content and hasattr(event.content, 'parts'): - for part in event.content.parts: - if hasattr(part, 'text') and part.text: - responses.append(part.text) - except Exception as retry_error: - logger.error(f"Retry also failed: {retry_error}") - return f"I encountered an error processing your request." - else: + # Always try again with base agent on any error + logger.info("Retrying with base agent (no MCP tools)...") + base_runner = Runner( + app_name="agents", + agent=self.agent, # Use base agent without MCP tools + session_service=InMemorySessionService(), + ) + try: + result = await base_runner.run_debug(user_messages=[message]) + for event in result: + if hasattr(event, 'content') and event.content and hasattr(event.content, 'parts'): + for part in event.content.parts: + if hasattr(part, 'text') and part.text: + responses.append(part.text) + except Exception as retry_error: + logger.error(f"Retry also failed: {retry_error}") return f"I encountered an error processing your request." await self._cleanup_agent(agent) @@ -169,16 +166,51 @@ async def _cleanup_agent(self, agent: Agent): async def _initialize_agent(self, auth, auth_handler_name, turn_context): """Initialize the agent with MCP tools and authentication.""" try: - # Add MCP tools to the agent - tool_service = McpToolRegistrationService() - return await tool_service.add_tool_servers_to_agent( - agent=self.agent, - agentic_app_id=os.getenv("AGENTIC_APP_ID", "agent123"), - auth=auth, - auth_handler_name=auth_handler_name, - context=turn_context, - auth_token=os.getenv("BEARER_TOKEN", ""), - ) + # Get bearer token from environment variable + bearer_token = os.getenv("BEARER_TOKEN", "") or None + + # Check if agentic auth is enabled + use_agentic_auth = os.getenv("USE_AGENTIC_AUTH", "false").lower() == "true" + + # Priority 1: Agentic auth enabled (production/Teams authentication) + if use_agentic_auth: + logger.info("🔒 Using agentic auth for MCP servers (USE_AGENTIC_AUTH=true)") + tool_service = McpToolRegistrationService() + return await tool_service.add_tool_servers_to_agent( + agent=self.agent, + agentic_app_id=os.getenv("AGENTIC_APP_ID", "agent123"), + auth=auth, + auth_handler_name=auth_handler_name, + context=turn_context, + ) + # Priority 2: Bearer token provided (for local dev/testing) + elif bearer_token: + logger.info("🔑 Using bearer token for MCP servers") + tool_service = McpToolRegistrationService() + return await tool_service.add_tool_servers_to_agent( + agent=self.agent, + agentic_app_id=os.getenv("AGENTIC_APP_ID", "agent123"), + auth=auth, + auth_handler_name=auth_handler_name, + context=turn_context, + auth_token=bearer_token, + ) + # Priority 3: Auth handler configured + elif auth_handler_name: + logger.info(f"🔒 Using auth handler '{auth_handler_name}' for MCP servers") + tool_service = McpToolRegistrationService() + return await tool_service.add_tool_servers_to_agent( + agent=self.agent, + agentic_app_id=os.getenv("AGENTIC_APP_ID", "agent123"), + auth=auth, + auth_handler_name=auth_handler_name, + context=turn_context, + ) + # Priority 4: No auth configured - skip MCP tools + else: + logger.warning("⚠️ No authentication configured - running without MCP tools") + logger.info("💡 To enable MCP: set USE_AGENTIC_AUTH=true, provide BEARER_TOKEN, or configure AUTH_HANDLER_NAME") + return self.agent except Exception as e: logger.error(f"Error during agent initialization: {e}") return self.agent \ No newline at end of file diff --git a/python/google-adk/sample-agent/mcp_tool_registration_service.py b/python/google-adk/sample-agent/mcp_tool_registration_service.py index 460693ab..42b88f28 100644 --- a/python/google-adk/sample-agent/mcp_tool_registration_service.py +++ b/python/google-adk/sample-agent/mcp_tool_registration_service.py @@ -67,6 +67,11 @@ async def add_tool_servers_to_agent( self._logger.info(f"Loaded {len(mcp_server_configs)} MCP server configurations") + # If no MCP servers configured, return the original agent + if not mcp_server_configs: + self._logger.info("No MCP servers configured, returning agent without MCP tools") + return agent + # Convert MCP server configs to MCPServerInfo objects mcp_servers_info = [] mcp_server_headers = { @@ -74,14 +79,24 @@ async def add_tool_servers_to_agent( } for server_config in mcp_server_configs: - server_info = McpToolset( - connection_params=StreamableHTTPConnectionParams( - url=server_config.url, - headers=mcp_server_headers + try: + self._logger.info(f"Adding MCP server: {server_config.mcp_server_name} at {server_config.url}") + server_info = McpToolset( + connection_params=StreamableHTTPConnectionParams( + url=server_config.url, + headers=mcp_server_headers + ) ) - ) - - mcp_servers_info.append(server_info) + mcp_servers_info.append(server_info) + except Exception as e: + self._logger.error(f"Failed to add MCP server {server_config.mcp_server_name}: {e}") + # Continue with other servers if one fails + continue + + # If no MCP servers could be added, return the original agent + if not mcp_servers_info: + self._logger.warning("No MCP servers could be added, returning agent without MCP tools") + return agent all_tools = agent.tools + mcp_servers_info From 1aaeaf0ddef85914cd85d2fc27518290b16ff451 Mon Sep 17 00:00:00 2001 From: abdulanu0 Date: Fri, 6 Feb 2026 15:58:49 -0800 Subject: [PATCH 11/13] Trigger E2E tests after ADK tooling update From 4e51d76754c4dded7e43cbddd88cab83bff1330b Mon Sep 17 00:00:00 2001 From: abdulanu0 Date: Fri, 6 Feb 2026 16:13:11 -0800 Subject: [PATCH 12/13] fix: Add missing instruction to Agent and improve error messages in Google ADK sample --- python/google-adk/sample-agent/agent.py | 6 +++++- .../sample-agent/mcp_tool_registration_service.py | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/python/google-adk/sample-agent/agent.py b/python/google-adk/sample-agent/agent.py index 6bcfb941..a188358e 100644 --- a/python/google-adk/sample-agent/agent.py +++ b/python/google-adk/sample-agent/agent.py @@ -129,7 +129,11 @@ async def invoke_agent( responses.append(part.text) except Exception as retry_error: logger.error(f"Retry also failed: {retry_error}") - return f"I encountered an error processing your request." + # Include error details to help with debugging + error_msg = str(retry_error) + if "API key" in error_msg or "GOOGLE_API_KEY" in error_msg or "401" in error_msg: + return "I encountered an authentication error. Please check your GOOGLE_API_KEY configuration." + return f"I encountered an error processing your request: {error_msg}" await self._cleanup_agent(agent) diff --git a/python/google-adk/sample-agent/mcp_tool_registration_service.py b/python/google-adk/sample-agent/mcp_tool_registration_service.py index 42b88f28..07139f3c 100644 --- a/python/google-adk/sample-agent/mcp_tool_registration_service.py +++ b/python/google-adk/sample-agent/mcp_tool_registration_service.py @@ -104,5 +104,6 @@ async def add_tool_servers_to_agent( name=agent.name, model=agent.model, description=agent.description, + instruction=agent.instruction, tools=all_tools, ) From e8544cb236a18041642fd8e7ddb10f83403c6693 Mon Sep 17 00:00:00 2001 From: abdulanu0 Date: Tue, 10 Feb 2026 14:37:23 -0800 Subject: [PATCH 13/13] Remove Google ADK from E2E tests - Revert all changes to python/google-adk/sample-agent/ - Remove e2e-python-google-adk.yml workflow - Remove python-google-adk from e2e-orchestrator.yml Google ADK sample was hitting 429 RESOURCE_EXHAUSTED quota errors from the Google API. Will re-add when quota issues are resolved. --- .github/workflows/e2e-orchestrator.yml | 17 +- .github/workflows/e2e-python-google-adk.yml | 189 ------------------ python/google-adk/sample-agent/.env.template | 4 - python/google-adk/sample-agent/agent.py | 107 +++------- python/google-adk/sample-agent/hosting.py | 6 +- python/google-adk/sample-agent/main.py | 5 - .../mcp_tool_registration_service.py | 28 +-- python/google-adk/sample-agent/pyproject.toml | 1 - 8 files changed, 34 insertions(+), 323 deletions(-) delete mode 100644 .github/workflows/e2e-python-google-adk.yml diff --git a/.github/workflows/e2e-orchestrator.yml b/.github/workflows/e2e-orchestrator.yml index fa98e620..769db745 100644 --- a/.github/workflows/e2e-orchestrator.yml +++ b/.github/workflows/e2e-orchestrator.yml @@ -23,7 +23,6 @@ on: - '' - 'python-openai' - 'python-af' - - 'python-google-adk' - 'nodejs-openai' - 'nodejs-langchain' - 'dotnet-sk' @@ -45,12 +44,6 @@ jobs: if: ${{ github.event.inputs.sample == '' || github.event.inputs.sample == 'python-af' }} uses: ./.github/workflows/e2e-python-agent-framework.yml secrets: inherit - - python-google-adk: - name: Python Google ADK E2E - if: ${{ github.event.inputs.sample == '' || github.event.inputs.sample == 'python-google-adk' }} - uses: ./.github/workflows/e2e-python-google-adk.yml - secrets: inherit nodejs-openai: name: Node.js OpenAI E2E @@ -83,7 +76,7 @@ jobs: e2e-status: name: E2E Status runs-on: ubuntu-latest - needs: [python-openai, python-af, python-google-adk, nodejs-openai, nodejs-langchain, dotnet-sk, dotnet-af] + needs: [python-openai, python-af, nodejs-openai, nodejs-langchain, dotnet-sk, dotnet-af] if: always() steps: @@ -92,7 +85,7 @@ jobs: echo "Checking E2E test results..." # Get all job results (skipped jobs are fine, failed jobs are not) - results="${{ needs.python-openai.result }} ${{ needs.python-af.result }} ${{ needs.python-google-adk.result }} ${{ needs.nodejs-openai.result }} ${{ needs.nodejs-langchain.result }} ${{ needs.dotnet-sk.result }} ${{ needs.dotnet-af.result }}" + results="${{ needs.python-openai.result }} ${{ needs.python-af.result }} ${{ needs.nodejs-openai.result }} ${{ needs.nodejs-langchain.result }} ${{ needs.dotnet-sk.result }} ${{ needs.dotnet-af.result }}" echo "Job results: $results" @@ -112,7 +105,6 @@ jobs: OVERALL_STATUS="✅ All Tests Passed" if [[ "${{ needs.python-openai.result }}" != "success" ]] || \ [[ "${{ needs.python-af.result }}" != "success" ]] || \ - [[ "${{ needs.python-google-adk.result }}" != "success" ]] || \ [[ "${{ needs.nodejs-openai.result }}" != "success" ]] || \ [[ "${{ needs.nodejs-langchain.result }}" != "success" ]] || \ [[ "${{ needs.dotnet-sk.result }}" != "success" ]] || \ @@ -123,7 +115,6 @@ jobs: # Generate status icons using if-else for reliability if [[ "${{ needs.python-openai.result }}" == "success" ]]; then PYTHON_OPENAI_ICON="✅"; else PYTHON_OPENAI_ICON="❌"; fi if [[ "${{ needs.python-af.result }}" == "success" ]]; then PYTHON_AF_ICON="✅"; else PYTHON_AF_ICON="❌"; fi - if [[ "${{ needs.python-google-adk.result }}" == "success" ]]; then PYTHON_GOOGLE_ADK_ICON="✅"; else PYTHON_GOOGLE_ADK_ICON="❌"; fi if [[ "${{ needs.nodejs-openai.result }}" == "success" ]]; then NODEJS_OPENAI_ICON="✅"; else NODEJS_OPENAI_ICON="❌"; fi if [[ "${{ needs.nodejs-langchain.result }}" == "success" ]]; then NODEJS_LANGCHAIN_ICON="✅"; else NODEJS_LANGCHAIN_ICON="❌"; fi if [[ "${{ needs.dotnet-sk.result }}" == "success" ]]; then DOTNET_SK_ICON="✅"; else DOTNET_SK_ICON="❌"; fi @@ -137,7 +128,6 @@ jobs: echo "|--------|--------|--------|" >> $GITHUB_STEP_SUMMARY echo "| Python OpenAI | $PYTHON_OPENAI_ICON | ${{ needs.python-openai.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Python Agent Framework | $PYTHON_AF_ICON | ${{ needs.python-af.result }} |" >> $GITHUB_STEP_SUMMARY - echo "| Python Google ADK | $PYTHON_GOOGLE_ADK_ICON | ${{ needs.python-google-adk.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Node.js OpenAI | $NODEJS_OPENAI_ICON | ${{ needs.nodejs-openai.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Node.js LangChain | $NODEJS_LANGCHAIN_ICON | ${{ needs.nodejs-langchain.result }} |" >> $GITHUB_STEP_SUMMARY echo "| .NET Semantic Kernel | $DOTNET_SK_ICON | ${{ needs.dotnet-sk.result }} |" >> $GITHUB_STEP_SUMMARY @@ -152,7 +142,6 @@ jobs: script: | const pythonOpenaiIcon = '${{ needs.python-openai.result }}' === 'success' ? '✅' : '❌'; const pythonAfIcon = '${{ needs.python-af.result }}' === 'success' ? '✅' : '❌'; - const pythonGoogleAdkIcon = '${{ needs.python-google-adk.result }}' === 'success' ? '✅' : '❌'; const nodejsOpenaiIcon = '${{ needs.nodejs-openai.result }}' === 'success' ? '✅' : '❌'; const nodejsLangchainIcon = '${{ needs.nodejs-langchain.result }}' === 'success' ? '✅' : '❌'; const dotnetSkIcon = '${{ needs.dotnet-sk.result }}' === 'success' ? '✅' : '❌'; @@ -160,7 +149,6 @@ jobs: const allPassed = '${{ needs.python-openai.result }}' === 'success' && '${{ needs.python-af.result }}' === 'success' && - '${{ needs.python-google-adk.result }}' === 'success' && '${{ needs.nodejs-openai.result }}' === 'success' && '${{ needs.nodejs-langchain.result }}' === 'success' && '${{ needs.dotnet-sk.result }}' === 'success' && @@ -175,7 +163,6 @@ jobs: '|--------|--------|--------|', `| Python OpenAI | ${pythonOpenaiIcon} | ${{ needs.python-openai.result }} |`, `| Python Agent Framework | ${pythonAfIcon} | ${{ needs.python-af.result }} |`, - `| Python Google ADK | ${pythonGoogleAdkIcon} | ${{ needs.python-google-adk.result }} |`, `| Node.js OpenAI | ${nodejsOpenaiIcon} | ${{ needs.nodejs-openai.result }} |`, `| Node.js LangChain | ${nodejsLangchainIcon} | ${{ needs.nodejs-langchain.result }} |`, `| .NET Semantic Kernel | ${dotnetSkIcon} | ${{ needs.dotnet-sk.result }} |`, diff --git a/.github/workflows/e2e-python-google-adk.yml b/.github/workflows/e2e-python-google-adk.yml deleted file mode 100644 index d72ba29d..00000000 --- a/.github/workflows/e2e-python-google-adk.yml +++ /dev/null @@ -1,189 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -name: E2E - Python Google ADK - -on: - workflow_call: # Allow orchestrator to call this workflow - workflow_dispatch: # Allow manual triggering - push: - branches: [main] - paths: - - 'python/google-adk/**' - - 'scripts/e2e/**' - - '.github/workflows/e2e-python-google-adk.yml' - pull_request: - branches: [main] - paths: - - 'python/google-adk/**' - - 'scripts/e2e/**' - -permissions: - contents: read - -env: - SAMPLE_PATH: python/google-adk/sample-agent - AGENT_PORT: 3978 - SCRIPTS_PATH: scripts/e2e - E2E_TESTS_PATH: tests/e2e - -jobs: - python-google-adk: - name: Python Google ADK Agent - runs-on: windows-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '8.0.x' - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install uv package manager - run: pip install uv - - - name: Install dependencies - working-directory: ${{ env.SAMPLE_PATH }} - run: uv sync - - - name: Log SDK Versions - shell: pwsh - run: | - & "${{ env.SCRIPTS_PATH }}/Get-SDKVersions.ps1" ` - -Runtime "python" ` - -WorkingDirectory "${{ env.SAMPLE_PATH }}" - - - name: Acquire Bearer Token (ROPC) - id: token - shell: pwsh - run: | - $token = & "${{ env.SCRIPTS_PATH }}/Acquire-BearerToken.ps1" ` - -ClientId "${{ secrets.MCP_CLIENT_ID }}" ` - -TenantId "${{ secrets.MCP_TENANT_ID }}" ` - -Username "${{ secrets.MCP_TEST_USERNAME }}" ` - -Password "${{ secrets.MCP_TEST_PASSWORD }}" - echo "BEARER_TOKEN=$token" >> $env:GITHUB_OUTPUT - echo "::add-mask::$token" - - - name: Copy ToolingManifest.json - shell: pwsh - run: | - & "${{ env.SCRIPTS_PATH }}/Copy-ToolingManifest.ps1" -TargetPath "${{ env.SAMPLE_PATH }}" - - - name: Generate .env configuration - shell: pwsh - run: | - $configMappings = @{ - "GOOGLE_API_KEY" = "${{ secrets.PYTHON_GOOGLE_ADK_API_KEY }}" - "GOOGLE_GENAI_USE_VERTEXAI" = "FALSE" - "USE_AGENTIC_AUTH" = "false" - "MCP_PLATFORM_ENDPOINT" = "https://agent365.svc.cloud.microsoft" - "AGENT_ID" = "${{ secrets.PYTHON_GOOGLE_ADK_AGENT_ID }}" - "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__AUTHTYPE" = "ClientSecret" - "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID" = "${{ secrets.PYTHON_GOOGLE_ADK_AGENT_ID }}" - "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET" = "${{ secrets.PYTHON_GOOGLE_ADK_CLIENT_SECRET }}" - "CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID" = "${{ secrets.TENANT_ID }}" - "CONNECTIONSMAP__0__SERVICEURL" = "*" - "CONNECTIONSMAP__0__CONNECTION" = "SERVICE_CONNECTION" - } - & "${{ env.SCRIPTS_PATH }}/Generate-EnvConfig.ps1" ` - -OutputPath "${{ env.SAMPLE_PATH }}/.env" ` - -BearerToken "${{ steps.token.outputs.BEARER_TOKEN }}" ` - -Port ${{ env.AGENT_PORT }} ` - -ConfigMappings $configMappings - - - name: Start Agent - id: start-agent - shell: pwsh - run: | - $agentPid = & "${{ env.SCRIPTS_PATH }}/Start-Agent.ps1" ` - -AgentPath "${{ env.SAMPLE_PATH }}" ` - -StartCommand "uv run python main.py" ` - -Port ${{ env.AGENT_PORT }} ` - -BearerToken "${{ steps.token.outputs.BEARER_TOKEN }}" ` - -Environment "Development" ` - -Runtime "python" - echo "AGENT_PID=$agentPid" >> $env:GITHUB_OUTPUT - - - name: Verify Agent Running - shell: pwsh - run: | - Write-Host "=== Verifying Agent ===" -ForegroundColor Cyan - - $agentPid = "${{ steps.start-agent.outputs.AGENT_PID }}" - Write-Host "Checking agent process (PID: $agentPid)..." -ForegroundColor Gray - - if ($agentPid) { - try { - $proc = Get-Process -Id $agentPid -ErrorAction Stop - Write-Host "Agent process (PID: $agentPid) is running: $($proc.ProcessName)" -ForegroundColor Green - } catch { - Write-Host "ERROR: Agent process (PID: $agentPid) is NOT running!" -ForegroundColor Red - - $logFile = "${{ env.SAMPLE_PATH }}/agent.log" - if (Test-Path $logFile) { - Write-Host "Agent logs:" -ForegroundColor Yellow - Get-Content $logFile - } - throw "Agent process has stopped" - } - } - - $agentUrl = "http://localhost:${{ env.AGENT_PORT }}" - Write-Host "Verifying agent at $agentUrl..." -ForegroundColor Gray - $healthResponse = Invoke-WebRequest -Uri "$agentUrl/api/health" -Method GET -UseBasicParsing -ErrorAction SilentlyContinue - Write-Host "Health check: $($healthResponse.StatusCode)" -ForegroundColor Green - - - name: Restore E2E Test Dependencies - shell: pwsh - run: | - dotnet restore "${{ env.E2E_TESTS_PATH }}/Agent365.E2E.Tests.csproj" - - - name: Run .NET E2E Tests - shell: pwsh - run: | - dotnet test "${{ env.E2E_TESTS_PATH }}/Agent365.E2E.Tests.csproj" ` - --no-restore ` - --filter "Category=HTTP" ` - --logger "trx;LogFileName=test-results.trx" ` - --results-directory "${{ runner.temp }}/TestResults" - env: - AGENT_PORT: ${{ env.AGENT_PORT }} - AGENT_URL: http://localhost:${{ env.AGENT_PORT }} - TEST_RESULTS_DIR: ${{ runner.temp }}/TestConversations - - - name: Upload Test Results - if: always() - uses: actions/upload-artifact@v4 - with: - name: test-results-python-google-adk - path: ${{ runner.temp }}/TestResults/ - retention-days: 30 - - - name: Emit Test Conversations - if: always() - shell: pwsh - run: | - & "${{ env.SCRIPTS_PATH }}/Emit-TestConversations.ps1" ` - -TestResultsDir "${{ runner.temp }}/TestConversations" - - - name: Capture Agent Logs - if: always() - shell: pwsh - run: | - & "${{ env.SCRIPTS_PATH }}/Capture-AgentLogs.ps1" -AgentPath "${{ env.SAMPLE_PATH }}" - - - name: Cleanup Agent Process - if: always() - shell: pwsh - run: | - & "${{ env.SCRIPTS_PATH }}/Stop-AgentProcess.ps1" ` - -AgentPID "${{ steps.start-agent.outputs.AGENT_PID }}" ` - -Port ${{ env.AGENT_PORT }} diff --git a/python/google-adk/sample-agent/.env.template b/python/google-adk/sample-agent/.env.template index 90e3884e..80dd31c5 100644 --- a/python/google-adk/sample-agent/.env.template +++ b/python/google-adk/sample-agent/.env.template @@ -1,10 +1,6 @@ GOOGLE_GENAI_USE_VERTEXAI=FALSE GOOGLE_API_KEY= -# Auth Handler Configuration -# Set to AGENTIC for production agentic authentication, leave empty for no auth -AUTH_HANDLER_NAME=AGENTIC - # Agent365 Agentic Authentication Configuration CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID= CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET= diff --git a/python/google-adk/sample-agent/agent.py b/python/google-adk/sample-agent/agent.py index a188358e..d8a252f6 100644 --- a/python/google-adk/sample-agent/agent.py +++ b/python/google-adk/sample-agent/agent.py @@ -92,48 +92,24 @@ async def invoke_agent( ) responses = [] - try: - result = await runner.run_debug( - user_messages=[message] - ) + result = await runner.run_debug( + user_messages=[message] + ) - # Extract text responses from the result - if not hasattr(result, '__iter__'): - return "I couldn't get a response from the agent. :(" + # Extract text responses from the result + if not hasattr(result, '__iter__'): + return "I couldn't get a response from the agent. :(" - for event in result: - if not (hasattr(event, 'content') and event.content): - continue + for event in result: + if not (hasattr(event, 'content') and event.content): + continue - if not hasattr(event.content, 'parts'): - continue + if not hasattr(event.content, 'parts'): + continue - for part in event.content.parts: - if hasattr(part, 'text') and part.text: - responses.append(part.text) - except Exception as e: - logger.error(f"Error during agent invocation: {e}") - # Always try again with base agent on any error - logger.info("Retrying with base agent (no MCP tools)...") - base_runner = Runner( - app_name="agents", - agent=self.agent, # Use base agent without MCP tools - session_service=InMemorySessionService(), - ) - try: - result = await base_runner.run_debug(user_messages=[message]) - for event in result: - if hasattr(event, 'content') and event.content and hasattr(event.content, 'parts'): - for part in event.content.parts: - if hasattr(part, 'text') and part.text: - responses.append(part.text) - except Exception as retry_error: - logger.error(f"Retry also failed: {retry_error}") - # Include error details to help with debugging - error_msg = str(retry_error) - if "API key" in error_msg or "GOOGLE_API_KEY" in error_msg or "401" in error_msg: - return "I encountered an authentication error. Please check your GOOGLE_API_KEY configuration." - return f"I encountered an error processing your request: {error_msg}" + for part in event.content.parts: + if hasattr(part, 'text') and part.text: + responses.append(part.text) await self._cleanup_agent(agent) @@ -170,51 +146,16 @@ async def _cleanup_agent(self, agent: Agent): async def _initialize_agent(self, auth, auth_handler_name, turn_context): """Initialize the agent with MCP tools and authentication.""" try: - # Get bearer token from environment variable - bearer_token = os.getenv("BEARER_TOKEN", "") or None - - # Check if agentic auth is enabled - use_agentic_auth = os.getenv("USE_AGENTIC_AUTH", "false").lower() == "true" - - # Priority 1: Agentic auth enabled (production/Teams authentication) - if use_agentic_auth: - logger.info("🔒 Using agentic auth for MCP servers (USE_AGENTIC_AUTH=true)") - tool_service = McpToolRegistrationService() - return await tool_service.add_tool_servers_to_agent( - agent=self.agent, - agentic_app_id=os.getenv("AGENTIC_APP_ID", "agent123"), - auth=auth, - auth_handler_name=auth_handler_name, - context=turn_context, - ) - # Priority 2: Bearer token provided (for local dev/testing) - elif bearer_token: - logger.info("🔑 Using bearer token for MCP servers") - tool_service = McpToolRegistrationService() - return await tool_service.add_tool_servers_to_agent( - agent=self.agent, - agentic_app_id=os.getenv("AGENTIC_APP_ID", "agent123"), - auth=auth, - auth_handler_name=auth_handler_name, - context=turn_context, - auth_token=bearer_token, - ) - # Priority 3: Auth handler configured - elif auth_handler_name: - logger.info(f"🔒 Using auth handler '{auth_handler_name}' for MCP servers") - tool_service = McpToolRegistrationService() - return await tool_service.add_tool_servers_to_agent( - agent=self.agent, - agentic_app_id=os.getenv("AGENTIC_APP_ID", "agent123"), - auth=auth, - auth_handler_name=auth_handler_name, - context=turn_context, - ) - # Priority 4: No auth configured - skip MCP tools - else: - logger.warning("⚠️ No authentication configured - running without MCP tools") - logger.info("💡 To enable MCP: set USE_AGENTIC_AUTH=true, provide BEARER_TOKEN, or configure AUTH_HANDLER_NAME") - return self.agent + # Add MCP tools to the agent + tool_service = McpToolRegistrationService() + return await tool_service.add_tool_servers_to_agent( + agent=self.agent, + agentic_app_id=os.getenv("AGENTIC_APP_ID", "agent123"), + auth=auth, + auth_handler_name=auth_handler_name, + context=turn_context, + auth_token=os.getenv("BEARER_TOKEN", ""), + ) except Exception as e: logger.error(f"Error during agent initialization: {e}") return self.agent \ No newline at end of file diff --git a/python/google-adk/sample-agent/hosting.py b/python/google-adk/sample-agent/hosting.py index 7e18442e..505aae1b 100644 --- a/python/google-adk/sample-agent/hosting.py +++ b/python/google-adk/sample-agent/hosting.py @@ -71,16 +71,14 @@ def __init__(self, agent: AgentInterface): ) self.agent = agent - # Auth handler name - defaults to empty (no auth handler) for E2E tests - # Set AUTH_HANDLER_NAME=AGENTIC for production agentic auth - self.auth_handler_name = os.getenv("AUTH_HANDLER_NAME", "") or None + self.auth_handler_name = "AGENTIC" self.agent_notification = AgentNotification(self) self._setup_handlers() def _setup_handlers(self): """Set up activity handlers for the agent.""" - auth_handlers = [self.auth_handler_name] if self.auth_handler_name else None + auth_handlers = [self.auth_handler_name] @self.conversation_update("membersAdded") async def help_handler(context: TurnContext, _: TurnState): diff --git a/python/google-adk/sample-agent/main.py b/python/google-adk/sample-agent/main.py index de482869..f1e85977 100644 --- a/python/google-adk/sample-agent/main.py +++ b/python/google-adk/sample-agent/main.py @@ -52,13 +52,8 @@ async def anonymous_claims(request, handler): if (auth_config and isProduction): middlewares.append(jwt_authorization_middleware) - # Health endpoint - async def health(_req: Request) -> Response: - return Response(text='{"status": "healthy"}', content_type='application/json') - # Configure App app = Application(middlewares=middlewares) - app.router.add_get("/api/health", health) app.router.add_post("/api/messages", entry_point) app["agent_configuration"] = auth_config diff --git a/python/google-adk/sample-agent/mcp_tool_registration_service.py b/python/google-adk/sample-agent/mcp_tool_registration_service.py index 07139f3c..ab167173 100644 --- a/python/google-adk/sample-agent/mcp_tool_registration_service.py +++ b/python/google-adk/sample-agent/mcp_tool_registration_service.py @@ -67,11 +67,6 @@ async def add_tool_servers_to_agent( self._logger.info(f"Loaded {len(mcp_server_configs)} MCP server configurations") - # If no MCP servers configured, return the original agent - if not mcp_server_configs: - self._logger.info("No MCP servers configured, returning agent without MCP tools") - return agent - # Convert MCP server configs to MCPServerInfo objects mcp_servers_info = [] mcp_server_headers = { @@ -79,24 +74,14 @@ async def add_tool_servers_to_agent( } for server_config in mcp_server_configs: - try: - self._logger.info(f"Adding MCP server: {server_config.mcp_server_name} at {server_config.url}") - server_info = McpToolset( - connection_params=StreamableHTTPConnectionParams( - url=server_config.url, - headers=mcp_server_headers - ) + server_info = McpToolset( + connection_params=StreamableHTTPConnectionParams( + url=server_config.mcp_server_unique_name, + headers=mcp_server_headers ) - mcp_servers_info.append(server_info) - except Exception as e: - self._logger.error(f"Failed to add MCP server {server_config.mcp_server_name}: {e}") - # Continue with other servers if one fails - continue + ) - # If no MCP servers could be added, return the original agent - if not mcp_servers_info: - self._logger.warning("No MCP servers could be added, returning agent without MCP tools") - return agent + mcp_servers_info.append(server_info) all_tools = agent.tools + mcp_servers_info @@ -104,6 +89,5 @@ async def add_tool_servers_to_agent( name=agent.name, model=agent.model, description=agent.description, - instruction=agent.instruction, tools=all_tools, ) diff --git a/python/google-adk/sample-agent/pyproject.toml b/python/google-adk/sample-agent/pyproject.toml index 42267443..286cc813 100644 --- a/python/google-adk/sample-agent/pyproject.toml +++ b/python/google-adk/sample-agent/pyproject.toml @@ -18,7 +18,6 @@ dependencies = [ # Core dependencies "python-dotenv", "aiohttp", - "wrapt", # HTTP server support for MCP servers "uvicorn[standard]>=0.20.0",