feat: MCP-Server für RAG-Retrieval + Webhook-Härtung
app/mcp_server.py: FastMCP (mcp SDK), streamable-http auf /mcp, statischer Bearer-Token (constant-time ASGI-Middleware), Fail-Fast ohne RAG_MCP_TOKEN. Tools rag_search (mit semester/fach/typ-Filter) + get_file_chunks. Läuft aus demselben Image wie der Ingestor und reused den Embed-Pfad → Vektoren sind garantiert kompatibel zum Ingest (der offizielle qdrant-MCP-Server kann nur fastembed → Dimension-/Schema-Mismatch). app/qdrant_store.py: search_chunks (query_points + optionaler Payload-Filter) und get_chunks_by_path (scroll, nach chunk_index sortiert). app/bulk.py: Amplification-Guard — /bulk-import lehnt mit 409 ab solange ein vorheriger Bulk noch BackgroundTasks abarbeitet. docker-compose.coolify.yml: rag-mcp-Service (nicht public, externes metamcp-net statt Stack-Coupling) + Traefik-Rate-Limit-Middleware am ingestor. tests/conftest.py: Settings-env_file in Tests neutralisieren (Dev-.env darf die Suite nicht kontaminieren). 68 passed, ruff clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
40
README.md
40
README.md
@@ -68,6 +68,46 @@ Embedding-Inferenz ist CPU-only und skaliert per Default auf alle verfügbaren C
|
||||
|
||||
Beide Werte sind in `docker-compose.yml` für die lokale Entwicklung gesetzt und sollten in Coolify entsprechend mitgepflegt werden. Folge: konstante ~2 CPU statt Peaks bis 8 CPU, dafür längere Bulk-Laufzeiten.
|
||||
|
||||
## MCP-Server (Retrieval via MetaMCP)
|
||||
|
||||
`app/mcp_server.py` exponiert das indexierte Wissen als MCP-Server (streamable-http, Endpoint `/mcp`). Er läuft aus **demselben Image** wie der Ingestor und nutzt denselben Embed-Pfad (Ollama `qwen3-embedding:0.6b`) — Query-Vektoren sind damit garantiert kompatibel zu den beim Ingest geschriebenen 1024-Dim-Vektoren. Der offizielle `qdrant/mcp-server-qdrant` ist *nicht* nutzbar: er kann nur fastembed (Dimension-/Modell-Mismatch), kennt unser Payload-Schema nicht und bietet kein Metadata-Filtering.
|
||||
|
||||
Tools:
|
||||
|
||||
- `rag_search(query, limit=5, semester?, fach?, typ?)` — semantische Suche mit optionalen Payload-Filtern; liefert `text` + Quell-Metadaten + `score`.
|
||||
- `get_file_chunks(file_path)` — alle Chunks eines Dokuments in `chunk_index`-Reihenfolge.
|
||||
|
||||
### Lokal starten
|
||||
|
||||
```bash
|
||||
RAG_MCP_TOKEN=dev-token uv run python -m app.mcp_server
|
||||
```
|
||||
|
||||
Lauscht auf `0.0.0.0:${RAG_MCP_PORT:-9009}`. Ohne `RAG_MCP_TOKEN` verweigert der Server den Start (Fail-Fast). Jeder Request braucht `Authorization: Bearer <RAG_MCP_TOKEN>`.
|
||||
|
||||
### Coolify + MetaMCP
|
||||
|
||||
Der `rag-mcp`-Service in `docker-compose.coolify.yml` ist bewusst **nicht public** (keine Domain / kein `expose`). Erreichbar ist er nur über das dedizierte externe Netz `metamcp-net` — qdrant/ollama/ingestor bleiben außen vor (kein Shared-Network-Coupling des ganzen Stacks).
|
||||
|
||||
1. Netz einmalig anlegen: `docker network create metamcp-net`
|
||||
2. In der MetaMCP-Compose `networks: [metamcp-net]` ergänzen, damit MetaMCP demselben Netz beitritt.
|
||||
3. `RAG_MCP_TOKEN` als Secret in Coolify setzen (Wert aus `.env.coolify`).
|
||||
4. In MetaMCP einen streamable-http-Downstream registrieren: URL `http://rag-mcp:9009/mcp`, Header `Authorization: Bearer <RAG_MCP_TOKEN>`.
|
||||
|
||||
Externe Kontrolle/Sharing macht MetaMCP als Single-Endpoint; der Token + die Netz-Isolation sind die zwei Kontrollschichten auf dem internen Hop.
|
||||
|
||||
### Webhook-Härtung
|
||||
|
||||
- **Rate-Limit:** `docker-compose.coolify.yml` definiert am `ingestor` eine Traefik-Middleware `rag-ratelimit` (30 req/min, Burst 15). Das *Binding* an den Coolify-generierten Router trägt eine env-spezifische UUID und gehört daher **nicht** ins Repo, sondern als Custom-Label in die Coolify-UI des ingestor-Service:
|
||||
|
||||
```
|
||||
traefik.http.routers.https-0-<COOLIFY-UUID>-ingestor.middlewares=gzip,rag-ratelimit
|
||||
```
|
||||
|
||||
(UUID aus der von Coolify gerenderten Compose entnehmen; bestehende `gzip`-Middleware beibehalten.)
|
||||
- **Bulk-Amplification-Guard:** `/bulk-import` lehnt mit `409` ab, solange ein vorheriger Bulk noch Tasks abarbeitet — verhindert unbegrenztes Anhäufen von BackgroundTasks durch wiederholte Calls oder einen fehlfeuernden Nextcloud-Flow.
|
||||
- CORS ist bewusst nicht konfiguriert: der Webhook wird Server-zu-Server gerufen, CORS ist kein Spam-Schutz und für Browser-Clients hier irrelevant.
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user