Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,102 @@ testing-status: ## Show status of testing services
testing-logs: ## Show testing stack logs
$(COMPOSE_CMD_MONITOR) --profile testing logs -f --tail=100

# =============================================================================
# help: 🤖 A2A DEMO AGENTS (Issue #2002 Authentication Testing)
# help: demo-a2a-up - Start all 3 A2A demo agents (basic, bearer, apikey) with auto-registration
# help: demo-a2a-down - Stop all A2A demo agents
# help: demo-a2a-status - Show status of A2A demo agents
# help: demo-a2a-basic - Start only Basic Auth demo agent (port 9001)
# help: demo-a2a-bearer - Start only Bearer Token demo agent (port 9002)
# help: demo-a2a-apikey - Start only X-API-Key demo agent (port 9003)

# A2A Demo Agent configuration
DEMO_A2A_BASIC_PORT ?= 9001
DEMO_A2A_BEARER_PORT ?= 9002
DEMO_A2A_APIKEY_PORT ?= 9003
DEMO_A2A_BASIC_PID := /tmp/demo-a2a-basic.pid
DEMO_A2A_BEARER_PID := /tmp/demo-a2a-bearer.pid
DEMO_A2A_APIKEY_PID := /tmp/demo-a2a-apikey.pid

.PHONY: demo-a2a-up demo-a2a-down demo-a2a-status demo-a2a-basic demo-a2a-bearer demo-a2a-apikey

demo-a2a-up: ## Start all 3 A2A demo agents with auto-registration
@echo "🤖 Starting A2A demo agents for authentication testing (Issue #2002)..."
@echo ""
@# Start Basic Auth agent (PYTHONUNBUFFERED=1 ensures print output is captured immediately)
@echo "Starting Basic Auth agent on port $(DEMO_A2A_BASIC_PORT)..."
@PYTHONUNBUFFERED=1 uv run python scripts/demo_a2a_agent_auth.py \
--auth-type basic --port $(DEMO_A2A_BASIC_PORT) --auto-register > /tmp/demo-a2a-basic.log 2>&1 & echo $$! > $(DEMO_A2A_BASIC_PID)
@sleep 1
@# Start Bearer Token agent
@echo "Starting Bearer Token agent on port $(DEMO_A2A_BEARER_PORT)..."
@PYTHONUNBUFFERED=1 uv run python scripts/demo_a2a_agent_auth.py \
--auth-type bearer --port $(DEMO_A2A_BEARER_PORT) --auto-register > /tmp/demo-a2a-bearer.log 2>&1 & echo $$! > $(DEMO_A2A_BEARER_PID)
@sleep 1
@# Start X-API-Key agent
@echo "Starting X-API-Key agent on port $(DEMO_A2A_APIKEY_PORT)..."
@PYTHONUNBUFFERED=1 uv run python scripts/demo_a2a_agent_auth.py \
--auth-type apikey --port $(DEMO_A2A_APIKEY_PORT) --auto-register > /tmp/demo-a2a-apikey.log 2>&1 & echo $$! > $(DEMO_A2A_APIKEY_PID)
@sleep 2
@echo ""
@echo "✅ A2A demo agents started!"
@echo ""
@echo " 🔐 Basic Auth: http://localhost:$(DEMO_A2A_BASIC_PORT) (log: /tmp/demo-a2a-basic.log)"
@echo " 🎫 Bearer Token: http://localhost:$(DEMO_A2A_BEARER_PORT) (log: /tmp/demo-a2a-bearer.log)"
@echo " 🔑 X-API-Key: http://localhost:$(DEMO_A2A_APIKEY_PORT) (log: /tmp/demo-a2a-apikey.log)"
@echo ""
@echo " View credentials: cat /tmp/demo-a2a-*.log | grep -A5 'Configuration:'"
@echo " Stop agents: make demo-a2a-down"
@echo ""

demo-a2a-down: ## Stop all A2A demo agents
@echo "🤖 Stopping A2A demo agents..."
@# Send SIGTERM first to allow graceful unregistration
@-if [ -f $(DEMO_A2A_BASIC_PID) ]; then kill -15 $$(cat $(DEMO_A2A_BASIC_PID)) 2>/dev/null || true; fi
@-if [ -f $(DEMO_A2A_BEARER_PID) ]; then kill -15 $$(cat $(DEMO_A2A_BEARER_PID)) 2>/dev/null || true; fi
@-if [ -f $(DEMO_A2A_APIKEY_PID) ]; then kill -15 $$(cat $(DEMO_A2A_APIKEY_PID)) 2>/dev/null || true; fi
@sleep 2
@# Force kill any remaining processes
@-if [ -f $(DEMO_A2A_BASIC_PID) ]; then kill -9 $$(cat $(DEMO_A2A_BASIC_PID)) 2>/dev/null || true; rm -f $(DEMO_A2A_BASIC_PID); fi
@-if [ -f $(DEMO_A2A_BEARER_PID) ]; then kill -9 $$(cat $(DEMO_A2A_BEARER_PID)) 2>/dev/null || true; rm -f $(DEMO_A2A_BEARER_PID); fi
@-if [ -f $(DEMO_A2A_APIKEY_PID) ]; then kill -9 $$(cat $(DEMO_A2A_APIKEY_PID)) 2>/dev/null || true; rm -f $(DEMO_A2A_APIKEY_PID); fi
@echo "✅ A2A demo agents stopped."

