[GH-ISSUE #24032] issue: Can't connect to MCP server with self-signed certificate - RuntimeWarning: coroutine 'create_insecure_httpx_client' was never awaited #58826

Closed
opened 2026-05-06 00:15:09 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @doss22 on GitHub (Apr 23, 2026).
Original GitHub issue: https://github.com/open-webui/open-webui/issues/24032

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.9.1

Ollama Version (if applicable)

No response

Operating System

Ubuntu 22.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. My steps:
  • Start with the initial platform/version/OS and dependencies used,
  • Specify exact install/launch/configure commands,
  • List URLs visited, user input (incl. example values/emails/passwords if needed),
  • Describe all options and toggles enabled or changed,
  • Include any files or environmental changes,
  • Identify the expected and actual result at each stage,
  • Ensure any reasonably skilled user can follow and hit the same issue.

Expected Behavior

I am trying to connect to my mcp server with an self-signed certificate via streamable http. I am using Bearer to authenticate.

Actual Behavior

Connection can't be established. Open WebUI fails to create create_insecure_httpx_client.

Steps to Reproduce

I am using the offical docker image and have a nginx in front of Open WebUI. Other external connections, like connections to vllm or litellm are working properly.

Env Vars:

ENABLE_WEBSOCKET_SUPPORT=True
ENABLE_ADMIN_CHAT_ACCESS=False

ENABLE_WEB_LOADER_SSL_VERIFICATION = False
AIOHTTP_CLIENT_SESSION_SSL = False
AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL = False
PYTHONTRACEMALLOC = 1

docker-compose.yml

services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:v0.9.1
    container_name: open-webui
    restart: always
    environment:
      - PORT=${OPEN_WEBUI_PORT}
    env_file:
      - .env.openwebui
    volumes:
      - ~/open-webui-data:/app/backend/data
    networks:
      - allow-external
      - openwebui-nginx
networks:
  allow-external:
    name: allow-external
    driver: bridge
  openwebui-nginx:
    internal: true
    name: openwebui-nginx

Logs & Screenshots

Trying to reach the MCP Server via Chat Interface produces the following log:

open-webui  | /app/backend/open_webui/utils/middleware.py:2671: RuntimeWarning: coroutine 'create_insecure_httpx_client' was never awaited
open-webui  |   continue
open-webui  | Object allocated at (most recent call last):
open-webui  |   File "/usr/local/lib/python3.11/site-packages/mcp/client/streamable_http.py", lineno 709
open-webui  |     client = httpx_client_factory(

Test Connection in Admin Panel produces following log:

open-webui  | /usr/local/lib/python3.11/site-packages/starlette/_exception_handler.py:63: RuntimeWarning: coroutine 'create_insecure_httpx_client' was never awaited
open-webui  |   await response(scope, receive, sender)
open-webui  | Object allocated at (most recent call last):
open-webui  |   File "/usr/local/lib/python3.11/site-packages/mcp/client/streamable_http.py", lineno 709
open-webui  |     client = httpx_client_factory(

Additional Information

No response

Originally created by @doss22 on GitHub (Apr 23, 2026). Original GitHub issue: https://github.com/open-webui/open-webui/issues/24032 ### 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.9.1 ### Ollama Version (if applicable) _No response_ ### Operating System Ubuntu 22.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**. My steps: - Start with the initial platform/version/OS and dependencies used, - Specify exact install/launch/configure commands, - List URLs visited, user input (incl. example values/emails/passwords if needed), - Describe all options and toggles enabled or changed, - Include any files or environmental changes, - Identify the expected and actual result at each stage, - Ensure any reasonably skilled user can follow and hit the same issue. ### Expected Behavior I am trying to connect to my mcp server with an self-signed certificate via streamable http. I am using Bearer to authenticate. ### Actual Behavior Connection can't be established. Open WebUI fails to create `create_insecure_httpx_client`. ### Steps to Reproduce I am using the offical docker image and have a nginx in front of Open WebUI. Other external connections, like connections to vllm or litellm are working properly. Env Vars: ``` ENABLE_WEBSOCKET_SUPPORT=True ENABLE_ADMIN_CHAT_ACCESS=False ENABLE_WEB_LOADER_SSL_VERIFICATION = False AIOHTTP_CLIENT_SESSION_SSL = False AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL = False PYTHONTRACEMALLOC = 1 ``` docker-compose.yml ``` services: open-webui: image: ghcr.io/open-webui/open-webui:v0.9.1 container_name: open-webui restart: always environment: - PORT=${OPEN_WEBUI_PORT} env_file: - .env.openwebui volumes: - ~/open-webui-data:/app/backend/data networks: - allow-external - openwebui-nginx networks: allow-external: name: allow-external driver: bridge openwebui-nginx: internal: true name: openwebui-nginx ``` ### Logs & Screenshots Trying to reach the MCP Server via Chat Interface produces the following log: ``` open-webui | /app/backend/open_webui/utils/middleware.py:2671: RuntimeWarning: coroutine 'create_insecure_httpx_client' was never awaited open-webui | continue open-webui | Object allocated at (most recent call last): open-webui | File "/usr/local/lib/python3.11/site-packages/mcp/client/streamable_http.py", lineno 709 open-webui | client = httpx_client_factory( ``` Test Connection in Admin Panel produces following log: ``` open-webui | /usr/local/lib/python3.11/site-packages/starlette/_exception_handler.py:63: RuntimeWarning: coroutine 'create_insecure_httpx_client' was never awaited open-webui | await response(scope, receive, sender) open-webui | Object allocated at (most recent call last): open-webui | File "/usr/local/lib/python3.11/site-packages/mcp/client/streamable_http.py", lineno 709 open-webui | client = httpx_client_factory( ``` ### Additional Information _No response_
GiteaMirror added the bug label 2026-05-06 00:15:09 -05:00
Author
Owner

@doss22 commented on GitHub (Apr 23, 2026):

Top reproduce you can also run backend/open_webui/utils/mcp/client.py
https://github.com/open-webui/open-webui/blob/main/backend/open_webui/utils/mcp/client.py


import asyncio
import logging
from typing import Optional
from contextlib import AsyncExitStack

log = logging.getLogger(__name__)

import anyio

from mcp import ClientSession
from mcp.client.auth import OAuthClientProvider, TokenStorage
from mcp.client.streamable_http import streamablehttp_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
import httpx

###### MODIFIED ##########
AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL = False
AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = 10
#########################

def _build_httpx_client(headers=None, timeout=None, auth=None, verify=True):
    """Create an httpx AsyncClient for MCP transport.

    Falls back to AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER when the caller
    (i.e. the MCP SDK) does not supply an explicit timeout.

    Note: verify must be passed at construction time because httpx
    configures the SSL context during __init__. Setting client.verify = False
    after construction does not affect the underlying transport's SSL context.
    """
    kwargs = {
        'follow_redirects': True,
        'verify': verify,
    }
    if timeout is not None:
        kwargs['timeout'] = timeout
    elif AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER is not None:
        kwargs['timeout'] = float(AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER)
    if headers is not None:
        kwargs['headers'] = headers
    if auth is not None:
        kwargs['auth'] = auth
    return httpx.AsyncClient(**kwargs)


def create_httpx_client(headers=None, timeout=None, auth=None):
    return _build_httpx_client(headers=headers, timeout=timeout, auth=auth, verify=True)


def create_insecure_httpx_client(headers=None, timeout=None, auth=None):
    return _build_httpx_client(headers=headers, timeout=timeout, auth=auth, verify=False)


class MCPClient:
    def __init__(self):
        self.session: Optional[ClientSession] = None
        self.exit_stack = None

    async def connect(self, url: str, headers: Optional[dict] = None):
        async with AsyncExitStack() as exit_stack:
            try:
                self._streams_context = streamablehttp_client(
                    url,
                    headers=headers,
                    httpx_client_factory=create_httpx_client
                    if AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL
                    else create_insecure_httpx_client,
                )

                transport = await exit_stack.enter_async_context(self._streams_context)
                read_stream, write_stream, _ = transport

                self._session_context = ClientSession(read_stream, write_stream)  # pylint: disable=W0201

                self.session = await exit_stack.enter_async_context(self._session_context)
                with anyio.fail_after(10):
                    await self.session.initialize()
                self.exit_stack = exit_stack.pop_all()
            except Exception as e:
                await asyncio.shield(self.disconnect())
                raise e

    async def list_tool_specs(self) -> Optional[dict]:
        if not self.session:
            raise RuntimeError('MCP client is not connected.')

        result = await self.session.list_tools()
        tools = result.tools

        tool_specs = []
        for tool in tools:
            name = tool.name
            description = tool.description

            inputSchema = tool.inputSchema

            # TODO: handle outputSchema if needed
            outputSchema = getattr(tool, 'outputSchema', None)

            tool_specs.append({'name': name, 'description': description, 'parameters': inputSchema})

        return tool_specs

    async def call_tool(self, function_name: str, function_args: dict) -> Optional[dict]:
        if not self.session:
            raise RuntimeError('MCP client is not connected.')

        result = await self.session.call_tool(function_name, function_args)
        if not result:
            raise Exception('No result returned from MCP tool call.')

        result_dict = result.model_dump(mode='json')
        result_content = result_dict.get('content', {})

        if result.isError:
            raise Exception(result_content)
        else:
            return result_content

    async def list_resources(self, cursor: Optional[str] = None) -> Optional[dict]:
        if not self.session:
            raise RuntimeError('MCP client is not connected.')

        result = await self.session.list_resources(cursor=cursor)
        if not result:
            raise Exception('No result returned from MCP list_resources call.')

        result_dict = result.model_dump()
        resources = result_dict.get('resources', [])

        return resources

    async def read_resource(self, uri: str) -> Optional[dict]:
        if not self.session:
            raise RuntimeError('MCP client is not connected.')

        result = await self.session.read_resource(uri)
        if not result:
            raise Exception('No result returned from MCP read_resource call.')
        result_dict = result.model_dump()

        return result_dict

    async def disconnect(self):
        """Clean up and close the session.

        This method is idempotent — calling it multiple times or on a
        client that was never connected is safe.  It shields the close
        operation from CancelledError and adds a timeout so a hung MCP
        server cannot block the event loop indefinitely.
        """
        exit_stack = self.exit_stack
        if exit_stack is None:
            return

        # Prevent double-close from concurrent callers
        self.exit_stack = None
        self.session = None

        try:
            await asyncio.wait_for(
                asyncio.shield(exit_stack.aclose()),
                timeout=5.0,
            )
        except asyncio.TimeoutError:
            log.warning('MCPClient.disconnect() timed out after 5 s')
        except RuntimeError as exc:
            # The MCP SDK's streamable_http transport uses anyio task
            # groups and async generators internally.  When we close
            # a session that was interrupted mid-flight these can
            # raise RuntimeError ("aclose(): asynchronous generator is
            # already running" or "Attempted to exit cancel scope in a
            # different task").  Swallowing the error here prevents the
            # orphaned coroutines from spinning at 100 % CPU.
            log.debug('MCPClient.disconnect() suppressed RuntimeError: %s', exc)
        except Exception as exc:
            log.debug('MCPClient.disconnect() error: %s', exc)

    async def __aenter__(self):
        await self.exit_stack.__aenter__()
        return self

    async def __aexit__(self, exc_type, exc_value, traceback):
        await self.exit_stack.__aexit__(exc_type, exc_value, traceback)
        await self.disconnect()


if __name__ == "__main__":
    client = MCPClient()
    client.connect("https://<mcp-server>/mcp")
<!-- gh-comment-id:4304473111 --> @doss22 commented on GitHub (Apr 23, 2026): Top reproduce you can also run backend/open_webui/utils/mcp/client.py https://github.com/open-webui/open-webui/blob/main/backend/open_webui/utils/mcp/client.py ```python import asyncio import logging from typing import Optional from contextlib import AsyncExitStack log = logging.getLogger(__name__) import anyio from mcp import ClientSession from mcp.client.auth import OAuthClientProvider, TokenStorage from mcp.client.streamable_http import streamablehttp_client from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken import httpx ###### MODIFIED ########## AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL = False AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = 10 ######################### def _build_httpx_client(headers=None, timeout=None, auth=None, verify=True): """Create an httpx AsyncClient for MCP transport. Falls back to AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER when the caller (i.e. the MCP SDK) does not supply an explicit timeout. Note: verify must be passed at construction time because httpx configures the SSL context during __init__. Setting client.verify = False after construction does not affect the underlying transport's SSL context. """ kwargs = { 'follow_redirects': True, 'verify': verify, } if timeout is not None: kwargs['timeout'] = timeout elif AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER is not None: kwargs['timeout'] = float(AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER) if headers is not None: kwargs['headers'] = headers if auth is not None: kwargs['auth'] = auth return httpx.AsyncClient(**kwargs) def create_httpx_client(headers=None, timeout=None, auth=None): return _build_httpx_client(headers=headers, timeout=timeout, auth=auth, verify=True) def create_insecure_httpx_client(headers=None, timeout=None, auth=None): return _build_httpx_client(headers=headers, timeout=timeout, auth=auth, verify=False) class MCPClient: def __init__(self): self.session: Optional[ClientSession] = None self.exit_stack = None async def connect(self, url: str, headers: Optional[dict] = None): async with AsyncExitStack() as exit_stack: try: self._streams_context = streamablehttp_client( url, headers=headers, httpx_client_factory=create_httpx_client if AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL else create_insecure_httpx_client, ) transport = await exit_stack.enter_async_context(self._streams_context) read_stream, write_stream, _ = transport self._session_context = ClientSession(read_stream, write_stream) # pylint: disable=W0201 self.session = await exit_stack.enter_async_context(self._session_context) with anyio.fail_after(10): await self.session.initialize() self.exit_stack = exit_stack.pop_all() except Exception as e: await asyncio.shield(self.disconnect()) raise e async def list_tool_specs(self) -> Optional[dict]: if not self.session: raise RuntimeError('MCP client is not connected.') result = await self.session.list_tools() tools = result.tools tool_specs = [] for tool in tools: name = tool.name description = tool.description inputSchema = tool.inputSchema # TODO: handle outputSchema if needed outputSchema = getattr(tool, 'outputSchema', None) tool_specs.append({'name': name, 'description': description, 'parameters': inputSchema}) return tool_specs async def call_tool(self, function_name: str, function_args: dict) -> Optional[dict]: if not self.session: raise RuntimeError('MCP client is not connected.') result = await self.session.call_tool(function_name, function_args) if not result: raise Exception('No result returned from MCP tool call.') result_dict = result.model_dump(mode='json') result_content = result_dict.get('content', {}) if result.isError: raise Exception(result_content) else: return result_content async def list_resources(self, cursor: Optional[str] = None) -> Optional[dict]: if not self.session: raise RuntimeError('MCP client is not connected.') result = await self.session.list_resources(cursor=cursor) if not result: raise Exception('No result returned from MCP list_resources call.') result_dict = result.model_dump() resources = result_dict.get('resources', []) return resources async def read_resource(self, uri: str) -> Optional[dict]: if not self.session: raise RuntimeError('MCP client is not connected.') result = await self.session.read_resource(uri) if not result: raise Exception('No result returned from MCP read_resource call.') result_dict = result.model_dump() return result_dict async def disconnect(self): """Clean up and close the session. This method is idempotent — calling it multiple times or on a client that was never connected is safe. It shields the close operation from CancelledError and adds a timeout so a hung MCP server cannot block the event loop indefinitely. """ exit_stack = self.exit_stack if exit_stack is None: return # Prevent double-close from concurrent callers self.exit_stack = None self.session = None try: await asyncio.wait_for( asyncio.shield(exit_stack.aclose()), timeout=5.0, ) except asyncio.TimeoutError: log.warning('MCPClient.disconnect() timed out after 5 s') except RuntimeError as exc: # The MCP SDK's streamable_http transport uses anyio task # groups and async generators internally. When we close # a session that was interrupted mid-flight these can # raise RuntimeError ("aclose(): asynchronous generator is # already running" or "Attempted to exit cancel scope in a # different task"). Swallowing the error here prevents the # orphaned coroutines from spinning at 100 % CPU. log.debug('MCPClient.disconnect() suppressed RuntimeError: %s', exc) except Exception as exc: log.debug('MCPClient.disconnect() error: %s', exc) async def __aenter__(self): await self.exit_stack.__aenter__() return self async def __aexit__(self, exc_type, exc_value, traceback): await self.exit_stack.__aexit__(exc_type, exc_value, traceback) await self.disconnect() if __name__ == "__main__": client = MCPClient() client.connect("https://<mcp-server>/mcp") ```
Author
Owner

@tjbck commented on GitHub (Apr 24, 2026):

Addressed in dev.

<!-- gh-comment-id:4311464638 --> @tjbck commented on GitHub (Apr 24, 2026): Addressed in dev.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/open-webui#58826