Building A2A Compliant Client and Server
Explore how to create a fully A2A-compliant client and server from scratch. Learn agent discovery, JSON-RPC communication, structured message validation, and task lifecycle management using Python tools like FastAPI and Pydantic. This lesson helps you build foundational A2A agents capable of interoperable AI interactions.
Now that we understand A2A’s core concepts, it’s time to build something real. From scratch, we’ll create a complete A2A-compliant system: a simple echo agent that receives messages from A2A clients over HTTP and responds with echoed content, paired with a client that discovers and communicates with it.
We’ll build incrementally, starting with the bare minimum and adding A2A compliance step-by-step. By the end, we’ll have a working agent that any A2A client can discover and use, and a client that can talk to any A2A-compliant agent. Along the way, we’ll explain each library, pattern, and design choice so you understand what to code and why.
What are we building?
Our goal is straightforward: create the simplest possible A2A interaction. We’ll build an echo agent that receives text messages and responds with “You said: [your message].” This might seem trivial, but it demonstrates all the essential A2A patterns:
Agent discovery through a standard endpoint.
JSON-RPC 2.0 communication protocol.
Task life cycle management (submitted to completed).
Structured message exchange with proper parts.
Error handling for malformed requests.
Think of this as the “hello world” of agent-to-agent communication, simple enough to understand completely, but comprehensive enough to serve as a foundation for more complex agents.
Before we start coding, ensure that we have the right tools. We’ll use Python with a few key libraries that handle the heavy lifting:
# Create a clean workspacemkdir a2a-echo-examplecd a2a-echo-example# Set up virtual environmentpython3 -m venv venvsource venv/bin/activate # On Windows: venv\Scripts\activate# Install core dependenciespip install fastapi uvicorn pydantic requests
Here’s what each library does.
FastAPI: A modern Python web framework that makes building APIs easy. It handles HTTP routing, request validation, and automatic API documentation. We chose it because it’s beginner-friendly, has excellent type checking, and integrates seamlessly with A2A’s JSON-based communication.
Uvicorn: An ASGI server that runs our FastAPI application. Think of it as the engine that listens for HTTP requests and routes them to our agent code. It’s lightweight, fast, and perfect for development.
Pydantic: A data validation library which ensures that our A2A messages have the correct structure. It catches errors early and provides clear feedback when something goes wrong, which is essential for protocol compliance.
Requests: The standard Python library for making HTTP calls. Our client will use it to discover agents and send messages.
These libraries handle networking, validation, and HTTP complexity, leaving us free to focus on A2A-specific logic.
How to build the A2A server
We’ll start with the absolute minimum and build up to full A2A compliance. Create the server in a file named echo_agent.py.
In the code above:
Lines 4–6: Loads the framework and creates the app instance that handles incoming HTTP requests.
Lines 8: Tell FastAPI to call the next function when it receives a GET request at
/.Lines 9–10: Returns a dictionary, which FastAPI automatically converts to JSON.
Line 12: Ensures this code runs only when executed directly, and not when imported.
Lines 13–15: Starts the HTTP server and listens on port 8000.
When we run it with the python echo_agent.py command, then visit http://localhost:8000. We should see:
{"message": "Hello from our future A2A agent!"}
FastAPI automatically parses the request, calls the function, converts the Python dictionary to JSON, sets the proper headers, and returns the response.
It’s not A2A-compliant yet, but it’s our foundation. Now, we’ll add A2A features step-by-step.
How to make the system A2A compliant
To make the agent discoverable, add the standard A2A discovery endpoint:
In the code above:
Lines 2–9: Create the standard discovery endpoint at
/.well-known/agent-card.json.Lines 11–26: Return the agent’s metadata and capabilities.
Lines 13–17: Define basic identity information.
Lines 18–22: Provide skill definitions with examples.
Lines 23–25: List capabilities and data formats.
If we restart the server now, we can test both endpoints:
http://localhost:8000/.http://localhost:8000/.well-known/agent-card.json.
Any A2A client can now discover our agent by fetching this endpoint.
How to add message structure validation
Now, we’ll add validation for A2A messages. Include these imports and models near the top:
In the code above:
Lines 1–4: Import dependencies for data validation and unique IDs.
Lines 6–8: Define a
Partmodel for message segments.Lines 10–18: Define a
Messagemodel with ID, role, and content.Lines 19–23: Wrap messages in a JSON-RPC 2.0 request.
Then, add this POST endpoint:
FastAPI now validates incoming messages automatically.
How to implement the agent logic
We can simply add a POST handler for our agent logic:
@app.post("/")def handle_message(request: JSONRPCRequest):"""A2A Message Handler"""# Validate the methodif request.method != "message/send":return {"jsonrpc": "2.0","id": request.id,"error": {"code": -32601,"message": "Method not found"}}# Extract the user's messageuser_message = request.params.messageuser_text = user_message.parts[0].text if user_message.parts else "No text"# Create our A2A response as a completed tasktask = {"kind": "task","id": str(uuid.uuid4()),"contextId": str(uuid.uuid4()),"status": {"state": "completed","timestamp": datetime.utcnow().isoformat() + "Z"},"history": [# Include the original user messageuser_message.dict(),# Add our agent response{"kind": "message","messageId": str(uuid.uuid4()),"role": "agent","parts": [{"kind": "text", "text": f"You said: '{user_text}'"}]}]}return {"jsonrpc": "2.0", "id": request.id, "result": task}
In the code above:
Lines 5–13: Validate method is “message/send” and return JSON-RPC error (-32601) if not.
Lines 16–17: Extract user message from request parameters and safely get text content.
Line 20: Create a complete A2A task object with IDs, status, and conversation history.
Lines 21–24: Generate unique task and context identifiers using UUID4.
Lines 25–25: Mark task as “completed” with ISO 8601 timestamp.
Lines 28–38: Build conversation history with original user message and agent response.
Line 41: Return task wrapped in JSON-RPC 2.0 response envelope.
This is now a complete, A2A-compliant agent. The key insight is that A2A agents respond with task objects, not simple text. This provides structure, traceability, and room for complex interactions later.
Here’s the complete echo_agent.py after all incremental steps:
How to build the A2A client
Now, let’s create a client that can discover our agent and send it messages. We can create this in a file named simple_a2a_client.py:
In the code above:
Lines 6–8: Import libraries for HTTP requests, unique IDs, and JSON handling.
Lines 12–18: Discover agent by fetching Agent Card from standard
.well-known/agent-card.jsonendpoint.Lines 21–34: Build complete JSON-RPC 2.0 request with nested A2A message structure.
Lines 25–26: Create outer JSON-RPC envelope with protocol version and unique request ID.
Lines 29–33: Create inner A2A message with user role, message ID, and text content.
Lines 36–40: Send JSON request to agent endpoint and parse response.
Lines 43–54: Extract task status and find agent’s reply in conversation history.
This client works with any A2A-compliant agent because it follows the standard discovery and communication protocols.
Testing our A2A system
Let’s see everything working together.
Start the server in one terminal:
python echo_agent.py
Run the client in another terminal:
cd apppython simple_a2a_client.py
You should see output like:
🔍 Discovering A2A agent...✅ Found: Echo Agent - A simple agent that echoes your messages back💬 Sending message...📋 Task Status: completed🤖 Agent: You said: 'Hello A2A world!'✨ A2A communication complete!
You can also test discovery manually:
curl http://0.0.0.0:8000/.well-known/agent-card.json
Our simple echo agent demonstrates all the core A2A patterns, mentioned below.
Protocol compliance: We use JSON-RPC 2.0, standard discovery endpoints, and proper message structures.
Discoverability: Any A2A client can find and understand our agent through the Agent Card.
Structured communication: Messages have roles, unique IDs, and typed content parts.
Task management: Even simple responses are wrapped in task objects with proper life cycle tracking.
Error handling: We return standard JSON-RPC errors for invalid requests.
Extensibility: Our foundation can easily grow to support streaming, file handling, multi-turn conversations, and more complex skills.
Go ahead and observe the output yourself in the terminal below:
When the terminal connects you would see a “+” button right beside Terminal 1 tab, and you can run the second command in that tab to see the output of both of the commands yourself.
As we build more sophisticated agents, keep the patterns below in mind.
Unique IDs everywhere: Tasks, messages, and contexts all need unique identifiers for tracking and debugging.
Fail gracefully: Return structured errors instead of crashing. Clients need predictable error formats.
Follow the spec: A2A’s value comes from consistency. Small deviations break interoperability.
Think in tasks: Even quick operations benefit from the task wrapper; it provides structure for logging, monitoring, and future extensions.
Validate early: Use Pydantic models to catch malformed messages before they reach your business logic.
You now have a complete, working A2A system. But this is just the beginning. The echo agent you built today will serve as the foundation for all these advanced topics. You’ve become familiar with the essential patterns, everything else builds on what you already know. More importantly, you’ve joined the A2A ecosystem. Your agent can now communicate with any other A2A-compliant agent, and your client can discover and use agents built by teams around the world. That’s the vision of A2A: a network of specialized, interoperable agents working together to solve complex problems.