demo-a2a-status: ## Show status of A2A demo agents
@echo "🤖 A2A demo agent status:"
@echo ""
@if [ -f $(DEMO_A2A_BASIC_PID) ] && kill -0 $$(cat $(DEMO_A2A_BASIC_PID)) 2>/dev/null; then \
echo " ✅ Basic Auth (port $(DEMO_A2A_BASIC_PORT)): running (PID $$(cat $(DEMO_A2A_BASIC_PID)))"; \
else \
echo " ❌ Basic Auth (port $(DEMO_A2A_BASIC_PORT)): stopped"; \
rm -f $(DEMO_A2A_BASIC_PID) 2>/dev/null || true; \
fi
@if [ -f $(DEMO_A2A_BEARER_PID) ] && kill -0 $$(cat $(DEMO_A2A_BEARER_PID)) 2>/dev/null; then \
echo " ✅ Bearer Token (port $(DEMO_A2A_BEARER_PORT)): running (PID $$(cat $(DEMO_A2A_BEARER_PID)))"; \
else \
echo " ❌ Bearer Token (port $(DEMO_A2A_BEARER_PORT)): stopped"; \
rm -f $(DEMO_A2A_BEARER_PID) 2>/dev/null || true; \
fi
@if [ -f $(DEMO_A2A_APIKEY_PID) ] && kill -0 $$(cat $(DEMO_A2A_APIKEY_PID)) 2>/dev/null; then \
echo " ✅ X-API-Key (port $(DEMO_A2A_APIKEY_PORT)): running (PID $$(cat $(DEMO_A2A_APIKEY_PID)))"; \
else \
echo " ❌ X-API-Key (port $(DEMO_A2A_APIKEY_PORT)): stopped"; \
rm -f $(DEMO_A2A_APIKEY_PID) 2>/dev/null || true; \
fi
@echo ""

demo-a2a-basic: ## Start only Basic Auth demo agent
@echo "🔐 Starting Basic Auth demo agent on port $(DEMO_A2A_BASIC_PORT)..."
uv run python scripts/demo_a2a_agent_auth.py --auth-type basic --port $(DEMO_A2A_BASIC_PORT) --auto-register

demo-a2a-bearer: ## Start only Bearer Token demo agent
@echo "🎫 Starting Bearer Token demo agent on port $(DEMO_A2A_BEARER_PORT)..."
uv run python scripts/demo_a2a_agent_auth.py --auth-type bearer --port $(DEMO_A2A_BEARER_PORT) --auto-register

demo-a2a-apikey: ## Start only X-API-Key demo agent
@echo "🔑 Starting X-API-Key demo agent on port $(DEMO_A2A_APIKEY_PORT)..."
uv run python scripts/demo_a2a_agent_auth.py --auth-type apikey --port $(DEMO_A2A_APIKEY_PORT) --auto-register

# =============================================================================
# help: 🎯 BENCHMARK STACK (Go benchmark-server)
# help: benchmark-up - Start benchmark stack (MCP servers + auto-registration)
Expand Down
26 changes: 16 additions & 10 deletions mcpgateway/services/a2a_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from mcpgateway.utils.correlation_id import get_correlation_id
from mcpgateway.utils.create_slug import slugify
from mcpgateway.utils.pagination import unified_paginate
from mcpgateway.utils.services_auth import encode_auth # ,decode_auth
from mcpgateway.utils.services_auth import decode_auth, encode_auth
from mcpgateway.utils.sqlalchemy_modifier import json_contains_expr

# Cache import (lazy to avoid circular dependencies)
Expand Down Expand Up @@ -1107,12 +1107,19 @@ async def invoke_agent(
agent_type = agent.agent_type
agent_protocol_version = agent.protocol_version
agent_auth_type = agent.auth_type

# Fetch auth_value if needed (before closing session)
auth_token_value = None
if agent_auth_type in ("api_key", "bearer"):
db_row = db.execute(select(DbA2AAgent).where(DbA2AAgent.name == agent_name)).scalar_one_or_none()
auth_token_value = getattr(db_row, "auth_value", None) if db_row else None
agent_auth_value = agent.auth_value

# Decode auth_value for supported auth types (before closing session)
auth_headers = {}
if agent_auth_type in ("basic", "bearer", "authheaders") and agent_auth_value:
# Decrypt auth_value and extract headers (follows gateway_service pattern)
if isinstance(agent_auth_value, str):
try:
auth_headers = decode_auth(agent_auth_value)
except Exception as e:
raise A2AAgentError(f"Failed to decrypt authentication for agent '{agent_name}': {e}")
elif isinstance(agent_auth_value, dict):
auth_headers = {str(k): str(v) for k, v in agent_auth_value.items()}

# ═══════════════════════════════════════════════════════════════════════════
# CRITICAL: Release DB connection back to pool BEFORE making HTTP calls
Expand Down Expand Up @@ -1146,9 +1153,8 @@ async def invoke_agent(
client = await get_http_client()
headers = {"Content-Type": "application/json"}

# Add authentication if configured
if auth_token_value:
headers["Authorization"] = f"Bearer {auth_token_value}"
# Add authentication if configured (using decoded auth headers)
headers.update(auth_headers)

# Add correlation ID to outbound headers for distributed tracing
correlation_id = get_correlation_id()
Expand Down
Loading
Loading