Agent-to-Agent Protocol (A2A)

Agent2Agent Protocol - Open standard for communication between independent AI agent systems

What is the Agent-to-Agent (A2A) Protocol?

The Agent2Agent (A2A) Protocol is an open standard developed by Google (now under the Linux Foundation) for enabling communication and interoperability between independent, potentially opaque AI agent systems. While MCP focuses on connecting AI to tools, A2A focuses on connecting AI agents to each other.

In an ecosystem where agents might be built using different frameworks, languages, or by different vendors, A2A provides a common language and interaction model that enables agents to:

  • Discover each other’s capabilities
  • Negotiate interaction modalities (text, forms, media)
  • Securely collaborate on long-running tasks
  • Operate without exposing their internal state, memory, or tools

Key Insight: A2A treats agents as opaque peers - they collaborate based on declared capabilities and exchanged information, without needing to share their internal thoughts, plans, or tool implementations.

A2A vs MCP: Complementary Protocols

A2A and MCP serve different purposes in the AI ecosystem:

AspectMCP (Model Context Protocol)A2A (Agent-to-Agent Protocol)
FocusConnecting AI to toolsConnecting agents to agents
RelationshipAI → Tool (master/slave)Agent ↔ Agent (peer-to-peer)
VisibilityFull tool accessOpaque execution
StateTool state visibleAgent state hidden
Use CaseFunction calling, data accessAgent collaboration, delegation

How They Work Together

┌──────────────────────────────────────────────────────────────────┐
│                        Client Agent                               │
│                                                                   │
│  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐         │
│  │  MCP Client │     │  A2A Client │     │  AI Model   │         │
│  └──────┬──────┘     └──────┬──────┘     └─────────────┘         │
└─────────┼───────────────────┼────────────────────────────────────┘
          │                   │
          ▼                   ▼
   ┌──────────────┐    ┌──────────────┐
   │  MCP Server  │    │ Remote Agent │ ◄── Uses MCP internally
   │  (Database)  │    │  (A2A Server)│     for its own tools
   └──────────────┘    └──────────────┘

An A2A Client agent might request an A2A Server agent to perform a complex task. The Server agent, in turn, might use MCP internally to interact with databases, APIs, or other tools.

Core A2A Concepts

Agent Card

The Agent Card is a JSON metadata document published by an A2A Server describing:

  • Identity and provider information
  • Available capabilities and skills
  • Supported input/output formats
  • Authentication requirements
  • Service endpoints
{
  "name": "Research Assistant Agent",
  "description": "AI agent for academic research and fact-checking",
  "version": "1.0.0",
  "supportedInterfaces": [
    {
      "url": "https://research/-agent.example.com/a2a/v1",
      "protocolBinding": "HTTP+JSON",
      "protocolVersion": "1.0"
    }
  ],
  "provider": {
    "organization": "Example Research Inc.",
    "url": "https://example/-research.com"
  },
  "capabilities": {
    "streaming": true,
    "pushNotifications": true
  },
  "defaultInputModes": ["text/plain", "application/json"],
  "defaultOutputModes": ["text/plain", "application/json"],
  "skills": [
    {
      "id": "academic-search",
      "name": "Academic Paper Search",
      "description": "Searches academic databases for relevant papers",
      "tags": ["research", "academic", "papers"],
      "examples": [
        "Find peer-reviewed articles on climate change",
        "Search for recent papers on machine learning"
      ]
    },
    {
      "id": "fact-check",
      "name": "Fact Checker",
      "description": "Verifies claims against trusted sources",
      "tags": ["verification", "facts", "sources"]
    }
  ],
  "securitySchemes": {
    "bearer": {
      "type": "http",
      "scheme": "bearer"
    }
  }
}

Task

A Task is the fundamental unit of work in A2A. Tasks have:

  • Unique ID - Server-generated identifier
  • Context ID - Groups related tasks and messages
  • Status - Current state with timestamp
  • History - Message exchange history
  • Artifacts - Output data produced by the task

