@clicbase/mcp 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -80,6 +80,33 @@ projet** (`/dashboard?studio=<id>&section=api`) : « API REST » donne l'URL,
80
80
  « Cle service » la cle. Ce n'est PAS une cle `cbk_` : celles-la servent au
81
81
  serveur tout-en-un et n'apparaissent que pour qui possede un VPS ou un Docker.
82
82
 
83
+ ## Ce que l'assistant sait avant son premier appel
84
+
85
+ Un serveur MCP qui n'expose que des outils laisse le modele deviner les
86
+ conventions de la plateforme. Depuis la 0.1.2, celui-ci remplit le champ
87
+ `instructions` du protocole : le client le pose dans le contexte du modele A LA
88
+ CONNEXION, avant tout appel. On ne peut donc pas oublier de le lire.
89
+
90
+ Six pieges y sont enumeres, et ils ont en commun de repondre **200 OK** :
91
+
92
+ 1. `with check` n'est pas `using`. Sur INSERT et UPDATE, PostgreSQL n'evalue
93
+ QUE `with check`.
94
+ 2. Chaque nouvelle table accorde le CRUD a `authenticated` : un `grant select`
95
+ ne restreint rien, il faut `revoke`.
96
+ 3. Toute DDL se termine par `notify pgrst, 'reload schema';`.
97
+ 4. `service_role` a besoin de `BYPASSRLS`, sinon 200 OK et un tableau vide.
98
+ 5. Une fonction Edge rend `{ status, body }`, jamais `new Response(...)`.
99
+ 6. L'auth du PROJET (schema `auth` de la base, `auth.uid()`) ne se confond pas
100
+ avec celle de la PLATEFORME.
101
+
102
+ L'outil `clicbase_conventions` en rend le detail a la demande : formes exactes
103
+ des policies, ordre `revoke` puis `grant`, operateurs PostgREST. Il est classe
104
+ en LECTURE, donc il survit au mode restreint · c'est ce mode qui en a le plus
105
+ besoin, puisqu'il ne laisse que trois autres outils.
106
+
107
+ Le texte vit dans `savoir.mjs`, sans aucun import, pour rester chargeable par
108
+ les tests du depot.
109
+
83
110
  ## Quand l'utiliser
84
111
 
85
112
  `run_sql` exécute du SQL arbitraire avec les droits du propriétaire de la base,
@@ -12,6 +12,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
13
  import { z } from "zod";
14
14
  import { filtre, lectureSeule } from "./portee.mjs";
15
+ import { CONVENTIONS, INSTRUCTIONS } from "./savoir.mjs";
15
16
 
16
17
  const BASE = process.env.CLICBASE_DB_URL ?? process.env.ROROUIRA_DB_URL;
17
18
  const KEY = process.env.CLICBASE_SERVICE_KEY ?? process.env.ROROUIRA_SERVICE_KEY;
