fayna-rag-mcp
OfficialREADME.md
# fayna-rag-mcp — lokalna baza wiedzy z RAG i MCP



**Opracowane przez [Fayna Digital](https://www.fayna.agency)**
**Autor: Volodymyr Shevchenko**
---
**Problem:** zespół gromadzi dokumentację (polityki, podręczniki, notatki) w
plikach i szybko traci do niej dostęp w języku naturalnym — wyszukiwanie po
nazwie pliku czy Ctrl+F nie skaluje się, a wysyłanie wewnętrznych dokumentów do
chmurowego serwisu LLM nie zawsze jest akceptowalne ze względu na prywatność.
**Rozwiązanie:** lokalny potok RAG (FAISS + wielojęzyczne embeddingi) na bazie
lokalnego LLM (Ollama) — a ten sam wyszukiwacz/Q&A wystawiony jako **serwer MCP**,
aby mógł z niego korzystać dowolny klient MCP (Claude Desktop/Code itp.) lub
zewnętrzny zautomatyzowany workflow przez proste trasy REST. **Żadne dane nie
opuszczają maszyny, na której to działa.**
**Rezultat:** pięć narzędzi MCP i cztery trasy REST do wyszukiwania
semantycznego, RAG Q&A ze źródłami, czytania dokumentów i offline-katalogowania —
gotowe do podłączenia do Claude czy n8n w kilka minut, bez zależności chmurowych.
## Możliwości
| Narzędzie (MCP) | Przeznaczenie |
|---|---|
| `read_document(file_path)` | odczyt pełnego pliku z bazy (z guardem na path-traversal w obrębie `DOCUMENTS_DIR`) |
| `list_documents()` | lista wszystkich dokumentów (`.txt`, `.md`, `.pdf`, `.docx`) |
| `search_documents(query)` | wyszukiwanie semantyczne FAISS → top-relewantne chunki |
| `ask_knowledge_base(question)` | odpowiedź RAG: FAISS-retrieve + Ollama LLM, ze źródłami |
| `show_catalog()` | read-only katalog tagów dokumentów (temat/typ/język/audytorium) |
| Trasa REST | Body | Co robi |
|---|---|---|
| `POST /search` | `{"query": …}` | FAISS-retrieve → `{results:[{source,text}]}` |
| `POST /ask` | `{"question": …}` | odpowiedź RAG → `{answer, sources}` |
| `POST /find` | `{"query": …}` | dokładne wyszukiwanie podciągu po `.md`/`.txt` w korpusie |
| `POST /hybrid` | `{"query": …}` | hybryda: transliteracja cyrylica↔łacinka + token-match, dobór semantyką |
## Stack
Python 3.10+ · FAISS (`faiss-cpu`) · `sentence-transformers` · `tiktoken` ·
Ollama · FastMCP · Tesseract/poppler/Whisper do multi-formatowego ingestu ·
Docker.
## Potok RAG
```
docs/ → load (.txt/.md/.pdf/.docx) → chunk (tiktoken) → embed (mpnet) → FAISS → retrieve → Ollama → answer + sources
```
- **Chunking — po tokenach `tiktoken` (`cl100k_base`), nie po znakach.** Domyślnie
`CHUNK_SIZE=700` tokenów, `CHUNK_OVERLAP=100` tokenów.
- **Embeddingi:** `paraphrase-multilingual-mpnet-base-v2` — model wielojęzyczny
(UA/PL/EN/RU i inne), aby wyszukiwanie działało niezależnie od języka zapytania.
- **LLM:** dowolny model Ollama, domyślnie `qwen2.5:7b`.
- **Indeks:** FAISS `IndexFlatIP` (bliskość kosinusowa), `TOP_K=5`.
## Szybki start
```bash
pip install -r src/requirements.txt
# Przykład: demo-korpus na kilka dokumentów (sample-docs/)
export DOCUMENTS_DIR=./sample-docs
python -m src.main build-index # → src/index/index.faiss + chunks.pkl
# Interaktywne Q&A (CLI)
python -m src.main
# Serwer MCP (transport z env MCP_TRANSPORT: stdio|http)
python -m src.mcp.server
```
Testy:
```bash
pip install -r tests/requirements-dev.txt
pytest -q
```
## Docker
```bash
docker compose up -d
```
Domyślne wartości w `docker-compose.yml`/`Dockerfile` są zaprojektowane pod Ollama
działające na hoście (przez `host.docker.internal`); dopasuj `OLLAMA_URL` do
własnej sieci (adres bridge na Linuxie, osobny kontener Ollama itp.).
## Konfiguracja (`src/config.py`, wszystko przez env)
| Env | Domyślnie | Opis |
|---|---|---|
| `DOCUMENTS_DIR` | `./docs` | korzeń bazy wiedzy |
| `EMBEDDING_MODEL` | `paraphrase-multilingual-mpnet-base-v2` | model embeddingów |
| `OLLAMA_MODEL` | `qwen2.5:7b` | LLM do odpowiedzi RAG |
| `OLLAMA_URL` | `http://localhost:11434/api/generate` | endpoint Ollama |
| `CHUNK_SIZE` / `CHUNK_OVERLAP` | `700` / `100` | tokeny (`tiktoken`), nie znaki |
| `TOP_K` | `5` | ile chunków zwraca retrieve |
| `MCP_TRANSPORT` | `stdio` | `stdio` (dla lokalnego klienta MCP) lub `http` → FastMCP streamable-http |
| `MCP_HOST` / `MCP_PORT` | `0.0.0.0` / `8765` | adres przy `MCP_TRANSPORT=http` |
Podłączenie do dowolnego klienta MCP — przez jego konfigurację serwerów MCP,
komendą `python -m src.mcp.server` (stdio) lub URL kontenera (http).
## Struktura
```
src/
├── config.py # wszystkie env-zmienne + domyślne
├── main.py # CLI: build-index | interaktywne Q&A
├── assistant.py # CompanyKBAssistant (LLM decyduje czy wołać MCP-toolki)
├── catalog.py # offline-klasyfikacja dokumentów przez Ollama → JSON+HTML
├── ingest.py # multi-formatowy ingest: OCR skanów, vision-opis diagramów, Whisper-transkrypcja
├── rag/
│ ├── ingest.py # load_document (.txt/.md/.pdf/.docx)
│ ├── chunk.py # chunk_text (tiktoken cl100k_base, overlap)
│ ├── embed.py # embed_chunks (sentence-transformers)
│ ├── build_index.py # build_index → FAISS + pickle
│ └── query.py # retrieve / build_prompt / ask
└── mcp/
├── server.py # FastMCP: 5 MCP-toolków + 4 trasy REST
└── client.py # MCPClient (JSON-RPC przez subprocess)
```
Multi-formatowy ingest (OCR skanów przez Tesseract, opis rysunków/schematów przez
model vision, transkrypcja audio/wideo przez Whisper) — osobna, cięższa pod
względem zależności ścieżka, niepotrzebna dla podstawowego korpusu tekstowego powyżej.
## Licencja
MIT — patrz [LICENSE](LICENSE). © Fayna Digital.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues