diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 4c8289578e..5cd7377876 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -588,28 +588,20 @@ async def get_tool_server_data(token: str, url: str) -> Dict[str, Any]: error = str(err) raise Exception(error) - data = { - "openapi": res, - "info": res.get("info", {}), - "specs": convert_openapi_to_tool_payload(res), - } - - log.info(f"Fetched data: {data}") - return data + log.debug(f"Fetched data: {res}") + return res async def get_tool_servers_data(servers: List[Dict[str, Any]]) -> List[Dict[str, Any]]: # Prepare list of enabled servers along with their original index + + tasks = [] server_entries = [] for idx, server in enumerate(servers): if ( server.get("config", {}).get("enable") and server.get("type", "openapi") == "openapi" ): - # Path (to OpenAPI spec URL) can be either a full URL or a path to append to the base URL - openapi_path = server.get("path", "openapi.json") - full_url = get_tool_server_url(server.get("url"), openapi_path) - info = server.get("info", {}) auth_type = server.get("auth_type", "bearer") @@ -625,12 +617,34 @@ async def get_tool_servers_data(servers: List[Dict[str, Any]]) -> List[Dict[str, if not id: id = str(idx) - server_entries.append((id, idx, server, full_url, info, token)) + server_url = server.get("url") + spec_type = server.get("spec_type", "url") - # Create async tasks to fetch data - tasks = [ - get_tool_server_data(token, url) for (_, _, _, url, _, token) in server_entries - ] + # Create async tasks to fetch data + task = None + if spec_type == "url": + # Path (to OpenAPI spec URL) can be either a full URL or a path to append to the base URL + openapi_path = server.get("path", "openapi.json") + spec_url = get_tool_server_url(server_url, openapi_path) + # Fetch from URL + task = get_tool_server_data(token, spec_url) + elif spec_type == "json" and server.get("spec", ""): + # Use provided JSON spec + spec_json = None + try: + spec_json = json.loads(server.get("spec", "")) + except Exception as e: + log.error(f"Error parsing JSON spec for tool server {id}: {e}") + + if spec_json: + task = asyncio.sleep( + 0, + result=spec_json, + ) + + if task: + tasks.append(task) + server_entries.append((id, idx, server, server_url, info, token)) # Execute tasks concurrently responses = await asyncio.gather(*tasks, return_exceptions=True) @@ -642,8 +656,13 @@ async def get_tool_servers_data(servers: List[Dict[str, Any]]) -> List[Dict[str, log.error(f"Failed to connect to {url} OpenAPI tool server") continue - openapi_data = response.get("openapi", {}) + response = { + "openapi": response, + "info": response.get("info", {}), + "specs": convert_openapi_to_tool_payload(response), + } + openapi_data = response.get("openapi", {}) if info and isinstance(openapi_data, dict): openapi_data["info"] = openapi_data.get("info", {}) diff --git a/src/lib/components/AddToolServerModal.svelte b/src/lib/components/AddToolServerModal.svelte index c6894ddeeb..d47de8ebbd 100644 --- a/src/lib/components/AddToolServerModal.svelte +++ b/src/lib/components/AddToolServerModal.svelte @@ -27,11 +27,14 @@ export let direct = false; export let connection = null; - let url = ''; - let path = 'openapi.json'; - let type = 'openapi'; // 'openapi', 'mcp' + let url = ''; + + let spec_type = 'url'; // 'url', 'json' + let spec = ''; // used when spec_type is 'json' + let path = 'openapi.json'; + let auth_type = 'bearer'; let key = ''; @@ -149,10 +152,26 @@ return; } + // validate spec + if (spec_type === 'json') { + try { + const specJSON = JSON.parse(spec); + spec = JSON.stringify(specJSON, null, 2); + } catch (e) { + toast.error($i18n.t('Please enter a valid JSON spec')); + loading = false; + return; + } + } + const connection = { - url, - path, type, + url, + + spec_type, + spec, + path, + auth_type, key, config: { @@ -173,9 +192,12 @@ show = false; // reset form - url = ''; - path = 'openapi.json'; type = 'openapi'; + url = ''; + + spec_type = 'url'; + spec = ''; + path = 'openapi.json'; key = ''; auth_type = 'bearer'; @@ -191,10 +213,13 @@ const init = () => { if (connection) { + type = connection?.type ?? 'openapi'; url = connection.url; + + spec_type = connection?.spec_type ?? 'url'; + spec = connection?.spec ?? ''; path = connection?.path ?? 'openapi.json'; - type = connection?.type ?? 'openapi'; auth_type = connection?.auth_type ?? 'bearer'; key = connection?.key ?? ''; @@ -326,35 +351,81 @@ - - {#if ['', 'openapi'].includes(type)} -
- - -
- {/if} {#if ['', 'openapi'].includes(type)} -
- {$i18n.t(`WebUI will make requests to "{{url}}"`, { - url: path.includes('://') - ? path - : `${url}${path.startsWith('/') ? '' : '/'}${path}` - })} +
+
+
+
+
+ {$i18n.t('OpenAPI Spec')} +
+
+
+ +
+
+ +
+ +
+ {#if spec_type === 'url'} +
+ + +
+ {:else if spec_type === 'json'} +
+ +