7373# Base URL Extraction
7474# -----------------------------------------------------------------------------
7575def _extract_base_url (url : str ) -> str :
76- """
77- Extract the base URL (scheme and network location) from a full URL.
76+ """Return the gateway-level base URL.
77+
78+ The function keeps any application root path (`APP_ROOT_PATH`) that the
79+ remote gateway is mounted under (for example `/gateway`) while removing
80+ the `/servers/<id>` suffix that appears in catalog endpoints. It also
81+ discards any query string or fragment.
7882
7983 Args:
80- url (str): The full URL to parse, e.g., "https://example.com/path?query=1".
84+ url (str): Full catalog URL, e.g.
85+ `https://host.com/gateway/servers/1`.
8186
8287 Returns:
83- str: The base URL, including scheme and netloc, e.g., "https://example.com".
88+ str: Clean base URL suitable for building `/tools/`, `/prompts/`,
89+ or `/resources/` endpoints—for example
90+ `https://host.com/gateway`.
8491
8592 Raises:
86- ValueError: If the URL does not contain a scheme or netloc.
87-
88- Example:
89- >>> _extract_base_url("https://www.example.com/path/to/resource")
90- 'https://www.example.com'
93+ ValueError: If *url* lacks a scheme or network location.
94+
95+ Examples:
96+ >>> _extract_base_url("https://host.com/servers/2")
97+ 'https://host.com'
98+ >>> _extract_base_url("https://host.com/gateway/servers/2")
99+ 'https://host.com/gateway'
100+ >>> _extract_base_url("https://host.com/gateway/servers")
101+ 'https://host.com/gateway'
102+ >>> _extract_base_url("https://host.com/gateway")
103+ 'https://host.com/gateway'
104+
105+ Note:
106+ If the target server was started with `APP_ROOT_PATH=/gateway`, the
107+ resulting catalog URLs include that prefix. This helper preserves the
108+ prefix so the wrapper's follow-up calls remain correctly scoped.
91109 """
92110 parsed = urlparse (url )
93111 if not parsed .scheme or not parsed .netloc :
94112 raise ValueError (f"Invalid URL provided: { url } " )
95113
96- if "/servers/" in url :
97- before_servers = parsed .path .split ("/servers" )[0 ]
98- return f"{ parsed .scheme } ://{ parsed .netloc } { before_servers } "
99-
100- return f"{ url } "
114+ path = parsed .path or ""
115+ if "/servers/" in path :
116+ path = path .split ("/servers" )[0 ] # ".../servers/123" -> "..."
117+ elif path .endswith ("/servers" ):
118+ path = path [:- len ("/servers" )] # ".../servers" -> "..."
119+ # otherwise keep the existing path (supports APP_ROOT_PATH)
101120
121+ return f"{ parsed .scheme } ://{ parsed .netloc } { path } "
102122
103123BASE_URL : str = _extract_base_url (SERVER_CATALOG_URLS [0 ]) if SERVER_CATALOG_URLS else ""
104124
@@ -162,10 +182,6 @@ async def get_tools_from_mcp_server(catalog_urls: List[str]) -> List[str]:
162182
163183 Returns:
164184 List[str]: A list of tool ID strings extracted from the server catalog.
165-
166- Raises:
167- httpx.RequestError: If a network problem occurs.
168- httpx.HTTPStatusError: If the server returns a 4xx or 5xx response.
169185 """
170186 server_ids = [url .split ("/" )[- 1 ] for url in catalog_urls ]
171187 url = f"{ BASE_URL } /servers/"
@@ -187,10 +203,6 @@ async def tools_metadata(tool_ids: List[str]) -> List[Dict[str, Any]]:
187203
188204 Returns:
189205 List[Dict[str, Any]]: A list of metadata dictionaries for each tool.
190-
191- Raises:
192- httpx.RequestError: If a network problem occurs.
193- httpx.HTTPStatusError: If the server returns a 4xx or 5xx response.
194206 """
195207 if not tool_ids :
196208 return []
@@ -212,10 +224,6 @@ async def get_prompts_from_mcp_server(catalog_urls: List[str]) -> List[str]:
212224
213225 Returns:
214226 List[str]: A list of prompt ID strings.
215-
216- Raises:
217- httpx.RequestError: If a network problem occurs.
218- httpx.HTTPStatusError: If the server returns a 4xx or 5xx response.
219227 """
220228 server_ids = [url .split ("/" )[- 1 ] for url in catalog_urls ]
221229 url = f"{ BASE_URL } /servers/"
@@ -237,10 +245,6 @@ async def prompts_metadata(prompt_ids: List[str]) -> List[Dict[str, Any]]:
237245
238246 Returns:
239247 List[Dict[str, Any]]: A list of metadata dictionaries for each prompt.
240-
241- Raises:
242- httpx.RequestError: If a network problem occurs.
243- httpx.HTTPStatusError: If the server returns a 4xx or 5xx response.
244248 """
245249 if not prompt_ids :
246250 return []
@@ -261,10 +265,6 @@ async def get_resources_from_mcp_server(catalog_urls: List[str]) -> List[str]:
261265
262266 Returns:
263267 List[str]: A list of resource ID strings.
264-
265- Raises:
266- httpx.RequestError: If a network problem occurs.
267- httpx.HTTPStatusError: If the server returns a 4xx or 5xx response.
268268 """
269269 server_ids = [url .split ("/" )[- 1 ] for url in catalog_urls ]
270270 url = f"{ BASE_URL } /servers/"
@@ -286,10 +286,6 @@ async def resources_metadata(resource_ids: List[str]) -> List[Dict[str, Any]]:
286286
287287 Returns:
288288 List[Dict[str, Any]]: A list of metadata dictionaries for each resource.
289-
290- Raises:
291- httpx.RequestError: If a network problem occurs.
292- httpx.HTTPStatusError: If the server returns a 4xx or 5xx response.
293289 """
294290 if not resource_ids :
295291 return []
0 commit comments