diff --git a/README.md b/README.md index fc1379b..5ef16c4 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,69 @@ itself. The container still needs to resolve and reach the Git host (and the MCP host, if used). +### Hosting the gitea MCP server + +`@mcp` expects an MCP endpoint over HTTP that takes the bot token as `Authorization: Bearer` on +every request. The official [gitea-mcp](https://gitea.com/gitea/gitea-mcp) server does this in +HTTP mode: it serves `POST /mcp` and forwards the per-request token to Gitea, so it needs no token +of its own and every call runs with exactly the bot's access. A setup next to a docker-compose +Gitea behind Caddy: + +1. Add the service to Gitea's `docker-compose.yml`. Point `GITEA_HOST` at Gitea's internal + address so MCP traffic doesn't loop back out through Caddy, and leave `GITEA_ACCESS_TOKEN` + unset on purpose: + + ```yaml + services: + gitea-mcp: + image: docker.gitea.com/gitea-mcp-server:latest # better: pin a release tag + restart: unless-stopped + command: ["/app/gitea-mcp", "-t", "http", "-p", "8080"] + environment: + GITEA_HOST: http://gitea:3000 # Gitea's service name and internal port + GITEA_READONLY: "true" # hides every write tool + depends_on: + - gitea + # Only if Caddy runs on the host rather than in Docker: + # ports: + # - "127.0.0.1:8080:8080" + ``` + +2. Add a Caddy site. Without a token, requests would reach Gitea anonymously, so refuse them at + the proxy: + + ```caddy + mcp.example.com { + @noauth not header Authorization * + respond @noauth 401 + + reverse_proxy gitea-mcp:8080 # Caddy in Docker, on gitea-mcp's network + # reverse_proxy 127.0.0.1:8080 # Caddy on the host + } + ``` + + Make sure `mcp.example.com` resolves from the DDEV container, the same way the Git host does. + +3. Give the bot token at least `read:repository` and `read:issue` (PRs count as issues), plus + `read:user` and `read:organization` if those tools should work. + +4. Check from inside the container (`ddev ssh`): + + ```bash + curl -sS -X POST https://mcp.example.com/mcp \ + -H "Authorization: Bearer $(tr -d '[:space:]' < ~/.gitea-ro-token)" \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json, text/event-stream' \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}' + ``` + + A JSON-RPC result with `serverInfo` means it works. Then `/mcp` inside `ddev claude` should list + the server as connected. + +The server is registered once and the token is copied into `~/.claude.json` at that point. After +changing the URL or rotating the token, run `claude mcp remove -s user` and then +`.ddev/claude-code/refs-sync.sh --setup-only` (or just `ddev claude`) to register it again. + ## Installed managed files .ddev/config.codex-claude.yaml