A simple echo agent built with the Google A2A Python SDK that demonstrates Agent-to-Agent (A2A) protocol integration with Microsoft Copilot Studio.
Important: This implementation includes custom modifications required for Copilot Studio compatibility. See COPILOT_STUDIO_INTEGRATION.md for details.
- ✅ Built with official Google A2A Python SDK (v0.3.22)
- ✅ Custom
AgentExecutorimplementation - ✅ Full Copilot Studio compatibility with custom Task response handling
- ✅ Comprehensive debug logging for troubleshooting
- ✅ JSON-RPC 2.0 protocol over HTTP
- ✅ Simple echo functionality with test catchphrase ("I love pancakes!")
- ✅ No LLM/API key required for basic functionality
- Python 3.11+
- UV package manager (Installation guide)
- No API key needed for echo functionality
git clone https://github.com/Roelzz/a2a-python-sdk-agent.git
cd a2a-python-sdk-agentCopy the example environment file and configure if needed:
cp .env.example .envEdit .env to set your AGENT_BASE_URL:
# For local development
AGENT_BASE_URL=http://localhost:8001
# For production (publicly accessible URL)
AGENT_BASE_URL=https://your-domain.comuv syncuv run echo_agent.pyThe agent will start on http://0.0.0.0:8001:
- JSON-RPC Endpoint:
http://localhost:8001/ - Agent Card:
http://localhost:8001/.well-known/agent-card.json
curl -X POST http://localhost:8001/ \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "1",
"method": "message/send",
"params": {
"message": {
"message_id": "msg-001",
"role": "user",
"parts": [
{
"text": "Hello, echo agent!"
}
]
}
}
}'Expected Response:
{
"id": "1",
"jsonrpc": "2.0",
"result": {
"kind": "task",
"id": "task-id",
"contextId": "context-id",
"status": {"state": "completed"},
"history": [
{
"role": "user",
"parts": [{"kind": "text", "text": "Hello, echo agent!"}]
},
{
"role": "agent",
"parts": [{"kind": "text", "text": "Echo: I received your message: 'Hello, echo agent!' - I love pancakes!"}]
}
]
}
}Look for the catchphrase "I love pancakes!" to confirm the agent is working correctly.
-
Expose your agent publicly (use ngrok, tunneling service, or deploy to cloud)
# Example with ngrok ngrok http 8001 -
Configure Copilot Studio:
- Open Microsoft Copilot Studio
- Navigate to Settings → Agent-to-Agent (A2A)
- Click Add Other Agents → A2A Protocol
- Enter your agent URL:
https://your-ngrok-url.ngrok.ioor your production URL - Save and test the connection
-
Test the Integration:
- Send a message from Copilot Studio
- You should see the echo response with "I love pancakes!"
- Check agent logs for debugging information
The standard A2A SDK pattern returns Message objects, but Copilot Studio requires Task objects with message history. This implementation:
- Returns a
Taskobject instead of just aMessage - Includes full conversation history (user + agent messages)
- Sets proper task status (
completed) - Maintains consistent
context_idandtask_id
See COPILOT_STUDIO_INTEGRATION.md for:
- Detailed explanation of the issue
- Root cause analysis
- Custom implementation details
- Debugging tips
┌─────────────────────┐
│ Copilot Studio │
│ (Client) │
└──────────┬──────────┘
│ JSON-RPC 2.0 (message/send)
▼
┌─────────────────────┐
│ A2A Starlette App │
│ (HTTP Server) │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ DefaultRequestHandler│
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ EchoAgentExecutor │
│ (Custom Logic) │
└─────────────────────┘
Key Components:
- A2AStarletteApplication: Handles HTTP/JSON-RPC protocol
- DefaultRequestHandler: Routes requests to agent executor
- EchoAgentExecutor: Custom agent logic (returns Task with history)
- InMemoryTaskStore: Stores task state in memory
a2a-python-sdk-agent/
├── echo_agent.py # Main agent implementation
├── echo_agent.py.backup # Backup with response logging middleware
├── COPILOT_STUDIO_INTEGRATION.md # Integration documentation
├── README.md # This file
├── .env # Environment variables (not in git)
├── .env.example # Environment template
├── .gitignore # Git ignore rules
├── pyproject.toml # Project dependencies
└── uv.lock # Dependency lock file
Edit the EchoAgentExecutor.execute() method in echo_agent.py:
class EchoAgentExecutor(AgentExecutor):
async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
# Get user input
user_message = context.get_user_input()
# Your custom logic here
response_text = f"Your custom response: {user_message}"
# Create response message
response_message = new_agent_text_message(
text=response_text,
context_id=context.context_id,
task_id=context.task_id
)
# Build task with history (required for Copilot Studio)
history = []
if context.message:
history.append(context.message)
history.append(response_message)
completed_task = Task(
id=context.task_id,
context_id=context.context_id,
status=TaskStatus(state=TaskState.completed),
history=history
)
# Return the task
await event_queue.enqueue_event(completed_task)
await event_queue.close()To add AI capabilities (OpenAI, Anthropic, Google, etc.):
-
Add the client library to
pyproject.toml:uv add openai # or anthropic, google-generativeai, etc. -
Add your API key to
.env:OPENAI_API_KEY=your-api-key-here
-
Call the LLM in
execute():from openai import OpenAI client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) async def execute(self, context: RequestContext, event_queue: EventQueue): user_message = context.get_user_input() # Call OpenAI response = client.chat.completions.create( model="gpt-4", messages=[{"role": "user", "content": user_message}] ) response_text = response.choices[0].message.content # ... rest of the implementation
The agent includes comprehensive logging:
logger.debug(f"[INCOMING] Context ID: {context.context_id}, Task ID: {context.task_id}")
logger.debug(f"[INCOMING] User message: {user_message}")
logger.debug(f"[OUTGOING] Response: {response_text}")
logger.debug("[COMPLETED] Task completed successfully")Common Issues:
| Issue | Solution |
|---|---|
| Copilot Studio shows "task completed" but no message | Verify you're returning a Task object, not just a Message |
| Agent not accessible from Copilot Studio | Ensure AGENT_BASE_URL is publicly accessible (use ngrok or deploy) |
| "Method not found" error | Use message/send method (not createTask) |
Missing message_id error |
Include message_id field in request |
Set AGENT_BASE_URL to your production URL:
AGENT_BASE_URL=https://your-domain.comThis URL is used in the agent card metadata.
- Cloud Run (Google Cloud)
- AWS Lambda with API Gateway
- Azure Container Apps
- Heroku / Railway / Render
- Docker container
Example Dockerfile:
FROM python:3.11-slim
WORKDIR /app
# Install uv
RUN pip install uv
# Copy project files
COPY pyproject.toml uv.lock ./
COPY echo_agent.py .
# Install dependencies
RUN uv sync
# Expose port
EXPOSE 8001
# Run the agent
CMD ["uv", "run", "echo_agent.py"]This is a reference implementation demonstrating A2A protocol integration with Copilot Studio. Feel free to:
- 🍴 Fork this repository for your own projects
- 🐛 Report issues you encounter
- 💡 Share improvements via pull requests
- 📖 Use this as a template for building A2A agents
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
- A2A Python SDK: github.com/google/a2a-python
- A2A Protocol: a2aprotocol.ai
- Copilot Studio Documentation: Microsoft Learn
- A2A Specification: github.com/a2aproject/A2A
Roel Schenk GitHub: @Roelzz
Last Updated: 2026-01-07 A2A SDK Version: 0.3.22 Python Version: 3.11+