@lyrra/mcp-server 1.0.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 +214 -0
- package/dist/client.d.ts +17 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +92 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/dist/resources/block-types.d.ts +318 -0
- package/dist/resources/block-types.d.ts.map +1 -0
- package/dist/resources/block-types.js +297 -0
- package/dist/resources/block-types.js.map +1 -0
- package/dist/resources/flow-schema.d.ts +147 -0
- package/dist/resources/flow-schema.d.ts.map +1 -0
- package/dist/resources/flow-schema.js +143 -0
- package/dist/resources/flow-schema.js.map +1 -0
- package/dist/tools/admin.d.ts +133 -0
- package/dist/tools/admin.d.ts.map +1 -0
- package/dist/tools/admin.js +114 -0
- package/dist/tools/admin.js.map +1 -0
- package/dist/tools/ai-designer.d.ts +149 -0
- package/dist/tools/ai-designer.d.ts.map +1 -0
- package/dist/tools/ai-designer.js +85 -0
- package/dist/tools/ai-designer.js.map +1 -0
- package/dist/tools/analytics.d.ts +48 -0
- package/dist/tools/analytics.d.ts.map +1 -0
- package/dist/tools/analytics.js +40 -0
- package/dist/tools/analytics.js.map +1 -0
- package/dist/tools/auth.d.ts +31 -0
- package/dist/tools/auth.d.ts.map +1 -0
- package/dist/tools/auth.js +32 -0
- package/dist/tools/auth.js.map +1 -0
- package/dist/tools/blocks.d.ts +201 -0
- package/dist/tools/blocks.d.ts.map +1 -0
- package/dist/tools/blocks.js +163 -0
- package/dist/tools/blocks.js.map +1 -0
- package/dist/tools/connections.d.ts +87 -0
- package/dist/tools/connections.d.ts.map +1 -0
- package/dist/tools/connections.js +77 -0
- package/dist/tools/connections.js.map +1 -0
- package/dist/tools/eduflow.d.ts +186 -0
- package/dist/tools/eduflow.d.ts.map +1 -0
- package/dist/tools/eduflow.js +104 -0
- package/dist/tools/eduflow.js.map +1 -0
- package/dist/tools/participants.d.ts +111 -0
- package/dist/tools/participants.d.ts.map +1 -0
- package/dist/tools/participants.js +66 -0
- package/dist/tools/participants.js.map +1 -0
- package/dist/tools/presentation.d.ts +117 -0
- package/dist/tools/presentation.d.ts.map +1 -0
- package/dist/tools/presentation.js +53 -0
- package/dist/tools/presentation.js.map +1 -0
- package/dist/tools/projects.d.ts +66 -0
- package/dist/tools/projects.d.ts.map +1 -0
- package/dist/tools/projects.js +51 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/resources.d.ts +47 -0
- package/dist/tools/resources.d.ts.map +1 -0
- package/dist/tools/resources.js +33 -0
- package/dist/tools/resources.js.map +1 -0
- package/dist/tools/store.d.ts +63 -0
- package/dist/tools/store.d.ts.map +1 -0
- package/dist/tools/store.js +62 -0
- package/dist/tools/store.js.map +1 -0
- package/mcp-config.example.json +15 -0
- package/package.json +24 -0
- package/src/client.ts +99 -0
- package/src/config.ts +6 -0
- package/src/index.ts +112 -0
- package/src/resources/block-types.ts +298 -0
- package/src/resources/flow-schema.ts +148 -0
- package/src/tools/admin.ts +122 -0
- package/src/tools/ai-designer.ts +91 -0
- package/src/tools/analytics.ts +43 -0
- package/src/tools/auth.ts +33 -0
- package/src/tools/blocks.ts +174 -0
- package/src/tools/connections.ts +77 -0
- package/src/tools/eduflow.ts +112 -0
- package/src/tools/participants.ts +71 -0
- package/src/tools/presentation.ts +55 -0
- package/src/tools/projects.ts +55 -0
- package/src/tools/resources.ts +35 -0
- package/src/tools/store.ts +61 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
export const FLOW_SCHEMA_RESOURCE = {
|
|
2
|
+
uri: 'lyrra://flow-construction-guide',
|
|
3
|
+
name: 'Guide de construction d\'un parcours EduFlow',
|
|
4
|
+
description: 'Guide complet pour construire un parcours pédagogique dans LYRRA Studio : logique de construction, règles de connexion, bonnes pratiques.',
|
|
5
|
+
mimeType: 'application/json',
|
|
6
|
+
content: {
|
|
7
|
+
overview: `Un parcours EduFlow est un graphe orienté composé de blocs (nœuds) reliés par des edges (connexions).
|
|
8
|
+
Le parcours définit le chemin que suivra l'apprenant, avec possibilité de branchements conditionnels.`,
|
|
9
|
+
|
|
10
|
+
construction_steps: [
|
|
11
|
+
{
|
|
12
|
+
step: 1,
|
|
13
|
+
title: 'Créer le parcours',
|
|
14
|
+
description: 'Utiliser eduflow_create avec un titre et une description. Un bloc "start" et un bloc "end" sont automatiquement créés.',
|
|
15
|
+
tool: 'eduflow_create',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
step: 2,
|
|
19
|
+
title: 'Ajouter des blocs de contenu',
|
|
20
|
+
description: 'Créer des blocs entre start et end : text, video, audio, quiz, etc. Utiliser block_create pour chaque bloc.',
|
|
21
|
+
tool: 'block_create',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
step: 3,
|
|
25
|
+
title: 'Connecter les blocs',
|
|
26
|
+
description: 'Relier les blocs avec connection_add. Chaque connexion va de source vers target.',
|
|
27
|
+
tool: 'connection_add',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
step: 4,
|
|
31
|
+
title: 'Configurer les objectifs',
|
|
32
|
+
description: 'Définir les objectifs pédagogiques avec ai_generate_objectives ou ai_save_objectives.',
|
|
33
|
+
tool: 'ai_generate_objectives',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
step: 5,
|
|
37
|
+
title: 'Créer la page de présentation',
|
|
38
|
+
description: 'Configurer la page publique avec presentation_update et presentation_toggle.',
|
|
39
|
+
tool: 'presentation_update',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
step: 6,
|
|
43
|
+
title: 'Publier',
|
|
44
|
+
description: 'Changer le statut en "published" avec eduflow_change_status pour rendre le parcours accessible.',
|
|
45
|
+
tool: 'eduflow_change_status',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
|
|
49
|
+
connection_rules: {
|
|
50
|
+
general: [
|
|
51
|
+
'Le graphe doit être connexe : chaque bloc doit être atteignable depuis le bloc start',
|
|
52
|
+
'Pas de boucles infinies sans condition de sortie',
|
|
53
|
+
'Chaque chemin doit mener à au moins un bloc end',
|
|
54
|
+
],
|
|
55
|
+
by_block_type: {
|
|
56
|
+
start: {
|
|
57
|
+
incoming: 0,
|
|
58
|
+
outgoing: '1+',
|
|
59
|
+
notes: 'Exactement 1 par parcours. Point d\'entrée unique.',
|
|
60
|
+
},
|
|
61
|
+
end: {
|
|
62
|
+
incoming: '1+',
|
|
63
|
+
outgoing: 0,
|
|
64
|
+
notes: 'Peut avoir plusieurs blocs end pour différentes fins.',
|
|
65
|
+
},
|
|
66
|
+
text: { incoming: '1+', outgoing: '1', notes: 'Flux linéaire.' },
|
|
67
|
+
audio: { incoming: '1+', outgoing: '1', notes: 'Flux linéaire.' },
|
|
68
|
+
video: { incoming: '1+', outgoing: '1', notes: 'Flux linéaire.' },
|
|
69
|
+
pdf: { incoming: '1+', outgoing: '1', notes: 'Flux linéaire.' },
|
|
70
|
+
image: { incoming: '1+', outgoing: '1', notes: 'Flux linéaire.' },
|
|
71
|
+
quiz: {
|
|
72
|
+
incoming: '1+',
|
|
73
|
+
outgoing: '1-N',
|
|
74
|
+
notes: 'Sans branchement: 1 sortie. binary: 2 sorties (correct/incorrect). per-answer: N sorties.',
|
|
75
|
+
},
|
|
76
|
+
evaluation: { incoming: '1+', outgoing: '1-2', notes: '1 sortie (toujours) ou 2 (réussite/échec).' },
|
|
77
|
+
form: { incoming: '1+', outgoing: '1-N', notes: 'Peut avoir des règles conditionnelles.' },
|
|
78
|
+
split: { incoming: '1', outgoing: '2+', notes: 'Crée des branches parallèles.' },
|
|
79
|
+
merge: { incoming: '2+', outgoing: '1', notes: 'Rejoint les branches. Correspond à un split.' },
|
|
80
|
+
loop: { incoming: '1+', outgoing: '2', notes: '1 sortie "continuer" + 1 sortie "quitter la boucle".' },
|
|
81
|
+
timer: { incoming: '1+', outgoing: '1-2', notes: '1 sortie normale + 1 sortie timeout (optionnel).' },
|
|
82
|
+
subflow: { incoming: '1+', outgoing: '1', notes: 'Le sous-parcours s\'exécute entièrement puis continue.' },
|
|
83
|
+
certification: { incoming: '1+', outgoing: '1', notes: 'Génère le certificat puis continue.' },
|
|
84
|
+
email: { incoming: '1+', outgoing: '1', notes: 'Envoie l\'email puis continue.' },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
positioning: {
|
|
89
|
+
description: 'Les blocs ont des coordonnées X/Y pour l\'éditeur visuel.',
|
|
90
|
+
recommendations: [
|
|
91
|
+
'Espacer horizontalement de 300px entre les blocs séquentiels',
|
|
92
|
+
'Espacer verticalement de 150px pour les branches parallèles',
|
|
93
|
+
'Le bloc start en position (100, 300)',
|
|
94
|
+
'Flux de gauche à droite',
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
patterns: {
|
|
99
|
+
linear: {
|
|
100
|
+
name: 'Parcours linéaire',
|
|
101
|
+
description: 'start → text → video → quiz → end',
|
|
102
|
+
usage: 'Parcours simple sans branchement.',
|
|
103
|
+
},
|
|
104
|
+
branching: {
|
|
105
|
+
name: 'Parcours avec branchement',
|
|
106
|
+
description: 'start → quiz → [correct: text_avancé → end] [incorrect: text_revision → quiz]',
|
|
107
|
+
usage: 'Adaptation au niveau de l\'apprenant.',
|
|
108
|
+
},
|
|
109
|
+
parallel: {
|
|
110
|
+
name: 'Parcours parallèle',
|
|
111
|
+
description: 'start → split → [branche1: text+video] [branche2: audio+pdf] → merge → end',
|
|
112
|
+
usage: 'L\'apprenant choisit son parcours.',
|
|
113
|
+
},
|
|
114
|
+
loop_pattern: {
|
|
115
|
+
name: 'Boucle d\'apprentissage',
|
|
116
|
+
description: 'start → text → quiz → [échec: loop → text] [succès: end]',
|
|
117
|
+
usage: 'Répétition jusqu\'à maîtrise.',
|
|
118
|
+
},
|
|
119
|
+
certification_pattern: {
|
|
120
|
+
name: 'Parcours certifiant',
|
|
121
|
+
description: 'start → text → evaluation → [réussi: certification → email → end] [échoué: end]',
|
|
122
|
+
usage: 'Parcours avec certificat de réussite.',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
gamification: {
|
|
127
|
+
description: 'Configuration optionnelle pour engager les apprenants.',
|
|
128
|
+
options: {
|
|
129
|
+
badges: 'Badges débloqués par des actions (complétion, score parfait, etc.)',
|
|
130
|
+
missions: 'Objectifs à atteindre avec récompenses',
|
|
131
|
+
leaderboard: 'Classement entre participants',
|
|
132
|
+
certifications: 'Certificats PDF personnalisés',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
best_practices: [
|
|
137
|
+
'Commencer par définir les objectifs pédagogiques avant de créer les blocs',
|
|
138
|
+
'Alterner les types de blocs (texte → vidéo → quiz) pour maintenir l\'engagement',
|
|
139
|
+
'Utiliser des quiz formatifs (sans score) entre les blocs de contenu',
|
|
140
|
+
'Limiter à 15-20 blocs par parcours pour ne pas surcharger',
|
|
141
|
+
'Toujours avoir un message d\'accueil dans le bloc start',
|
|
142
|
+
'Toujours avoir un message de félicitations dans le bloc end',
|
|
143
|
+
'Utiliser les labels sur les connexions pour les branchements (ex: "Bonne réponse")',
|
|
144
|
+
'Créer une version avant chaque modification majeure',
|
|
145
|
+
'Tester le parcours avec un participant test avant de publier',
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { client } from '../client.js';
|
|
3
|
+
|
|
4
|
+
export const adminTools = {
|
|
5
|
+
// --- Versions ---
|
|
6
|
+
version_list: {
|
|
7
|
+
description: 'Lister toutes les versions d\'un parcours EduFlow.',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
10
|
+
}),
|
|
11
|
+
handler: async ({ flowId }: { flowId: string }) => {
|
|
12
|
+
return client.get(`/flows/${flowId}/versions`, 'eduflow');
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
version_create: {
|
|
17
|
+
description: 'Créer une nouvelle version (snapshot) du parcours. Permet de revenir en arrière si besoin.',
|
|
18
|
+
inputSchema: z.object({
|
|
19
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
20
|
+
versionLabel: z.string().optional().describe('Label de la version (ex: "v2.0 - Ajout quiz")'),
|
|
21
|
+
}),
|
|
22
|
+
handler: async ({ flowId, versionLabel }: any) => {
|
|
23
|
+
return client.post(`/flows/${flowId}/versions`, { versionLabel }, 'eduflow');
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
version_activate: {
|
|
28
|
+
description: 'Activer une version spécifique comme version publique du parcours.',
|
|
29
|
+
inputSchema: z.object({
|
|
30
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
31
|
+
versionId: z.string().uuid().describe('ID de la version à activer'),
|
|
32
|
+
}),
|
|
33
|
+
handler: async ({ flowId, versionId }: { flowId: string; versionId: string }) => {
|
|
34
|
+
return client.post(`/flows/${flowId}/versions/${versionId}/activate`, {}, 'eduflow');
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// --- Gamification ---
|
|
39
|
+
gamification_stats: {
|
|
40
|
+
description: 'Récupérer les statistiques de gamification de l\'utilisateur (niveau, streak, badges, missions).',
|
|
41
|
+
inputSchema: z.object({}),
|
|
42
|
+
handler: async () => {
|
|
43
|
+
return client.get('/gamification/stats');
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
gamification_objectives: {
|
|
48
|
+
description: 'Récupérer les prochains objectifs de gamification à atteindre.',
|
|
49
|
+
inputSchema: z.object({}),
|
|
50
|
+
handler: async () => {
|
|
51
|
+
return client.get('/gamification/objectives');
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// --- Activity ---
|
|
56
|
+
activity_history: {
|
|
57
|
+
description: 'Récupérer l\'historique d\'activité de l\'utilisateur avec pagination.',
|
|
58
|
+
inputSchema: z.object({
|
|
59
|
+
page: z.number().optional().describe('Page (défaut: 1)'),
|
|
60
|
+
limit: z.number().optional().describe('Nombre par page (défaut: 20)'),
|
|
61
|
+
}),
|
|
62
|
+
handler: async ({ page, limit }: any) => {
|
|
63
|
+
const params = new URLSearchParams();
|
|
64
|
+
if (page) params.set('page', String(page));
|
|
65
|
+
if (limit) params.set('limit', String(limit));
|
|
66
|
+
const query = params.toString() ? `?${params}` : '';
|
|
67
|
+
return client.get(`/activity${query}`);
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
activity_stats: {
|
|
72
|
+
description: 'Statistiques d\'utilisation par période (jour/semaine/mois).',
|
|
73
|
+
inputSchema: z.object({
|
|
74
|
+
period: z.enum(['day', 'week', 'month']).optional().describe('Période (défaut: week)'),
|
|
75
|
+
}),
|
|
76
|
+
handler: async ({ period }: { period?: string }) => {
|
|
77
|
+
const query = period ? `?period=${period}` : '';
|
|
78
|
+
return client.get(`/activity/stats${query}`);
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// --- Webhooks ---
|
|
83
|
+
webhook_list: {
|
|
84
|
+
description: 'Lister les webhooks configurés pour recevoir des événements (block.completed, flow.completed, etc.).',
|
|
85
|
+
inputSchema: z.object({}),
|
|
86
|
+
handler: async () => {
|
|
87
|
+
return client.get('/webhooks');
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
webhook_create: {
|
|
92
|
+
description: 'Créer un webhook pour recevoir des notifications en temps réel sur des événements.',
|
|
93
|
+
inputSchema: z.object({
|
|
94
|
+
url: z.string().url().describe('URL du webhook (doit être accessible publiquement)'),
|
|
95
|
+
events: z.array(z.string()).describe('Événements à écouter (ex: ["block.completed", "flow.completed"])'),
|
|
96
|
+
name: z.string().optional().describe('Nom du webhook'),
|
|
97
|
+
}),
|
|
98
|
+
handler: async (data: any) => {
|
|
99
|
+
return client.post('/webhooks', data);
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
webhook_delete: {
|
|
104
|
+
description: 'Supprimer un webhook.',
|
|
105
|
+
inputSchema: z.object({
|
|
106
|
+
webhookId: z.string().uuid().describe('ID du webhook'),
|
|
107
|
+
}),
|
|
108
|
+
handler: async ({ webhookId }: { webhookId: string }) => {
|
|
109
|
+
return client.delete(`/webhooks/${webhookId}`);
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
webhook_test: {
|
|
114
|
+
description: 'Tester un webhook en envoyant un événement de test.',
|
|
115
|
+
inputSchema: z.object({
|
|
116
|
+
webhookId: z.string().uuid().describe('ID du webhook à tester'),
|
|
117
|
+
}),
|
|
118
|
+
handler: async ({ webhookId }: { webhookId: string }) => {
|
|
119
|
+
return client.post(`/webhooks/${webhookId}/test`, {});
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { client } from '../client.js';
|
|
3
|
+
|
|
4
|
+
export const aiDesignerTools = {
|
|
5
|
+
ai_generate_plan: {
|
|
6
|
+
description: 'Générer automatiquement un plan/structure de parcours pédagogique à partir d\'un sujet et d\'objectifs. L\'IA propose une liste de blocs organisés.',
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
9
|
+
topic: z.string().describe('Sujet du parcours (ex: "Les fractions en CM2")'),
|
|
10
|
+
targetAudience: z.string().optional().describe('Public cible (ex: "Élèves de CM2, 9-10 ans")'),
|
|
11
|
+
objectives: z.string().optional().describe('Objectifs pédagogiques souhaités'),
|
|
12
|
+
duration: z.string().optional().describe('Durée estimée (ex: "30 minutes")'),
|
|
13
|
+
context: z.string().optional().describe('Contexte additionnel pour l\'IA'),
|
|
14
|
+
}),
|
|
15
|
+
handler: async ({ flowId, ...data }: any) => {
|
|
16
|
+
return client.post(`/flows/${flowId}/chatbot/generate-plan`, data, 'eduflow');
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
ai_generate_block: {
|
|
21
|
+
description: 'Générer le contenu d\'un bloc spécifique avec l\'IA. Peut créer du texte enrichi, des quiz, des évaluations, des graphiques, etc.',
|
|
22
|
+
inputSchema: z.object({
|
|
23
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
24
|
+
blockType: z.string().describe('Type de bloc à générer (text, quiz, evaluation, chart, timeline, etc.)'),
|
|
25
|
+
instructions: z.string().describe('Instructions pour le contenu du bloc (ex: "Créer un quiz de 5 questions sur les fractions")'),
|
|
26
|
+
context: z.string().optional().describe('Contexte du parcours pour l\'IA'),
|
|
27
|
+
}),
|
|
28
|
+
handler: async ({ flowId, ...data }: any) => {
|
|
29
|
+
return client.post(`/flows/${flowId}/chatbot/generate-block`, data, 'eduflow');
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
ai_generate_presentation: {
|
|
34
|
+
description: 'Générer automatiquement la page de présentation du parcours (description, objectifs affichés, image, structure visible).',
|
|
35
|
+
inputSchema: z.object({
|
|
36
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
37
|
+
style: z.string().optional().describe('Style de présentation souhaité'),
|
|
38
|
+
}),
|
|
39
|
+
handler: async ({ flowId, ...data }: any) => {
|
|
40
|
+
return client.post(`/flows/${flowId}/chatbot/generate-presentation`, data, 'eduflow');
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
ai_generate_objectives: {
|
|
45
|
+
description: 'Générer automatiquement les objectifs pédagogiques du parcours à partir du contenu existant.',
|
|
46
|
+
inputSchema: z.object({
|
|
47
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
48
|
+
}),
|
|
49
|
+
handler: async ({ flowId }: { flowId: string }) => {
|
|
50
|
+
return client.post(`/flows/${flowId}/chatbot/generate-objectives`, {}, 'eduflow');
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
ai_save_objectives: {
|
|
55
|
+
description: 'Sauvegarder les objectifs pédagogiques (globaux et par étape) sur le parcours.',
|
|
56
|
+
inputSchema: z.object({
|
|
57
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
58
|
+
globalObjective: z.string().describe('Objectif global du parcours'),
|
|
59
|
+
steps: z.array(z.object({
|
|
60
|
+
id: z.string().describe('ID de l\'étape'),
|
|
61
|
+
title: z.string().describe('Titre de l\'objectif'),
|
|
62
|
+
description: z.string().optional().describe('Description détaillée'),
|
|
63
|
+
blockId: z.string().optional().describe('Bloc associé à cet objectif'),
|
|
64
|
+
})).describe('Liste des objectifs par étape'),
|
|
65
|
+
}),
|
|
66
|
+
handler: async ({ flowId, ...objectives }: any) => {
|
|
67
|
+
return client.post(`/flows/${flowId}/chatbot/save-objectives`, objectives, 'eduflow');
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
ai_chat_message: {
|
|
72
|
+
description: 'Envoyer un message au designer IA pour discuter de la conception du parcours, poser des questions, demander des modifications.',
|
|
73
|
+
inputSchema: z.object({
|
|
74
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
75
|
+
message: z.string().describe('Message à envoyer à l\'IA designer'),
|
|
76
|
+
}),
|
|
77
|
+
handler: async ({ flowId, message }: { flowId: string; message: string }) => {
|
|
78
|
+
return client.post(`/flows/${flowId}/chatbot/message`, { message }, 'eduflow');
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
ai_get_conversation: {
|
|
83
|
+
description: 'Récupérer l\'historique de conversation avec le designer IA pour un parcours.',
|
|
84
|
+
inputSchema: z.object({
|
|
85
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
86
|
+
}),
|
|
87
|
+
handler: async ({ flowId }: { flowId: string }) => {
|
|
88
|
+
return client.get(`/flows/${flowId}/chatbot/conversation`, 'eduflow');
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { client } from '../client.js';
|
|
3
|
+
|
|
4
|
+
export const analyticsTools = {
|
|
5
|
+
analytics_overview: {
|
|
6
|
+
description: 'Tableau de bord global : nombre de projets, parcours EduFlow, apprenants, crédits utilisés, avec évolution par période.',
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
period: z.enum(['day', 'week', 'month', 'semester', 'year']).optional().describe('Période d\'analyse (défaut: month)'),
|
|
9
|
+
}),
|
|
10
|
+
handler: async ({ period }: { period?: string }) => {
|
|
11
|
+
const query = period ? `?period=${period}` : '';
|
|
12
|
+
return client.get(`/analytics/overview${query}`);
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
analytics_flow_learners: {
|
|
17
|
+
description: 'Statistiques des apprenants pour un parcours spécifique : progression, scores, temps passé par apprenant.',
|
|
18
|
+
inputSchema: z.object({
|
|
19
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
20
|
+
}),
|
|
21
|
+
handler: async ({ flowId }: { flowId: string }) => {
|
|
22
|
+
return client.get(`/analytics/flow/${flowId}/learners`);
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
analytics_flow_dashboard: {
|
|
27
|
+
description: 'Dashboard analytics complet d\'un parcours : vue d\'ensemble, taux de complétion, blocs les plus/moins visités.',
|
|
28
|
+
inputSchema: z.object({
|
|
29
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
30
|
+
}),
|
|
31
|
+
handler: async ({ flowId }: { flowId: string }) => {
|
|
32
|
+
return client.get(`/flows/${flowId}/analytics`, 'eduflow');
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
analytics_my_stats: {
|
|
37
|
+
description: 'Mes statistiques personnelles : projets créés, parcours créés, temps total, crédits utilisés.',
|
|
38
|
+
inputSchema: z.object({}),
|
|
39
|
+
handler: async () => {
|
|
40
|
+
return client.get('/analytics/my-stats');
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { client } from '../client.js';
|
|
3
|
+
|
|
4
|
+
export const authTools = {
|
|
5
|
+
auth_login: {
|
|
6
|
+
description: 'Se connecter à LYRRA Studio avec email et mot de passe. Retourne un token JWT.',
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
email: z.string().email().describe('Adresse email du compte'),
|
|
9
|
+
password: z.string().describe('Mot de passe'),
|
|
10
|
+
}),
|
|
11
|
+
handler: async ({ email, password }: { email: string; password: string }) => {
|
|
12
|
+
const result = await client.post('/auth/login', { email, password });
|
|
13
|
+
if (result.token) client.setToken(result.token);
|
|
14
|
+
return result;
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
auth_get_profile: {
|
|
19
|
+
description: 'Récupérer le profil de l\'utilisateur connecté (nom, email, crédits, rôle, institution, etc.).',
|
|
20
|
+
inputSchema: z.object({}),
|
|
21
|
+
handler: async () => {
|
|
22
|
+
return client.get('/auth/me');
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
auth_list_api_keys: {
|
|
27
|
+
description: 'Lister toutes les clés API de l\'utilisateur connecté.',
|
|
28
|
+
inputSchema: z.object({}),
|
|
29
|
+
handler: async () => {
|
|
30
|
+
return client.get('/api-keys');
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { client } from '../client.js';
|
|
3
|
+
|
|
4
|
+
const blockContentSchema = z.record(z.any()).describe(
|
|
5
|
+
'Contenu du bloc (structure variable selon le type). Utiliser la resource lyrra://block-types pour connaître la structure attendue.'
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
export const blocksTools = {
|
|
9
|
+
block_list_types: {
|
|
10
|
+
description: `Lister tous les types de blocs disponibles avec leur documentation complète.
|
|
11
|
+
Types disponibles : text, audio, quiz, video, pdf, image, loop, evaluation, split, merge, timer, start, end, chart, timeline, dys_reader, dys_image_zones, dys_reading_practice, dys_clock, glossary, subflow, mindmap, form, certification, email, voice_assessment, presentation, browser, self_assessment.
|
|
12
|
+
Utiliser plutôt la resource lyrra://block-types pour la documentation complète.`,
|
|
13
|
+
inputSchema: z.object({}),
|
|
14
|
+
handler: async () => {
|
|
15
|
+
return {
|
|
16
|
+
types: [
|
|
17
|
+
{ type: 'start', name: 'Début', description: 'Point de départ du parcours. Un seul par flux.' },
|
|
18
|
+
{ type: 'end', name: 'Fin', description: 'Point de fin du parcours. Peut avoir plusieurs fins.' },
|
|
19
|
+
{ type: 'text', name: 'Texte', description: 'Contenu textuel/HTML enrichi. TOUJOURS utiliser format: "html".' },
|
|
20
|
+
{ type: 'audio', name: 'Audio', description: 'Intègre un projet audio LYRRA avec texte synchronisé.' },
|
|
21
|
+
{ type: 'video', name: 'Vidéo', description: 'Intègre une vidéo (URL YouTube/Vimeo ou fichier uploadé).' },
|
|
22
|
+
{ type: 'pdf', name: 'PDF', description: 'Affiche un document PDF intégré.' },
|
|
23
|
+
{ type: 'image', name: 'Image', description: 'Affiche une image avec légende optionnelle.' },
|
|
24
|
+
{ type: 'quiz', name: 'Quiz', description: 'Question QCM/vrai-faux/ordonnancement avec branchement conditionnel.' },
|
|
25
|
+
{ type: 'evaluation', name: 'Évaluation', description: 'Évaluation complète avec plusieurs questions et scoring.' },
|
|
26
|
+
{ type: 'form', name: 'Formulaire', description: 'Formulaire avec champs personnalisés et logique conditionnelle.' },
|
|
27
|
+
{ type: 'chart', name: 'Graphique', description: 'Graphique interactif (bar, line, pie, scatter, radar, area, doughnut).' },
|
|
28
|
+
{ type: 'timeline', name: 'Frise chronologique', description: 'Frise avec événements datés.' },
|
|
29
|
+
{ type: 'mindmap', name: 'Carte mentale', description: 'Carte mentale interactive avec nœuds et liens.' },
|
|
30
|
+
{ type: 'glossary', name: 'Glossaire', description: 'Liste de termes et définitions.' },
|
|
31
|
+
{ type: 'split', name: 'Embranchement', description: 'Sépare le flux en branches parallèles.' },
|
|
32
|
+
{ type: 'merge', name: 'Fusion', description: 'Rejoint les branches parallèles.' },
|
|
33
|
+
{ type: 'loop', name: 'Boucle', description: 'Permet de répéter une séquence de blocs.' },
|
|
34
|
+
{ type: 'timer', name: 'Minuteur', description: 'Bloc avec temps limité, pause, rappel ou expiration.' },
|
|
35
|
+
{ type: 'subflow', name: 'Sous-parcours', description: 'Intègre un autre parcours EduFlow comme bloc.' },
|
|
36
|
+
{ type: 'certification', name: 'Certification', description: 'Génère un certificat PDF personnalisé.' },
|
|
37
|
+
{ type: 'email', name: 'Email', description: 'Envoie un email automatique (certification, rappel).' },
|
|
38
|
+
{ type: 'voice_assessment', name: 'Évaluation vocale', description: 'Enregistrement et évaluation de la voix de l\'étudiant.' },
|
|
39
|
+
{ type: 'self_assessment', name: 'Auto-évaluation', description: 'L\'étudiant s\'évalue lui-même sur des critères définis.' },
|
|
40
|
+
{ type: 'presentation', name: 'Présentation', description: 'Diaporama avec slides.' },
|
|
41
|
+
{ type: 'browser', name: 'Navigateur', description: 'Intègre une page web dans un iframe.' },
|
|
42
|
+
{ type: 'dys_reader', name: 'Lecteur DYS', description: 'Texte adapté pour la dyslexie avec police et espacement spéciaux.' },
|
|
43
|
+
{ type: 'dys_image_zones', name: 'Zones d\'image DYS', description: 'Image avec zones cliquables pour la dyslexie.' },
|
|
44
|
+
{ type: 'dys_reading_practice', name: 'Pratique lecture DYS', description: 'Exercice de lecture adapté dyslexie.' },
|
|
45
|
+
{ type: 'dys_clock', name: 'Horloge DYS', description: 'Exercice de lecture de l\'heure pour la dyscalculie.' },
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
block_get: {
|
|
52
|
+
description: 'Récupérer les détails complets d\'un bloc (type, contenu, position, conditions, commentaire).',
|
|
53
|
+
inputSchema: z.object({
|
|
54
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
55
|
+
blockId: z.string().uuid().describe('ID du bloc'),
|
|
56
|
+
}),
|
|
57
|
+
handler: async ({ flowId, blockId }: { flowId: string; blockId: string }) => {
|
|
58
|
+
return client.get(`/flows/${flowId}/blocks/${blockId}`, 'eduflow');
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
block_create: {
|
|
63
|
+
description: `Créer un nouveau bloc dans un parcours EduFlow. Le bloc est ajouté mais pas encore connecté (utiliser connection_add ensuite).
|
|
64
|
+
IMPORTANT pour le type "text" : toujours utiliser content.format = "html" et du HTML dans content.text (pas de Markdown).`,
|
|
65
|
+
inputSchema: z.object({
|
|
66
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
67
|
+
type: z.enum([
|
|
68
|
+
'text', 'audio', 'quiz', 'video', 'pdf', 'image', 'loop', 'evaluation',
|
|
69
|
+
'split', 'merge', 'timer', 'start', 'end', 'chart', 'timeline',
|
|
70
|
+
'dys_reader', 'dys_image_zones', 'dys_reading_practice', 'dys_clock',
|
|
71
|
+
'glossary', 'subflow', 'mindmap', 'form', 'certification', 'email',
|
|
72
|
+
'voice_assessment', 'presentation', 'browser', 'self_assessment',
|
|
73
|
+
]).describe('Type de bloc à créer'),
|
|
74
|
+
title: z.string().describe('Titre du bloc'),
|
|
75
|
+
content: blockContentSchema,
|
|
76
|
+
positionX: z.number().optional().describe('Position X dans l\'éditeur visuel (défaut: 0)'),
|
|
77
|
+
positionY: z.number().optional().describe('Position Y dans l\'éditeur visuel (défaut: 0)'),
|
|
78
|
+
order: z.number().optional().describe('Ordre d\'affichage'),
|
|
79
|
+
}),
|
|
80
|
+
handler: async ({ flowId, ...blockData }: any) => {
|
|
81
|
+
// Use batch update to add a single block
|
|
82
|
+
const flow = await client.get(`/flows/${flowId}`, 'eduflow');
|
|
83
|
+
const existingBlocks = flow.blocks || [];
|
|
84
|
+
const newBlock = {
|
|
85
|
+
id: crypto.randomUUID(),
|
|
86
|
+
...blockData,
|
|
87
|
+
positionX: blockData.positionX ?? (existingBlocks.length * 300),
|
|
88
|
+
positionY: blockData.positionY ?? 200,
|
|
89
|
+
order: blockData.order ?? existingBlocks.length,
|
|
90
|
+
};
|
|
91
|
+
const allBlocks = [...existingBlocks.map((b: any) => ({
|
|
92
|
+
id: b.id, type: b.type, title: b.title,
|
|
93
|
+
content: b.content, positionX: b.positionX, positionY: b.positionY,
|
|
94
|
+
order: b.order, conditions: b.conditions, comment: b.comment,
|
|
95
|
+
})), newBlock];
|
|
96
|
+
await client.put(`/flows/${flowId}/blocks`, allBlocks, 'eduflow');
|
|
97
|
+
return { success: true, block: newBlock };
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
block_update: {
|
|
102
|
+
description: 'Mettre à jour un bloc existant (titre, contenu, position, conditions).',
|
|
103
|
+
inputSchema: z.object({
|
|
104
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
105
|
+
blockId: z.string().uuid().describe('ID du bloc à modifier'),
|
|
106
|
+
title: z.string().optional().describe('Nouveau titre'),
|
|
107
|
+
content: blockContentSchema.optional(),
|
|
108
|
+
positionX: z.number().optional().describe('Nouvelle position X'),
|
|
109
|
+
positionY: z.number().optional().describe('Nouvelle position Y'),
|
|
110
|
+
order: z.number().optional().describe('Nouvel ordre'),
|
|
111
|
+
conditions: z.any().optional().describe('Conditions de branchement'),
|
|
112
|
+
comment: z.string().optional().describe('Commentaire créateur'),
|
|
113
|
+
}),
|
|
114
|
+
handler: async ({ flowId, blockId, ...data }: any) => {
|
|
115
|
+
return client.put(`/flows/${flowId}/blocks/${blockId}`, data, 'eduflow');
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
block_batch_update: {
|
|
120
|
+
description: 'Mettre à jour plusieurs blocs en une seule opération. Utile pour repositionner ou réorganiser tous les blocs.',
|
|
121
|
+
inputSchema: z.object({
|
|
122
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
123
|
+
blocks: z.array(z.object({
|
|
124
|
+
id: z.string().describe('ID du bloc'),
|
|
125
|
+
type: z.string().describe('Type du bloc'),
|
|
126
|
+
title: z.string().describe('Titre'),
|
|
127
|
+
content: z.any().describe('Contenu'),
|
|
128
|
+
positionX: z.number().optional(),
|
|
129
|
+
positionY: z.number().optional(),
|
|
130
|
+
order: z.number().optional(),
|
|
131
|
+
conditions: z.any().optional(),
|
|
132
|
+
comment: z.string().optional(),
|
|
133
|
+
})).describe('Liste complète des blocs (REMPLACE tous les blocs existants)'),
|
|
134
|
+
}),
|
|
135
|
+
handler: async ({ flowId, blocks }: any) => {
|
|
136
|
+
return client.put(`/flows/${flowId}/blocks`, blocks, 'eduflow');
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
block_delete: {
|
|
141
|
+
description: 'Supprimer un bloc d\'un parcours. Les connexions liées sont automatiquement supprimées.',
|
|
142
|
+
inputSchema: z.object({
|
|
143
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
144
|
+
blockId: z.string().uuid().describe('ID du bloc à supprimer'),
|
|
145
|
+
}),
|
|
146
|
+
handler: async ({ flowId, blockId }: { flowId: string; blockId: string }) => {
|
|
147
|
+
// Get current flow, remove block, remove related edges, batch update
|
|
148
|
+
const flow = await client.get(`/flows/${flowId}`, 'eduflow');
|
|
149
|
+
const blocks = (flow.blocks || []).filter((b: any) => b.id !== blockId);
|
|
150
|
+
const edges = (flow.edges || []).filter(
|
|
151
|
+
(e: any) => e.source !== blockId && e.target !== blockId
|
|
152
|
+
);
|
|
153
|
+
const mappedBlocks = blocks.map((b: any) => ({
|
|
154
|
+
id: b.id, type: b.type, title: b.title,
|
|
155
|
+
content: b.content, positionX: b.positionX, positionY: b.positionY,
|
|
156
|
+
order: b.order, conditions: b.conditions, comment: b.comment,
|
|
157
|
+
}));
|
|
158
|
+
await client.put(`/flows/${flowId}/blocks`, mappedBlocks, 'eduflow');
|
|
159
|
+
await client.put(`/flows/${flowId}`, { edges }, 'eduflow');
|
|
160
|
+
return { success: true, remainingBlocks: blocks.length, remainingEdges: edges.length };
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
block_generate_tts: {
|
|
165
|
+
description: 'Générer l\'audio TTS (text-to-speech) pour un bloc texte. L\'audio est attaché au bloc.',
|
|
166
|
+
inputSchema: z.object({
|
|
167
|
+
flowId: z.string().uuid().describe('ID du parcours'),
|
|
168
|
+
blockId: z.string().uuid().describe('ID du bloc'),
|
|
169
|
+
}),
|
|
170
|
+
handler: async ({ flowId, blockId }: { flowId: string; blockId: string }) => {
|
|
171
|
+
return client.post(`/blocks/${blockId}/generate-tts`, { flowId }, 'eduflow');
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
};
|