6
6
7
7
import logging
8
8
from typing import Any , Literal
9
+ from urllib .parse import urlparse
9
10
10
11
import uvicorn
11
12
from a2a .server .apps import A2AFastAPIApplication , A2AStarletteApplication
@@ -31,6 +32,8 @@ def __init__(
31
32
# AgentCard
32
33
host : str = "0.0.0.0" ,
33
34
port : int = 9000 ,
35
+ http_url : str | None = None ,
36
+ serve_at_root : bool = False ,
34
37
version : str = "0.0.1" ,
35
38
skills : list [AgentSkill ] | None = None ,
36
39
):
@@ -40,13 +43,34 @@ def __init__(
40
43
agent: The Strands Agent to wrap with A2A compatibility.
41
44
host: The hostname or IP address to bind the A2A server to. Defaults to "0.0.0.0".
42
45
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.
43
53
version: The version of the agent. Defaults to "0.0.1".
44
54
skills: The list of capabilities or functions the agent can perform.
45
55
"""
46
56
self .host = host
47
57
self .port = port
48
- self .http_url = f"http://{ self .host } :{ self .port } /"
49
58
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
+
50
74
self .strands_agent = agent
51
75
self .name = self .strands_agent .name
52
76
self .description = self .strands_agent .description
@@ -58,6 +82,25 @@ def __init__(
58
82
self ._agent_skills = skills
59
83
logger .info ("Strands' integration with A2A is experimental. Be aware of frequent breaking changes." )
60
84
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
+
61
104
@property
62
105
def public_agent_card (self ) -> AgentCard :
63
106
"""Get the public AgentCard for this agent.
@@ -119,24 +162,42 @@ def agent_skills(self, skills: list[AgentSkill]) -> None:
119
162
def to_starlette_app (self ) -> Starlette :
120
163
"""Create a Starlette application for serving this agent via HTTP.
121
164
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 .
124
167
125
168
Returns:
126
169
Starlette: A Starlette application configured to serve this agent.
127
170
"""
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
129
181
130
182
def to_fastapi_app (self ) -> FastAPI :
131
183
"""Create a FastAPI application for serving this agent via HTTP.
132
184
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 .
135
187
136
188
Returns:
137
189
FastAPI: A FastAPI application configured to serve this agent.
138
190
"""
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
140
201
141
202
def serve (
142
203
self ,
0 commit comments