mirror of
https://github.com/open-webui/open-webui.git
synced 2026-05-06 02:48:13 -05:00
[PR #24106] fix(mcp): execute MCP tool calls server-side in non-streaming native function calling mode #43138
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/open-webui/open-webui/pull/24106
Author: @looselyhuman
Created: 4/24/2026
Status: 🔄 Open
Base:
dev← Head:gaia-patch-3📝 Commits (10+)
fe6783cMerge pull request #19030 from open-webui/devfc05e0aMerge pull request #19405 from open-webui/deve3faec6Merge pull request #19416 from open-webui/dev9899293Merge pull request #19448 from open-webui/dev140605eMerge pull request #19462 from open-webui/dev6f1486fMerge pull request #19466 from open-webui/devd95f533Merge pull request #19729 from open-webui/deva7271530.6.43 (#20093)6adde20Merge pull request #20394 from open-webui/devf9b0534Merge pull request #20522 from open-webui/dev📊 Changes
1 file changed (+170 additions, -0 deletions)
View changed files
📝
backend/open_webui/utils/middleware.py(+170 -0)📄 Description
Pull Request Checklist
devbranch.dev.fixprefix used.Problem
When using native function calling (
function_calling=native) with a non-streaming LLM response (e.g. Ollama/Gemma), MCP tool calls are never executed. The model returnsfinish_reason=tool_callsbut the tools are never invoked — the MCP server only ever receivesListToolsRequest, neverCallToolRequest. The model's final answer is therefore ungrounded and ignores the tool results entirely.Additionally, when Ollama prefixes tool names with the server name (e.g.
"oracle:oracle_vault_ask"), thetools_dictlookup fails silently in the streaming path as well.Root Cause
The tool-execution loop in
middleware.pylives entirely insidestreaming_chat_response_handler.non_streaming_chat_response_handleronly inspectschoices[0].message.contentand has no code path forfinish_reason=tool_calls. Tool calls in non-streaming responses are silently dropped.Fix
_execute_tool_calls_non_streaming()helper that:tool_callsfrom the non-streaming LLM response.metadata['tools'], stripping Ollama'sservername:prefix if present.mcp_client.call_tool()for MCP tools or calls the builtin callable directly.role=toolmessages and makes a second LLM call to produce the final grounded answer._execute_tool_calls_non_streaming()fromnon_streaming_chat_response_handlerwheneverfunction_calling==nativeandtool_callsare present.servername:toolname→toolname) in the streaming path's tool-execution loop so both paths handle prefixed names consistently.Changelog Entry
Description
Bug fix for native function calling in non-streaming mode: MCP tool calls returned by the LLM were silently dropped because
non_streaming_chat_response_handlerhad no tool-execution path.Fixed
finish_reason=tool_callsis present in a non-streaming response, tool calls are executed and a second LLM call is made to ground the final answer in the tool results.servername:toolnameprefix) is now applied in both the streaming and non-streaming paths.Testing
I tested this end-to-end on my self-hosted OpenWebUI instance running behind a Cloudflare reverse proxy tunnel. Setup: MCP server using FastMCP with stateless HTTP transport, authenticated via Bearer token, connected through Admin → Tool Servers UI. Model: Gemma4-26b via local Ollama. Settings:
function_calling: native,reasoning_effort: none.I sent chat messages with an MCP tool selected. Before this fix, the MCP server logs showed only
ListToolsRequest— tools were never called. After applying this fix (along with PRs #24104 and #24105), I confirmed via the MCP server logs thatCallToolRequestwas received after the model emitted a tool call, and the model's final reply was grounded in the actual tool results. Tested specifically on the non-streaming path (stream: falsevia direct API call).Contributor License Agreement
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.