Runbook baserow-schema-mcp : 4 outils de gestion de schema Baserow
Documente le nouveau service compagnon (create_table/delete_table/create_field/ delete_field) et les 4 pieges rencontres (JWT obligatoire sur ces routes, API bas niveau SseServerTransport instable entre versions mcp, DNS-rebinding protection allowed_hosts=[] par defaut, 409 transitoire apres link_row). Resync complet de common/ports-registry.md (plusieurs semaines de derive). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a097f7def4
commit
2d8ed89502
@@ -10,6 +10,7 @@
|
||||
|
||||
| Runbook | Date | Tags |
|
||||
|---------|------|------|
|
||||
| [baserow-schema-mcp — 4 outils de gestion de schema (create/delete table/field), companion service au MCP natif Baserow](common/baserow-schema-mcp.md) | 2026-08-17 | baserow, mcp, schema, jwt |
|
||||
| [Rotation des secrets — inventaire, portée réelle par empreinte, anomalies inter-sphères](common/rotation-secrets-inventaire.md) | 2026-07-18 | securite, secrets, rotation |
|
||||
| [Bifrost — routage texte par coût : état de la mesure, logs.db, clés virtuelles, incident 401](common/bifrost-routage-par-cout.md) | 2026-07-18 | bifrost, llm, cout, routage |
|
||||
| [Skills n8n officiels (n8n-io/skills) — socle 14 skills, tap auto 3 instances](common/n8n-skills-officiels.md) | 2026-07-10 | n8n, skills, hermes, tap |
|
||||
|
||||
@@ -423,3 +423,7 @@ Avant d'ajouter une nouvelle capacite a une instance Hermes (nouvel outil, nouve
|
||||
|
||||
Regle distincte de l'anti-invention benchmarks du 27/07/2026 (qui couvre la veille/recherche). Ici, toute valeur inseree dans un document officiel genere pour la Direction Zone Sud (DCO, courrier, evaluation AO, notification) doit venir textuellement d'une source verifiee pour ce dossier precis, jamais d'une estimation, d'un arrondi de convention, ou d'une valeur dupliquee d'un autre dossier par plausibilite. Champ absent ou ambigu -> le signaler a Nabil et marquer `[A VERIFIER]` dans le document plutot que le remplir a l'estime. Deploye le 17/08/2026 dans dco-generation-tt, gsd-ao-evaluation, courriers-officiels-tt (skills hermes-tt, non versionnes sur Gitea -- data/ gitignore, persistance via volume Docker uniquement). Reste a faire, avec Nabil : peupler `references/exemples-valides/` de chaque skill avec 2-3 documents deja valides, pour un cadrage few-shot. Detail : common/regle-source-generation-documentaire-zone-sud.md.
|
||||
|
||||
|
||||
## FIX -- baserow-schema-mcp : 4 outils manquants (create/delete table/field) ajoutes en service compagnon, pas en extension du MCP natif (2026-08-17)
|
||||
|
||||
Le MCP natif de Baserow (image officielle, non patchable) ne couvre que les lignes (create_rows/delete_rows/list_tables/...), pas le schema (tables/champs). Nouveau service independant `baserow-schema-mcp` (port 3101, meme famille que le sidecar OAuth `baserow-oauth-stub`) proxyant 4 outils vers l'API REST native de Baserow. Trois pieges a retenir pour tout futur service MCP maison : (1) ces 4 routes de schema exigent un JWT utilisateur, le Database API Token ne fonctionne pas dessus (confirme via /api/redoc/) -- re-login proactif + retry sur 401 pour eviter de reproduire l'incident "rotation Baserow" deja vu sur dashboard-terrain et gsparc-mezzouna-api ; (2) l'API bas niveau `mcp.server.sse.SseServerTransport` a change entre la version figee par context-hub et `mcp==1.29.0` (methodes disparues) -- preferer l'API haut niveau `FastMCP` (`@mcp.tool()` + `mcp.sse_app()`), plus stable dans le temps ; (3) `mcp>=1.29` protege par defaut contre le DNS rebinding avec `allowed_hosts=[]`, ce qui rejette TOUT Host header tant qu'on ne le configure pas explicitement. Reste a faire (manuel, hors perimetre agent) : reverse-proxy DSM `baserow-schema.bolbol.tn` -> 3101, puis ajout du connecteur cote Claude.ai. Detail complet : common/baserow-schema-mcp.md.
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
# baserow-schema-mcp — serveur MCP compagnon pour la gestion de schema Baserow (17/08/2026)
|
||||
|
||||
**Statut** : PRODUCTION (4 outils testes end-to-end). Reste une action manuelle :
|
||||
reverse-proxy DSM `baserow-schema.bolbol.tn` → 3101, puis ajout du connecteur
|
||||
cote Claude.ai.
|
||||
|
||||
## Probleme
|
||||
|
||||
Le MCP natif de Baserow (`https://baserow.bolbol.tn/mcp/<token>/sse`, cf.
|
||||
`baserow-mcp-oauth-fix.md`) expose uniquement des outils de LIGNES
|
||||
(`list_databases`, `list_tables`, `get_table_schema`, `create_rows`,
|
||||
`update_rows`, `delete_rows`, `list_table_rows`) — aucun outil de gestion de
|
||||
**schema** (creer/supprimer une table ou un champ). Baserow tourne en image
|
||||
officielle (`baserow/baserow:latest`, watchtower), non patchable directement
|
||||
(meme constat que `baserow-mcp-oauth-fix.md`) : impossible d'ajouter ces
|
||||
outils au MCP natif.
|
||||
|
||||
## Solution
|
||||
|
||||
Nouveau service **independant**, meme famille que `baserow-oauth-stub`
|
||||
(sidecar OAuth rubber-stamp devant un vrai serveur), a
|
||||
`/volume1/docker/baserow-schema-mcp/` (Gitea `bolbol/baserow-schema-mcp`,
|
||||
prive) :
|
||||
|
||||
- **baserow-schema-mcp** (FastAPI-free, `mcp.server.fastmcp.FastMCP`,
|
||||
build local, pas de port publie) : 4 outils, perimetre strict, rien
|
||||
d'autre (pas de `update_field`, pas de gestion de databases/workspaces) :
|
||||
- `create_table(database_id, name, data?, first_row_header?)` → `POST /api/database/tables/database/{database_id}/`
|
||||
- `delete_table(table_id)` → `DELETE /api/database/tables/{table_id}/`
|
||||
- `create_field(table_id, field)` → `POST /api/database/fields/table/{table_id}/` (`field` = payload brut Baserow, passthrough)
|
||||
- `delete_field(field_id)` → `DELETE /api/database/fields/{field_id}/`
|
||||
- **baserow-schema-mcp-oauth-app** + **baserow-schema-mcp-proxy** (nginx,
|
||||
port **3101**→80) : copie conforme du sidecar OAuth de
|
||||
`baserow-oauth-stub`, adaptee au domaine `baserow-schema.bolbol.tn`.
|
||||
Rubber-stamp uniquement — la vraie protection est le token secret dans
|
||||
l'URL `/mcp/<token>/sse` (meme logique que Baserow natif).
|
||||
- Les 3 services sont **uniquement** sur le reseau externe `n8n` (pas de
|
||||
reseau `default` supplementaire) — lecon deja tiree dans
|
||||
`baserow-mcp-oauth-fix.md`, reappliquee directement ici sans la
|
||||
redecouvrir.
|
||||
|
||||
## Piege n°1 — les 4 routes de schema n'acceptent PAS le Database API Token
|
||||
|
||||
Confirme via `/api/redoc/` (schema OpenAPI complet, `security` par route) :
|
||||
`POST /api/database/tables/database/{id}/`, `DELETE /api/database/tables/{id}/`,
|
||||
`DELETE /api/database/fields/{id}/` n'acceptent QUE `JWT`/`UserSource JWT`.
|
||||
Seul `POST /api/database/fields/table/{id}/` (creation de champ) accepte
|
||||
aussi `Database token` — mais pas les 3 autres. Il faut donc un login
|
||||
email+mot de passe (`POST /api/user/token-auth/`, header ensuite
|
||||
`Authorization: JWT <access_token>`), pas le token Baserow simple utilise
|
||||
partout ailleurs sur cette infra (rla-api, gsparc-mezzouna, context-hub...).
|
||||
|
||||
**Lien avec les incidents connus** : `dashboard-terrain` et
|
||||
`gsparc-mezzouna-api` ont deja ete casses par un JWT Baserow cache
|
||||
indefiniment qui expire/rotate silencieusement (mot de passe ou
|
||||
`BASEROW_JWT_SIGNING_KEY` change), et le fix a chaque fois ete de **fuir**
|
||||
le JWT au profit d'un Database token statique. Ici c'est impossible (API
|
||||
Baserow l'exige). Mitigation dans `app/baserow_client.py` :
|
||||
re-login proactif toutes les 12h (moitie de l'expiration 24h du JWT
|
||||
Baserow) + re-login automatique et une seule retentative sur toute reponse
|
||||
`401`, plutot que de laisser echouer silencieusement comme les incidents
|
||||
precedents.
|
||||
|
||||
## Piege n°2 — API bas niveau `mcp.server.sse.SseServerTransport` non stable entre versions
|
||||
|
||||
Premiere version du code copiait le pattern SSE de `bolbol/context-hub`
|
||||
(`transport.read_stream()`, `transport.write_stream()`,
|
||||
`transport.handle_sse(request)`) — ces methodes **n'existent plus** dans
|
||||
`mcp==1.29.0` (installe ici ; context-hub avait fige une version bien plus
|
||||
ancienne du meme `mcp>=1.2.0,<2.0.0`). Erreur en prod :
|
||||
`AttributeError: 'SseServerTransport' object has no attribute 'handle_sse'`.
|
||||
|
||||
L'API bas niveau actuelle expose `connect_sse(scope, receive, send)` (context
|
||||
manager async qui ecrit directement la reponse ASGI, incompatible avec un
|
||||
simple `return EventSourceResponse(...)` FastAPI) — fragile a cabler a la
|
||||
main. **Fix retenu : passer par l'API haut niveau `mcp.server.fastmcp.FastMCP`**
|
||||
(`@mcp.tool()` + `mcp.sse_app()`), maintenue par le SDK et deja correcte
|
||||
pour la version installee, plutot que de re-cabler le bas niveau a chaque
|
||||
bump de version. Le token secret est baked directement dans le chemin du
|
||||
`Mount` (`Mount(f"/mcp/{TOKEN}", app=mcp.sse_app())`), pas de validation
|
||||
manuelle necessaire.
|
||||
|
||||
**A retenir pour tout futur service MCP maison sur cette infra** : preferer
|
||||
`FastMCP` a la construction manuelle de `Server` + `SseServerTransport` —
|
||||
plus court ET plus stable dans le temps.
|
||||
|
||||
## Piege n°3 — DNS-rebinding protection de `mcp` >=1.29 rejette tout par defaut
|
||||
|
||||
`TransportSecuritySettings.allowed_hosts` vaut `[]` par defaut avec
|
||||
`enable_dns_rebinding_protection=True` → **tout** Host header est rejete
|
||||
(`421 Invalid Host header`), y compris en interne. Fix : passer explicitement
|
||||
`transport_security=TransportSecuritySettings(allowed_hosts=[...], allowed_origins=[...])`
|
||||
a `FastMCP(...)` avec la liste exacte des Host attendus (domaine public +
|
||||
nom de service Docker:port pour les tests internes directs).
|
||||
|
||||
## Piege n°4 — 409 Conflict transitoire apres un champ `link_row`
|
||||
|
||||
Creer un champ juste apres un `link_row` (qui declenche la creation du
|
||||
champ inverse dans la table liee, cf verrou table cote Baserow) peut
|
||||
renvoyer un `409 Conflict` sur le `create_field` suivant immediat. Mitigation
|
||||
appliquee lors de la creation des tables CI-CPT 2026 : retry avec backoff
|
||||
(jusqu'a 6 tentatives, 2s/4s/6s...) + 1-1.5s de pause entre chaque
|
||||
`create_field`. Un seul 409 rencontre en pratique (table "Bons de Commande
|
||||
CI-CPT 2026", champ `lots_couverts` juste apres `fournisseur`), resolu au
|
||||
1er retry.
|
||||
|
||||
## Reste a faire (action manuelle Nabil)
|
||||
|
||||
1. Reverse-proxy DSM : `baserow-schema.bolbol.tn` → `3101` (meme manip que
|
||||
`cin.bolbol.tn`/`formation.bolbol.tn`, pas d'automatisation connue depuis
|
||||
le NAS/Mac pour cette etape).
|
||||
2. Ajouter le connecteur MCP personnalise cote Claude.ai avec l'URL
|
||||
`https://baserow-schema.bolbol.tn/mcp/<TOKEN>/sse` (TOKEN = `MCP_SECRET_TOKEN`
|
||||
dans `/volume1/docker/baserow-schema-mcp/.env`, non commite sur Gitea).
|
||||
|
||||
## Fichiers
|
||||
|
||||
`/volume1/docker/baserow-schema-mcp/` : `app/main.py` (FastMCP + 4 tools),
|
||||
`app/baserow_client.py` (JWT client), `oauth_app.py`/`nginx.conf`/
|
||||
`docker-compose.yml` (sidecar OAuth, copie du pattern `baserow-oauth-stub`
|
||||
adaptee). Depot Gitea `bolbol/baserow-schema-mcp` (prive, `.env` gitignore).
|
||||
Entree `ports-registry.md` (port 3101) et miroir `common/ports-registry.md`
|
||||
a mettre a jour dans la meme session.
|
||||
|
||||
## Usage — CI-CPT 2026 (premier cas d'usage reel)
|
||||
|
||||
3 tables creees dans la base "Zone Sud Achats" (id 229) via ces 4 outils :
|
||||
`Bons de Commande CI-CPT 2026` (id 1090), `Articles CI-CPT 2026` (id 1091),
|
||||
`Répartition régionale CI-CPT 2026` (id 1092). Astuce utilisee pour nommer
|
||||
le champ primaire sans PATCH separe : `create_table(..., data=[["nom_du_champ"]],
|
||||
first_row_header=true)` — cree une table avec un seul champ primaire texte
|
||||
nomme comme voulu, sans les champs d'exemple par defaut (Name/Notes/Active).
|
||||
15 tables obsoletes supprimees dans la foulee (AO 19/2026, Reglement
|
||||
definitif, AO 27/2026 — sous-ensemble confirme) avec `delete_table`.
|
||||
@@ -25,6 +25,9 @@ Colonne Exposition = binding reel constate. Restart/Health signales quand a risq
|
||||
| Baserow-REDIS | interne | - | baserow_baserow_network | interne Docker | unless-stopped | healthy | Redis Baserow |
|
||||
| baserow-oauth-proxy | 3887->80 | baserow.bolbol.tn (a repointer : reverse-proxy DSM 3888->3887, manuel) | n8n | 0.0.0.0 | unless-stopped | - | nginx : intercepte /.well-known/oauth-*, /register, /authorize, /token pour connecteur Claude.ai (404 natif sur Baserow) ; proxy transparent WS+SSE vers Baserow sinon ; ajoute 28/07/2026 |
|
||||
| baserow-oauth-app | interne (8080) | - | n8n | interne Docker | unless-stopped | - | Stub OAuth Starlette (register/authorize/token, PKCE) pour baserow-oauth-proxy ; ajoute 28/07/2026 |
|
||||
| baserow-schema-mcp | interne (8000) | - | n8n | interne Docker | unless-stopped | - | Serveur MCP compagnon : create_table/delete_table/create_field/delete_field (proxy JWT vers API REST Baserow, Database token insuffisant sur ces routes) ; ajoute 17/08/2026 |
|
||||
| baserow-schema-mcp-oauth-app | interne (8080) | - | n8n | interne Docker | unless-stopped | - | Stub OAuth (meme pattern que baserow-oauth-app) pour baserow-schema-mcp-proxy ; ajoute 17/08/2026 |
|
||||
| baserow-schema-mcp-proxy | 3101->80 | baserow-schema.bolbol.tn (reverse-proxy DSM a creer manuellement) | n8n | 0.0.0.0 | unless-stopped | - | nginx : OAuth discovery + proxy SSE vers baserow-schema-mcp ; ajoute 17/08/2026 |
|
||||
| gitea | 2222->22, 3232->3232 | gitea.bolbol.tn | n8n | 0.0.0.0 | unless-stopped | healthy | Versioning (bolbol) ; SSH 2222 + 3232 |
|
||||
| portainer | 9000->9000, 9443->9443 | portainer.bolbol.tn | bridge | 0.0.0.0 | unless-stopped | **none** | Gestion containers ; reseau bridge |
|
||||
| nyora-notes | 8787->8787 | - | n8n | 0.0.0.0 | unless-stopped | healthy | Memoire inter-sessions Hermes (8787) |
|
||||
@@ -33,12 +36,13 @@ Colonne Exposition = binding reel constate. Restart/Health signales quand a risq
|
||||
| bifrost-proxy | 3086->80 | - | n8n | 0.0.0.0 | unless-stopped | **none** | Auth rewrite + cap 16384 tokens ; PAS de healthcheck |
|
||||
| hermes-workspace-tt | 3010->3000 | hermes-tt.bolbol.tn | hermes-tt-net,n8n | 0.0.0.0 | unless-stopped | healthy | Workspace TT (agent=8650 interne) |
|
||||
| hermes-workspace-nyora | 3020->3000 | hermes-nyora.bolbol.tn | hermes-nyora-net,n8n | 0.0.0.0 | unless-stopped | healthy | Workspace Nyora (agent=8660 interne) |
|
||||
| hermes-workspace-perso | 3030->3000 | hermes-perso.bolbol.tn | hermes-perso-net,n8n | 0.0.0.0 | unless-stopped | healthy | Workspace Perso (agent=8670 interne) |
|
||||
| hermes-workspace-perso | 3031->3000 | hermes-perso.bolbol.tn | hermes-perso-net,n8n | 0.0.0.0 | unless-stopped | healthy | Workspace Perso -- TEMP remap 3030->3031 (12/08/2026) : port 3030 orphelin cote host, docker-proxy sans PID visible, non liberable sans sudo -- restaurer 3030 au prochain reboot NAS |
|
||||
| hermes-agent-tt | interne | interne | hermes-tt-net,n8n | interne Docker | unless-stopped | healthy | Agent TT ; DeepSeek V4 Flash |
|
||||
| hermes-agent-nyora | interne | interne | hermes-nyora-net,n8n | interne Docker | unless-stopped | healthy | Agent Nyora ; DeepSeek V4 Flash |
|
||||
| hermes-agent-perso | interne | interne | hermes-perso-net,n8n | interne Docker | unless-stopped | healthy | Agent Perso ; DeepSeek V4 Flash |
|
||||
| hermes-mail-proxy | 3060->8000 | - | n8n | 0.0.0.0 | unless-stopped | **none** | Proxy mail Hermes (3060) |
|
||||
| nyora-doc-api | 3050->8000 | - | n8n | 0.0.0.0 | unless-stopped | healthy | Moteur doc unique (docx/xlsx/pptx/pdf) charte Nyora |
|
||||
| nyora-doc-api | 3050->8000 | - | n8n | 0.0.0.0 | unless-stopped | healthy | Moteur doc unique (docx/xlsx/pptx/pdf) charte Nyora | MCP ajoute sur /mcp/ (04/08/2026, tool generate_document) |
|
||||
| nyora-convert-api | 3096->8000 | - | n8n | 0.0.0.0 | unless-stopped | (pas de healthcheck) | Conversion universelle document->MD (Mimo V2.5/Bifrost, texte+vision) 04/08/2026 |
|
||||
| redaction-pro | 3092->80 | llm.bolbol.tn | n8n | 0.0.0.0 | always | **none** | Assistant redaction LLM (proxy Bifrost) |
|
||||
| context-hub | 3093->8000 | context.bolbol.tn | n8n | 0.0.0.0 | unless-stopped | healthy | Contexte agents 5 scopes ; MCP SSE |
|
||||
| family-help | 3041->8000 | help.bolbol.tn | n8n | 0.0.0.0 | unless-stopped | **none** | IA familiale FastAPI+SQLite ; PAS de healthcheck |
|
||||
@@ -120,10 +124,11 @@ Colonne Exposition = binding reel constate. Restart/Health signales quand a risq
|
||||
| 8015 | LIBRE (pptx-tt-api) |
|
||||
| 9998 | LIBRE (tika) |
|
||||
| 8021 | LIBRE (paperless) |
|
||||
| 3100-3199 | APIs internes |
|
||||
| 3100-3199 | APIs internes (3101 pris : baserow-schema-mcp-proxy) |
|
||||
| 8800-8899 | UIs web (8801 pris : formation-consultant) |
|
||||
| 8900-8999 | Experimental |
|
||||
| 3887 | pris : baserow-oauth-proxy |
|
||||
| 3120 | reserve : nyora-notes-tt (RAG personnel mail O365, port API interne) |
|
||||
|
||||
---
|
||||
|
||||
@@ -140,6 +145,12 @@ Colonne Exposition = binding reel constate. Restart/Health signales quand a risq
|
||||
## Historique des modifications (recentes)
|
||||
| Date | Action | Port | Service |
|
||||
|------|--------|------|---------|
|
||||
| 2026-08-17 | Ajout baserow-schema-mcp (serveur MCP compagnon : create_table/delete_table/create_field/delete_field, proxy JWT vers API REST Baserow -- l'image officielle Baserow n'est pas patchable et son MCP natif ne couvre que les lignes). Reste a faire : reverse-proxy DSM baserow-schema.bolbol.tn -> 3101 (manuel) puis ajout du connecteur cote Claude.ai | 3101 | baserow-schema-mcp, baserow-schema-mcp-oauth-app, baserow-schema-mcp-proxy |
|
||||
| 2026-08-15 | Retrait dsh-test + dsh-filebrowser (test isole NAS termine, migration definitive vers dsh-vps sur VPS hermes-nabil, valide en itinerance) | 8900,8901 | dsh-test, dsh-filebrowser |
|
||||
| 2026-08-14 | Ajout dsh-filebrowser (Filebrowser sur port 8901 pour upload/gestion fichiers workspace dsh-test) | 8901 | dsh-filebrowser |
|
||||
| 2026-08-14 | Ajout dsh-test (DeepSeek Harness test isolé, connecté à DeepSeek V4 Flash via Bifrost) | 8900 | dsh-test |
|
||||
| 2026-08-04 | Ajout SearXNG (recherche meta self-hosted, API JSON active, alternative Firecrawl quota epuise) | 8888 | searxng |
|
||||
| 2026-07-30 | Reservation port + dossier scope cree pour Gemini (acces restreint, pas de scope tt context-hub, pas de credentials O365) | 3120 | nyora-notes-tt |
|
||||
| 2026-07-28 | Fix connecteur Claude.ai sur Baserow MCP : sidecar OAuth stub devant Baserow (meme pattern que mcp-vps). Reste a faire : repointer reverse-proxy DSM baserow.bolbol.tn de 3888 vers 3887 (manuel) | 3887 | baserow-oauth-proxy, baserow-oauth-app |
|
||||
| 2026-07-25 | Ajout formation-consultant (nginx+basic auth, formation.bolbol.tn a creer manuellement en reverse-proxy DSM) | 8801 | formation-consultant |
|
||||
| 2026-07-12 | RESYNC v4 : -prowlarr/radarr/sonarr/wg-easy, +vaultwarden/wg-admin-client, flaresolverr marque orphelin | — | ports-registry.md v4 |
|
||||
|
||||
Reference in New Issue
Block a user