Skip to content

Commit 1334349

Browse files
mkmeralContainerized Agent
andauthored
fix: update agent card URL when host/port overridden in A2AServer.ser… (strands-agents#1626)
Co-authored-by: Containerized Agent <agent@containerized-strands.local>
1 parent 56ad50d commit 1334349

2 files changed

Lines changed: 164 additions & 2 deletions

File tree

src/strands/multiagent/a2a/server.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def __init__(
7979
# Parse the provided URL to extract components for mounting
8080
self.public_base_url, self.mount_path = self._parse_public_url(http_url)
8181
self.http_url = http_url.rstrip("/") + "/"
82+
self._http_url_explicit = True
8283

8384
# Override mount path if serve_at_root is requested
8485
if serve_at_root:
@@ -88,6 +89,7 @@ def __init__(
8889
self.public_base_url = f"http://{host}:{port}"
8990
self.http_url = f"{self.public_base_url}/"
9091
self.mount_path = ""
92+
self._http_url_explicit = False
9193

9294
self.strands_agent = agent
9395
self.name = self.strands_agent.name
@@ -253,12 +255,25 @@ def serve(
253255
port: The port number to bind the server to. Defaults to 9000.
254256
**kwargs: Additional keyword arguments to pass to uvicorn.run.
255257
"""
258+
# Update host/port if overridden, and recalculate URLs if http_url wasn't explicitly set
259+
if host is not None:
260+
self.host = host
261+
if port is not None:
262+
self.port = port
263+
264+
if host is not None or port is not None:
265+
# Only update the URL if it wasn't explicitly set via http_url parameter
266+
# (i.e., if the URL was auto-generated from host/port in __init__)
267+
if not self._http_url_explicit:
268+
self.public_base_url = f"http://{self.host}:{self.port}"
269+
self.http_url = f"{self.public_base_url}/"
270+
256271
try:
257272
logger.info("Starting Strands A2A server...")
258273
if app_type == "fastapi":
259-
uvicorn.run(self.to_fastapi_app(), host=host or self.host, port=port or self.port, **kwargs)
274+
uvicorn.run(self.to_fastapi_app(), host=self.host, port=self.port, **kwargs)
260275
else:
261-
uvicorn.run(self.to_starlette_app(), host=host or self.host, port=port or self.port, **kwargs)
276+
uvicorn.run(self.to_starlette_app(), host=self.host, port=self.port, **kwargs)
262277
except KeyboardInterrupt:
263278
logger.warning("Strands A2A server shutdown requested (KeyboardInterrupt).")
264279
except Exception:

tests/strands/multiagent/a2a/test_server.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,3 +876,150 @@ def test_to_fastapi_app_with_app_kwargs(mock_strands_agent):
876876

877877
assert isinstance(app, FastAPI)
878878
assert app.title == "Custom Agent Title"
879+
880+
881+
@patch("uvicorn.run")
882+
def test_serve_with_overridden_host_port_updates_agent_card_url(mock_run, mock_strands_agent):
883+
"""Test that serve() with host/port overrides updates the agent card URL.
884+
885+
This test verifies the fix for issue #1258 where specifying host/port in serve()
886+
did not update the agent card URL, causing clients to fail when trying to connect.
887+
"""
888+
mock_strands_agent.tool_registry.get_all_tools_config.return_value = {}
889+
890+
a2a_agent = A2AServer(mock_strands_agent, skills=[])
891+
892+
# Verify initial URL from constructor defaults
893+
assert a2a_agent.http_url == "http://127.0.0.1:9000/"
894+
assert a2a_agent.public_base_url == "http://127.0.0.1:9000"
895+
896+
# Call serve with different host and port
897+
a2a_agent.serve(host="localhost", port=9210)
898+
899+
# Verify URL was updated to match the actual serve parameters
900+
assert a2a_agent.http_url == "http://localhost:9210/"
901+
assert a2a_agent.public_base_url == "http://localhost:9210"
902+
assert a2a_agent.host == "localhost"
903+
assert a2a_agent.port == 9210
904+
905+
# Verify the agent card reflects the updated URL
906+
card = a2a_agent.public_agent_card
907+
assert card.url == "http://localhost:9210/"
908+
909+
# Verify uvicorn was called with the overridden parameters
910+
mock_run.assert_called_once()
911+
_, kwargs = mock_run.call_args
912+
assert kwargs["host"] == "localhost"
913+
assert kwargs["port"] == 9210
914+
915+
916+
@patch("uvicorn.run")
917+
def test_serve_with_overridden_port_only_updates_url(mock_run, mock_strands_agent):
918+
"""Test that serve() with only port override updates the agent card URL."""
919+
mock_strands_agent.tool_registry.get_all_tools_config.return_value = {}
920+
921+
a2a_agent = A2AServer(mock_strands_agent, skills=[])
922+
923+
# Call serve with different port only
924+
a2a_agent.serve(port=8080)
925+
926+
# Verify URL was updated with the new port
927+
assert a2a_agent.http_url == "http://127.0.0.1:8080/"
928+
assert a2a_agent.port == 8080
929+
930+
# Verify uvicorn was called with the correct parameters
931+
mock_run.assert_called_once()
932+
_, kwargs = mock_run.call_args
933+
assert kwargs["host"] == "127.0.0.1"
934+
assert kwargs["port"] == 8080
935+
936+
937+
@patch("uvicorn.run")
938+
def test_serve_with_overridden_host_only_updates_url(mock_run, mock_strands_agent):
939+
"""Test that serve() with only host override updates the agent card URL."""
940+
mock_strands_agent.tool_registry.get_all_tools_config.return_value = {}
941+
942+
a2a_agent = A2AServer(mock_strands_agent, skills=[])
943+
944+
# Call serve with different host only
945+
a2a_agent.serve(host="0.0.0.0")
946+
947+
# Verify URL was updated with the new host
948+
assert a2a_agent.http_url == "http://0.0.0.0:9000/"
949+
assert a2a_agent.host == "0.0.0.0"
950+
951+
# Verify uvicorn was called with the correct parameters
952+
mock_run.assert_called_once()
953+
_, kwargs = mock_run.call_args
954+
assert kwargs["host"] == "0.0.0.0"
955+
assert kwargs["port"] == 9000
956+
957+
958+
@patch("uvicorn.run")
959+
def test_serve_with_explicit_http_url_does_not_override_url(mock_run, mock_strands_agent):
960+
"""Test that serve() with host/port does not override explicitly set http_url.
961+
962+
When a user explicitly sets http_url in the constructor (e.g., for load balancer scenarios),
963+
the serve() method should NOT override the URL even if host/port are provided.
964+
"""
965+
mock_strands_agent.tool_registry.get_all_tools_config.return_value = {}
966+
967+
# Create server with explicit http_url (simulating load balancer scenario)
968+
a2a_agent = A2AServer(
969+
mock_strands_agent,
970+
host="0.0.0.0",
971+
port=8080,
972+
http_url="https://my-alb.amazonaws.com/agent1",
973+
skills=[],
974+
)
975+
976+
# Verify initial URL is the explicit one
977+
assert a2a_agent.http_url == "https://my-alb.amazonaws.com/agent1/"
978+
assert a2a_agent._http_url_explicit is True
979+
980+
# Call serve with different host/port (the local binding)
981+
a2a_agent.serve(host="0.0.0.0", port=9000)
982+
983+
# Verify URL was NOT changed (explicit http_url should be preserved)
984+
assert a2a_agent.http_url == "https://my-alb.amazonaws.com/agent1/"
985+
assert a2a_agent.public_base_url == "https://my-alb.amazonaws.com"
986+
987+
# But host/port should still be updated for the actual binding
988+
assert a2a_agent.host == "0.0.0.0"
989+
assert a2a_agent.port == 9000
990+
991+
# Verify the agent card still shows the public URL
992+
card = a2a_agent.public_agent_card
993+
assert card.url == "https://my-alb.amazonaws.com/agent1/"
994+
995+
996+
@patch("uvicorn.run")
997+
def test_serve_without_overrides_does_not_change_url(mock_run, mock_strands_agent):
998+
"""Test that serve() without host/port parameters does not modify the URL."""
999+
mock_strands_agent.tool_registry.get_all_tools_config.return_value = {}
1000+
1001+
a2a_agent = A2AServer(mock_strands_agent, host="localhost", port=8000, skills=[])
1002+
1003+
# Verify initial URL
1004+
assert a2a_agent.http_url == "http://localhost:8000/"
1005+
1006+
# Call serve without overrides
1007+
a2a_agent.serve()
1008+
1009+
# Verify URL was NOT changed
1010+
assert a2a_agent.http_url == "http://localhost:8000/"
1011+
assert a2a_agent.host == "localhost"
1012+
assert a2a_agent.port == 8000
1013+
1014+
1015+
def test_http_url_explicit_flag_set_correctly(mock_strands_agent):
1016+
"""Test that _http_url_explicit flag is set correctly during initialization."""
1017+
mock_strands_agent.tool_registry.get_all_tools_config.return_value = {}
1018+
1019+
# Without explicit http_url
1020+
server1 = A2AServer(mock_strands_agent, skills=[])
1021+
assert server1._http_url_explicit is False
1022+
1023+
# With explicit http_url
1024+
server2 = A2AServer(mock_strands_agent, http_url="http://example.com/agent", skills=[])
1025+
assert server2._http_url_explicit is True

0 commit comments

Comments
 (0)