MCP Elasticsearch Demo
# Demo: MCP dla wyszukiwarki dokumentacji (Elasticsearch)
Działający przykład tego, jak może wyglądać serwer MCP opisany w propozycji
technicznej: 5 narzędzi, które dowolny agent LLM (Claude Desktop, Claude Code,
własny chatbot na Claude API) może wywołać, żeby przeszukać bazę instrukcji,
filmów instruktażowych, PDF-ów, DOCX-ów i zdjęć — zamiast dostawać surowy
dostęp do indeksu.
Demo ma dwa tryby, ten sam kod serwera i te same narzędzia w obu:
- **`memory`** (domyślny) — 10 przykładowych dokumentów z `data/documents.json`,
wyszukiwanie hybrydowe (BM25 + wektor) liczone w pamięci procesu Node.
Zero zależności zewnętrznych, działa od razu, po `npm install`.
- **`elasticsearch`** — te same narzędzia, ale dane trzymane w prawdziwym
Elasticsearch (docker-compose). Pokazuje ścieżkę do wdrożenia produkcyjnego.
## Szybki start (tryb memory, bez Dockera)
```bash
npm install
npm run test:e2e # uruchamia realnego klienta MCP i sprawdza wszystkie narzędzia
```
`test/demo-client.js` to nie jest test jednostkowy funkcji — to prawdziwy
klient MCP, który odpala `src/server.js` jako osobny proces i rozmawia z nim
po protokole MCP przez stdio, dokładnie tak jak zrobiłby to Claude Desktop.
## Podłączenie do Claude Desktop
1. Otwórz konfigurację Claude Desktop (Settings → Developer → Edit Config).
2. Dopisz zawartość `claude_desktop_config.example.json`, podmieniając
ścieżkę na pełną ścieżkę do `src/server.js` na Twoim dysku.
3. Zrestartuj Claude Desktop.
4. Zapytaj np. *"jak zresetować ekspres X200?"* albo *"pokaż jak wymienić
filtr wody"* — Claude sam wywoła `search_documents`, ewentualnie
doprecyzuje przez `list_facets`, i odpowie z cytowaniem źródła (dla
filmu — z konkretną minutą).
## Narzędzia (tools)
| Narzędzie | Do czego służy |
|---|---|
| `search_documents` | Wyszukiwanie hybrydowe (BM25 + wektor) z filtrami `doc_type`, `product`, `tags`. Dla wideo zwraca znacznik czasu najlepiej pasującego segmentu. |
| `get_document` | Pełna treść i metadane jednego dokumentu po `id`. |
| `list_facets` | Dostępne wartości pola (`doc_type`, `product`, `tags`) z licznikiem — żeby agent znał opcje filtrowania zamiast zgadywać. |
| `find_similar` | Dokumenty podobne semantycznie do podanego. |
| `get_video_segment` | Transkrypt fragmentu filmu z zadanego zakresu czasu (sekundy). |
Przykładowe zapytania testowe (patrz `test/demo-client.js`) celowo używają
innych słów niż dokumenty źródłowe — np. *"zacięta kartka w drukarce"*
trafia w dokument mówiący o *"zacięciu papieru"*, a *"jak zresetować"*
trafia w dokumenty mówiące o *"przywracaniu ustawień fabrycznych"*. To
demonstruje mechanizm opisany w propozycji: dopasowanie działa mimo że
słowa się nie pokrywają.
## Ważne zastrzeżenie co do jakości wyszukiwania w trybie `memory`
Wektor semantyczny w `src/embeddings.js` to **uproszczony zamiennik demo**
(hashed bag-of-words + ręczna normalizacja kilkunastu synonimów domenowych),
nie prawdziwy model embeddingowy. Wystarcza, żeby pokazać mechanizm hybrydowego
wyszukiwania i to, że synonimy/parafrazy trafiają w wynik — ale nie ma
prawdziwego rozumienia znaczenia zdań. W produkcji `embed()` zamienia się na
wywołanie realnego modelu (self-hosted bge-m3/multilingual-e5, albo API
OpenAI/Voyage/Cohere) — interfejs (`embed(text) -> wektor`) zostaje ten sam,
zmienia się tylko ta jedna funkcja.
## Tryb produkcyjny: prawdziwy Elasticsearch (lokalnie)
```bash
docker compose up -d # startuje ES 8.15 lokalnie
npm run seed:elasticsearch # tworzy indeks i ładuje data/documents.json
STORE_BACKEND=elasticsearch npm start
```
`src/store/elasticsearchStore.js` implementuje dokładnie ten sam kontrakt co
`memoryStore.js` (patrz `src/store/interface.js`) — serwer MCP przełącza się
między nimi zmienną środowiskową `STORE_BACKEND`, bez zmian w warstwie
narzędzi (`src/createMcpServer.js`). To pokazuje, że warstwa MCP jest
niezależna od tego, gdzie faktycznie stoją dane.
Uwaga: zapytanie hybrydowe w `elasticsearchStore.js` używa `rank.rrf`, które
działa na ES 8.9–8.12. Od 8.13 zalecany jest nowszy DSL `retriever` — przed
wdrożeniem u klienta sprawdź wersję klastra (patrz komentarz w tym pliku).
## Wdrożenie na zdalny serwer (Docker, HTTP)
Do lokalnego użycia z Claude Desktop służy `src/server.js` (transport stdio).
Do wdrożenia na serwerze, gdzie ma być dostępny przez sieć, służy
**`src/httpServer.js`** — te same 5 narzędzi, transport HTTP zamiast stdio,
z autoryzacją Bearer-token (`MCP_API_KEY`).
Pełna instrukcja krok po kroku (kopiowanie na serwer, `docker-compose.prod.yml`
z Elasticsearch + seed + appką, reverse proxy z TLS, podłączenie zdalnego
serwera MCP do klienta) jest w **[DEPLOY.md](./DEPLOY.md)**.
## Struktura projektu
```
mcp-elasticsearch-demo/
data/documents.json przykładowe dokumenty (pdf/docx/jpg/mp4)
src/embeddings.js uproszczony "embedding" demo + synonimy
src/store/interface.js kontrakt wspólny dla obu backendów
src/store/memoryStore.js backend w pamięci (domyślny)
src/store/elasticsearchStore.js backend na prawdziwym ES
src/createMcpServer.js rejestracja 5 narzędzi MCP (współdzielona)
src/storeFactory.js wybór backendu wg STORE_BACKEND
src/server.js wejście stdio — lokalnie, Claude Desktop
src/httpServer.js wejście HTTP — zdalny serwer, patrz DEPLOY.md
scripts/seed-elasticsearch.js tworzy indeks + ładuje dane do ES
docker-compose.yml lokalny klaster ES (do dev, bez appki)
docker-compose.prod.yml pełny stack do wdrożenia (ES + seed + app)
Dockerfile obraz dla src/httpServer.js
.env.example szablon zmiennych (MCP_API_KEY, PORT)
DEPLOY.md instrukcja wdrożenia na serwer
test/demo-client.js klient MCP do testu end-to-end
claude_desktop_config.example.json jak podłączyć do Claude Desktop (stdio)
```
## Co dalej (poza zakresem tego demo)
- Prawdziwy model embeddingowy zamiast `src/embeddings.js`.
- Indeksowanie segmentów wideo jako osobnych dokumentów ES (trafność na
poziomie minuty filmu, nie całego pliku) — patrz uwaga w `elasticsearchStore.js`.
- Autoryzacja / filtrowanie wyników wg uprawnień użytkownika po stronie
serwera MCP, nie po stronie agenta.
- Warstwa RAG (synteza odpowiedzi z cytowaniami) jako kolejne narzędzie
albo prompt MCP — patrz faza 3 w dokumencie technicznym.
TDQS
Scored across 5 tools
Each tool has a distinct retrieval purpose: search_documents queries by text, find_similar queries by example id, list_facets enumerates filter values, get_document fetches a full doc by id, and get_video_segment pulls a time-bounded transcript. The main mild overlap is search_documents vs find_similar (both return ranked recommendations), but the input mode (query vs id) and descriptions differentiate them adequately.
All five names follow a consistent snake_case verb_noun pattern (get_video_segment, search_documents, get_document, list_facets, find_similar). Verb choice is predictable and readable across the set.
Five tools is well-scoped for a hybrid search/retrieval server, covering search, drill-down, filtering discovery, similarity, and a media-specific accessor. Every tool earns its place with no redundant filler.
The retrieval lifecycle is well covered: search, fetch full content, discover facets, find similar, and extract a video segment. It lacks any indexing/write operations (create/update/delete or list-all), which may be intentional for a read-only demo but leaves the surface one-sided.