[GH-ISSUE #21760] OpenAPI tool servers — frontend sends empty tool_servers=[] so middleware never injects schemas #35089

Closed
opened 2026-04-25 09:17:20 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @antonioromero-pm on GitHub (Feb 23, 2026).
Original GitHub issue: https://github.com/open-webui/open-webui/issues/21760

Check Existing Issues

  • I have searched for any existing and/or related issues.
  • I have searched for any existing and/or related discussions.
  • I have also searched in the CLOSED issues AND CLOSED discussions and found no related items (your issue might already be addressed on the development branch!).
  • I am using the latest version of Open WebUI.

Installation Method

Docker

Open WebUI Version

v0.8.3 (Docker, ghcr.io/open-webui/open-webui:main, built 2026-02-14)

Ollama Version (if applicable)

0.16.3

Operating System

Windows 11 + WSL2 Ubuntu 24.04

Browser (if applicable)

No response

Confirmation

  • I have read and followed all instructions in README.md.
  • I am using the latest version of both Open WebUI and Ollama.
  • I have included the browser console logs.
  • I have included the Docker container logs.
  • I have provided every relevant configuration, setting, and environment variable used in my setup.
  • I have clearly listed every relevant configuration, custom setting, environment variable, and command-line option that influences my setup (such as Docker Compose overrides, .env values, browser settings, authentication configurations, etc).
  • I have documented step-by-step reproduction instructions that are precise, sequential, and leave nothing to interpretation.

Description

When OpenAPI-type tool servers are configured in Admin Panel > Settings > External Tools, the model never receives their schemas and cannot call any tools. The failure is completely silent — no error is shown to the user and the chat completes normally without tool use.

Expected Behavior

The tool_servers field in the request payload should contain the OpenAPI specs for all enabled tool servers, so that middleware.py can inject them as a tools array into the upstream model request.

Actual Behavior

The tool_servers field is always [] (empty array) even when tool servers are configured, verified, and toggled on.

The tool_ids field is correctly populated (e.g. ["server:1", "server:open-meteo", "server:3"]) but the middleware receives no specs to work with.

Steps to Reproduce

  1. Deploy any OpenAPI-compatible tool server (e.g. MCPO bridging MCP servers to OpenAPI)
  2. In Admin Panel > Settings > External Tools, add a connection:
    • Type: OpenAPI
    • URL: pointing to your tool server
  3. Verify the connection succeeds (green tick / "Connection successful")
  4. In Workspace > Models, enable the tool server for a model
  5. Open a new chat with that model, confirm the tool toggle is ON via the icon
  6. Send a message that should trigger a tool (e.g. "What is the weather in New York?")
  7. Inspect the /api/chat/completions request in browser DevTools > Network tab

Logs & Screenshots

Request payload from DevTools:

{
  "model": "my-model",
  "messages": [{"role": "user", "content": "What is the weather in New York?"}],
  "tool_ids": ["server:1", "server:open-meteo", "server:3"],
  "tool_servers": [],
  ...
}

Logs

Backend log during the request:

INFO | uvicorn... - "POST /api/chat/completions HTTP/1.1" 200

No tool-related log lines appear. By contrast, MCP-type tool servers (with server:mcp:* prefixed IDs) work correctly because their execution path is fully server-side.


Additional Information

https://github.com/open-webui/open-webui/issues/21770 is directly related -- the other half of the same fix.

Root Cause Analysis (traced through source)

middleware.py line 2274 reads tool server specs from the frontend request:

direct_tool_servers = metadata.get("tool_servers", None)

Line 2441 skips the entire tool injection block if this is falsy:

if direct_tool_servers:
    for tool_server in direct_tool_servers:
        ...

An empty list [] is falsy, so no tools are ever injected.

The backend does have a populated cache: app.state.TOOL_SERVERS is correctly built at startup by main.py:

await set_tool_servers(mock_request)
log.info(f"Initialized {len(app.state.TOOL_SERVERS)} tool server(s)")

However, app.state.TOOL_SERVERS (and get_tool_servers()) is never referenced in middleware.py:

$ grep -n "TOOL_SERVERS\|get_tool_servers" /app/backend/open_webui/utils/middleware.py
(no output)

The middleware relies entirely on the frontend to embed specs in the request, but the frontend never does so for admin-configured OpenAPI tool servers.


Suggested Fix

After line 2274 in middleware.py, add a fallback to the server-side cache when the frontend sends an empty array:

direct_tool_servers = metadata.get("tool_servers", None)

# Fallback: frontend sends tool_servers=[] for admin-configured OpenAPI servers.
# Resolve from server-side cache when tool_ids contains "server:*" entries.
if not direct_tool_servers and tool_ids:
    openapi_tool_ids = [
        tid for tid in tool_ids
        if tid.startswith("server:") and not tid.startswith("server:mcp:")
    ]
    if openapi_tool_ids:
        all_tool_servers = await get_tool_servers(request)
        direct_tool_servers = [
            s for s in all_tool_servers
            if f"server:{s['id']}" in openapi_tool_ids
            or f"server:{s['idx'] + 1}" in openapi_tool_ids
        ]

Note: get_tool_servers is already importable from open_webui.utils.tools — it just needs to be added to the import at the top of middleware.py.

The tool_ids matching needs to check both server:{id} (string name) and server:{idx+1} (1-based numeric position) because the frontend generates IDs inconsistently — some servers get their configured string ID, others get a positional integer.


Additional Notes

  • MCP-type tool servers (server:mcp:* IDs) are unaffected — they are handled server-side and work correctly
  • The USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS config suggests frontend-embedded specs are intended as a user-facing "direct tool servers" feature — admin-configured OpenAPI servers appear to share this broken path unintentionally
  • A second related bug (tool execution delegated to frontend) is filed separately
Originally created by @antonioromero-pm on GitHub (Feb 23, 2026). Original GitHub issue: https://github.com/open-webui/open-webui/issues/21760 ### Check Existing Issues - [x] I have searched for any existing and/or related issues. - [x] I have searched for any existing and/or related discussions. - [x] I have also searched in the CLOSED issues AND CLOSED discussions and found no related items (your issue might already be addressed on the development branch!). - [x] I am using the latest version of Open WebUI. ### Installation Method Docker ### Open WebUI Version v0.8.3 (Docker, `ghcr.io/open-webui/open-webui:main`, built 2026-02-14) ### Ollama Version (if applicable) 0.16.3 ### Operating System Windows 11 + WSL2 Ubuntu 24.04 ### Browser (if applicable) _No response_ ### Confirmation - [x] I have read and followed all instructions in `README.md`. - [x] I am using the latest version of **both** Open WebUI and Ollama. - [x] I have included the browser console logs. - [x] I have included the Docker container logs. - [x] I have **provided every relevant configuration, setting, and environment variable used in my setup.** - [x] I have clearly **listed every relevant configuration, custom setting, environment variable, and command-line option that influences my setup** (such as Docker Compose overrides, .env values, browser settings, authentication configurations, etc). - [x] I have documented **step-by-step reproduction instructions that are precise, sequential, and leave nothing to interpretation**. ## Description When OpenAPI-type tool servers are configured in Admin Panel > Settings > External Tools, the model never receives their schemas and cannot call any tools. The failure is completely silent — no error is shown to the user and the chat completes normally without tool use. ### Expected Behavior The `tool_servers` field in the request payload should contain the OpenAPI specs for all enabled tool servers, so that `middleware.py` can inject them as a `tools` array into the upstream model request. ### Actual Behavior The `tool_servers` field is always `[]` (empty array) even when tool servers are configured, verified, and toggled on. The `tool_ids` field is correctly populated (e.g. `["server:1", "server:open-meteo", "server:3"]`) but the middleware receives no specs to work with. ### Steps to Reproduce 1. Deploy any OpenAPI-compatible tool server (e.g. MCPO bridging MCP servers to OpenAPI) 2. In Admin Panel > Settings > External Tools, add a connection: - Type: `OpenAPI` - URL: pointing to your tool server 3. Verify the connection succeeds (green tick / "Connection successful") 4. In Workspace > Models, enable the tool server for a model 5. Open a new chat with that model, confirm the tool toggle is ON via the ➕ icon 6. Send a message that should trigger a tool (e.g. "What is the weather in New York?") 7. Inspect the `/api/chat/completions` request in browser DevTools > Network tab ### Logs & Screenshots ### Request payload from DevTools: ```json { "model": "my-model", "messages": [{"role": "user", "content": "What is the weather in New York?"}], "tool_ids": ["server:1", "server:open-meteo", "server:3"], "tool_servers": [], ... } ``` --- ## Logs ### Backend log during the request: ``` INFO | uvicorn... - "POST /api/chat/completions HTTP/1.1" 200 ``` No tool-related log lines appear. By contrast, MCP-type tool servers (with `server:mcp:*` prefixed IDs) work correctly because their execution path is fully server-side. --- ### Additional Information https://github.com/open-webui/open-webui/issues/21770 is directly related -- the other half of the same fix. ## Root Cause Analysis (traced through source) `middleware.py` line 2274 reads tool server specs from the frontend request: ```python direct_tool_servers = metadata.get("tool_servers", None) ``` Line 2441 skips the entire tool injection block if this is falsy: ```python if direct_tool_servers: for tool_server in direct_tool_servers: ... ``` An empty list `[]` is falsy, so no tools are ever injected. The backend **does** have a populated cache: `app.state.TOOL_SERVERS` is correctly built at startup by `main.py`: ```python await set_tool_servers(mock_request) log.info(f"Initialized {len(app.state.TOOL_SERVERS)} tool server(s)") ``` However, `app.state.TOOL_SERVERS` (and `get_tool_servers()`) is **never referenced in `middleware.py`**: ```bash $ grep -n "TOOL_SERVERS\|get_tool_servers" /app/backend/open_webui/utils/middleware.py (no output) ``` The middleware relies entirely on the frontend to embed specs in the request, but the frontend never does so for admin-configured OpenAPI tool servers. --- ## Suggested Fix After line 2274 in `middleware.py`, add a fallback to the server-side cache when the frontend sends an empty array: ```python direct_tool_servers = metadata.get("tool_servers", None) # Fallback: frontend sends tool_servers=[] for admin-configured OpenAPI servers. # Resolve from server-side cache when tool_ids contains "server:*" entries. if not direct_tool_servers and tool_ids: openapi_tool_ids = [ tid for tid in tool_ids if tid.startswith("server:") and not tid.startswith("server:mcp:") ] if openapi_tool_ids: all_tool_servers = await get_tool_servers(request) direct_tool_servers = [ s for s in all_tool_servers if f"server:{s['id']}" in openapi_tool_ids or f"server:{s['idx'] + 1}" in openapi_tool_ids ] ``` Note: `get_tool_servers` is already importable from `open_webui.utils.tools` — it just needs to be added to the import at the top of `middleware.py`. The `tool_ids` matching needs to check **both** `server:{id}` (string name) and `server:{idx+1}` (1-based numeric position) because the frontend generates IDs inconsistently — some servers get their configured string ID, others get a positional integer. --- ## Additional Notes - MCP-type tool servers (`server:mcp:*` IDs) are unaffected — they are handled server-side and work correctly - The `USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS` config suggests frontend-embedded specs are intended as a user-facing "direct tool servers" feature — admin-configured OpenAPI servers appear to share this broken path unintentionally - A second related bug (tool execution delegated to frontend) is filed separately
GiteaMirror added the bug label 2026-04-25 09:17:20 -05:00
Author
Owner

@antonioromero-pm commented on GitHub (Feb 23, 2026):

Updated the description after further analysis and arriving at a fix that works in my environment. Will submit a second related issue, both of which must be fixed to get this working.

<!-- gh-comment-id:3943438554 --> @antonioromero-pm commented on GitHub (Feb 23, 2026): Updated the description after further analysis and arriving at a fix that works in my environment. Will submit a second related issue, both of which must be fixed to get this working.
Author
Owner

@tjbck commented on GitHub (Feb 23, 2026):

Unable to reproduce.

<!-- gh-comment-id:3947703021 --> @tjbck commented on GitHub (Feb 23, 2026): Unable to reproduce.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/open-webui#35089