Skip to main content
Glama
dev-com2020

MCP Elasticsearch Demo

by dev-com2020
README.md
# 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

A3.7/5.0

Scored across 5 tools

Disambiguation4/5

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.

Naming Consistency5/5

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.

Tool Count5/5

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.

Completeness4/5

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.

Maintenance

ActivityMaintained
ResponsivenessNo issues