Task Lifecycle

                    ┌───────────────────────────────────────┐
                    │           Task States                 │
                    └───────────────────────────────────────┘

    ┌──────────┐     ┌──────────┐     ┌──────────────────┐
    │ Submitted│────►│ Working  │────►│    Completed     │ (terminal)
    └──────────┘     └────┬─────┘     └──────────────────┘
                          │
                          ├──────────►┌──────────────────┐
                          │           │     Failed       │ (terminal)
                          │           └──────────────────┘
                          │
                          ├──────────►┌──────────────────┐
                          │           │    Canceled      │ (terminal)
                          │           └──────────────────┘
                          │
                          ├──────────►┌──────────────────┐
                          │           │    Rejected      │ (terminal)
                          │           └──────────────────┘
                          │
                          ├──────────►┌──────────────────┐
                          │           │ Input Required   │ (interrupted)
                          │           └────────┬─────────┘
                          │                    │
                          │◄───────────────────┘ (user provides input)
                          │
                          └──────────►┌──────────────────┐
                                      │  Auth Required   │ (interrupted)
                                      └──────────────────┘

Messages and Parts

Messages are communication units between client and server:

{
  "messageId": "msg-uuid-123",
  "role": "ROLE_USER",
  "parts": [
    {
      "text": "Analyze this document and summarize key findings"
    },
    {
      "url": "https://storage.example.com/document.pdf",
      "mediaType": "application/pdf",
      "filename": "research-paper.pdf"
    }
  ],
  "contextId": "context-uuid-456"
}

Parts can contain:

  • Text - Plain text content
  • Raw bytes - Binary data (base64 encoded)
  • URL - Reference to external file
  • Structured data - JSON objects

Artifacts

Artifacts are task outputs containing one or more parts:

{
  "artifactId": "artifact-uuid-789",
  "name": "Research Summary",
  "description": "Summary of analyzed research papers",
  "parts": [
    {
      "text": "## Key Findings\n\n1. Climate change...",
      "mediaType": "text/markdown"
    },
    {
      "data": {
        "paperCount": 42,
        "topTopics": ["climate", "sustainability"],
        "confidence": 0.95
      },
      "mediaType": "application/json"
    }
  ]
}

A2A Protocol Bindings

A2A supports multiple transport protocols:

BindingTransportUse Case
JSON-RPCHTTP(S) + SSEStandard web integration
gRPCHTTP/2 + ProtobufHigh-performance, streaming
HTTP+JSON/RESTHTTP(S)RESTful integration

JSON-RPC Methods

OperationMethodDescription
Send MessageSendMessageInitiate or continue a task
Stream MessageSendStreamingMessageReal-time streaming updates
Get TaskGetTaskRetrieve task status
List TasksListTasksList tasks with filtering
Cancel TaskCancelTaskRequest task cancellation
SubscribeSubscribeToTaskSubscribe to task updates

HTTP+JSON/REST Endpoints

OperationMethodEndpoint
Send MessagePOST/message:send
Stream MessagePOST/message:stream
Get TaskGET/tasks/{id}
List TasksGET/tasks
Cancel TaskPOST/tasks/{id}:cancel
SubscribePOST/tasks/{id}:subscribe
Agent CardGET/.well-known/agent-card.json

A2A in .NET

The official A2A .NET SDK is available via NuGet:

dotnet add package A2A

A2A Server Implementation

using A2A;
using A2A.Server;
using System.Text.Json;

/// <summary>
/// A2A agent server for document analysis.
/// </summary>
public sealed class DocumentAnalysisAgent : IA2AAgent
{
    private readonly IDocumentAnalyzer _analyzer;
    private readonly ILogger<DocumentAnalysisAgent> _logger;

    /// <summary>
    /// Initializes the document analysis agent.
    /// </summary>
    public DocumentAnalysisAgent(
        IDocumentAnalyzer analyzer,
        ILogger<DocumentAnalysisAgent> logger)
    {
        _analyzer = analyzer;
        _logger = logger;
    }

