Wraps the official gitea-mcp server (binary copied from docker.gitea.com/gitea-mcp-server:1.7.0) and serves it two ways from one container: - :8000 mcpo (OpenAPI) for Open WebUI, using the container's service token and guarded by MCPO_API_KEY. - :8080 native streamable-HTTP MCP at /mcp for coding agents, with the service token stripped so each client must send its own Gitea token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# gitea-mcpo-docker
|
||||
|
||||
A single Docker image that exposes the official [Gitea MCP server](https://gitea.com/gitea/gitea-mcp)
|
||||
in two ways at once:
|
||||
|
||||
| Port | Protocol | For | Gitea identity |
|
||||
|---|---|---|---|
|
||||
| `8000` | OpenAPI, via [`mcpo`](https://github.com/open-webui/mcpo) | Open WebUI | The container's service token (`GITEA_ACCESS_TOKEN`) |
|
||||
| `8080` | Native MCP (streamable HTTP) at `/mcp` | Coding agents (Claude Code etc.) | Each client's own token, sent as `Authorization: Bearer <token>` |
|
||||
|
||||
Both are served by the same `gitea-mcp` binary: `mcpo` spawns it as a stdio child,
|
||||
and a second instance runs in `gitea-mcp`'s own HTTP mode.
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
docker build -t gitea-mcpo-docker .
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```sh
|
||||
docker run -p 8000:8000 -p 8080:8080 \
|
||||
-e GITEA_HOST=https://code.aneur.in \
|
||||
-e GITEA_ACCESS_TOKEN=<service-account token> \
|
||||
-e MCPO_API_KEY=some-secret \
|
||||
gitea-mcpo-docker
|
||||
```
|
||||
|
||||
### Open WebUI
|
||||
|
||||
Add a tool server pointing at `http://<host>:8000`, with bearer auth set to
|
||||
`MCPO_API_KEY`. Interactive API docs are at `http://<host>:8000/docs` (and
|
||||
`/openapi.json`). These are served without authentication even when `MCPO_API_KEY`
|
||||
is set; only the tool endpoints themselves require it.
|
||||
|
||||
### Claude Code (or any MCP client)
|
||||
|
||||
```sh
|
||||
claude mcp add --transport http gitea http://<host>:8080/mcp \
|
||||
--header "Authorization: Bearer <your Gitea token>"
|
||||
```
|
||||
|
||||
The HTTP endpoint never falls back to the service token: `GITEA_ACCESS_TOKEN` and
|
||||
`GITEA_ACCESS_TOKEN_FILE` are stripped from that process's environment. A request
|
||||
without a token gets a `token is required` tool error, and a request with one acts
|
||||
as that token's user. Commits, comments and PRs are therefore attributed to whichever
|
||||
account the client's token belongs to.
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Effect |
|
||||
|---|---|
|
||||
| `GITEA_HOST` | **Required.** Base URL of the Gitea instance, e.g. `https://code.aneur.in`. |
|
||||
| `GITEA_ACCESS_TOKEN` | Token that the mcpo side (Open WebUI) uses. Not visible to the `:8080` MCP endpoint. |
|
||||
| `GITEA_ACCESS_TOKEN_FILE` | Alternative to the above: path to a file holding the token (e.g. a Docker secret). |
|
||||
| `MCPO_API_KEY` | If set, mcpo tool endpoints require `Authorization: Bearer <value>`. If unset, mcpo serves unauthenticated. |
|
||||
| `GITEA_READONLY` | `true` exposes only read-only tools, on **both** ports. |
|
||||
| `GITEA_SCOPES` / `GITEA_TOOLS` | Comma-separated allowlist of tool scopes / tool names, on both ports (see `gitea-mcp --help`). |
|
||||
| `GITEA_INSECURE` | `true` ignores TLS certificate errors talking to Gitea. |
|
||||
| `GITEA_DEBUG` | `true` enables `gitea-mcp` debug logging. |
|
||||
| `MCPO_PORT` | Port mcpo listens on. 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`. |
|
||||
|
||||
`MCP_MODE` is ignored: `gitea-mcp` would otherwise let it override the transport,
|
||||
so the entrypoint unsets it.
|
||||
|
||||
## 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. `docker stop` shuts both down promptly.
|
||||
|
||||
## Known characteristics
|
||||
|
||||
- **No TLS termination.** This image serves plain HTTP; put a reverse proxy in front
|
||||
of it if you need TLS or network restriction.
|
||||
- **The `:8080` endpoint has no auth of its own.** It relies entirely on Gitea tokens:
|
||||
anyone who can reach it and holds a valid Gitea token can use it as that user,
|
||||
and no more than that user could do directly against the Gitea API.
|
||||
- `gitea-mcp`'s HTTP endpoint also serves `/healthz` (unauthenticated).
|
||||
|
||||
## Pinned revisions
|
||||
|
||||
- `gitea-mcp`: pinned via `ARG GITEA_MCP_VERSION` in the `Dockerfile`. The binary is
|
||||
copied from the official image `docker.gitea.com/gitea-mcp-server` (tags have no
|
||||
`v` prefix). When bumping it, check `gitea-mcp --help` still accepts what
|
||||
`entrypoint.sh` uses.
|
||||
- `mcpo`: pinned via `ARG MCPO_VERSION`, with the `mcp` SDK pinned alongside it via
|
||||
`ARG MCP_SDK_VERSION` (see the comment in the `Dockerfile` for why).
|
||||
|
||||
## CI
|
||||
|
||||
`.gitea/workflows/docker-build.yml` builds and pushes
|
||||
`code.aneur.in/cloud/gitea-mcpo-docker:latest` on every push to `main`, first
|
||||
re-tagging the existing `:latest` as `:previous`. It logs in to the registry as the
|
||||
triggering user, with the repo secret `BUILD_API_TOKEN` as the password. The default
|
||||
Gitea Actions token can't write to the package registry on this instance, so that
|
||||
secret must be a personal access token with package-write scope.
|
||||
Reference in New Issue
Block a user