From ee47fa04e16f4b45314e45ae0f268866b6d31a0e Mon Sep 17 00:00:00 2001 From: David Gageot Date: Tue, 10 Feb 2026 19:24:54 +0100 Subject: [PATCH] Fix A2A agent card advertising unroutable wildcard address Replace wildcard listen addresses (::, 0.0.0.0) with localhost in the agent card URL so clients can actually reach the server. Also switch TCP listeners to tcp4 to avoid binding to IPv6 on dual-stack systems. Fixes #1679 Assisted-By: cagent --- pkg/a2a/server.go | 15 ++++++++++++++- pkg/a2a/server_test.go | 33 +++++++++++++++++++++++++++++++++ pkg/server/listen.go | 2 +- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 pkg/a2a/server_test.go diff --git a/pkg/a2a/server.go b/pkg/a2a/server.go index 9cb919146..2c510cc0e 100644 --- a/pkg/a2a/server.go +++ b/pkg/a2a/server.go @@ -23,6 +23,19 @@ import ( "github.com/docker/cagent/pkg/version" ) +// routableAddr replaces wildcard listen addresses (like "0.0.0.0" or "::") with +// "localhost" so the agent card URL is actually usable by clients. +func routableAddr(addr string) string { + host, port, err := net.SplitHostPort(addr) + if err != nil { + return addr + } + if host == "" || host == "0.0.0.0" || host == "::" { + return net.JoinHostPort("localhost", port) + } + return addr +} + func Run(ctx context.Context, agentFilename, agentName string, runConfig *config.RuntimeConfig, ln net.Listener) error { slog.Debug("Starting A2A server", "agent", agentName, "addr", ln.Addr().String()) @@ -46,7 +59,7 @@ func Run(ctx context.Context, agentFilename, agentName string, runConfig *config return fmt.Errorf("failed to create ADK agent adapter: %w", err) } - baseURL := &url.URL{Scheme: "http", Host: ln.Addr().String()} + baseURL := &url.URL{Scheme: "http", Host: routableAddr(ln.Addr().String())} slog.Debug("A2A server listening", "url", baseURL.String()) diff --git a/pkg/a2a/server_test.go b/pkg/a2a/server_test.go new file mode 100644 index 000000000..d334d9bc0 --- /dev/null +++ b/pkg/a2a/server_test.go @@ -0,0 +1,33 @@ +package a2a + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRoutableAddr(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + addr string + want string + }{ + {"ipv6 wildcard", "[::]:8080", "localhost:8080"}, + {"ipv4 wildcard", "0.0.0.0:8080", "localhost:8080"}, + {"empty host", ":8080", "localhost:8080"}, + {"localhost stays", "localhost:8080", "localhost:8080"}, + {"ipv4 loopback stays", "127.0.0.1:8080", "127.0.0.1:8080"}, + {"specific ip stays", "192.168.1.1:9090", "192.168.1.1:9090"}, + {"hostname stays", "my-host:8080", "my-host:8080"}, + {"invalid addr returned as-is", "not-a-host-port", "not-a-host-port"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.want, routableAddr(tt.addr)) + }) + } +} diff --git a/pkg/server/listen.go b/pkg/server/listen.go index 24ff9b843..b565dbeff 100644 --- a/pkg/server/listen.go +++ b/pkg/server/listen.go @@ -36,5 +36,5 @@ func listenUnix(ctx context.Context, path string) (net.Listener, error) { func listenTCP(ctx context.Context, addr string) (net.Listener, error) { var lc net.ListenConfig - return lc.Listen(ctx, "tcp", addr) + return lc.Listen(ctx, "tcp4", addr) }