    /// <inheritdoc/>
    public AgentCard GetAgentCard()
    {
        return new AgentCard
        {
            Name = "Document Analysis Agent",
            Description = "Analyzes documents and extracts insights",
            Version = "1.0.0",
            SupportedInterfaces =
            [
                new AgentInterface
                {
                    Url = "https://doc/-agent.example.com/a2a",
                    ProtocolBinding = "HTTP+JSON",
                    ProtocolVersion = "1.0"
                }
            ],
            Provider = new AgentProvider
            {
                Organization = "Example Corp",
                Url = "https://example.com/"
            },
            Capabilities = new AgentCapabilities
            {
                Streaming = true,
                PushNotifications = true
            },
            DefaultInputModes = ["text/plain", "application/pdf"],
            DefaultOutputModes = ["application/json", "text/markdown"],
            Skills =
            [
                new AgentSkill
                {
                    Id = "summarize",
                    Name = "Document Summarization",
                    Description = "Summarizes documents into key points",
                    Tags = ["summary", "analysis", "documents"],
                    Examples = ["Summarize this PDF document"]
                },
                new AgentSkill
                {
                    Id = "extract-entities",
                    Name = "Entity Extraction",
                    Description = "Extracts named entities from text",
                    Tags = ["entities", "NER", "extraction"]
                }
            ]
        };
    }

    /// <inheritdoc/>
    public async Task<SendMessageResponse> SendMessageAsync(
        SendMessageRequest request,
        CancellationToken cancellationToken = default)
    {
        _logger.LogInformation("Received message: {MessageId}", request.Message.MessageId);

        // Create a new task
        A2ATask task = new()
        {
            Id = Guid.NewGuid().ToString(),
            ContextId = request.Message.ContextId ?? Guid.NewGuid().ToString(),
            Status = new TaskStatus
            {
                State = TaskState.Working,
                Timestamp = DateTimeOffset.UtcNow
            }
        };

        // Process the message asynchronously
        _ = ProcessMessageAsync(task, request.Message, cancellationToken);

        return new SendMessageResponse { Task = task };
    }

    private async Task ProcessMessageAsync(
        A2ATask task,
        Message message,
        CancellationToken cancellationToken)
    {
        try
        {
            // Extract text content from parts
            string textContent = string.Join("\n",
                message.Parts
                    .Where(p => !string.IsNullOrEmpty(p.Text))
                    .Select(p => p.Text));

            // Analyze the document
            DocumentAnalysisResult result = await _analyzer.AnalyzeAsync(
                textContent, cancellationToken);

            // Create artifact with results
            Artifact artifact = new()
            {
                ArtifactId = Guid.NewGuid().ToString(),
                Name = "Analysis Results",
                Description = "Document analysis output",
                Parts =
                [
                    new Part
                    {
                        Text = result.Summary,
                        MediaType = "text/markdown"
                    },
                    new Part
                    {
                        Data = JsonSerializer.SerializeToElement(new
                        {
                            result.WordCount,
                            result.SentimentScore,
                            result.Entities,
                            result.Topics
                        }),
                        MediaType = "application/json"
                    }
                ]
            };

            // Update task to completed
            task.Status = new TaskStatus
            {
                State = TaskState.Completed,
                Timestamp = DateTimeOffset.UtcNow
            };
            task.Artifacts = [artifact];
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Task {TaskId} failed", task.Id);

            task.Status = new TaskStatus
            {
                State = TaskState.Failed,
                Message = new Message
                {
                    Role = Role.Agent,
                    Parts = [new Part { Text = $"Analysis failed: {ex.Message}" }]
                },
                Timestamp = DateTimeOffset.UtcNow
            };
        }
    }
}

/// <summary>
/// Result of document analysis.
/// </summary>
public sealed record DocumentAnalysisResult(
    string Summary,
    int WordCount,
    double SentimentScore,
    IReadOnlyList<string> Entities,
    IReadOnlyList<string> Topics);

A2A Client Implementation

using A2A;
using A2A.Client;
using System.Net.Http.Headers;

/// <summary>
/// Client for communicating with A2A agents.
/// </summary>
public sealed class A2AAgentClient : IDisposable
{
    private readonly HttpClient _httpClient;
    private readonly ILogger<A2AAgentClient> _logger;

    /// <summary>
    /// Initializes the A2A client.
    /// </summary>
    public A2AAgentClient(
        HttpClient httpClient,
        ILogger<A2AAgentClient> logger)
    {
        _httpClient = httpClient;
        _logger = logger;
    }

