diff --git a/Gitea MCP Connector/README.md b/Gitea MCP Connector/README.md new file mode 100644 index 0000000..31b875b --- /dev/null +++ b/Gitea MCP Connector/README.md @@ -0,0 +1,105 @@ +# Gitea MCP Connector + +Stellt die [Forgejo/Gitea](https://git.vogt.de.com)-API als MCP-Server (Model Context Protocol) per HTTP bereit, damit Claude (claude.ai / Claude Desktop) lesend und schreibend auf Repositories, Issues, Pull Requests, Wiki-Seiten etc. zugreifen kann. + +Basiert auf dem Open-Source-Projekt [ronmi/forgejo-mcp](https://github.com/ronmi/forgejo-mcp). + +## Architektur + +``` +Claude Desktop (mcp-remote Bridge, --header X-Api-Key) + │ HTTPS + ▼ +Pangolin Reverse Proxy (gitea-mcp.vogt.de.com) + - terminiert TLS + - prüft HTTP-Header X-Api-Key + │ HTTP intern + ▼ +gitea-mcp Container (Docker, Port 8080, HTTP-Transport) + │ + ▼ +Gitea/Forgejo-Instanz (git.vogt.de.com) via REST-API + FORGEJOMCP_TOKEN +``` + +## Docker Compose + +```yaml +services: + gitea-mcp: + image: ronmi/forgejo-mcp:latest + container_name: gitea-mcp + restart: always + command: http --address :8080 --server https://git.vogt.de.com + environment: + FORGEJOMCP_TOKEN: '${FORGEJOMCP_TOKEN}' + ports: + - '8081:8080' +``` + +`.env`: +``` +FORGEJOMCP_TOKEN= +``` + +## Reverse Proxy (Pangolin) + +- **Target:** `http://:8081` +- **Domain:** `gitea-mcp.vogt.de.com` +- **Pfad:** `/` (kein Pfad-Rewrite – die volle URL für Claude lautet `https://gitea-mcp.vogt.de.com/mcp`) +- **Absicherung:** HTTP-Header-Regel, identisch zum Muster bei `mealie-mcp`: + - Header-Name: `X-Api-Key` + - Value: zufälliger Secret-String (`openssl rand -hex 32`) + - Ohne korrekten Header blockt Pangolin den Request bereits auf Proxy-Ebene, bevor er den Container erreicht. + +**Wichtig:** `FORGEJOMCP_TOKEN` (App-Token gegenüber der Gitea-API) und `X-Api-Key` (Pangolin-Proxy-Secret) sind zwei unabhängige Werte und dürfen nicht verwechselt werden – genau wie bei `MEALIE_API_TOKEN` vs. `X-Api-Key` beim Mealie-Connector. + +## Verbindung mit Claude + +Wie beim Mealie-Connector läuft die Verbindung über die **lokale `mcp-remote`-Bridge in Claude Desktop**, da claude.ai-Custom-Connectors keine zuverlässige Möglichkeit bieten, eigene HTTP-Header mitzugeben. + +In der Claude-Desktop-Config (`claude_desktop_config.json`, Menü *Konnektoren → Lokale MCP-Server → Config bearbeiten*): + +```json +{ + "mcpServers": { + "gitea": { + "command": "npx", + "args": [ + "mcp-remote", + "https://gitea-mcp.vogt.de.com/mcp", + "--header", + "X-Api-Key:${GITEA_MCP_KEY}" + ], + "env": { + "GITEA_MCP_KEY": "" + } + } + } +} +``` + +Danach Claude Desktop neu starten. Die Gitea-Tools (Repos, Issues, Pull Requests, Wiki, Releases, …) stehen dann in jedem Chat zur Verfügung. + +## Testen + +Handshake ohne Header (sollte von Pangolin blockiert werden, sobald der Header aktiv ist): +```bash +curl -i https://gitea-mcp.vogt.de.com/mcp +``` + +Vollständiger MCP-Handshake mit korrekten Headern: +```bash +curl -X POST https://gitea-mcp.vogt.de.com/mcp \ + -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -H "X-Api-Key: " \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' +``` +Eine erfolgreiche Antwort enthält `serverInfo` mit `"name":"gitea"` (bzw. dem vom Server gemeldeten Namen). + +## Sicherheitshinweise + +- Der `gitea-mcp`-Server selbst hat **keine eigene Authentifizierung** – wer die URL ohne den `X-Api-Key`-Schutz erreicht, hat vollen Zugriff auf alle Repos, die für `FORGEJOMCP_TOKEN` sichtbar sind (inkl. Schreibrechte, je nach Token-Scope). +- Empfohlen: dedizierter Gitea-Service-User mit möglichst eng gefasstem Token-Scope statt eines Admin-Tokens für `FORGEJOMCP_TOKEN`. +- Der Secret-Wert für `X-Api-Key` muss identisch in Pangolin und in der Claude-Desktop-Config gepflegt werden. +- Da im Compose-File aktuell ein Klartext-Token vorkam: Token in Gitea rotieren/neu ausstellen, sobald der bisherige Wert irgendwo (z. B. Chat-Verlauf) sichtbar war, und stattdessen über `.env` einbinden (siehe oben).