diff --git a/docs/decisions/0001-agent-run-response.md b/docs/decisions/0001-agent-run-response.md
index bf589d7fe0..b60878adff 100644
--- a/docs/decisions/0001-agent-run-response.md
+++ b/docs/decisions/0001-agent-run-response.md
@@ -499,7 +499,7 @@ We need to decide what AIContent types, each agent response type will be mapped
| AWS (Strands) | **Approach 2** Supports a special invocation method called [structured_output](https://strandsagents.com/latest/api-reference/agent/#strands.agent.agent.Agent.structured_output) |
| LangGraph | **Approach 1** Supports [configuring an agent](https://langchain-ai.github.io/langgraph/agents/agents/?h=structured#6-configure-structured-output) at agent construction time, and a [structured response](https://langchain-ai.github.io/langgraph/agents/run_agents/#output-format) can be retrieved as a special property on the agent response |
| Agno | **Approach 1** Supports [configuring an agent](https://docs.agno.com/examples/getting-started/structured-output) at agent construction time |
-| A2A | **Informal Approach 2** Doesn't formally support schema negotiation, but [hints can be provided via metadata](https://a2aproject.github.io/A2A/v0.2.5/specification/#97-structured-data-exchange-requesting-and-providing-json) at invocation time |
+| A2A | **Informal Approach 2** Doesn't formally support schema negotiation, but [hints can be provided via metadata](https://a2a-protocol.org/latest/specification/#97-structured-data-exchange-requesting-and-providing-json) at invocation time |
| Protocol Activity | Supports returning [Complex types](https://github.com/microsoft/Agents/blob/main/specs/activity/protocol-activity.md#complex-types) but no support for requesting a type |
### Response Reason Support
@@ -511,5 +511,5 @@ We need to decide what AIContent types, each agent response type will be mapped
| AWS (Strands) | Exposes a [stop_reason](https://strandsagents.com/latest/api-reference/types/#strands.types.event_loop.StopReason) property on the [AgentResult](https://strandsagents.com/latest/api-reference/agent/#strands.agent.agent_result.AgentResult) class with options that are tied closely to LLM operations. |
| LangGraph | No equivalent present, output contains only [messages](https://langchain-ai.github.io/langgraph/agents/run_agents/#output-format) |
| Agno | [No equivalent present](https://docs.agno.com/reference/agents/run-response) |
-| A2A | No equivalent present, response only contains a [message](https://a2aproject.github.io/A2A/v0.2.5/specification/#64-message-object) or [task](https://a2aproject.github.io/A2A/v0.2.5/specification/#61-task-object). |
+| A2A | No equivalent present, response only contains a [message](https://a2a-protocol.org/latest/specification/#64-message-object) or [task](https://a2a-protocol.org/latest/specification/#61-task-object). |
| Protocol Activity | [No equivalent present.](https://github.com/microsoft/Agents/blob/main/specs/activity/protocol-activity.md) |
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 400b35d298..bba35b5703 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -21,6 +21,10 @@
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj b/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj
new file mode 100644
index 0000000000..341798a38e
--- /dev/null
+++ b/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj
@@ -0,0 +1,25 @@
+
+
+
+ Exe
+ net9.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/Program.cs b/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/Program.cs
new file mode 100644
index 0000000000..f0b138c2c7
--- /dev/null
+++ b/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/Program.cs
@@ -0,0 +1,86 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to represent an A2A agent as a set of function tools, where each function tool
+// corresponds to a skill of the A2A agent, and register these function tools with another AI agent so
+// it can leverage the A2A agent's skills.
+
+using System.Text.RegularExpressions;
+using A2A;
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+using OpenAI;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+var a2aAgentHost = Environment.GetEnvironmentVariable("A2A_AGENT_HOST") ?? throw new InvalidOperationException("A2A_AGENT_HOST is not set.");
+
+// Initialize an A2ACardResolver to get an A2A agent card.
+A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost));
+
+// Get the agent card
+AgentCard agentCard = await agentCardResolver.GetAgentCardAsync();
+
+// Create an instance of the AIAgent for an existing A2A agent specified by the agent card.
+AIAgent a2aAgent = await agentCard.GetAIAgentAsync();
+
+// Create the main agent, and provide the a2a agent skills as a function tools.
+AIAgent agent = new AzureOpenAIClient(
+ new Uri(endpoint),
+ new AzureCliCredential())
+ .GetChatClient(deploymentName)
+ .CreateAIAgent(
+ instructions: "You are a helpful assistant that helps people with travel planning.",
+ tools: [.. CreateFunctionTools(a2aAgent, agentCard)]
+ );
+
+// Invoke the agent and output the text result.
+Console.WriteLine(await agent.RunAsync("Plan a route from '1600 Amphitheatre Parkway, Mountain View, CA' to 'San Francisco International Airport' avoiding tolls"));
+
+static IEnumerable CreateFunctionTools(AIAgent a2aAgent, AgentCard agentCard)
+{
+ foreach (var skill in agentCard.Skills)
+ {
+ // A2A agent skills don't have schemas describing the expected shape of their inputs and outputs.
+ // Schemas can be beneficial for AI models to better understand the skill's contract, generate
+ // the skill's input accordingly and to know what to expect in the skill's output.
+ // However, the A2A specification defines properties such as name, description, tags, examples,
+ // inputModes, and outputModes to provide context about the skill's purpose, capabilities, usage,
+ // and supported MIME types. These properties are added to the function tool description to help
+ // the model determine the appropriate shape of the skill's input and output.
+ AIFunctionFactoryOptions options = new()
+ {
+ Name = FunctionNameSanitizer.Sanitize(skill.Name),
+ Description = $$"""
+ {
+ "description": "{{skill.Description}}",
+ "tags": "[{{string.Join(", ", skill.Tags ?? [])}}]",
+ "examples": "[{{string.Join(", ", skill.Examples ?? [])}}]",
+ "inputModes": "[{{string.Join(", ", skill.InputModes ?? [])}}]",
+ "outputModes": "[{{string.Join(", ", skill.OutputModes ?? [])}}]"
+ }
+ """,
+ };
+
+ yield return AIFunctionFactory.Create(RunAgentAsync, options);
+ }
+
+ async Task RunAgentAsync(string input, CancellationToken cancellationToken)
+ {
+ var response = await a2aAgent.RunAsync(input, cancellationToken: cancellationToken).ConfigureAwait(false);
+
+ return response.Text;
+ }
+}
+
+internal static partial class FunctionNameSanitizer
+{
+ public static string Sanitize(string name)
+ {
+ return InvalidNameCharsRegex().Replace(name, "_");
+ }
+
+ [GeneratedRegex("[^0-9A-Za-z]+")]
+ private static partial Regex InvalidNameCharsRegex();
+}
diff --git a/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/README.md b/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/README.md
new file mode 100644
index 0000000000..6cbd56dca4
--- /dev/null
+++ b/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/README.md
@@ -0,0 +1,22 @@
+# A2A Agent as Function Tools
+
+This sample demonstrates how to represent an A2A agent as a set of function tools, where each function tool corresponds to a skill of the A2A agent,
+and register these function tools with another AI agent so it can leverage the A2A agent's skills.
+
+# Prerequisites
+
+Before you begin, ensure you have the following prerequisites:
+
+- .NET 8.0 SDK or later
+- Access to the A2A agent host service
+
+**Note**: These samples need to be run against a valid A2A server. If no A2A server is available, they can be run against the echo-agent that can be
+spun up locally by following the guidelines at: https://github.com/a2aproject/a2a-dotnet/blob/main/samples/AgentServer/README.md
+
+Set the following environment variables:
+
+```powershell
+$env:A2A_AGENT_HOST="https://your-a2a-agent-host" # Replace with your A2A agent host endpoint
+$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI resource endpoint
+$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
+```
\ No newline at end of file
diff --git a/dotnet/samples/GettingStarted/A2A/README.md b/dotnet/samples/GettingStarted/A2A/README.md
new file mode 100644
index 0000000000..3ddac95996
--- /dev/null
+++ b/dotnet/samples/GettingStarted/A2A/README.md
@@ -0,0 +1,50 @@
+# Agent-to-Agent (A2A) Samples
+
+These samples demonstrate how to work with Agent-to-Agent (A2A) specific features in the Agent Framework.
+
+For other samples that demonstrate how to use AIAgent instances,
+see the [Getting Started With Agents](../Agents/README.md) samples.
+
+## Prerequisites
+
+See the README.md for each sample for the prerequisites for that sample.
+
+## Samples
+
+|Sample|Description|
+|---|---|
+|[A2A Agent As Function Tools](./A2AAgent_AsFunctionTools/)|This sample demonstrates how to represent an A2A agent as a set of function tools, where each function tool corresponds to a skill of the A2A agent, and register these function tools with another AI agent so it can leverage the A2A agent's skills.|
+
+## Running the samples from the console
+
+To run the samples, navigate to the desired sample directory, e.g.
+
+```powershell
+cd A2AAgent_AsFunctionTools
+```
+
+Set the required environment variables as documented in the sample readme.
+If the variables are not set, you will be prompted for the values when running the samples.
+Execute the following command to build the sample:
+
+```powershell
+dotnet build
+```
+
+Execute the following command to run the sample:
+
+```powershell
+dotnet run --no-build
+```
+
+Or just build and run in one step:
+
+```powershell
+dotnet run
+```
+
+## Running the samples from Visual Studio
+
+Open the solution in Visual Studio and set the desired sample project as the startup project. Then, run the project using the built-in debugger or by pressing `F5`.
+
+You will be prompted for any required environment variables if they are not already set.
diff --git a/dotnet/samples/GettingStarted/README.md b/dotnet/samples/GettingStarted/README.md
index 82bb0299d0..e7249ac33d 100644
--- a/dotnet/samples/GettingStarted/README.md
+++ b/dotnet/samples/GettingStarted/README.md
@@ -9,6 +9,7 @@ of the agent framework.
|---|---|
|[Agents](./Agents/README.md)|Step by step instructions for getting started with agents|
|[Agent Providers](./AgentProviders/README.md)|Getting started with creating agents using various providers|
+|[A2A](./A2A/README.md)|Getting started with A2A (Agent-to-Agent) specific features|
|[Agent Open Telemetry](./AgentOpenTelemetry/README.md)|Getting started with OpenTelemetry for agents|
|[Agent With OpenAI exchange types](./AgentWithOpenAI/README.md)|Using OpenAI exchange types with agents|
|[Workflow](./Workflows/README.md)|Getting started with Workflow|
diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AAgentCardExtensions.cs b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AAgentCardExtensions.cs
new file mode 100644
index 0000000000..3c59abcef0
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AAgentCardExtensions.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.Logging;
+
+namespace A2A;
+
+///
+/// Provides extension methods for to simplify the creation of A2A agents.
+///
+///
+/// These extensions bridge the gap between A2A SDK client and .
+///
+public static class A2AAgentCardExtensions
+{
+ ///
+ /// Retrieves an instance of for an existing A2A agent.
+ ///
+ ///
+ /// This method can be used to access A2A agents that support the
+ /// Curated Registries (Catalog-Based Discovery)
+ /// discovery mechanism.
+ ///
+ /// The to use for the agent creation.
+ /// The to use for HTTP requests.
+ /// The logger factory for enabling logging within the agent.
+ /// An instance backed by the A2A agent.
+ public static async Task GetAIAgentAsync(this AgentCard card, HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null)
+ {
+ // Create the A2A client using the agent URL from the card.
+ var a2aClient = new A2AClient(new Uri(card.Url), httpClient);
+
+ return a2aClient.GetAIAgent(name: card.Name, description: card.Description, loggerFactory: loggerFactory);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2ACardResolverExtensions.cs b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2ACardResolverExtensions.cs
index d9fe2a9a1c..f458f74a1f 100644
--- a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2ACardResolverExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2ACardResolverExtensions.cs
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
-using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@@ -29,11 +28,9 @@ public static class A2ACardResolverExtensions
/// Retrieves an instance of for an existing A2A agent.
///
///
- /// Creates an AI agent for an A2A agent whose host supports one of the A2A discovery mechanisms:
- ///
- /// - Well-Known URI
- /// - Curated Registries (Catalog-Based Discovery)
- ///
+ /// This method can be used to access A2A agents that support the
+ /// Well-Known URI
+ /// discovery mechanism.
///
/// The to use for the agent creation.
/// The to use for HTTP requests.
@@ -45,9 +42,6 @@ public static class A2ACardResolverExtensions
// Obtain the agent card from the resolver.
var agentCard = await resolver.GetAgentCardAsync(cancellationToken).ConfigureAwait(false);
- // Create the A2A client using the agent URL from the card.
- var a2aClient = new A2AClient(new Uri(agentCard.Url), httpClient);
-
- return a2aClient.GetAIAgent(name: agentCard.Name, description: agentCard.Description, loggerFactory: loggerFactory);
+ return await agentCard.GetAIAgentAsync(httpClient, loggerFactory).ConfigureAwait(false);
}
}
diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AClientExtensions.cs
index 300bafbb90..095481c0d4 100644
--- a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AClientExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AClientExtensions.cs
@@ -25,7 +25,7 @@ public static class A2AClientExtensions
/// Retrieves an instance of for an existing A2A agent.
///
///
- /// This method can be used to create AI agents for A2A agents whose hosts support the
+ /// This method can be used to access A2A agents that support the
/// Direct Configuration / Private Discovery
/// discovery mechanism.
///
diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AAgentCardExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AAgentCardExtensionsTests.cs
new file mode 100644
index 0000000000..48d1c699e9
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AAgentCardExtensionsTests.cs
@@ -0,0 +1,108 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using A2A;
+
+namespace Microsoft.Agents.AI.A2A.UnitTests;
+
+///
+/// Unit tests for the class.
+///
+public sealed class A2AAgentCardExtensionsTests
+{
+ private readonly AgentCard _agentCard;
+
+ public A2AAgentCardExtensionsTests()
+ {
+ this._agentCard = new AgentCard
+ {
+ Name = "Test Agent",
+ Description = "A test agent for unit testing",
+ Url = "http://test-endpoint/agent"
+ };
+ }
+
+ [Fact]
+ public async Task GetAIAgentAsync_ReturnsAIAgentAsync()
+ {
+ // Act
+ var agent = await this._agentCard.GetAIAgentAsync();
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ Assert.Equal("A test agent for unit testing", agent.Description);
+ }
+
+ [Fact]
+ public async Task RunIAgentAsync_SendsRequestToTheUrlSpecifiedInAgentCardAsync()
+ {
+ // Arrange
+ using var handler = new HttpMessageHandlerStub();
+ using var httpClient = new HttpClient(handler, false);
+
+ handler.ResponsesToReturn.Enqueue(new AgentMessage
+ {
+ Role = MessageRole.Agent,
+ Parts = [new TextPart { Text = "Response" }],
+ });
+
+ var agent = await this._agentCard.GetAIAgentAsync(httpClient);
+
+ // Act
+ await agent.RunAsync("Test input");
+
+ // Assert
+ Assert.Single(handler.CapturedUris);
+ Assert.Equal(new Uri("http://test-endpoint/agent"), handler.CapturedUris[0]);
+ }
+
+ internal sealed class HttpMessageHandlerStub : HttpMessageHandler
+ {
+ public Queue ResponsesToReturn { get; } = new();
+
+ public List CapturedUris { get; } = [];
+
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ this.CapturedUris.Add(request.RequestUri!);
+
+ var response = this.ResponsesToReturn.Dequeue();
+
+ if (response is AgentCard agentCard)
+ {
+ var json = JsonSerializer.Serialize(agentCard);
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent(json, Encoding.UTF8, "application/json")
+ };
+ }
+ else if (response is AgentMessage message)
+ {
+ var jsonRpcResponse = JsonRpcResponse.CreateJsonRpcResponse("response-id", message);
+
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent(JsonSerializer.Serialize(jsonRpcResponse), Encoding.UTF8, "application/json")
+ };
+ }
+
+ // Return empty agent card if none specified
+ var emptyCard = new AgentCard();
+ var emptyJson = JsonSerializer.Serialize(emptyCard);
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent(emptyJson, Encoding.UTF8, "application/json")
+ };
+ }
+ }
+}