13 min readNov 30, 2025
โ
This is a follow-up to Deploying Claude Agent on Amazon Bedrock AgentCore. In the previous article, we deployed a Claude Agent to AgentCore Runtime and invoked it via CLI. Now, letโs take it further by exposing it as an A2A (Agent-to-Agent) API that other applications and agents can consume within your VPC.
Why A2A Protocol?
Before diving into implementation, letโs understand why the A2A protocol matters for modern AI systems.
The A2A protocol is a standardized communication protocol for AI agents, built on JSON-RPC 2.0. As AI systems evolve from single-agent applications to complex multi-agent ecosystems, the need for standardized inter-agent coโฆ
13 min readNov 30, 2025
โ
This is a follow-up to Deploying Claude Agent on Amazon Bedrock AgentCore. In the previous article, we deployed a Claude Agent to AgentCore Runtime and invoked it via CLI. Now, letโs take it further by exposing it as an A2A (Agent-to-Agent) API that other applications and agents can consume within your VPC.
Why A2A Protocol?
Before diving into implementation, letโs understand why the A2A protocol matters for modern AI systems.
The A2A protocol is a standardized communication protocol for AI agents, built on JSON-RPC 2.0. As AI systems evolve from single-agent applications to complex multi-agent ecosystems, the need for standardized inter-agent communication becomes critical. The A2A protocol addresses this by enabling:
- Interoperability: Any A2A-compliant client can communicate with your agent, regardless of the underlying framework
- Discoverability: Agent Cards describe capabilities in a standardized format, making integration straightforward
- Multi-agent Collaboration: Agents built with different frameworks (Strands, OpenAI SDK, Google ADK, LangGraph, Claude Agents SDK) can coordinate seamlessly
- Loose Coupling: Each agent operates as an independent unit that can be developed, tested, deployed, and upgraded without disrupting the entire system
According to AWSโs announcement, while MCP (Model Context Protocol) connects a single agent to its tools and data, A2A lets multiple agents coordinate with one another. For example, a retail inventory agent might use MCP to query product databases, then use A2A to communicate with external supplier agents to place orders.
Understanding AgentCoreโs A2A Architecture
According to the official AWS documentation, Amazon Bedrock AgentCoreโs A2A protocol support enables seamless integration by acting as a transparent proxy layer.
Key characteristics:
- Agent Cards: Built-in agent discovery at
/.well-known/agent-card.json - Protocol Transparency: JSON-RPC payloads from the InvokeAgentRuntime API are passed through directly to the A2A container without modification
- Session Isolation: Platform automatically adds
X-Amzn-Bedrock-AgentCore-Runtime-Session-Idheader for session isolation - Authentication: Supports both SigV4 and OAuth 2.0 authentication schemes
- Streaming Support: Server-Sent Events (SSE) for real-time responses
Architecture: AgentCore Runtime is AWS-Managed
A critical point to understand: AgentCore Runtime runs in AWS-managed infrastructure, not inside your VPC. When you configure VPC connectivity, AgentCore creates Elastic Network Interfaces (ENIs) in your VPC, enabling your agent to access private resources. However, the Runtime itself remains AWS-managed.
According to the VPC configuration documentation:
โWhen you configure VPC connectivity for Amazon Bedrock AgentCore Runtime and tools, Amazon Bedrock creates elastic network interfaces (ENIs) in your VPC using the service-linked role. These ENIs enable your Amazon Bedrock AgentCore Runtime and tools to securely communicate with resources in your VPC.โ
To access the AgentCore API from within your VPC without internet traversal, you use AWS PrivateLink by creating a VPC Interface Endpoint for AgentCore.
Press enter or click to view image in full size
Prerequisites
Before you begin, ensure you have the following set up:
1. AWS Account with Required Permissions
You need an AWS account with permissions for:
- Amazon Bedrock AgentCore
- Amazon ECR (Elastic Container Registry)
- AWS CodeBuild
- IAM role management
- Amazon Cognito (for OAuth authentication)
- Amazon VPC and EC2 (for VPC endpoints)
2. Bedrock Model Access
Enable Claude models in your AWS account:
- Go to Amazon Bedrock > Model access
- Enable Claude 4.5 Sonnet
3. Required Tools
# Install AgentCore Starter Toolkit pip install bedrock-agentcore-starter-toolkit # Verify installation agentcore --help # Configure AWS credentials aws configure # Verify Bedrock access aws bedrock list-foundation-models --region us-west-2 | grep claude
4. Python Environment
- Python 3.12 or higher
- Basic understanding of Python and async programming
- Understanding of the A2A protocol concepts
Step 1: Create VPC Interface Endpoints
To enable private communication between your VPC and AWS services without internet traversal, you need to create VPC Interface Endpoints. This section explains why each endpoint is necessary.
VPC Endpoint for AgentCore (Required)
This endpoint allows applications within your VPC to call AgentCore APIs (like InvokeAgentRuntime) without going through the public internet:
# Create VPC Interface Endpoint for AgentCore aws ec2 create-vpc-endpoint \ --vpc-id vpc-xxxxxxxxx \ --service-name com.amazonaws.us-west-2.bedrock-agentcore \ --vpc-endpoint-type Interface \ --subnet-ids subnet-xxx subnet-yyy \ --security-group-ids sg-xxxxxxxxx \ --private-dns-enabled
With --private-dns-enabled, you can use the standard AgentCore endpoint URL (bedrock-agentcore.us-west-2.amazonaws.com) and traffic will automatically route through PrivateLink.
VPC Endpoint for Bedrock Runtime (Optional)
When is this needed? This endpoint is only required if your VPC does not have internet access (no NAT Gateway). If your VPC has a NAT Gateway configured, your agent can access Bedrock Runtime through the public internet, and this endpoint is optional.
However, using a VPC endpoint is recommended for:
- Better security: Traffic stays within AWS network, never traversing the public internet
- Lower latency: Direct connection to Bedrock Runtime
- Cost optimization: Avoid NAT Gateway data processing charges for Bedrock API calls
# Bedrock Runtime - Optional if VPC has NAT Gateway, required if no internet access aws ec2 create-vpc-endpoint \ --vpc-id vpc-xxxxxxxxx \ --service-name com.amazonaws.us-west-2.bedrock-runtime \ --vpc-endpoint-type Interface \ --subnet-ids subnet-xxxxxxxxx subnet-yyyyyyyyy \ --security-group-ids sg-xxxxxxxxx \ --private-dns-enabled
VPC Endpoint for CloudWatch Logs (Optional)
When is this needed? Similar to Bedrock Runtime, this endpoint is only required if your VPC does not have internet access. If your VPC has a NAT Gateway, logs can be sent to CloudWatch through the public internet.
Using a VPC endpoint is recommended for the same security, latency, and cost benefits mentioned above.
# CloudWatch Logs - Optional if VPC has NAT Gateway, required if no internet access aws ec2 create-vpc-endpoint \ --vpc-id vpc-xxxxxxxxx \ --service-name com.amazonaws.us-west-2.logs \ --vpc-endpoint-type Interface \ --subnet-ids subnet-xxxxxxxxx subnet-yyyyyyyyy \ --security-group-ids sg-xxxxxxxxx \ --private-dns-enabled
Security Group for the Endpoints
# Allow HTTPS inbound from your VPC CIDR aws ec2 authorize-security-group-ingress \ --group-id sg-endpoint-xxx \ --protocol tcp \ --port 443 \ --cidr 10.0.0.0/16
Step 2: Create Your A2A Server
Now letโs create the A2A server. This step is the foundation of your agent โ it defines how your agent processes requests and responds using the A2A protocol.
Project Structure
Create a project structure:
my-a2a-agent/ โโโ my_a2a_server.py โโโ requirements.txt
requirements.txt
Based on the official documentation, the required packages are:
strands-agents[a2a] bedrock-agentcore strands-agents-tools
my_a2a_server.py
Hereโs a complete A2A server implementation using Strands Agents SDK. Note the key differences from HTTP protocol: port 9000 and root path mounting.
""" A2A Protocol Server for AgentCore This server implements the A2A (Agent-to-Agent) protocol using JSON-RPC 2.0. It runs inside AgentCore containers and provides standardized agent communication. Key Requirements (per AWS documentation): - Host: 0.0.0.0 - Port: 9000 (different from HTTP protocol's 8080) - Path: / (root path, different from HTTP's /invocations) - Platform: ARM64 container Endpoints: - POST / : JSON-RPC 2.0 endpoint for A2A messages - GET /.well-known/agent-card.json : Agent Card for discovery - GET /ping : Health check """ import logging import os from strands_tools.calculator import calculator from strands import Agent from strands.multiagent.a2a import A2AServer import uvicorn from fastapi import FastAPI logging.basicConfig(level=logging.INFO) # Use the complete runtime URL from environment variable, fallback to local # This URL is automatically set by AgentCore Runtime after deployment runtime_url = os.environ.get('AGENTCORE_RUNTIME_URL', 'http://127.0.0.1:9000/') logging.info(f"Runtime URL: {runtime_url}") # Create your Strands Agent with tools and capabilities strands_agent = Agent( name="My A2A Agent", description="An A2A-compatible AI agent powered by Amazon Bedrock AgentCore", tools=[calculator], # Add your tools here callback_handler=None ) # A2A servers run on port 9000 (not 8080 like HTTP protocol) host, port = "0.0.0.0", 9000 # Create A2A Server wrapper # serve_at_root=True ensures the server mounts at / regardless of remote URL complexity a2a_server = A2AServer( agent=strands_agent, http_url=runtime_url, serve_at_root=True ) app = FastAPI() # Health check endpoint required by AgentCore Runtime @app.get("/ping") def ping(): """ Health check endpoint for AgentCore Runtime. Must return HTTP 200 for healthy status. """ return {"status": "healthy"} # Mount A2A server at root path # This handles: # - POST / : JSON-RPC 2.0 messages # - GET /.well-known/agent-card.json : Agent Card app.mount("/", a2a_server.to_fastapi_app()) if __name__ == "__main__": uvicorn.run(app, host=host, port=port)
Understanding the Code
- strands_agent: The core agent instance configured with specific tools and capabilities
- A2AServer: A wrapper that makes the agent compatible with the A2A communication protocol
- runtime_url: Dynamically builds the appropriate URL using the
AGENTCORE_RUNTIME_URLenvironment variable, adapting to different deployment contexts - port=9000: The default port for A2A servers in AgentCore Runtime
- serve_at_root=True: Configures the server to mount at the root path (
/), simplifying routing regardless of how complex the remote URL structure might be
Step 3: Local Testing
Before deploying to AgentCore, itโs essential to test your server locally. This step helps you catch issues early and ensures your A2A implementation is correct before incurring cloud deployment costs.
Start Your A2A Server
cd my-a2a-agent # Create virtual environment python3 -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate # Install dependencies pip install -r requirements.txt # Run the server python my_a2a_server.py
You should see output indicating the server is running on port 9000.
Test the Endpoints
Now letโs verify each endpoint works correctly:
# 1. Health check - AgentCore uses this to verify your agent is ready curl http://localhost:9000/ping # Expected: {"status":"healthy"} # 2. Agent Card - Other agents use this for discovery curl http://localhost:9000/.well-known/agent-card.json | jq . # 3. A2A Protocol - message/send (synchronous) curl -X POST http://localhost:9000/ \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "req-001", "method": "message/send", "params": { "message": { "role": "user", "parts": [ { "kind": "text", "text": "What is 101 * 11?" } ], "messageId": "msg-001" } } }' | jq .
Expected Response Format
A successful A2A response looks like:
{ "jsonrpc": "2.0", "id": "req-001", "result": { "artifacts": [ { "artifactId": "unique-artifact-id", "name": "agent_response", "parts": [ { "kind": "text", "text": "101 * 11 = 1111" } ] } ] } }
Remote Testing with A2A Inspector
You can also test your server using the A2A Inspector, a tool specifically designed for testing A2A protocol implementations.
Step 4: Set Up Authentication
Before deploying to AgentCore, you need to configure authentication. AgentCore supports OAuth 2.0 for secure access to your deployed A2A server. This step is crucial because without proper authentication, your agent wonโt be accessible after deployment.
Set Up Cognito User Pool
For detailed Cognito setup instructions, refer to the official AWS documentation on setting up Cognito user pool for authentication.
Get Xue Langpingโs stories in your inbox
Join Medium for free to get updates from this writer.
The basic steps are:
- Create a Cognito User Pool(Machine-to-machine application) in the AWS Console
- Create an App Client with client credentials grant type
- Configure a Resource Server with appropriate scopes
- Note down the following values:
- User Pool ID (e.g.,
us-west-2_YWxsTABCD) - App Client ID (e.g.,
2hfbm2c52ddav0huvamfpc6789) - App Client Secret
- Scope
- Cognito Domain
Get Bearer Token
After setting up Cognito, you can obtain a bearer token for testing:
# Get OAuth token from Cognito TOKEN=$(curl -X POST "https://<your-cognito-domain>.auth.<region>.amazoncognito.com/oauth2/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=<your-client-id>" \ -d "client_secret=<your-client-secret>" \ -d "scope=<your-scope>" | jq -r '.access_token') export BEARER_TOKEN=$TOKEN
Step 5: Deploy to AgentCore Runtime with VPC Configuration
With local testing complete and authentication configured, weโre ready to deploy to AgentCore Runtime. The key difference for VPC deployment is that you must specify VPC configuration and OAuth settings during the **agentcore configure** step.
Configure Your A2A Server for VPC Deployment
Use the following command to configure your agent with VPC and OAuth settings in a single step:
cd my-a2a-agent # Configure for A2A protocol with VPC and OAuth agentcore configure \ -e my_a2a_server.py \ --protocol A2A \ --vpc \ --subnets subnet-xxxxxxxxx \ --security-groups sg-xxxxxxxxx \ --authorizer-config '{ "customJWTAuthorizer": { "discoveryUrl": "https://cognito-idp.us-west-2.amazonaws.com/<your-user-pool-id>/.well-known/openid-configuration", "allowedClients": ["<your-client-id>"] } }' \ --request-header-allowlist "Authorization"
Configuration Parameters Explained:
- โ protocol A2A: Specifies the A2A protocol, which automatically configures the server to use port 9000 and mount at the root path (
/) - โ vpc: Enables VPC (Virtual Private Cloud) mode for the agent runtime, allowing private network deployment
- โ subnets: Specifies the subnet IDs where AgentCore will create ENIs (Elastic Network Interfaces)
- โ security-groups: Defines the security groups to attach to the ENIs for network access control
- โ authorizer-config: Configures OAuth 2.0 authentication using Amazon Cognito
- โ request-header-allowlist: Specifies which HTTP headers should be forwarded through to your agent
Deploy to AWS
agentcore launch
After successful deployment, youโll receive an agent runtime ARN:
arn:aws:bedrock-agentcore:us-west-2:ACCOUNT_ID:runtime/my_a2a_server-xyz123
Check Deployment Status
agentcore status
Expected output sample:
โญโโโโโโโโโโโโโโโโโโโโ Agent Status: my_a2a_server โโโโโโโโโโโโโโโโโโโโโฎ โ Ready - Agent deployed and endpoint available โ โ โ โ Agent Details: โ โ Agent Name: my_a2a_server โ โ Agent ARN: arn:aws:bedrock-agentcore:us-west-2:ACCOUNT:runtime/xxx โ โ Protocol: A2A โ โ Network Mode: VPC โ โ Region: us-west-2 | Account: ACCOUNT_ID โ โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
Step 6: Access Your Deployed A2A Agent from Within VPC
Once deployed, your A2A agent is accessible via the AgentCore Runtime endpoint. Since youโve created the VPC Interface Endpoint with private DNS enabled, you can use the standard endpoint URL and traffic will automatically route through PrivateLink without internet traversal.
Set Up Environment Variables
# Export your agent ARN (URL-encoded) export AGENT_ARN="arn:aws:bedrock-agentcore:us-west-2:ACCOUNT_ID:runtime/my_a2a_server-xyz123" # URL-encode the ARN for use in the endpoint URL export ENCODED_ARN=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$AGENT_ARN', safe=''))") # Export bearer token (from Step 4) export BEARER_TOKEN="<your-bearer-token>" # Set the base endpoint URL export ENDPOINT="https://bedrock-agentcore.us-west-2.amazonaws.com/runtimes/${ENCODED_ARN}/invocations"
Retrieve Agent Card
The Agent Card is essential for agent discovery. Use curl to retrieve it:
# Generate a unique session ID SESSION_ID=$(uuidgen || cat /proc/sys/kernel/random/uuid) # Retrieve Agent Card curl -X GET "${ENDPOINT}/.well-known/agent-card.json" \ -H "Accept: application/json" \ -H "Authorization: Bearer ${BEARER_TOKEN}" \ -H "X-Amzn-Bedrock-AgentCore-Runtime-Session-Id: ${SESSION_ID}" | jq .
Expected response sample:
{ "name": "My A2A Agent", "description": "An A2A-compatible AI agent powered by Amazon Bedrock AgentCore", "url": "https://bedrock-agentcore.us-west-2.amazonaws.com/runtimes/.../invocations/", "version": "1.0.0", "protocolVersion": "0.3.0", "preferredTransport": "JSONRPC", "capabilities": { "streaming": true }, "defaultInputModes": ["text"], "defaultOutputModes": ["text"], "skills": [ { "id": "calculator", "name": "Calculator", "description": "Perform arithmetic operations", "tags": ["math", "calculator"] } ] }
Invoke Your A2A Agent
Send a message to your deployed agent using the A2A protocol:
# Generate a unique session ID SESSION_ID=$(uuidgen || cat /proc/sys/kernel/random/uuid) # Send A2A message/send request curl -X POST "${ENDPOINT}/" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${BEARER_TOKEN}" \ -H "X-Amzn-Bedrock-AgentCore-Runtime-Session-Id: ${SESSION_ID}" \ -d '{ "jsonrpc": "2.0", "id": "req-001", "method": "message/send", "params": { "message": { "role": "user", "parts": [ { "kind": "text", "text": "What is 101 * 11?" } ], "messageId": "msg-001" } } }' | jq .
Streaming Response (SSE)
For streaming responses, use the message/stream method:
SESSION_ID=$(uuidgen || cat /proc/sys/kernel/random/uuid) curl -X POST "${ENDPOINT}/" \ -H "Content-Type: application/json" \ -H "Accept: text/event-stream" \ -H "Authorization: Bearer ${BEARER_TOKEN}" \ -H "X-Amzn-Bedrock-AgentCore-Runtime-Session-Id: ${SESSION_ID}" \ -N \ -d '{ "jsonrpc": "2.0", "id": "req-002", "method": "message/stream", "params": { "message": { "role": "user", "parts": [ { "kind": "text", "text": "Tell me a short story" } ], "messageId": "msg-002" } } }'
A2A Protocol Reference
Supported Methods
- message/send: Handles synchronous message processing, returning a complete JSON response once the operation finishes
- message/stream: Handles streaming responses, delivering data progressively via SSE (Server-Sent Events) for real-time output
Request Format (message/send)
{ "jsonrpc": "2.0", "method": "message/send", "id": "unique-request-id", "params": { "message": { "role": "user", "parts": [{"kind": "text", "text": "Your question here"}], "messageId": "unique-message-id" } } }
Response Format
{ "jsonrpc": "2.0", "id": "unique-request-id", "result": { "artifacts": [ { "artifactId": "unique-artifact-id", "name": "agent_response", "parts": [ {"kind": "text", "text": "Response..."} ] } ] } }
Error Codes
Based on the A2A protocol contract:
- -32600 / InvalidRequestException: Returns HTTP 400, indicating an invalid request such as a missing method field
- -32501 / ResourceNotFoundException: Returns HTTP 404, indicating the requested resource was not found
- -32502 / ValidationException: Returns HTTP 400, indicating a validation error in the request data
- -32503 / ThrottlingException: Returns HTTP 429, indicating the rate limit has been exceeded
- -32504 / ResourceConflictException: Returns HTTP 409, indicating a conflict with the current state of the resource
- -32505 / RuntimeClientError: Returns HTTP 424, indicating a runtime client error (failed dependency)
Production Checklist
Before going live, verify:
- VPC Endpoints Created:
bedrock-agentcore- for calling AgentCore APIs from VPC (required)bedrock-runtime- for agent to call Bedrock models (required only if no NAT Gateway)logs- for agent logging to CloudWatch (required only if no NAT Gateway)- Security Groups: Properly configured for endpoint and agent access (allow HTTPS/443)
- Authentication: OAuth 2.0 (Cognito) configured with
--authorizer-config - Agent Card: Returns accurate capability information at
/.well-known/agent-card.json - Error Handling: Proper JSON-RPC error codes returned
- Observability: CloudWatch logs enabled via VPC endpoint
Troubleshooting
Common Issues
Port Conflicts
- A2A servers MUST run on port 9000 in AgentCore Runtime
- Ensure your code uses
port=9000, not 8080
Path Mounting Issues
- A2A servers are mounted at
/(root path) - Ensure
serve_at_root=Trueif using Strands SDK
JSON-RPC Errors (-32600: Invalid Request)
- This error occurs when the request payload is missing required fields like
method - Ensure your request includes
jsonrpc,method,id, andparamsfields - Example of correct format:
{ "jsonrpc": "2.0", "id": "req-001", "method": "message/send", "params": { ... }}
Authorization Mismatch
- Ensure your request uses the same authentication method (OAuth) that the agent was configured with
- Verify the Bearer token is valid and not expired
- Check that
--request-header-allowlist "Authorization"was included in configuration
VPC Connectivity Issues
- Verify all VPC endpoints are in โavailableโ state
- Check security group rules allow HTTPS (port 443) from your VPC CIDR
- Ensure private DNS is enabled for the endpoints
- Verify the agent was configured with
--vpcflag
Agent Cannot Call Bedrock Models
- Ensure
bedrock-runtimeVPC endpoint is created - Verify the agentโs security group allows outbound HTTPS traffic
Redeployment Example
agentcore destroy rm -rf .bedrock_agentcore/ agentcore configure \ -e my_a2a_server.py \ --protocol A2A \ --vpc \ --subnets subnet-xxx \ --security-groups sg-xxx \ --authorizer-config '{...}' \ --request-header-allowlist "Authorization" agentcore launch
Important Notes
AgentCore Gateway vs A2A
โ ๏ธClarification: AgentCore Gateway is designed for MCP (Model Context Protocol) to transform existing APIs and Lambda functions into agent-ready tools. It is NOT used for exposing A2A APIs.
For A2A protocol, you deploy directly to AgentCore Runtime using agentcore configure --protocol A2A and agentcore launch. The A2A endpoint is automatically exposed through the AgentCore Runtime service.
Network Configuration
AgentCore supports different network configurations:
- Public: Run with managed internet access (default)
- VPC: Access resources hosted in a customerโs VPC or connected via AWS PrivateLink endpoints
Conclusion
By following this guide, youโve deployed an A2A-compatible agent to AgentCore Runtime with secure VPC access. Key takeaways:
- AgentCore Runtime is AWS-managed โ It doesnโt run inside your VPC, but can access your VPC resources through ENIs
- Use PrivateLink for private access โ Create VPC Interface Endpoints to call AgentCore APIs and enable agent access to AWS services without internet traversal
- VPC Endpoints are essential โ
bedrock-agentcorefor API calls,bedrock-runtimefor agent LLM access,logsfor observability - A2A provides standardization โ The protocol enables interoperability between agents built with different frameworks
- OAuth 2.0 for security โ Cognito integration provides enterprise-grade authentication
Your agent can now:
- Receive A2A protocol requests from any application in your VPC
- Communicate with other A2A-compliant agents
- Be discovered through standardized Agent Cards
- Maintain enterprise-grade security with private networking and OAuth 2.0 authentication
References
- AWS Documentation: Deploy A2A servers in AgentCore Runtime
- AWS Documentation: Configure VPC for AgentCore
- AWS Documentation: Setting up Cognito for AgentCore
- A2A Protocol Specification
- A2A Protocol Contract
- Introducing A2A Protocol Support in AgentCore Runtime (AWS Blog)
- AgentCore Starter Toolkit
- A2A Inspector for Testing