@clicbase/mcp 0.1.4 → 0.3.0
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 +63 -0
- package/clicbase-mcp.mjs +101 -0
- package/package.json +1 -1
- package/portee.mjs +16 -0
- package/savoir.mjs +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,69 @@ Outils :
|
|
|
18
18
|
- `enable_realtime` — active le temps réel sur une table.
|
|
19
19
|
- `set_oauth_provider` — configure Google OAuth.
|
|
20
20
|
- `set_email_smtp` — configure un SMTP (sinon SMTP interne Clicbase par défaut).
|
|
21
|
+
- `list_site_files` — liste les fichiers d'un site heberge (nom, taille, date).
|
|
22
|
+
- `read_site_file` — rend le contenu d'UN fichier, en base64 cote API, decode cote outil.
|
|
23
|
+
- `clicbase_conventions` — les regles de la plateforme : policies RLS, droits, PostgREST.
|
|
24
|
+
- `list_sftp_accounts` — les comptes SFTP dedies d'un site (noms, jamais de mot de passe).
|
|
25
|
+
- `create_sftp_account` — cree un compte SFTP DEDIE, rend ses identifiants une seule fois.
|
|
26
|
+
- `delete_sftp_account` — le supprime.
|
|
27
|
+
|
|
28
|
+
## Les fichiers d'un site
|
|
29
|
+
|
|
30
|
+
Jusqu'a la 0.2.0, une cle pouvait ECRASER les fichiers d'un site sans pouvoir
|
|
31
|
+
les lire : l'interdit etait pose du mauvais cote, puisque ecrire est
|
|
32
|
+
strictement plus dangereux que lire. La seule issue etait un mot de passe SFTP,
|
|
33
|
+
c'est-a-dire un secret durable colle dans une conversation pour contourner un
|
|
34
|
+
endpoint absent.
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
list_site_files(site_id) -> un niveau de la racine publique
|
|
38
|
+
list_site_files(site_id, "assets") -> on descend
|
|
39
|
+
read_site_file(site_id, "index.html") -> le contenu, decode
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
L'id du site vient de `GET /api/v1/admin/sites`.
|
|
43
|
+
|
|
44
|
+
⚠️ `list_site_files` est une LECTURE, `read_site_file` est un SECRET. Le nom
|
|
45
|
+
d'un fichier revele une structure ; son contenu peut porter des identifiants
|
|
46
|
+
dans un `config.php` ou un `settings.js`. Le second disparait donc du mode
|
|
47
|
+
restreint, comme `get_credentials`.
|
|
48
|
+
|
|
49
|
+
⚠️ LES FICHIERS CACHES SONT HORS D'ATTEINTE, a tout niveau : `.env` comme
|
|
50
|
+
`assets/.env`. Le serveur refuse tout segment commencant par un point, aussi
|
|
51
|
+
bien au depot qu'a la lecture. Cette garde existait pour empecher qu'ils soient
|
|
52
|
+
SERVIS par le serveur web ; elle protege exactement le bon flanc.
|
|
53
|
+
|
|
54
|
+
Plafond : 2 Mo cote API, 256 Ko cote outil. Au-dela, le telechargement du
|
|
55
|
+
tableau de bord ou le SFTP.
|
|
56
|
+
|
|
57
|
+
## Le SFTP, sans donner le mot de passe du site
|
|
58
|
+
|
|
59
|
+
`ftpUsername` est l'IDENTITE d'un site : il ouvre tout le dossier, ne se scope
|
|
60
|
+
pas, n'expire pas, ne laisse aucune trace distinguable, et ne se revoque qu'en
|
|
61
|
+
le changeant partout ou il a ete colle. Le confier a un assistant, c'est lui
|
|
62
|
+
donner le site.
|
|
63
|
+
|
|
64
|
+
`create_sftp_account` fabrique un compte ADDITIONNEL sur le meme dossier, rend
|
|
65
|
+
host, port, identifiant et mot de passe UNE SEULE FOIS, et
|
|
66
|
+
`delete_sftp_account` le retire. Preter un badge, pas sa cle.
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
list_sftp_accounts(site_id) -> les comptes dedies existants
|
|
70
|
+
create_sftp_account(site_id) -> host, port, user, password
|
|
71
|
+
delete_sftp_account(site_id, username) -> on referme
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
⚠️ `create_sftp_account` est classe SECRET : il ne modifie aucune donnee et
|
|
75
|
+
rend pourtant de quoi ouvrir tout le dossier. Il disparait donc du mode
|
|
76
|
+
restreint, comme `get_credentials` et `read_site_file`.
|
|
77
|
+
|
|
78
|
+
⚠️ PLAFOND DE CINQ COMPTES PAR SITE, et cinq creations par tranche de cinq
|
|
79
|
+
minutes. Chaque compte est un utilisateur systeme reel : un agent qui reprend
|
|
80
|
+
sur erreur en recreant au lieu de reutiliser en fabriquerait des dizaines.
|
|
81
|
+
|
|
82
|
+
Pour une simple consultation, `list_site_files` et `read_site_file` suffisent
|
|
83
|
+
et ne creent rien.
|
|
21
84
|
|
|
22
85
|
## Lecture seule
|
|
23
86
|
|
package/clicbase-mcp.mjs
CHANGED
|
@@ -274,6 +274,107 @@ outils.tool(
|
|
|
274
274
|
async ({ name, ...cfg }) => { try { return out(await call(name, "/email/config", "POST", cfg)); } catch (e) { return fail(e); } },
|
|
275
275
|
);
|
|
276
276
|
|
|
277
|
+
// Les fichiers d'un site heberge. `admin` cible l'API d'administration, la ou
|
|
278
|
+
// `call` parle a la base d'un projet : deux surfaces differentes.
|
|
279
|
+
async function admin(chemin, methode = "GET") {
|
|
280
|
+
const base = API_LECTURE.replace(/\/projects$/, "");
|
|
281
|
+
const res = await fetch(`${base}${chemin}`, {
|
|
282
|
+
method: methode,
|
|
283
|
+
headers: { authorization: `Bearer ${CLE}` },
|
|
284
|
+
});
|
|
285
|
+
if (!res.ok) {
|
|
286
|
+
const e = await lireEchec(res);
|
|
287
|
+
throw new Error(`Clicbase ${res.status} — ${expliquer(res.status)} ${e.detail}`);
|
|
288
|
+
}
|
|
289
|
+
return res.json();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
outils.tool(
|
|
293
|
+
"list_site_files",
|
|
294
|
+
"Liste les fichiers d'un site heberge (nom, dossier ou non, taille, date). `dossier` pour descendre d'un niveau. L'id du site vient de GET /sites.",
|
|
295
|
+
{ site_id: z.string(), dossier: z.string().optional() },
|
|
296
|
+
async ({ site_id, dossier }) => {
|
|
297
|
+
try {
|
|
298
|
+
const q = dossier ? `?dossier=${encodeURIComponent(dossier)}` : "";
|
|
299
|
+
return out(await admin(`/sites/${encodeURIComponent(site_id)}/files${q}`));
|
|
300
|
+
} catch (e) {
|
|
301
|
+
return fail(e);
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
outils.tool(
|
|
307
|
+
"read_site_file",
|
|
308
|
+
"Rend le contenu d'UN fichier d'un site heberge, en base64. Plafond 2 Mo. Les fichiers caches (.env, .git) sont refuses par le serveur, a tout niveau.",
|
|
309
|
+
{ site_id: z.string(), chemin: z.string() },
|
|
310
|
+
async ({ site_id, chemin }) => {
|
|
311
|
+
try {
|
|
312
|
+
const d = await admin(
|
|
313
|
+
`/sites/${encodeURIComponent(site_id)}/files?chemin=${encodeURIComponent(chemin)}`,
|
|
314
|
+
);
|
|
315
|
+
// ⚠️ ON DECODE ICI, ET ON PLAFONNE PLUS BAS QUE L'API. Rendre du base64 a
|
|
316
|
+
// un modele lui fait depenser son contexte a le decoder, souvent mal. Et
|
|
317
|
+
// 2 Mo de source dans une conversation ne servent personne : au-dela de
|
|
318
|
+
// 256 Ko on refuse en le disant, plutot que de noyer la session.
|
|
319
|
+
const brut = Buffer.from(String(d.contenu ?? ""), "base64");
|
|
320
|
+
if (brut.byteLength > 256 * 1024) {
|
|
321
|
+
return fail(
|
|
322
|
+
new Error(
|
|
323
|
+
`Fichier de ${brut.byteLength} octets : trop gros pour une conversation. Telecharge-le par le tableau de bord ou par SFTP.`,
|
|
324
|
+
),
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
return out({ chemin: d.chemin, octets: d.octets, contenu: brut.toString("utf8") });
|
|
328
|
+
} catch (e) {
|
|
329
|
+
return fail(e);
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
outils.tool(
|
|
335
|
+
"list_sftp_accounts",
|
|
336
|
+
"Liste les comptes SFTP DEDIES d'un site (identifiants et dates, jamais les mots de passe). L'id du site vient de GET /sites.",
|
|
337
|
+
{ site_id: z.string() },
|
|
338
|
+
async ({ site_id }) => {
|
|
339
|
+
try {
|
|
340
|
+
return out(await admin(`/sites/${encodeURIComponent(site_id)}/sftp`));
|
|
341
|
+
} catch (e) {
|
|
342
|
+
return fail(e);
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
outils.tool(
|
|
348
|
+
"create_sftp_account",
|
|
349
|
+
"Cree un compte SFTP DEDIE sur un site et rend host, port, identifiant et mot de passe UNE SEULE FOIS. Ce n'est jamais le compte principal du site. Supprime-le quand tu as fini. Plafond : 5 comptes par site.",
|
|
350
|
+
{ site_id: z.string() },
|
|
351
|
+
async ({ site_id }) => {
|
|
352
|
+
try {
|
|
353
|
+
return out(await admin(`/sites/${encodeURIComponent(site_id)}/sftp`, "POST"));
|
|
354
|
+
} catch (e) {
|
|
355
|
+
return fail(e);
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
outils.tool(
|
|
361
|
+
"delete_sftp_account",
|
|
362
|
+
"Supprime un compte SFTP dedie, par son identifiant. A faire des que le transfert est termine : un compte oublie reste un acces ouvert.",
|
|
363
|
+
{ site_id: z.string(), username: z.string() },
|
|
364
|
+
async ({ site_id, username }) => {
|
|
365
|
+
try {
|
|
366
|
+
return out(
|
|
367
|
+
await admin(
|
|
368
|
+
`/sites/${encodeURIComponent(site_id)}/sftp?username=${encodeURIComponent(username)}`,
|
|
369
|
+
"DELETE",
|
|
370
|
+
),
|
|
371
|
+
);
|
|
372
|
+
} catch (e) {
|
|
373
|
+
return fail(e);
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
);
|
|
377
|
+
|
|
277
378
|
// ⚠️ LE MODE RESTREINT S'ANNONCE, SUR LA SORTIE D'ERREUR. Un serveur qui
|
|
278
379
|
// retire des outils en silence se decouvre en pleine session : l'assistant
|
|
279
380
|
// cherche une fonction qui devrait exister, ne la trouve pas, et conclut que le
|
package/package.json
CHANGED
package/portee.mjs
CHANGED
|
@@ -48,6 +48,22 @@ export const OUTILS = {
|
|
|
48
48
|
// restreint qui en a le plus besoin, puisqu'il ne reste alors que trois
|
|
49
49
|
// outils et qu'un assistant doit deviner le reste.
|
|
50
50
|
clicbase_conventions: "lecture",
|
|
51
|
+
// --- fichiers d'un site heberge ---
|
|
52
|
+
// ⚠️ LISTER EST UNE LECTURE, LIRE EST UN SECRET. Le nom et la taille d'un
|
|
53
|
+
// fichier revelent une structure ; son CONTENU peut porter des identifiants,
|
|
54
|
+
// dans un config.php ou un settings.js. Meme raisonnement que
|
|
55
|
+
// get_credentials : il ne modifie rien, et il rend quand meme les clefs de
|
|
56
|
+
// la maison. Les fichiers caches, eux, sont deja impossibles cote serveur.
|
|
57
|
+
list_site_files: "lecture",
|
|
58
|
+
read_site_file: "secret",
|
|
59
|
+
// --- comptes SFTP dedies ---
|
|
60
|
+
// ⚠️ CREER REND UN MOT DE PASSE, DONC C'EST UN SECRET. L'outil ne modifie
|
|
61
|
+
// pas de donnees, mais il rend de quoi ouvrir tout le dossier du site : meme
|
|
62
|
+
// raisonnement que get_credentials. Lister ne rend que des noms, supprimer
|
|
63
|
+
// ne rend rien mais ecrit.
|
|
64
|
+
list_sftp_accounts: "lecture",
|
|
65
|
+
create_sftp_account: "secret",
|
|
66
|
+
delete_sftp_account: "ecriture",
|
|
51
67
|
};
|
|
52
68
|
|
|
53
69
|
/** Ce qu'on garde en lecture seule. */
|
package/savoir.mjs
CHANGED
|
@@ -35,7 +35,7 @@ SIX PIÈGES QUI NE LÈVENT AUCUNE ERREUR. Ils répondent tous « 200 OK ».
|
|
|
35
35
|
5. Une fonction Edge renvoie \`{ status, body }\`, jamais \`new Response(...)\`.
|
|
36
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
37
|
|
|
38
|
-
CE QUI N'EXISTE PAS, ET QUE DEUX ASSISTANTS ONT DEJA INVENTE. Il n'y a AUCUN registre Docker : ni \`docker login\`, ni \`docker pull\`, ni \`registry.*.clicbase.com\`. Ne devine aucun nom d'hote.
|
|
38
|
+
CE QUI N'EXISTE PAS, ET QUE DEUX ASSISTANTS ONT DEJA INVENTE. Il n'y a AUCUN registre Docker : ni \`docker login\`, ni \`docker pull\`, ni \`registry.*.clicbase.com\`. Ne devine aucun nom d'hote. Les fichiers d'un site heberge se lisent par \`list_site_files\` et \`read_site_file\`, et s'ecrivent par POST /sites/<id>/files. Ne reclame JAMAIS le mot de passe SFTP principal du site : c'est son identite, elle ouvre tout et ne se revoque pas. Pour un transfert volumineux, \`create_sftp_account\` fabrique un compte DEDIE, rend ses identifiants une seule fois, et \`delete_sftp_account\` le retire. Supprime-le des que tu as fini. Les fichiers CACHES sont refuses a tout niveau, \`.env\` et \`.git\` compris : ils ne peuvent ni etre deposes ni etre lus. \`read_site_file\` est absent en lecture seule, parce qu'un source peut porter des identifiants dans un fichier non cache.
|
|
39
39
|
|
|
40
40
|
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\`.
|
|
41
41
|
|