    /// <summary>
    /// Discovers an agent's capabilities by fetching its Agent Card.
    /// </summary>
    public async Task<AgentCard> DiscoverAgentAsync(
        Uri agentBaseUri,
        CancellationToken cancellationToken = default)
    {
        Uri wellKnownUri = new(agentBaseUri, "/.well-known/agent-card.json");

        _logger.LogInformation("Discovering agent at {Uri}", wellKnownUri);

        HttpResponseMessage response = await _httpClient.GetAsync(
            wellKnownUri, cancellationToken);

        response.EnsureSuccessStatusCode();

        AgentCard? card = await response.Content.ReadFromJsonAsync<AgentCard>(
            cancellationToken: cancellationToken);

        return card ?? throw new InvalidOperationException("Invalid Agent Card");
    }

    /// <summary>
    /// Sends a message to an A2A agent and receives a task.
    /// </summary>
    public async Task<A2ATask> SendMessageAsync(
        Uri agentEndpoint,
        string messageText,
        string? contextId = null,
        CancellationToken cancellationToken = default)
    {
        SendMessageRequest request = new()
        {
            Message = new Message
            {
                MessageId = Guid.NewGuid().ToString(),
                Role = Role.User,
                ContextId = contextId,
                Parts = [new Part { Text = messageText }]
            },
            Configuration = new SendMessageConfiguration
            {
                AcceptedOutputModes = ["application/json", "text/plain"],
                Blocking = false
            }
        };

        _logger.LogInformation("Sending message to {Endpoint}", agentEndpoint);

        HttpResponseMessage response = await _httpClient.PostAsJsonAsync(
            new Uri(agentEndpoint, "/message:send"),
            request,
            cancellationToken);

        response.EnsureSuccessStatusCode();

        SendMessageResponse? result = await response.Content
            .ReadFromJsonAsync<SendMessageResponse>(cancellationToken: cancellationToken);

        return result?.Task ?? throw new InvalidOperationException("No task returned");
    }

    /// <summary>
    /// Polls for task completion.
    /// </summary>
    public async Task<A2ATask> WaitForTaskCompletionAsync(
        Uri agentEndpoint,
        string taskId,
        TimeSpan pollInterval,
        TimeSpan timeout,
        CancellationToken cancellationToken = default)
    {
        using CancellationTokenSource timeoutCts = new(timeout);
        using CancellationTokenSource linkedCts = CancellationTokenSource
            .CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);

        while (!linkedCts.Token.IsCancellationRequested)
        {
            A2ATask task = await GetTaskAsync(agentEndpoint, taskId, linkedCts.Token);

            if (IsTerminalState(task.Status.State))
            {
                return task;
            }

            _logger.LogDebug("Task {TaskId} is {State}, polling...", taskId, task.Status.State);

            await Task.Delay(pollInterval, linkedCts.Token);
        }

        throw new TimeoutException($"Task {taskId} did not complete within {timeout}");
    }

    /// <summary>
    /// Gets the current state of a task.
    /// </summary>
    public async Task<A2ATask> GetTaskAsync(
        Uri agentEndpoint,
        string taskId,
        CancellationToken cancellationToken = default)
    {
        Uri taskUri = new(agentEndpoint, $"/tasks/{taskId}");

        HttpResponseMessage response = await _httpClient.GetAsync(
            taskUri, cancellationToken);

        response.EnsureSuccessStatusCode();

        A2ATask? task = await response.Content.ReadFromJsonAsync<A2ATask>(
            cancellationToken: cancellationToken);

        return task ?? throw new InvalidOperationException("Task not found");
    }

    /// <summary>
    /// Streams task updates using Server-Sent Events.
    /// </summary>
    public async IAsyncEnumerable<StreamResponse> StreamMessageAsync(
        Uri agentEndpoint,
        string messageText,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        SendMessageRequest request = new()
        {
            Message = new Message
            {
                MessageId = Guid.NewGuid().ToString(),
                Role = Role.User,
                Parts = [new Part { Text = messageText }]
            }
        };

        using HttpRequestMessage httpRequest = new(HttpMethod.Post,
            new Uri(agentEndpoint, "/message:stream"));
        httpRequest.Content = JsonContent.Create(request);
        httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream"));

        using HttpResponseMessage response = await _httpClient.SendAsync(
            httpRequest,
            HttpCompletionOption.ResponseHeadersRead,
            cancellationToken);

        response.EnsureSuccessStatusCode();

        await using Stream stream = await response.Content.ReadAsStreamAsync(cancellationToken);
        using StreamReader reader = new(stream);

        while (!reader.EndOfStream && !cancellationToken.IsCancellationRequested)
        {
            string? line = await reader.ReadLineAsync(cancellationToken);

            if (string.IsNullOrEmpty(line) || !line.StartsWith("data: "))
            {
                continue;
            }

            string json = line[6..]; // Remove "data: " prefix
            StreamResponse? streamResponse = JsonSerializer.Deserialize<StreamResponse>(json);

            if (streamResponse is not null)
            {
                yield return streamResponse;
            }
        }
    }

    private static bool IsTerminalState(TaskState state) => state switch
    {
        TaskState.Completed => true,
        TaskState.Failed => true,
        TaskState.Canceled => true,
        TaskState.Rejected => true,
        _ => false
    };

    /// <inheritdoc/>
    public void Dispose()
    {
        _httpClient.Dispose();
    }
}

