Skip to content

Roelzz/mcs-a2a-python-sdk-agent

Repository files navigation

A2A Python SDK Echo Agent

Python Version License A2A SDK

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.

Features

  • ✅ Built with official Google A2A Python SDK (v0.3.22)
  • ✅ Custom AgentExecutor implementation
  • 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

Prerequisites

  • Python 3.11+
  • UV package manager (Installation guide)
  • No API key needed for echo functionality

Quick Start

1. Clone the Repository

git clone https://github.com/Roelzz/a2a-python-sdk-agent.git
cd a2a-python-sdk-agent

2. Configure Environment

Copy the example environment file and configure if needed:

cp .env.example .env

Edit .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.com

3. Install Dependencies

uv sync

4. Run the Agent

uv run echo_agent.py

The 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

Testing the Agent

Using curl (JSON-RPC 2.0)

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.

Integration with Microsoft Copilot Studio

  1. Expose your agent publicly (use ngrok, tunneling service, or deploy to cloud)

    # Example with ngrok
    ngrok http 8001
  2. Configure Copilot Studio:

    • Open Microsoft Copilot Studio
    • Navigate to SettingsAgent-to-Agent (A2A)
    • Click Add Other AgentsA2A Protocol
    • Enter your agent URL: https://your-ngrok-url.ngrok.io or your production URL
    • Save and test the connection
  3. 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

Copilot Studio Compatibility

⚠️ Important: This agent uses a custom implementation to work with Copilot Studio.

The standard A2A SDK pattern returns Message objects, but Copilot Studio requires Task objects with message history. This implementation:

  1. Returns a Task object instead of just a Message
  2. Includes full conversation history (user + agent messages)
  3. Sets proper task status (completed)
  4. Maintains consistent context_id and task_id

See COPILOT_STUDIO_INTEGRATION.md for:

  • Detailed explanation of the issue
  • Root cause analysis
  • Custom implementation details
  • Debugging tips

Architecture

┌─────────────────────┐
│  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

Project Structure

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

Customization

Modifying Agent Behavior

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()

Integrating with LLMs

To add AI capabilities (OpenAI, Anthropic, Google, etc.):

  1. Add the client library to pyproject.toml:

    uv add openai  # or anthropic, google-generativeai, etc.
  2. Add your API key to .env:

    OPENAI_API_KEY=your-api-key-here
  3. 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

Debugging

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

Production Deployment

Environment Configuration

Set AGENT_BASE_URL to your production URL:

AGENT_BASE_URL=https://your-domain.com

This URL is used in the agent card metadata.

Deployment Options

  • 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"]

Contributing

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

License

This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.

Resources

Author

Roel Schenk GitHub: @Roelzz


Last Updated: 2026-01-07 A2A SDK Version: 0.3.22 Python Version: 3.11+

About

A2A server example for a a2aproject python agent

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages