2026-09-27 07:11:57 +01:00
|
|
|
# webfetch-mcpo-docker
|
2026-09-26 15:08:51 +01:00
|
|
|
|
|
|
|
|
A single Docker image exposing [`webfetch-mcp`](https://code.aneur.in/mirrors/webfetch-mcp)'s
|
2026-10-06 17:27:28 +01:00
|
|
|
`web_search` and `web_fetch` tools in two ways at once:
|
2026-09-26 15:08:51 +01:00
|
|
|
|
2026-10-06 17:27:28 +01:00
|
|
|
| Port | Protocol | For |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| `8000` | OpenAPI, via [`mcpo`](https://github.com/open-webui/mcpo) | Open WebUI |
|
|
|
|
|
| `8080` | Native MCP (streamable HTTP) at `/mcp`, via [`supergateway`](https://github.com/supercorp-ai/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`.
|
2026-09-26 15:08:51 +01:00
|
|
|
|
|
|
|
|
## Build
|
|
|
|
|
|
|
|
|
|
```sh
|
2026-09-27 07:11:57 +01:00
|
|
|
docker build -t webfetch-mcpo-docker .
|
2026-09-26 15:08:51 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Run
|
|
|
|
|
|
|
|
|
|
```sh
|
2026-10-06 17:27:28 +01:00
|
|
|
docker run -p 8000:8000 -p 8080:8080 \
|
2026-09-26 15:08:51 +01:00
|
|
|
-e MCPO_API_KEY=some-secret \
|
|
|
|
|
-e SEARXNG_BASE=http://searxng.example.internal:8080 \
|
2026-09-27 07:11:57 +01:00
|
|
|
webfetch-mcpo-docker
|
2026-09-26 15:08:51 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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>`.
|
|
|
|
|
|
2026-10-06 17:27:28 +01:00
|
|
|
### opencode (or any MCP client)
|
|
|
|
|
|
|
|
|
|
In `opencode.json`:
|
|
|
|
|
|
|
|
|
|
```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`.
|
|
|
|
|
|
2026-09-26 15:08:51 +01:00
|
|
|
## 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`. |
|
2026-10-06 17:27:28 +01:00
|
|
|
| `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`. |
|
2026-09-26 15:08:51 +01:00
|
|
|
| `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. |
|
|
|
|
|
|
2026-10-06 17:27:28 +01:00
|
|
|
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
|
2026-09-26 15:08:51 +01:00
|
|
|
`MCPConnectionManager` source, not assumed.)
|
|
|
|
|
|
2026-10-06 17:27:28 +01:00
|
|
|
## 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.
|
|
|
|
|
|
2026-09-26 15:08:51 +01:00
|
|
|
## Known characteristics
|
|
|
|
|
|
2026-10-06 17:27:28 +01:00
|
|
|
- **The `:8080` endpoint is unauthenticated.** Intended for a trusted LAN only.
|
|
|
|
|
|
2026-09-26 15:08:51 +01:00
|
|
|
- **No URL filtering / SSRF protection.** `webfetch-mcp`'s `web_fetch` tool fetches
|
|
|
|
|
whatever URL it's given, including private/internal addresses. This is a known,
|
|
|
|
|
accepted characteristic of `webfetch-mcp` itself — 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-mcp` writes a detailed log file next to `server.mjs` inside the image
|
|
|
|
|
by default (`DETAILED_LOG` defaults 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 set `DETAILED_LOG=false`).
|
|
|
|
|
|
|
|
|
|
## Pinned revisions
|
|
|
|
|
|
|
|
|
|
- `webfetch-mcp`: pinned via `ARG WEBFETCH_MCP_COMMIT` in the `Dockerfile`, vendored
|
|
|
|
|
from the Gitea mirror at `https://code.aneur.in/mirrors/webfetch-mcp`, not upstream
|
|
|
|
|
GitHub.
|
|
|
|
|
- `mcpo`: pinned via `ARG MCPO_VERSION` in the `Dockerfile`, installed from PyPI.
|
|
|
|
|
|
2026-10-06 17:27:28 +01:00
|
|
|
- `supergateway`: pinned via `ARG SUPERGATEWAY_VERSION` in the `Dockerfile`, installed
|
|
|
|
|
from npm.
|
|
|
|
|
|
|
|
|
|
Bump any of them by updating the corresponding `ARG` default in the `Dockerfile`. When
|
2026-09-26 15:08:51 +01:00
|
|
|
bumping `mcpo`, double check its CLI flags still match what `entrypoint.sh` assumes
|
2026-10-06 17:27:28 +01:00
|
|
|
(`--host`, `--port`, `--api-key`, `--`); for `supergateway`, `--stdio`,
|
|
|
|
|
`--outputTransport`, `--port` and `--healthEndpoint`.
|
2026-09-26 15:08:51 +01:00
|
|
|
|
|
|
|
|
## CI
|
|
|
|
|
|
|
|
|
|
`.gitea/workflows/docker-build.yml` builds and pushes
|
2026-09-27 07:11:57 +01:00
|
|
|
`code.aneur.in/cloud/webfetch-mcpo-docker:latest` on every push to `main`. It
|
2026-09-26 15:08:51 +01:00
|
|
|
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.
|