Multi-Turn Conversation Example

using A2A;
using A2A.Client;

/// <summary>
/// Demonstrates multi-turn A2A conversation.
/// </summary>
public sealed class MultiTurnConversationExample
{
    private readonly A2AAgentClient _client;

    public MultiTurnConversationExample(A2AAgentClient client)
    {
        _client = client;
    }

    /// <summary>
    /// Runs a multi-turn conversation with an A2A agent.
    /// </summary>
    public async Task RunConversationAsync(
        Uri agentEndpoint,
        CancellationToken cancellationToken = default)
    {
        // Discover the agent first
        AgentCard card = await _client.DiscoverAgentAsync(agentEndpoint, cancellationToken);
        Console.WriteLine($"Connected to: {card.Name}");
        Console.WriteLine($"Skills: {string.Join(", ", card.Skills.Select(s => s.Name))}");

        string? contextId = null;
        string? taskId = null;

        while (true)
        {
            Console.Write("\nYou: ");
            string? input = Console.ReadLine();

            if (string.IsNullOrWhiteSpace(input) || input.Equals("quit", StringComparison.OrdinalIgnoreCase))
            {
                break;
            }

            // Send message with context for multi-turn
            SendMessageRequest request = new()
            {
                Message = new Message
                {
                    MessageId = Guid.NewGuid().ToString(),
                    Role = Role.User,
                    ContextId = contextId,
                    TaskId = taskId,
                    Parts = [new Part { Text = input }]
                },
                Configuration = new SendMessageConfiguration
                {
                    Blocking = true // Wait for completion
                }
            };

            // Use streaming for real-time response
            Console.Write("Agent: ");

            await foreach (StreamResponse response in _client.StreamMessageAsync(
                agentEndpoint, input, cancellationToken))
            {
                if (response.Task is not null)
                {
                    contextId = response.Task.ContextId;
                    taskId = response.Task.Id;
                }

                if (response.ArtifactUpdate?.Artifact.Parts is { } parts)
                {
                    foreach (Part part in parts)
                    {
                        if (!string.IsNullOrEmpty(part.Text))
                        {
                            Console.Write(part.Text);
                        }
                    }
                }

                if (response.StatusUpdate?.Status.State == TaskState.InputRequired)
                {
                    Console.WriteLine("\n[Agent needs more input]");
                }
            }

            Console.WriteLine();
        }
    }
}

Hosting A2A Server in ASP.NET Core

using A2A;
using A2A.Server;
using A2A.Server.AspNetCore;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

// Register A2A agent services
builder.Services.AddSingleton<IDocumentAnalyzer, DocumentAnalyzer>();
builder.Services.AddSingleton<IA2AAgent, DocumentAnalysisAgent>();

// Add A2A server infrastructure
builder.Services.AddA2AServer();

// Configure authentication
builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer(options =>
    {
        options.Authority = builder.Configuration["Auth:Authority"];
        options.Audience = builder.Configuration["Auth:Audience"];
    });

builder.Services.AddAuthorization();

WebApplication app = builder.Build();

// Serve Agent Card at well-known location
app.MapGet("/.well-known/agent-card.json", (IA2AAgent agent) =>
    Results.Json(agent.GetAgentCard()));

// Map A2A endpoints
app.MapA2AEndpoints()
    .RequireAuthorization();

app.Run();

Push Notifications

A2A supports webhook-based push notifications for long-running tasks:

using A2A;

