Files
mcp-wms-wiki/MONITORING.md
T

108 lines
7.9 KiB
Markdown

# Suivi projet - Migration MCP WMS Wiki (stdio -> HTTP)
Derniere mise a jour : 2026-05-31
## 1. Objectif
Rendre le serveur MCP `mcp-wms-wiki` "HTTP-compliant" (transport Streamable HTTP) pour
l'heberger en remote sur le serveur TrueNAS Scale "Fangtooth" en app Docker custom,
expose derriere Nginx Proxy Manager + Cloudflare sur le domaine cible
`mcp-wms.arthur-ria.fr`, consommable comme connecteur MCP distant depuis Claude Desktop.
## 2. Etat actuel (source : analyse Claude Code)
- Serveur MCP Node.js ESM (`"type": "module"`), repo : `git.arthur-ria.fr/Mecalux/mcp-wms-wiki`.
- Local sur disque : `/home/arthur/dev/mcp-wms-wiki`.
- Transport actuel : **stdio** (`StdioServerTransport`, `src/index.js:168-171`). Lance par `node src/index.js`, forke par le client.
- SDK : `@modelcontextprotocol/sdk` declare en `"latest"` mais lockfile fige **1.29.0** (= la version qui compte).
- Serveur instancie en haut niveau : `new McpServer({ name: 'wms-wiki', version: '1.0.0' })` (`src/index.js:63-66`).
- 5 tools (`src/index.js:70-126`) : `search_wiki`, `get_wiki_page`, `list_wiki_sections`, `get_glossary_term`, `get_related_pages`.
- 2 resources : `wiki://overview`, `wiki://entities-map` (`src/index.js:130-164`).
- Handlers stateless : recoivent un `ctx` immuable construit au demarrage (`{ pages, invertedIndex, glossary, wikiPath }`).
- Chargement wiki 100% local au demarrage (`buildWikiIndex`, `buildGlossary`, `loadSpecialPage`), tout en memoire. ~40 MB de markdown.
- Une seule var d'env reellement consommee : `WIKI_PATH`. `.env` lu par un parseur maison (dotenv declare mais non utilise).
- Pas de `.gitignore` -> `.env` est commite (aujourd'hui sans secret : `WIKI_PATH=./wiki`, `DEBUG=false`).
- Contenu potentiellement confidentiel (sous-dossier `limagrain/`, refs Mecalux).
## 3. Architecture cible
- Transport : **Streamable HTTP**, mode **stateless**, **`enableJsonResponse: true`** (reponse JSON one-shot, pas de SSE).
- App **Express** : `POST /mcp` (+ `GET /mcp` route sur le meme handler), `GET /healthz`.
- Un **McpServer neuf par requete** via factory `createServer(ctx)`, le `ctx` etant construit une seule fois et partage (immuable).
- Containerise (node:20-alpine), wiki embarque dans l'image, deploye en app Docker custom TrueNAS.
- Reverse proxy NPM + Cloudflare. Build de l'image directement sur le NAS (pas de registry externe).
## 4. Decisions actees
- Stateless : **oui**, avec factory `createServer(ctx)` par requete (correction du partage de McpServer).
- `enableJsonResponse` : **oui (true)** -> simplifie tout le reverse proxy, supprime le risque SSE/timeout Cloudflare.
- Wiki : **embarque dans l'image** en V1 (immuable, rollback par tag). Volume monte seulement si la cadence de maj devient genante.
- Registry : **build sur le TrueNAS** depuis le repo (coherent avec le workflow Whisper).
- Transport legacy `/sse` : **non** (YAGNI).
- `wiki_old_13-05-2026/` : **a supprimer** (40 MB inutiles).
- SDK : **epingler en 1.29.0**, retirer `"latest"`.
- Deps : ajouter `express ^4` + `zod ^3` (alignee SDK), retirer `dotenv`.
### Roadmap auth (point cle)
- **V1 : authless.** Ajout direct dans Claude Desktop via Settings > Connectors (connecteur distant authless, aucun MCP local / pas de `mcp-remote`).
- **V2 : OAuth cote serveur.** Le connecteur natif Claude Desktop gere OAuth + Dynamic Client Registration. Objectif explicite : eviter de faire tourner un MCP local juste pour l'auth. Le SDK fournit l'infra (`server/auth/`).
- Le code V1 doit etre structure pour accueillir l'OAuth en V2 : middleware `authMiddleware` (pass-through en V1) monte uniquement devant `/mcp`, avec commentaire indiquant ou brancher l'OAuth.
## 5. Amendements techniques au plan initial
- **Bug evite** : ne PAS partager un seul McpServer entre transports per-requete en stateless (cross-talk de reponses possible en concurrence). -> factory `createServer(ctx)` par requete, ctx partage immuable.
- **`enableJsonResponse: true`** rend inutile toute la conf NPM "Advanced" SSE (proxy_buffering off, read_timeout 3600, websockets) et supprime le risque Cloudflare "coupe SSE a 100s".
- **DNS rebinding** : `enableDnsRebindingProtection: true` + `allowedHosts` pilote par `MCP_ALLOWED_HOSTS`. Verifier que NPM forwarde bien `Host: $host` (l'app doit voir `mcp-wms.arthur-ria.fr`, pas `127.0.0.1:3000`). Tester au curl post-deploy.
- **Config par env** : `PORT` (3000), `HOST` (0.0.0.0), `WIKI_PATH` (/app/wiki), `MCP_ALLOWED_HOSTS`.
## 6. Securite
- **BLOQUANT (et critique car V1 authless) : audit traversee de repertoire** sur `get_wiki_page` (`src/services/page.js`). Resoudre par rapport a `wikiPath`, rejeter `..` et chemins absolus, confiner au sous-arbre. Corriger avant toute exposition.
- **Authless + contenu confidentiel** : tant qu'OAuth (V2) n'est pas la, ne pas exposer publiquement (LAN / Tailscale / VPN) OU mettre une Access List NPM / Cloudflare Access devant. Si passage en Cloudflare orange-cloud, desactiver le cache sur `/mcp`.
- **Secrets** : `.gitignore` + `git rm --cached .env` + `.env.example`. En prod TrueNAS, variables injectees par la definition de l'app, pas de `.env`. Rien a purger dans l'historique aujourd'hui (pas de secret commite), mais discipline a partir de maintenant.
## 7. Deploiement (cible)
- **TrueNAS app Docker custom** : service unique `mcp-wms-wiki`, image buildee sur le NAS, `restart: unless-stopped`, env (`PORT`, `HOST`, `WIKI_PATH`, `MCP_ALLOWED_HOSTS`), pas de volume (wiki embarque).
- **NPM** : proxy host `mcp-wms.arthur-ria.fr` -> `http://<ip_app>:3000`, HTTPS force + redirect. Avec JSON (pas de SSE), pas de directives avancees particulieres a prevoir.
- **Cloudflare** : DNS-only (gris) pour valider, puis orange possible (le risque SSE ayant disparu avec JSON). Si orange + contenu confidentiel : pas de cache sur `/mcp`.
## 8. Checklist d'implementation (V1)
- [ ] Factory `createServer(ctx)` + ctx construit une seule fois.
- [ ] App Express : `POST /mcp`, `GET /mcp` (meme handler), `GET /healthz`.
- [ ] Transport per-requete : stateless + `enableJsonResponse: true` + `enableDnsRebindingProtection: true` + `allowedHosts`.
- [ ] Middleware `authMiddleware` pass-through devant `/mcp` (placeholder V2 OAuth).
- [ ] package.json : SDK epingle 1.29.0, +express, +zod, -dotenv, engines node >=20.
- [ ] Audit + correction traversee de repertoire sur `page.js`.
- [ ] Dockerfile (node:20-alpine, npm ci --omit=dev, wiki embarque, USER node, HEALTHCHECK).
- [ ] `.dockerignore`, `.gitignore`, `.env.example`, `git rm --cached .env`.
- [ ] Suppression `wiki_old_13-05-2026/`.
- [ ] Snippet docker-compose TrueNAS + script smoke-test curl.
- [ ] Deploy TrueNAS + conf NPM + DNS Cloudflare.
- [ ] Test : ajout connecteur authless dans Claude Desktop (Settings > Connectors).
## 9. Questions ouvertes / a trancher plus tard
- V2 : choix precis du fournisseur OAuth cote serveur (l'utilisateur a l'habitude de FortiToken).
- Le MCP "API" separe doit-il aussi migrer en HTTP et partager le domaine (`/api` + `/wiki`) ou un sous-domaine distinct ? A cadrer car influence la conf NPM.
- Multi-utilisateurs prevus ? (sinon pas de rate-limiting / logs d'acces necessaires).
## 10. References
- Repo : `https://git.arthur-ria.fr/Mecalux/mcp-wms-wiki`
- Local : `/home/arthur/dev/mcp-wms-wiki`
- Domaine cible : `mcp-wms.arthur-ria.fr` (endpoint `/mcp`)
- SDK : `@modelcontextprotocol/sdk` 1.29.0 ; transport cible `StreamableHTTPServerTransport` (`dist/esm/server/streamableHttp.js`)
- Fichiers cles : `src/index.js` (transport l.168-171, McpServer l.63, tools l.70-126, resources l.130-164), `src/services/page.js` (a auditer), `src/config`, `src/resources`, `src/services`, `src/tools`
- Infra : TrueNAS Scale 25 Fangtooth, Nginx Proxy Manager, Cloudflare
## 11. Etat d'avancement
- [x] Analyse du repo et du transport actuel (Claude Code)
- [x] Plan de migration + critique + amendements
- [x] Decisions actees (stateless, JSON, wiki embarque, authless V1 / OAuth V2)
- [ ] Implementation V1 (en cours / a lancer cote Claude Code)
- [ ] Deploiement TrueNAS
- [ ] V2 OAuth