MCP Tools for Local LLMs: Connect MCP Servers to Open WebUI with mcpo (Linux, Windows, Mac)
Before you start
- 17 min read
- 14 to run in terminal
- 4 file contents
Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#
MCP lets a chat app hand your local model tools - the current time, a web page fetcher, a folder it may read - and mcpo is the small proxy that makes command-line MCP servers work in Open WebUI. Your model asks for a tool, Open WebUI runs it, and the result goes back to the model. Nothing leaves your computer except what a tool itself fetches.
Why bother? A local model only knows its training data. With tools it can check today's date, read a page you point it at, or look through a folder you share on purpose. It is the same idea agents like Claude Code use, now in your own chat window.
The short version of the choice:
- Server already speaks Streamable HTTP? Add it directly. Open WebUI has native MCP support for exactly that.
- Server is a command you start (uvx, npx)? Put mcpo in front of it. It turns the server into a normal OpenAPI tool server.
| Open WebUI | v0.12.4, released 2026-10-25, image ghcr.io/open-webui/open-webui:v0.12.4 |
| mcpo | v0.0.20, released 2026-02-27 (no newer release listed). Official image ghcr.io/open-webui/mcpo:main - there is no v0.0.20 image tag, so we pin a digest (Step 2) |
| Time needed | About 20 minutes plus the model download |
| RAM | Open WebUI alone idles near 300 MiB according to the official docs (a 0.12.3 slim-image test). That excludes the model, the Docker VM and tool servers. We did not measure the full stack. |
| Ports | 3000 (Open WebUI, bound to 127.0.0.1). mcpo (8000) and Ollama (11434) are not published. |
| Official docs | docs.openwebui.com MCP page, open-webui/mcpo |
Which version, and why (checked 2026-10-26). Open WebUI v0.12.4 is one day old. We found no repeated reports of failed installs or data loss, and it contains security fixes, so this guide uses it. We cannot call it long-proven, so back up before you run it. mcpo is different: issue #303 is still open and several users confirm that a fresh uvx mcpo pulls MCP SDK 2.x and fails at import. mcpo v0.0.19 is not a verified good fallback, so instead of an old release this guide constrains the SDK with mcp<2.
Already running Open WebUI with Ollama? This guide builds on the same idea as our older Open WebUI with Ollama and Docker Compose guide. You can add the tool parts to an existing setup.
How it fits together#
You chat in Open WebUI, Open WebUI talks to Ollama for answers and to mcpo for tools, and mcpo starts the MCP servers.
Not sure whether you need mcpo at all? Follow the arrows:
One thing trips people up: who makes the request. An admin ("global") tool connection is called by the Open WebUI server, so http://mcpo:8000/time works on the Docker network. A personal ("direct") connection is called by your browser, where mcpo means nothing and localhost means your own machine.
Before you start#
| You need | Note |
|---|---|
| Linux | Docker Engine and Compose v2 (docker compose, not the old docker-compose), and permission to run Docker |
| Windows | Docker Desktop with Linux containers and the WSL2 backend. Open WebUI's docs recommend running Docker commands inside the WSL terminal; the PowerShell commands below are the Docker Desktop version |
| Mac | Docker Desktop. Open WebUI supports ARM64 and x86_64. Check that the mcpo image supports your chip (VERIFY) |
| A tool-capable model | Pick an Ollama model that supports tool calling. A weak model may ignore tools even when everything is connected right |
Python 3.11+ and uv | Only if you run mcpo on the host instead of in Docker (the mcpo README says 3.8, but its code requires 3.11) |
Check your tools:
docker --version docker compose version
Checklist:
- [ ] Docker runs (
docker psworks) - [ ] Port 3000 is free
- [ ] You know which folder the model may read (we use an empty
sharedfolder) - [ ] You backed up any existing Open WebUI data folder
Tested on 2026-10-26: Linux = not run, Windows = not run, Mac = not run. Every command here is checked against the official docs and the open-source files named in Sources; we did not execute them. The official docs are the final word if a command differs.
Step 1: Create the folders and secrets#
All data lives under docker_volumes/ in your home folder. The .env file holds two random secrets: WEBUI_SECRET_KEY (keep it forever - if it changes, saved tool logins break) and MCPO_API_KEY (the password for mcpo).
Linux#
mkdir -p "$HOME/docker_volumes/open-webui/data" "$HOME/docker_volumes/ollama" \
"$HOME/docker_volumes/mcpo/shared" "$HOME/docker_volumes/mcp-stack/backups"
cd "$HOME/docker_volumes/mcp-stack"
umask 077
printf 'DATA_ROOT=%s/docker_volumes\n' "$HOME" > .env
printf 'WEBUI_SECRET_KEY=%s\n' "$(openssl rand -hex 32)" >> .env
printf 'MCPO_API_KEY=%s\n' "$(openssl rand -hex 32)" >> .env
Windows (PowerShell)#
$Root = Join-Path $HOME "docker_volumes"
$Stack = Join-Path $Root "mcp-stack"
"open-webui/data","ollama","mcpo/shared","mcp-stack/backups" | ForEach-Object {
New-Item -ItemType Directory -Force -Path (Join-Path $Root $_) | Out-Null }
Set-Location $Stack
function New-HexSecret { $b = New-Object byte[] 32; [Security.Cryptography.RandomNumberGenerator]::Fill($b); -join ($b | ForEach-Object { $_.ToString("x2") }) }
@("DATA_ROOT=$($Root.Replace('\','/'))", "WEBUI_SECRET_KEY=$(New-HexSecret)", "MCPO_API_KEY=$(New-HexSecret)") |
Set-Content -Encoding ascii .env
Do not mix Windows paths and WSL paths in one setup. If you work inside WSL, use the Linux commands.
Mac (Terminal)#
Same commands as Linux. In Docker Desktop, make sure your home folder is allowed under file sharing.
Check it worked#
ls -la ~/docker_volumes
You should see open-webui, ollama, mcpo and mcp-stack. On Windows run dir $Root.
Step 2: Pin the mcpo and Ollama images#
Open WebUI has a real version tag (v0.12.4). mcpo and Ollama are pulled as rolling images, so we pin the exact download by its digest and write it into .env. Rolling tags can change under you.
Linux#
docker pull ghcr.io/open-webui/mcpo:main
docker pull ollama/ollama:latest
printf 'MCPO_IMAGE=%s\n' "$(docker image inspect ghcr.io/open-webui/mcpo:main --format '{{index .RepoDigests 0}}')" >> .env
printf 'OLLAMA_IMAGE=%s\n' "$(docker image inspect ollama/ollama:latest --format '{{index .RepoDigests 0}}')" >> .env
set -a; . ./.env; set +a
docker run --rm --entrypoint python "$MCPO_IMAGE" -c 'import importlib.metadata as m; print("mcpo",m.version("mcpo")); print("mcp",m.version("mcp"))'
Windows (PowerShell)#
docker pull ghcr.io/open-webui/mcpo:main
docker pull ollama/ollama:latest
$McpoImage = docker image inspect ghcr.io/open-webui/mcpo:main --format '{{index .RepoDigests 0}}'
$OllamaImage = docker image inspect ollama/ollama:latest --format '{{index .RepoDigests 0}}'
"MCPO_IMAGE=$McpoImage" | Add-Content -Encoding ascii .env
"OLLAMA_IMAGE=$OllamaImage" | Add-Content -Encoding ascii .env
docker run --rm --entrypoint python $McpoImage -c 'import importlib.metadata as m; print("mcpo",m.version("mcpo")); print("mcp",m.version("mcp"))'
Mac (Terminal)#
Same commands as Linux.
Check it worked#
The last command prints two lines like mcpo <version> and mcp <version>. If mcp starts with 2, you hit issue #303. Build a tiny fixed image (the official image keeps its Python in /app/.venv):
FROM <paste your MCPO_IMAGE value here> RUN uv pip install --python /app/.venv/bin/python "mcp<2"
Save it as Dockerfile in a new folder, run docker build -t mcpo-fixed ., and set MCPO_IMAGE=mcpo-fixed in .env. This workaround is our reading of the issue thread and of the official Dockerfile (VERIFY it on your machine).
Step 3: Write config.json for three servers#
mcpo reads one file and gives every server its own address: /time, /fetch, /filesystem. Save this as ~/docker_volumes/mcpo/config.json (Windows: $HOME\docker_volumes\mcpo\config.json). JSON has no comments, so paste it as is.
{
"mcpServers": {
"time": {
"command": "uvx",
"args": ["--with", "mcp<2", "mcp-server-time", "--local-timezone=Europe/London"]
},
"fetch": {
"command": "uvx",
"args": ["--with", "mcp<2", "mcp-server-fetch"]
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/shared"]
}
}
}
Change the time zone to yours. Notes:
/sharedis a path inside the container. Step 4 mounts yoursharedfolder there.- The
mcp<2bit keeps the Python tool servers on the working 1.x SDK. It must be set on each server separately. The newestmcp-server-timeandmcp-server-fetch(2026.10.19 and later) requiremcp2.3, so with this constraintuvquietly picks the previous release (we resolved 2026.10.10 with mcp 1.30.0). If you pin exact versions later, pin that one or a tested newer one. - The package names are not version pins. Once it works, pin exact versions (
package==Xfor Python,package@Xfor npm) that you tested. - The official mcpo image contains
uvx,npxand Node 22 (from its Dockerfile), so no host install is needed.
Linux#
Save the file where shown above, for example with nano ~/docker_volumes/mcpo/config.json.
Windows (PowerShell)#
Save it with notepad $HOME\docker_volumes\mcpo\config.json. Keep the forward slash in /shared: that path is inside the Linux container.
Mac (Terminal)#
Same as Linux. open -e ~/docker_volumes/mcpo/config.json opens it in TextEdit; use plain text mode.
Check it worked#
python3 -c "import json,os;json.load(open(os.path.expanduser('~/docker_volumes/mcpo/config.json')));print('valid JSON')"
On Windows use Get-Content $Root/mcpo/config.json | ConvertFrom-Json | Out-Null - no error means it is valid JSON.
Step 4: Start the stack with Docker Compose#
Save this as docker-compose.yml in the mcp-stack folder. It has no obsolete version: line and publishes only Open WebUI, only to your own computer.
services:
ollama:
image: ${OLLAMA_IMAGE:?set OLLAMA_IMAGE in .env}
restart: unless-stopped
volumes:
- "${DATA_ROOT}/ollama:/root/.ollama"
open-webui:
image: ghcr.io/open-webui/open-webui:v0.12.4
restart: unless-stopped
ports:
- "127.0.0.1:3000:8080"
environment:
WEBUI_SECRET_KEY: ${WEBUI_SECRET_KEY:?set WEBUI_SECRET_KEY in .env}
OLLAMA_BASE_URL: http://ollama:11434
ENABLE_TOOL_SERVERS: "True"
volumes:
- "${DATA_ROOT}/open-webui/data:/app/backend/data"
depends_on:
- ollama
mcpo:
image: ${MCPO_IMAGE:?set MCPO_IMAGE in .env}
restart: unless-stopped
command:
- "--port"
- "8000"
- "--api-key"
- "${MCPO_API_KEY:?set MCPO_API_KEY in .env}"
- "--config"
- "/config/config.json"
volumes:
- "${DATA_ROOT}/mcpo/config.json:/config/config.json:ro"
- "${DATA_ROOT}/mcpo/shared:/shared:ro"
The shared mount is read-only on purpose. The filesystem server advertises write tools, and Docker will refuse them. Open a writable folder only when you really want the model to edit files.
Linux#
docker compose config --quiet docker compose up -d docker compose ps
Windows (PowerShell)#
docker compose config --quiet docker compose up -d docker compose ps
Mac (Terminal)#
Same commands as Linux.
Check it worked#
docker compose ps should list three services as running. Then look at the mcpo log:
docker compose logs --tail=50 mcpo
Expected: mcpo starts and lists the servers from your config. Do not paste a full docker compose config output anywhere - it shows your secrets.
Prefer one container? The Open WebUI project also ships ghcr.io/open-webui/open-webui:v0.12.4-ollama, which has Ollama inside. Run it with docker run -d --name open-webui --restart unless-stopped -p 127.0.0.1:3000:8080 -e WEBUI_SECRET_KEY=<YOUR_KEY> -v "$HOME/docker_volumes/open-webui/data:/app/backend/data" -v "$HOME/docker_volumes/ollama:/root/.ollama" ghcr.io/open-webui/open-webui:v0.12.4-ollama and run mcpo separately. Use it instead of the Compose ollama and open-webui services, never next to them.
mcpo on the host with uvx (optional)#
If you would rather not use Docker for mcpo, this works in Bash, PowerShell and Mac Terminal once uv is installed. Put the constraint on mcpo and on every child server:
uvx --python 3.11 --with "mcp<2" mcpo==0.0.20 --port 8000 --api-key "<YOUR_MCPO_KEY>" --config "/absolute/path/config.json"
On Windows, write the config path with forward slashes or double backslashes. We did not find a documented "listen on localhost only" flag (VERIFY with --help), so on a host install use your firewall to keep port 8000 private.
Step 5: First run in Open WebUI#
- Open
http://localhost:3000and create the first account. That account is the admin. - Go to Settings, Admin, Connections and check that Ollama points to
http://ollama:11434. - Pull a tool-capable model:
docker compose exec ollama ollama pull <your-model>. Chat once without tools to make sure the basics work. - Go to Settings, Admin, Integrations, External Tool Servers and add an OpenAPI connection (not MCP) for each mcpo route:
- URL:
http://mcpo:8000/time - If the form asks for the spec:
http://mcpo:8000/time/openapi.json - Authentication: Bearer, with your
MCPO_API_KEYfrom.env - Repeat for
/fetchand/filesystem.
- URL:
- Open the model's Advanced Parameters and set Function Calling to Native. It is the default today, and the old Legacy mode is not supported for new setups.
- In a new chat, switch the tool on from the tools menu and test one tool at a time:
- "Use the time tool to tell me the current time."
- "List the files in the shared folder. Do not change anything."
- "Fetch this page and summarise it: <a public URL>"
Exact button labels can shift between releases (VERIFY on your version). If the model answers without a tool call, that is not proof the network is broken - check whether a tool call really happened.
Linux#
Open http://localhost:3000 in your browser. The steps above are the same.
Windows (PowerShell)#
Same steps. Run the docker compose commands from the mcp-stack folder (Set-Location $Stack).
Mac (Terminal)#
Same steps as Linux.
A server that speaks Streamable HTTP? Admin, Integrations, External Tool Servers, add a connection of type MCP (Streamable HTTP), enter its MCP endpoint and the auth it needs. Native MCP is admin-only, and with OAuth each user must enable the tool in a chat and approve it once.
Global or personal? Admin connections run from the Open WebUI server and can be limited to users or groups. Personal ("direct") connections run from the browser, need the Direct Tool Servers permission, and cannot see mcpo. In v0.12.4, turning Direct Integrations off also stops saved personal connections from being used.
Put it behind HTTPS with Traefik#
This is only for a server with its own name where you already run Traefik. Home-only users can skip it. The labels below are an example, not an official Open WebUI recipe (VERIFY against your Traefik setup). They assume an entry point called websecure, a certificate resolver called internal, and an external network called proxy (docker network create proxy if missing).
Add this to the open-webui service and delete its ports: entry:
environment:
WEBUI_SECRET_KEY: ${WEBUI_SECRET_KEY}
OLLAMA_BASE_URL: http://ollama:11434
ENABLE_TOOL_SERVERS: "True"
WEBUI_URL: https://<your-domain>
CORS_ALLOW_ORIGIN: https://<your-domain>
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.openwebui.rule=Host(`<your-domain>`)"
- "traefik.http.routers.openwebui.entrypoints=websecure"
- "traefik.http.routers.openwebui.tls=true"
- "traefik.http.routers.openwebui.tls.certresolver=internal"
- "traefik.http.services.openwebui.loadbalancer.server.port=8080"
networks:
- default
- proxy
Then add the network at the top level of the file:
networks:
proxy:
external: true
Never add Traefik labels to mcpo or Ollama. WebSockets must pass through the proxy and responses must not be buffered, or chats stall.
Security and testing#
MCP servers run code on your machine, and a web-reachable proxy that can start servers is a way into your system. The official MCP security guidance says so plainly. A key and HTTPS help, but they do not replace limits on what the tools can touch.
- Keep mcpo and Ollama off the internet. In this stack they publish no ports.
- Keep Open WebUI sign-in on. Bind it to
127.0.0.1unless you add Traefik. - Mount one dedicated folder, read-only first. Never mount
/, your whole home folder, SSH keys, cloud credentials or the Docker socket. - Keep
.envprivate:chmod 600 .envon Linux and Mac; on Windows check that only your account can read it. The mcpo key is passed as a command argument, so anyone with Docker access can read it. Do not give untrusted people Docker access. - Give each tool only to the users or groups who need it.
- Treat fetched web pages and tool output as untrusted: a page can contain text that tries to steer the model.
- Check what is listening:
docker compose ps curl -I http://127.0.0.1:3000
The curl should answer with HTTP 200 and nothing should answer on port 8000 or 11434 from outside the containers. With Traefik, also run curl -I https://<your-domain>.
Troubleshooting#
| Problem | Solution |
|---|---|
mcpo fails at start with an import error about streamablehttp_client | MCP SDK 2.x. Use --with "mcp<2" on the host, or the fixed image from Step 2. See issue #303 |
ClosedResourceError | The tool server dropped its session. mcpo 0.0.20 reconnects; check the log for why it keeps dropping |
Error decrypting tokens after re-creating the container | WEBUI_SECRET_KEY changed. Put the old key back, or re-authorise the OAuth tools |
Failed to connect to MCP server '<id>' | Read the log line after it: wrong URL, wrong auth type or slow start. Do not pick Bearer with an empty key |
Cannot convert undefined or null to object at Object.entries | An MCP config was entered as an OpenAPI connection. Use type MCP for native servers and the /time/openapi.json style URL for mcpo |
| Tool works in your browser but not in a chat | Network namespace mismatch. Global connections use Docker names (http://mcpo:8000/time); direct connections use browser addresses |
| Personal tools stopped after upgrading to v0.12.4 | Direct Integrations is off. Turn it on only if you want personal connections |
| Slow start of an MCP server | Default initialize limit is 10 seconds. Set MCP_INITIALIZE_TIMEOUT to 30 on open-webui once the server is healthy |
| Model never calls the tool | Use a tool-capable model and Native function calling, and enable the tool in the chat |
Open WebUI backend required#
The exact message is "Open WebUI backend required. Oops! You're using an unsupported method (frontend only)." It means /api/config did not reach the backend. Check that your proxy sends traffic to container port 8080, clear the browser cache and old service workers. It does not mean the image is missing its backend.
Known bugs#
- mcpo #303: fresh installs resolve MCP 2.x and fail. Open at the time of writing. Workaround above.
- mcpo #302: after an upstream HTTP error one CPU core can stay at 100 percent. We could not confirm a fix (VERIFY). Watch
docker statsand restart mcpo if it happens.
Useful diagnostics:
docker compose ps docker compose logs --tail=100 mcpo docker compose logs --tail=100 open-webui docker stats --no-stream
Updates and backups#
Database changes in a new Open WebUI release can be one-way, so an old image may not read a migrated database. Always keep a backup from before the upgrade.
To update: read the release notes, back up, change the tag in docker-compose.yml, then docker compose pull && docker compose up -d. To update mcpo, pull the image again, check the versions as in Step 2 and update MCPO_IMAGE.
Linux#
cd "$HOME/docker_volumes/mcp-stack" STAMP="$(date +%Y%m%d-%H%M%S)" BACKUP="$PWD/backups/mcp-stack-$STAMP.tar.gz" docker compose stop tar -czf "$BACKUP" -C "$HOME/docker_volumes" open-webui mcpo mcp-stack/docker-compose.yml mcp-stack/.env docker compose start tar -tzf "$BACKUP" > /dev/null && echo "backup OK"
The archive holds your secrets: keep a copy off the machine and encrypt it. Add ollama to the tar line only if you want the models in the backup (they are large). To run it every night at 03:00, put the commands in backup.sh and add 0 3 * * * /bin/bash $HOME/docker_volumes/mcp-stack/backup.sh with crontab -e.
Windows (PowerShell)#
Set-Location $Stack
$Stamp = Get-Date -Format "yyyyMMdd-HHmmss"
$Backup = Join-Path $Stack "backups/mcp-stack-$Stamp.tar.gz"
docker compose stop
tar -czf $Backup -C $Root open-webui mcpo mcp-stack/docker-compose.yml mcp-stack/.env
docker compose start
tar -tzf $Backup | Out-Null; if ($LASTEXITCODE -eq 0) { "backup OK" }
Windows 11 ships tar. For a nightly run, save the commands as backup.ps1 and create a Task Scheduler task at 03:00 that runs pwsh -File <path>\backup.ps1.
Mac (Terminal)#
Same commands as Linux. For the schedule, use crontab -e as above (your Mac must be awake) or a launchd job.
Restore test#
Never test on live data. Unpack the backup somewhere else and start a second copy on another port:
RESTORE="$HOME/docker_volumes/restore-test" mkdir -p "$RESTORE" && tar -xzf "$BACKUP" -C "$RESTORE" docker run -d --name open-webui-restore-test -p 127.0.0.1:3001:8080 \ -e WEBUI_SECRET_KEY="$WEBUI_SECRET_KEY" \ -v "$RESTORE/open-webui/data:/app/backend/data" \ ghcr.io/open-webui/open-webui:v0.12.4
On Windows use the same docker run with --mount "type=bind,source=$Restore/open-webui/data,target=/app/backend/data". Open http://localhost:3001 and check: your login works, old chats are there, settings and tool connections exist, and the data survives docker restart open-webui-restore-test. This small copy has no Ollama or mcpo behind it; to test real tool calls restore a whole second Compose project with another DATA_ROOT and port. Finish with docker rm -f open-webui-restore-test.
Performance and resources#
- The model dominates everything: weights, context length and your CPU or GPU. Open WebUI and mcpo are small next to it.
- The official docs list about 300 MiB idle RAM for Open WebUI (slim image, v0.12.3 test). That is not a minimum for the stack. Measure yours with
docker statswhile you chat. - Every tool you switch on adds its description to the prompt. Enable only what the current chat needs.
- The first start of a
uvxornpxtool downloads packages, so the first call is slow. It needs internet once. - Keep one Open WebUI worker for a single-user setup. Do not add workers or replicas without the official scaling guide.
- Docker Desktop adds its own VM memory. Give it enough in its settings.
- If you want GPU, it has to reach the container that runs the model (Ollama), not Open WebUI.
FAQ#
Does my local model need to support MCP? No. Open WebUI is the MCP client and runs the tools. The model only needs to be good at tool calling.
Do I still need mcpo now that Open WebUI has native MCP? Only for command-line (stdio) servers and old SSE servers. A server that speaks Streamable HTTP can connect directly.
Why does http://localhost:8000 not work? localhost means whoever makes the request. For an admin connection that is the Open WebUI container, so use http://mcpo:8000/time. For a personal connection it is your browser's computer.
Can the filesystem tool see my whole computer? Only what you mount. Mount one folder, read-only first, and test with files you do not care about.
Can this run offline? Yes once images, models and tool packages are downloaded. A fetch tool still needs the internet, and the first uvx and npx runs download packages.
Sources#
Read on 2026-10-26. Checked against the official docs on 2026-10-26; not run on a test machine.
- Open WebUI v0.12.4 release notes (2026-10-25)
- Open WebUI Quick Start
- Open WebUI MCP documentation
- Open WebUI: OpenAPI tool servers
- Open WebUI environment variables
- Open WebUI updating
- Open WebUI connection troubleshooting
- mcpo README, mcpo releases (latest v0.0.20, 2026-02-27), mcpo Dockerfile
- MCP: connect local servers
- MCP: Streamable HTTP transport
- MCP: security best practices