/// <summary>
/// Configures push notifications for task updates.
/// </summary>
PushNotificationConfig config = new()
{
    Url = "https://my/-app.example.com/webhooks/a2a",
    Token = "secure-webhook-token",
    Authentication = new AuthenticationInfo
    {
        Schemes = ["Bearer"]
    }
};

SendMessageRequest request = new()
{
    Message = new Message
    {
        MessageId = Guid.NewGuid().ToString(),
        Role = Role.User,
        Parts = [new Part { Text = "Analyze this large dataset..." }]
    },
    Configuration = new SendMessageConfiguration
    {
        PushNotificationConfig = config,
        Blocking = false // Don't wait, use webhook
    }
};

Webhook Handler

using A2A;

/// <summary>
/// ASP.NET Core controller for A2A webhook notifications.
/// </summary>
[ApiController]
[Route("webhooks/a2a")]
public sealed class A2AWebhookController : ControllerBase
{
    private readonly ILogger<A2AWebhookController> _logger;

    public A2AWebhookController(ILogger<A2AWebhookController> logger)
    {
        _logger = logger;
    }

    /// <summary>
    /// Receives A2A push notifications.
    /// </summary>
    [HttpPost]
    public async Task<IActionResult> ReceiveNotificationAsync(
        [FromBody] StreamResponse notification)
    {
        if (notification.StatusUpdate is { } statusUpdate)
        {
            _logger.LogInformation(
                "Task {TaskId} status: {State}",
                statusUpdate.TaskId,
                statusUpdate.Status.State);

            if (statusUpdate.Status.State == TaskState.Completed)
            {
                // Handle completion
                await HandleTaskCompletedAsync(statusUpdate.TaskId);
            }
        }

        if (notification.ArtifactUpdate is { } artifactUpdate)
        {
            _logger.LogInformation(
                "Task {TaskId} artifact: {ArtifactId}",
                artifactUpdate.TaskId,
                artifactUpdate.Artifact.ArtifactId);

            // Process the artifact
            await ProcessArtifactAsync(artifactUpdate.Artifact);
        }

        return Ok();
    }

    private Task HandleTaskCompletedAsync(string taskId)
    {
        // Implementation
        return Task.CompletedTask;
    }

    private Task ProcessArtifactAsync(Artifact artifact)
    {
        // Implementation
        return Task.CompletedTask;
    }
}

Error Handling

A2A defines specific error types:

ErrorHTTPDescription
TaskNotFoundError404Task doesn’t exist or isn’t accessible
TaskNotCancelableError409Task already in terminal state
ContentTypeNotSupportedError415Media type not supported
UnsupportedOperationError400Operation not supported by agent
VersionNotSupportedError400Protocol version not supported
using A2A;
using System.Net;

/// <summary>
/// Handles A2A-specific errors.
/// </summary>
public async Task<A2ATask> SafeGetTaskAsync(
    A2AAgentClient client,
    Uri endpoint,
    string taskId,
    CancellationToken cancellationToken)
{
    try
    {
        return await client.GetTaskAsync(endpoint, taskId, cancellationToken);
    }
    catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
    {
        throw new A2AException("TaskNotFoundError",
            $"Task {taskId} not found or not accessible", ex);
    }
    catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.BadRequest)
    {
        throw new A2AException("UnsupportedOperationError",
            "Operation not supported by agent", ex);
    }
}

/// <summary>
/// A2A-specific exception.
/// </summary>
public sealed class A2AException : Exception
{
    public string ErrorType { get; }

    public A2AException(string errorType, string message, Exception? inner = null)
        : base(message, inner)
    {
        ErrorType = errorType;
    }
}

A2A Protocol Comparison

✅ A2A Advantages

  • Peer-to-peer agent communication
  • Agent opacity preserved
  • Multi-turn conversations
  • Streaming and push notifications
  • Enterprise-ready security
  • Multiple protocol bindings

⚠️ A2A Considerations

  • More complex than direct API calls
  • Requires A2A-compatible agents
  • Still evolving (v1.0 RC)
  • Limited .NET ecosystem currently

When to Use A2A vs REST vs MCP

ScenarioRecommended Protocol
AI agent needs to call a databaseMCP
AI agent delegates to another AI agentA2A
Mobile app calls backend APIREST
AI agent uses a search toolMCP
Multiple AI agents collaborate on a taskA2A
Standard CRUD operationsREST
AI agent orchestrates other agentsA2A

Resources