Run supergateway on port 8080 (streamable HTTP at /mcp) next to mcpo on 8000, each spawning its own webfetch-mcp child. Supervise both under tini. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
webfetch-mcpo-docker
A single Docker image exposing webfetch-mcp's
web_search and web_fetch tools in two ways at once:
| Port | Protocol | For |
|---|---|---|
8000 |
OpenAPI, via mcpo |
Open WebUI |
8080 |
Native MCP (streamable HTTP) at /mcp, via supergateway |
Coding agents (opencode etc.) |
webfetch-mcp is a stdio-only MCP server with no HTTP mode of its own. mcpo and
supergateway each expose a wrapped stdio server over HTTP by spawning it as a local
child process, so they have to live in the same image as webfetch-mcp. Each spawns
its own node server.mjs.
Build
docker build -t webfetch-mcpo-docker .
Run
docker run -p 8000:8000 -p 8080:8080 \
-e MCPO_API_KEY=some-secret \
-e SEARXNG_BASE=http://searxng.example.internal:8080 \
webfetch-mcpo-docker
Interactive API docs: http://localhost:8000/docs (and /openapi.json). These are
served without authentication even when MCPO_API_KEY is set — only the generated
tool endpoints (/web_search, /web_fetch) require it, as
Authorization: Bearer <MCPO_API_KEY>.
opencode (or any MCP client)
In opencode.json:
{
"mcp": {
"webfetch": { "type": "remote", "url": "http://<host>:8080/mcp" }
}
}
The :8080 endpoint has no authentication; MCPO_API_KEY applies only to :8000.
/healthz on :8080 returns ok.
Environment variables
| Variable | Effect |
|---|---|
MCPO_API_KEY |
If set, tool endpoints require Authorization: Bearer <value>. If unset, mcpo serves unauthenticated (its own default behavior). |
MCPO_PORT |
Port mcpo listens on inside the container. Default 8000. |
MCP_PORT |
Port the native MCP endpoint listens on. Default 8080. |
ENABLE_MCPO / ENABLE_MCP |
Set either to false to run only the other side. Both default true. |
SEARXNG_BASE |
Passed through to webfetch-mcp; its own default (http://localhost:8080) applies if unset. |
DEBUG |
Passed through to webfetch-mcp. "true" enables debug logging. |
DETAILED_LOG |
Passed through to webfetch-mcp. Defaults on; set to "false" to disable its detailed log file. |
No explicit plumbing is needed to get these into webfetch-mcp: both mcpo and
supergateway start the child with the container's environment inherited, so
anything set on the container's environment reaches node server.mjs automatically. (Verified against mcpo's
MCPConnectionManager source, not assumed.)
Process supervision
entrypoint.sh runs both servers under tini. If either one exits, the other is
stopped and the container exits non-zero, so the orchestrator restarts the whole
thing rather than leaving it half up.
Known characteristics
-
The
:8080endpoint is unauthenticated. Intended for a trusted LAN only. -
No URL filtering / SSRF protection.
webfetch-mcp'sweb_fetchtool fetches whatever URL it's given, including private/internal addresses. This is a known, accepted characteristic ofwebfetch-mcpitself — out of scope for this image to fix. -
No TLS termination. This image serves plain HTTP; put a reverse proxy or firewall in front of it if you need TLS or network restriction.
-
webfetch-mcpwrites a detailed log file next toserver.mjsinside the image by default (DETAILED_LOGdefaults on). That directory needs to stay writable at runtime — don't run this image with a read-only root filesystem without accounting for that (e.g. a volume mount, or setDETAILED_LOG=false).
Pinned revisions
-
webfetch-mcp: pinned viaARG WEBFETCH_MCP_COMMITin theDockerfile, vendored from the Gitea mirror athttps://code.aneur.in/mirrors/webfetch-mcp, not upstream GitHub. -
mcpo: pinned viaARG MCPO_VERSIONin theDockerfile, installed from PyPI. -
supergateway: pinned viaARG SUPERGATEWAY_VERSIONin theDockerfile, installed from npm.
Bump any of them by updating the corresponding ARG default in the Dockerfile. When
bumping mcpo, double check its CLI flags still match what entrypoint.sh assumes
(--host, --port, --api-key, --); for supergateway, --stdio,
--outputTransport, --port and --healthEndpoint.
CI
.gitea/workflows/docker-build.yml builds and pushes
code.aneur.in/cloud/webfetch-mcpo-docker:latest on every push to main. It
authenticates using the repo secrets PACKAGE_REGISTRY_USER and
PACKAGE_REGISTRY_TOKEN — the default Gitea Actions token doesn't have
package-registry write permission on this instance, so these must be created
manually under repo Settings → Actions → Secrets, backed by a personal access token
with package-write scope.