@@ -51,7 +52,15 @@ async function api(path, method = "GET", body) {
51
52
  const out = (d) => ({ content: [{ type: "text", text: JSON.stringify(d, null, 2) }] });
52
53
  const fail = (e) => ({ isError: true, content: [{ type: "text", text: String(e?.message ?? e) }] });
53
54
 
54
- const server = new McpServer({ name: "clicbase-db", version: "1.0.0" });
55
+ // ⚠️ `instructions` EST LU A LA CONNEXION, AVANT LE PREMIER APPEL. C'est le
56
+ // seul endroit qu'un assistant ne peut pas oublier de consulter : le client
57
+ // MCP le pose dans le contexte du modele. Sans lui, un assistant recevait
58
+ // TROIS outils et trente mots de description, puis devinait les conventions
59
+ // de la plateforme. Voir savoir.mjs.
60
+ const server = new McpServer(
61
+ { name: "clicbase-db", version: "1.0.0" },
62
+ { instructions: INSTRUCTIONS },
63
+ );
55
64
 
56
65
  // ⚠️ EN LECTURE SEULE, LES OUTILS QUI ECRIVENT NE SONT PAS ENREGISTRES. Voir
57
66
  // portee.mjs : un outil absent n'est jamais demande, un outil present qui
@@ -59,6 +68,13 @@ const server = new McpServer({ name: "clicbase-db", version: "1.0.0" });
59
68
  const SEULEMENT_LECTURE = lectureSeule();
60
69
  const outils = filtre(server, SEULEMENT_LECTURE);
61
70
 
71
+ outils.tool(
72
+ "clicbase_conventions",
73
+ "Les conventions Clicbase : formes exactes des policies RLS, droits par defaut, rechargement du schema, appels REST, fonctions Edge. A lire AVANT d'ecrire du SQL ou une policy.",
74
+ {},
75
+ async () => ({ content: [{ type: "text", text: CONVENTIONS }] }),
76
+ );
77
+
62
78
  outils.tool(
63
79
  "run_sql",
64
80
  "Exécute du SQL (DDL/DML/RLS) sur la base. Après un changement de schéma, termine par: NOTIFY pgrst, 'reload schema';",
package/clicbase-mcp.mjs CHANGED
@@ -21,6 +21,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
21
21
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22
22
  import { z } from "zod";
23
23
  import { filtre, lectureSeule } from "./portee.mjs";
24
+ import { CONVENTIONS, INSTRUCTIONS } from "./savoir.mjs";
24
25
 
25
26
  const API = process.env.CLICBASE_API ?? process.env.ROROUIRA_API ?? "https://clicbase.com/api/v1/databases";
26
27
 
@@ -77,7 +78,15 @@ async function call(name, path, method = "POST", body) {
77
78
  return data;
78
79
  }
79
80
 
80
- const server = new McpServer({ name: "clicbase", version: "2.0.0" });
81
+ // ⚠️ `instructions` EST LU A LA CONNEXION, AVANT LE PREMIER APPEL. C'est le
82
+ // seul endroit qu'un assistant ne peut pas oublier de consulter : le client
83
+ // MCP le pose dans le contexte du modele. Sans lui, un assistant recevait
84
+ // TROIS outils et trente mots de description, puis devinait les conventions
85
+ // de la plateforme. Voir savoir.mjs.
86
+ const server = new McpServer(
87
+ { name: "clicbase", version: "2.0.0" },
88
+ { instructions: INSTRUCTIONS },
89
+ );
81
90
 
82
91
  // ⚠️ EN LECTURE SEULE, LES OUTILS QUI ECRIVENT NE SONT PAS ENREGISTRES. Voir
83
92
  // portee.mjs : un outil absent n'est jamais demande, un outil present qui
@@ -85,6 +94,13 @@ const server = new McpServer({ name: "clicbase", version: "2.0.0" });
85
94
  const SEULEMENT_LECTURE = lectureSeule();
86
95
  const outils = filtre(server, SEULEMENT_LECTURE);
87
96
 
97
+ outils.tool(
98
+ "clicbase_conventions",
99
+ "Les conventions Clicbase : formes exactes des policies RLS, droits par defaut, rechargement du schema, appels REST, fonctions Edge. A lire AVANT d'ecrire du SQL ou une policy.",
100
+ {},
101
+ async () => ({ content: [{ type: "text", text: CONVENTIONS }] }),
102
+ );
103
+
88
104
  outils.tool(
89
105
  "create_database",
90
106
  "Crée (ou récupère, idempotent) une base Clicbase pour un site. Renvoie la chaîne de connexion PostgreSQL, l'URL de l'API REST et les clés anon/service.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clicbase/mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mcp": "clicbase-mcp.mjs",
@@ -35,6 +35,7 @@
35
35
  "clicbase-mcp.mjs",
36
36
  "clicbase-db-mcp.mjs",
37
37
  "portee.mjs",
38
+ "savoir.mjs",
38
39
  "README.md",
39
40
  "LICENSE"
40
41
  ],
package/portee.mjs CHANGED
@@ -43,6 +43,11 @@ export const OUTILS = {
43
43
  enable_rls: "ecriture",
44
44
  create_policy: "ecriture",
45
45
  get_oauth_provider: "lecture",
46
+ // ⚠️ RENDU EN LECTURE SEULE, ET C'EST TOUT L'INTERET. Cet outil ne rend que
47
+ // du texte : les conventions de la plateforme. C'est precisement le mode
48
+ // restreint qui en a le plus besoin, puisqu'il ne reste alors que trois
49
+ // outils et qu'un assistant doit deviner le reste.
50
+ clicbase_conventions: "lecture",
46
51
  };
47
52
 
48
53
  /** Ce qu'on garde en lecture seule. */
package/savoir.mjs ADDED
@@ -0,0 +1,120 @@
1
+ // CE QUE L'ASSISTANT DOIT SAVOIR, ET QU'AUCUN OUTIL NE LUI APPREND.
2
+ //
3
+ // Question de Teiki le 2026-09-17 : « quand un client ajoute la commande MCP à
4
+ // son IA, elle va tout comprendre ? » Mesuré dans le paquet publié, la réponse
5
+ // était non. En lecture seule, un assistant reçoit TROIS outils et une
6
+ // trentaine de mots de description. Il sait explorer un schéma. Il ignore tout
7
+ // ce qui fait perdre des journées ici.
8
+ //
9
+ // ⚠️ LES SIX PIÈGES CI-DESSOUS SONT RÉELS, PAS THÉORIQUES. Ils viennent de la
10
+ // section « Pièges qui ont déjà coûté cher » du dépôt, écrite après coup à
11
+ // chaque fois. Ils ont en commun de ne produire NI exception NI ligne de
12
+ // journal : une policy qui ne protège rien répond 200, un grant qui ne
13
+ // restreint pas répond 200, une clé service sans BYPASSRLS répond 200 avec un
14
+ // tableau vide. Un assistant qui les ignore croit avoir réussi.
15
+ //
16
+ // ⚠️ DEUX CANAUX, ET CE N'EST PAS UNE REDONDANCE. `INSTRUCTIONS` part dans le
17
+ // champ prévu par le protocole : le client MCP le pose dans le contexte du
18
+ // modèle À LA CONNEXION, avant le premier appel, et on ne peut donc pas
19
+ // oublier de le lire. `CONVENTIONS` est le détail, rendu par un outil quand
20
+ // l'assistant en a besoin. Tout mettre dans le premier gonflerait chaque
21
+ // conversation ; tout mettre dans le second laisserait l'assistant écrire une
22
+ // policy fausse sans avoir jamais su qu'il fallait demander.
23
+ //
24
+ // ⚠️ CE FICHIER N'IMPORTE RIEN, exprès : le lanceur de tests du dépôt charge
25
+ // les modules voisins avec leur extension, et un import casserait sa lecture.
26
+
27
+ /** Posé dans le contexte du modèle dès la connexion. Court par obligation. */
28
+ export const INSTRUCTIONS = `Tu es connecté à un projet Clicbase : une base PostgreSQL managée, exposée en API REST par PostgREST.
29
+
30
+ SIX PIÈGES QUI NE LÈVENT AUCUNE ERREUR. Ils répondent tous « 200 OK ».
31
+ 1. \`with check\` n'est PAS \`using\`. Sur INSERT et UPDATE, PostgreSQL n'évalue QUE \`with check\`. Une policy qui n'a qu'un \`using\` ne protège pas l'écriture.
32
+ 2. Chaque nouvelle table accorde le CRUD à \`authenticated\` par défaut. Un \`grant select\` ne restreint RIEN : il faut \`revoke\`.
33
+ 3. Après tout changement de schéma, termine par \`notify pgrst, 'reload schema';\` sinon l'API continue de servir l'ancien schéma.
34
+ 4. Le rôle \`service_role\` a besoin de \`BYPASSRLS\`. Sans lui : 200 OK et un tableau vide, sans explication.
35
+ 5. Une fonction Edge renvoie \`{ status, body }\`, jamais \`new Response(...)\`.
36
+ 6. Deux authentifications sans rapport coexistent. Celle du PROJET (tes utilisateurs finaux) vit dans le schéma \`auth\` de cette base et alimente \`auth.uid()\` dans les policies. Celle de la PLATEFORME (le compte Clicbase) ne s'écrit jamais dans une policy.
37
+
38
+ APPELS REST : en-têtes \`apikey\` ET \`Authorization: Bearer\`, la même clé dans les deux. Filtres dans l'URL, façon PostgREST : \`?select=id,title&status=eq.published&order=created_at.desc\`.
39
+
40
+ DEUX CLÉS À NE PAS CONFONDRE. \`anon\` est publique et soumise à la RLS, elle va dans le navigateur. \`service\` CONTOURNE la RLS et voit tout : serveur uniquement, jamais dans un front, jamais dans un dépôt.
41
+
42
+ Appelle \`clicbase_conventions\` avant d'écrire du SQL ou une policy : tu y trouveras les formes exactes.`;
43
+
44
+ /** Le détail, rendu à la demande par un outil de lecture. */
45
+ export const CONVENTIONS = `# Conventions Clicbase
46
+
47
+ ## Row Level Security
48
+
49
+ \`\`\`sql
50
+ alter table articles enable row level security;
51
+
52
+ -- LECTURE : using suffit.
53
+ create policy "lecture_publique" on articles
54
+ for select to anon, authenticated using (status = 'published');
55
+
56
+ -- ÉCRITURE : with check est OBLIGATOIRE, using ne sert pas.
57
+ create policy "chacun_ses_lignes" on articles
58
+ for insert to authenticated with check (user_id = auth.uid());
59
+
60
+ create policy "modifier_les_siennes" on articles
61
+ for update to authenticated
62
+ using (user_id = auth.uid()) -- quelles lignes il peut viser
63
+ with check (user_id = auth.uid()); -- ce qu'il peut y écrire
64
+
65
+ notify pgrst, 'reload schema';
66
+ \`\`\`
67
+
68
+ ⚠️ Sur UPDATE, les deux clauses ont des rôles différents : \`using\` choisit les
69
+ lignes visées, \`with check\` valide le résultat. Omettre la seconde laisse
70
+ réécrire \`user_id\` vers quelqu'un d'autre.
71
+
72
+ ## Les droits, avant la RLS
73
+
74
+ La RLS filtre les lignes ; les GRANT décident si la table est atteignable. Une
75
+ nouvelle table est ouverte en CRUD à \`authenticated\` par défaut, donc :
76
+
77
+ \`\`\`sql
78
+ revoke all on articles from anon, authenticated;
79
+ grant select on articles to anon, authenticated;
80
+ grant insert, update, delete on articles to authenticated;
81
+ \`\`\`
82
+
83
+ Un \`grant select\` seul n'enlève rien : il ajoute. C'est \`revoke\` qui ferme.
84
+
85
+ ## auth.uid()
86
+
87
+ Il rend le \`sub\` du JWT du projet, donc l'identifiant de l'utilisateur final
88
+ connecté. Il vaut NULL avec la clé \`anon\`. Une policy qui doit exiger un compte
89
+ écrit \`auth.uid() is not null\`, pas \`true\`.
90
+
91
+ ## API REST
92
+
93
+ \`\`\`
94
+ GET /db/<slug>/articles?select=id,title&status=eq.published&order=created_at.desc
95
+ en-têtes : apikey: <clé> et Authorization: Bearer <clé>
96
+ \`\`\`
97
+
98
+ Opérateurs : \`eq.\`, \`neq.\`, \`gt.\`, \`gte.\`, \`lt.\`, \`lte.\`, \`like.\`, \`ilike.\`,
99
+ \`in.(a,b)\`, \`is.null\`. Les relations se tirent par \`select=*,auteur(*)\`.
100
+
101
+ ## Après toute DDL
102
+
103
+ \`notify pgrst, 'reload schema';\` dans la même exécution. Sans cela, la table
104
+ existe en base et l'API répond 404 : le défaut ressemble à une erreur de nom.
105
+
106
+ ## Fonctions Edge
107
+
108
+ \`\`\`js
109
+ export default async function (req) {
110
+ return { status: 200, body: { ok: true } };
111
+ }
112
+ \`\`\`
113
+
114
+ Jamais \`new Response(...)\` : l'hôte attend un objet, pas une réponse HTTP.
115
+
116
+ ## Ce qu'aucun outil ne fera pour toi
117
+
118
+ Distinguer un \`drop table\` prévu d'un \`drop table\` accidentel. Le DDL est la
119
+ raison d'être de \`run_sql\`, et aucune liste de mots interdits ne sépare les
120
+ deux. Avant un ordre destructeur, dis ce que tu vas faire et demande.`;