66
77import logging
88from typing import Any , Literal
9+ from urllib .parse import urlparse
910
1011import uvicorn
1112from a2a .server .apps import A2AFastAPIApplication , A2AStarletteApplication
@@ -31,6 +32,8 @@ def __init__(
3132 # AgentCard
3233 host : str = "0.0.0.0" ,
3334 port : int = 9000 ,
35+ http_url : str | None = None ,
36+ serve_at_root : bool = False ,
3437 version : str = "0.0.1" ,
3538 skills : list [AgentSkill ] | None = None ,
3639 ):
@@ -40,13 +43,34 @@ def __init__(
4043 agent: The Strands Agent to wrap with A2A compatibility.
4144 host: The hostname or IP address to bind the A2A server to. Defaults to "0.0.0.0".
4245 port: The port to bind the A2A server to. Defaults to 9000.
46+ http_url: The public HTTP URL where this agent will be accessible. If provided,
47+ this overrides the generated URL from host/port and enables automatic
48+ path-based mounting for load balancer scenarios.
49+ Example: "http://my-alb.amazonaws.com/agent1"
50+ serve_at_root: If True, forces the server to serve at root path regardless of
51+ http_url path component. Use this when your load balancer strips path prefixes.
52+ Defaults to False.
4353 version: The version of the agent. Defaults to "0.0.1".
4454 skills: The list of capabilities or functions the agent can perform.
4555 """
4656 self .host = host
4757 self .port = port
48- self .http_url = f"http://{ self .host } :{ self .port } /"
4958 self .version = version
59+
60+ if http_url :
61+ # Parse the provided URL to extract components for mounting
62+ self .public_base_url , self .mount_path = self ._parse_public_url (http_url )
63+ self .http_url = http_url .rstrip ("/" ) + "/"
64+
65+ # Override mount path if serve_at_root is requested
66+ if serve_at_root :
67+ self .mount_path = ""
68+ else :
69+ # Fall back to constructing the URL from host and port
70+ self .public_base_url = f"http://{ host } :{ port } "
71+ self .http_url = f"{ self .public_base_url } /"
72+ self .mount_path = ""
73+
5074 self .strands_agent = agent
5175 self .name = self .strands_agent .name
5276 self .description = self .strands_agent .description
@@ -58,6 +82,25 @@ def __init__(
5882 self ._agent_skills = skills
5983 logger .info ("Strands' integration with A2A is experimental. Be aware of frequent breaking changes." )
6084
85+ def _parse_public_url (self , url : str ) -> tuple [str , str ]:
86+ """Parse the public URL into base URL and mount path components.
87+
88+ Args:
89+ url: The full public URL (e.g., "http://my-alb.amazonaws.com/agent1")
90+
91+ Returns:
92+ tuple: (base_url, mount_path) where base_url is the scheme+netloc
93+ and mount_path is the path component
94+
95+ Example:
96+ _parse_public_url("http://my-alb.amazonaws.com/agent1")
97+ Returns: ("http://my-alb.amazonaws.com", "/agent1")
98+ """
99+ parsed = urlparse (url .rstrip ("/" ))
100+ base_url = f"{ parsed .scheme } ://{ parsed .netloc } "
101+ mount_path = parsed .path if parsed .path != "/" else ""
102+ return base_url , mount_path
103+
61104 @property
62105 def public_agent_card (self ) -> AgentCard :
63106 """Get the public AgentCard for this agent.
@@ -119,24 +162,42 @@ def agent_skills(self, skills: list[AgentSkill]) -> None:
119162 def to_starlette_app (self ) -> Starlette :
120163 """Create a Starlette application for serving this agent via HTTP.
121164
122- This method creates a Starlette application that can be used to serve
123- the agent via HTTP using the A2A protocol .
165+ Automatically handles path-based mounting if a mount path was derived
166+ from the http_url parameter .
124167
125168 Returns:
126169 Starlette: A Starlette application configured to serve this agent.
127170 """
128- return A2AStarletteApplication (agent_card = self .public_agent_card , http_handler = self .request_handler ).build ()
171+ a2a_app = A2AStarletteApplication (agent_card = self .public_agent_card , http_handler = self .request_handler ).build ()
172+
173+ if self .mount_path :
174+ # Create parent app and mount the A2A app at the specified path
175+ parent_app = Starlette ()
176+ parent_app .mount (self .mount_path , a2a_app )
177+ logger .info ("Mounting A2A server at path: %s" , self .mount_path )
178+ return parent_app
179+
180+ return a2a_app
129181
130182 def to_fastapi_app (self ) -> FastAPI :
131183 """Create a FastAPI application for serving this agent via HTTP.
132184
133- This method creates a FastAPI application that can be used to serve
134- the agent via HTTP using the A2A protocol .
185+ Automatically handles path-based mounting if a mount path was derived
186+ from the http_url parameter .
135187
136188 Returns:
137189 FastAPI: A FastAPI application configured to serve this agent.
138190 """
139- return A2AFastAPIApplication (agent_card = self .public_agent_card , http_handler = self .request_handler ).build ()
191+ a2a_app = A2AFastAPIApplication (agent_card = self .public_agent_card , http_handler = self .request_handler ).build ()
192+
193+ if self .mount_path :
194+ # Create parent app and mount the A2A app at the specified path
195+ parent_app = FastAPI ()
196+ parent_app .mount (self .mount_path , a2a_app )
197+ logger .info ("Mounting A2A server at path: %s" , self .mount_path )
198+ return parent_app
199+
200+ return a2a_app
140201
141202 def serve (
142203 self ,
0 commit comments