diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Program.cs b/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Program.cs
index cb1a7e3cd9..46af2a5b19 100644
--- a/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Program.cs
+++ b/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Program.cs
@@ -107,8 +107,8 @@ Once the user has deduced what type (knight or knave) both Alice and Bob are, te
app.UseExceptionHandler();
// attach a2a with simple message communication
-app.MapA2A(agentName: "pirate", path: "/a2a/pirate");
-app.MapA2A(agentName: "knights-and-knaves", path: "/a2a/knights-and-knaves", agentCard: new()
+app.MapA2A(pirateAgentBuilder, path: "/a2a/pirate");
+app.MapA2A(knightsKnavesAgentBuilder, path: "/a2a/knights-and-knaves", agentCard: new()
{
Name = "Knights and Knaves",
Description = "An agent that helps you solve the knights and knaves puzzle.",
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/AIAgentExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/AIAgentExtensions.cs
index 43376d8fb2..c54af66bb8 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/AIAgentExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/AIAgentExtensions.cs
@@ -83,7 +83,16 @@ public static ITaskManager MapA2A(
{
// A2A SDK assigns the url on its own
// we can help user if they did not set Url explicitly.
- agentCard.Url ??= context;
+ if (string.IsNullOrEmpty(agentCard.Url))
+ {
+ var agentCardUrl = context.TrimEnd('/');
+ if (!context.EndsWith("/v1/card", StringComparison.Ordinal))
+ {
+ agentCardUrl += "/v1/card";
+ }
+
+ agentCard.Url = agentCardUrl;
+ }
return Task.FromResult(agentCard);
};
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AIntegrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AIntegrationTests.cs
new file mode 100644
index 0000000000..48cb19789a
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/A2AIntegrationTests.cs
@@ -0,0 +1,87 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Text.Json;
+using System.Threading.Tasks;
+using A2A;
+using Microsoft.Agents.AI.Hosting.A2A.UnitTests.Internal;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.Agents.AI.Hosting.A2A.UnitTests;
+
+public sealed class A2AIntegrationTests
+{
+ ///
+ /// Verifies that calling the A2A card endpoint with MapA2A returns an agent card with a URL populated.
+ ///
+ [Fact]
+ public async Task MapA2A_WithAgentCard_CardEndpointReturnsCardWithUrlAsync()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ builder.WebHost.UseTestServer();
+
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ IHostedAgentBuilder agentBuilder = builder.AddAIAgent("test-agent", "Test instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+
+ using WebApplication app = builder.Build();
+
+ var agentCard = new AgentCard
+ {
+ Name = "Test Agent",
+ Description = "A test agent for A2A communication",
+ Version = "1.0"
+ };
+
+ // Map A2A with the agent card
+ app.MapA2A(agentBuilder, "/a2a/test-agent", agentCard);
+
+ await app.StartAsync();
+
+ try
+ {
+ // Get the test server client
+ TestServer testServer = app.Services.GetRequiredService() as TestServer
+ ?? throw new InvalidOperationException("TestServer not found");
+ var httpClient = testServer.CreateClient();
+
+ // Act - Query the agent card endpoint
+ var requestUri = new Uri("/a2a/test-agent/v1/card", UriKind.Relative);
+ var response = await httpClient.GetAsync(requestUri);
+
+ // Assert
+ Assert.True(response.IsSuccessStatusCode, $"Expected successful response but got {response.StatusCode}");
+
+ var content = await response.Content.ReadAsStringAsync();
+ var jsonDoc = JsonDocument.Parse(content);
+ var root = jsonDoc.RootElement;
+
+ // Verify the card has expected properties
+ Assert.True(root.TryGetProperty("name", out var nameProperty));
+ Assert.Equal("Test Agent", nameProperty.GetString());
+
+ Assert.True(root.TryGetProperty("description", out var descProperty));
+ Assert.Equal("A test agent for A2A communication", descProperty.GetString());
+
+ // Verify the card has a URL property and it's not null/empty
+ Assert.True(root.TryGetProperty("url", out var urlProperty));
+ Assert.NotEqual(JsonValueKind.Null, urlProperty.ValueKind);
+
+ var url = urlProperty.GetString();
+ Assert.NotNull(url);
+ Assert.NotEmpty(url);
+ Assert.StartsWith("http", url, StringComparison.OrdinalIgnoreCase);
+ Assert.Equal($"{testServer.BaseAddress.ToString().TrimEnd('/')}/a2a/test-agent/v1/card", url);
+ }
+ finally
+ {
+ await app.StopAsync();
+ }
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/EndpointRouteA2ABuilderExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/EndpointRouteA2ABuilderExtensionsTests.cs
index 1ae0dda908..a848528888 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/EndpointRouteA2ABuilderExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/EndpointRouteA2ABuilderExtensionsTests.cs
@@ -1,10 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
using A2A;
+using Microsoft.Agents.AI.Hosting.A2A.UnitTests.Internal;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
@@ -478,25 +476,4 @@ public void MapA2A_WithAgentBuilder_FullAgentCard_Succeeds()
var result = app.MapA2A(agentBuilder, "/a2a", agentCard);
Assert.NotNull(result);
}
-
- private sealed class DummyChatClient : IChatClient
- {
- public void Dispose()
- {
- throw new NotImplementedException();
- }
-
- public Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
- {
- throw new NotImplementedException();
- }
-
- public object? GetService(Type serviceType, object? serviceKey = null) =>
- serviceType.IsInstanceOfType(this) ? this : null;
-
- public IAsyncEnumerable GetStreamingResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
- {
- throw new NotImplementedException();
- }
- }
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Internal/DummyChatClient.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Internal/DummyChatClient.cs
new file mode 100644
index 0000000000..efab140b68
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Internal/DummyChatClient.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.Hosting.A2A.UnitTests.Internal;
+
+internal sealed class DummyChatClient : IChatClient
+{
+ public void Dispose()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+
+ public object? GetService(Type serviceType, object? serviceKey = null) =>
+ serviceType.IsInstanceOfType(this) ? this : null;
+
+ public IAsyncEnumerable GetStreamingResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj
index 63387ae458..07dde4f802 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj
@@ -1,4 +1,4 @@
-
+
$(ProjectsCoreTargetFrameworks)
@@ -6,6 +6,9 @@
+
+
+