@lyrra/mcp-server 1.1.2 → 1.1.5

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.
Files changed (105) hide show
  1. package/README.md +59 -242
  2. package/dist/auth-session.js +160 -0
  3. package/dist/eduflow-block-docs.js +438 -0
  4. package/dist/index.js +179 -12
  5. package/dist/lyrra-http.js +80 -0
  6. package/dist/openapi-parse.js +61 -0
  7. package/dist/register-eduflow-block-tools.js +31 -0
  8. package/package.json +36 -13
  9. package/Dockerfile +0 -16
  10. package/dist/client.d.ts +0 -23
  11. package/dist/client.d.ts.map +0 -1
  12. package/dist/client.js +0 -92
  13. package/dist/client.js.map +0 -1
  14. package/dist/config.d.ts +0 -8
  15. package/dist/config.d.ts.map +0 -1
  16. package/dist/config.js +0 -8
  17. package/dist/config.js.map +0 -1
  18. package/dist/http-server.d.ts +0 -8
  19. package/dist/http-server.d.ts.map +0 -1
  20. package/dist/http-server.js +0 -476
  21. package/dist/http-server.js.map +0 -1
  22. package/dist/index.d.ts +0 -3
  23. package/dist/index.d.ts.map +0 -1
  24. package/dist/index.js.map +0 -1
  25. package/dist/resources/block-types.d.ts +0 -318
  26. package/dist/resources/block-types.d.ts.map +0 -1
  27. package/dist/resources/block-types.js +0 -297
  28. package/dist/resources/block-types.js.map +0 -1
  29. package/dist/resources/flow-schema.d.ts +0 -147
  30. package/dist/resources/flow-schema.d.ts.map +0 -1
  31. package/dist/resources/flow-schema.js +0 -143
  32. package/dist/resources/flow-schema.js.map +0 -1
  33. package/dist/server-factory.d.ts +0 -8
  34. package/dist/server-factory.d.ts.map +0 -1
  35. package/dist/server-factory.js +0 -82
  36. package/dist/server-factory.js.map +0 -1
  37. package/dist/tools/admin.d.ts +0 -265
  38. package/dist/tools/admin.d.ts.map +0 -1
  39. package/dist/tools/admin.js +0 -118
  40. package/dist/tools/admin.js.map +0 -1
  41. package/dist/tools/ai-designer.d.ts +0 -297
  42. package/dist/tools/ai-designer.d.ts.map +0 -1
  43. package/dist/tools/ai-designer.js +0 -89
  44. package/dist/tools/ai-designer.js.map +0 -1
  45. package/dist/tools/analytics.d.ts +0 -95
  46. package/dist/tools/analytics.d.ts.map +0 -1
  47. package/dist/tools/analytics.js +0 -44
  48. package/dist/tools/analytics.js.map +0 -1
  49. package/dist/tools/auth.d.ts +0 -61
  50. package/dist/tools/auth.d.ts.map +0 -1
  51. package/dist/tools/auth.js +0 -36
  52. package/dist/tools/auth.js.map +0 -1
  53. package/dist/tools/blocks.d.ts +0 -401
  54. package/dist/tools/blocks.d.ts.map +0 -1
  55. package/dist/tools/blocks.js +0 -167
  56. package/dist/tools/blocks.js.map +0 -1
  57. package/dist/tools/connections.d.ts +0 -173
  58. package/dist/tools/connections.d.ts.map +0 -1
  59. package/dist/tools/connections.js +0 -81
  60. package/dist/tools/connections.js.map +0 -1
  61. package/dist/tools/eduflow.d.ts +0 -409
  62. package/dist/tools/eduflow.d.ts.map +0 -1
  63. package/dist/tools/eduflow.js +0 -139
  64. package/dist/tools/eduflow.js.map +0 -1
  65. package/dist/tools/participants.d.ts +0 -221
  66. package/dist/tools/participants.d.ts.map +0 -1
  67. package/dist/tools/participants.js +0 -70
  68. package/dist/tools/participants.js.map +0 -1
  69. package/dist/tools/presentation.d.ts +0 -233
  70. package/dist/tools/presentation.d.ts.map +0 -1
  71. package/dist/tools/presentation.js +0 -57
  72. package/dist/tools/presentation.js.map +0 -1
  73. package/dist/tools/projects.d.ts +0 -131
  74. package/dist/tools/projects.d.ts.map +0 -1
  75. package/dist/tools/projects.js +0 -55
  76. package/dist/tools/projects.js.map +0 -1
  77. package/dist/tools/resources.d.ts +0 -93
  78. package/dist/tools/resources.d.ts.map +0 -1
  79. package/dist/tools/resources.js +0 -37
  80. package/dist/tools/resources.js.map +0 -1
  81. package/dist/tools/store.d.ts +0 -125
  82. package/dist/tools/store.d.ts.map +0 -1
  83. package/dist/tools/store.js +0 -66
  84. package/dist/tools/store.js.map +0 -1
  85. package/mcp-config.example.json +0 -14
  86. package/src/client.ts +0 -106
  87. package/src/config.ts +0 -7
  88. package/src/http-server.ts +0 -591
  89. package/src/index.ts +0 -23
  90. package/src/resources/block-types.ts +0 -298
  91. package/src/resources/flow-schema.ts +0 -148
  92. package/src/server-factory.ts +0 -109
  93. package/src/tools/admin.ts +0 -128
  94. package/src/tools/ai-designer.ts +0 -97
  95. package/src/tools/analytics.ts +0 -49
  96. package/src/tools/auth.ts +0 -39
  97. package/src/tools/blocks.ts +0 -180
  98. package/src/tools/connections.ts +0 -83
  99. package/src/tools/eduflow.ts +0 -150
  100. package/src/tools/participants.ts +0 -77
  101. package/src/tools/presentation.ts +0 -61
  102. package/src/tools/projects.ts +0 -61
  103. package/src/tools/resources.ts +0 -41
  104. package/src/tools/store.ts +0 -67
  105. package/tsconfig.json +0 -19
@@ -0,0 +1,438 @@
1
+ /**
2
+ * MCP documentation for each EduFlow block type (aligned with the React designer).
3
+ * Types: apps/frontend/src/types/eduflow.ts, palette apps/frontend/src/lib/eduflow-designer-palette-blueprint.ts
4
+ */
5
+ export const EDUFLOW_BLOCK_COMMON = `## Shared block data (JSON model)
6
+
7
+ Each block in \`EduFlow.blocks[]\` includes:
8
+ - \`id\` (UUID), \`type\` (string, e.g. \`quiz_mcq\`), \`position\`, \`connections\` (graph in/out)
9
+ - optional \`isRequired\`, \`isLocked\` (institution-locked block), \`order\`, \`label\`, \`color\`, \`metadata\`
10
+
11
+ **Object \`data\` (\`BlockData\`)** — shared:
12
+ - \`title\`, \`description\` (rich HTML in the designer)
13
+ - \`descriptionAudioUrl\`: TTS URL for the description (if generated)
14
+ - \`content\`: text or HTML depending on type (e.g. question stem)
15
+ - \`mediaUrl\`: primary media when applicable
16
+ - \`settings\`: \`Record<string, unknown>\` — **type-specific parameters** (see per-block sections below)
17
+ - \`scoring\`: max points, pass threshold, method (\`automatic\` | \`manual\` | \`peer\` | \`ai\`), optional rubric
18
+ - \`timeLimit\` (seconds or minutes depending on screen — e.g. timer UI may use “minutes” while the field is \`timeLimit\`)
19
+ - \`attempts\`: max attempts
20
+ - \`feedback\`: \`onCorrect\`, \`onIncorrect\`, \`onPartial\`, \`showExplanation\`, \`explanation\`
21
+
22
+ **Accessibility / adaptive replacement** (text, PDF, presentation, video, browser) in the side panel:
23
+ - \`settings.accessibilityMode\`: \`auto\` | \`assist\` | \`manual\`
24
+ - \`settings.forceAccessibleGame\`: boolean
25
+
26
+ **API persistence**: blocks are saved on the EduFlow document via \`/api/eduflows/:id\` (JSON update).
27
+ `;
28
+ export const EDUFLOW_BLOCK_DOCS = [
29
+ {
30
+ id: 'start',
31
+ category: 'Structure',
32
+ label: 'Start',
33
+ summary: 'Entry point of the EduFlow path.',
34
+ configurationMd: `### Role
35
+ Marks the beginning of the flow; often unique and institution-locked.
36
+
37
+ ### Specifics
38
+ No required \`settings\`. Use title/description as the designer cue.
39
+
40
+ ### Graph
41
+ One or more outputs to the following blocks.`,
42
+ },
43
+ {
44
+ id: 'end',
45
+ category: 'Structure',
46
+ label: 'End',
47
+ summary: 'Path completion / exit.',
48
+ configurationMd: `### Role
49
+ Ends the path (certificate, wrap-up, exit).
50
+
51
+ ### Specifics
52
+ No mandatory technical fields in the generic designer.
53
+
54
+ ### Graph
55
+ Input(s) from the main path or branches.`,
56
+ },
57
+ {
58
+ id: 'subflow',
59
+ category: 'Structure',
60
+ label: 'Subflow',
61
+ summary: 'Reference or embed another EduFlow.',
62
+ configurationMd: `### Role
63
+ Chains a submodule (another course or reusable segment).
64
+
65
+ ### Specifics
66
+ Dedicated \`SubflowBlockEditor\`: link to an existing EduFlow (id, embed options). Exact \`settings\` fields depend on the editor (target flow selection, etc.).`,
67
+ },
68
+ {
69
+ id: 'loop',
70
+ category: 'Structure',
71
+ label: 'Loop',
72
+ summary: 'Repeat a segment based on a condition.',
73
+ configurationMd: `### settings
74
+ - \`loopCondition\`: \`count\` | \`score\` | \`completion\`
75
+ - \`maxIterations\`: integer (1–100, default 3)
76
+
77
+ ### Role
78
+ Revisit a set of blocks until a criterion is met.`,
79
+ },
80
+ {
81
+ id: 'timer',
82
+ category: 'Structure',
83
+ label: 'Timer',
84
+ summary: 'Time limit for a step.',
85
+ configurationMd: `### data.timeLimit
86
+ Shown in **minutes** in the designer UI (integer ≥ 1).
87
+
88
+ > Note: the side panel may use a \`duration\` field on other screens; the main canvas uses \`timeLimit\`.`,
89
+ },
90
+ {
91
+ id: 'summary',
92
+ category: 'Structure',
93
+ label: 'Summary',
94
+ summary: 'Recap for the learner.',
95
+ configurationMd: `### Role
96
+ Consolidation block (structured text, key points).
97
+
98
+ ### Specifics
99
+ \`SummaryBlockEditor\`: rich content and internal options in \`settings\` / \`content\` per editor.`,
100
+ },
101
+ {
102
+ id: 'form',
103
+ category: 'Structure',
104
+ label: 'Form',
105
+ summary: 'Structured data collection (dynamic fields).',
106
+ configurationMd: `### settings.formFields
107
+ Array of field objects: \`id\`, \`type\` (\`text\` | \`textarea\` | \`email\` | \`number\` | \`select\` | \`checkbox\` | \`radio\` | \`date\` | \`toggle\` | \`subtitle\` | \`file\`), \`label\`, \`placeholder\`, \`required\`, \`options\` (for select/radio), \`defaultValue\`, \`min\` / \`max\`, \`maxLength\`, \`description\`, \`autoFill\` (\`email\` | \`firstName\` | \`lastName\` | \`fullName\`).
108
+
109
+ ### Conditional rules
110
+ \`settings.formRules\`: \`if\` / \`elseif\` / \`else\` rules with conditions on fields.
111
+
112
+ ### data
113
+ Title, description; optional description audio if enabled on the flow.`,
114
+ },
115
+ {
116
+ id: 'split',
117
+ category: 'Structure',
118
+ label: 'Split (branch)',
119
+ summary: 'Multiple optional paths.',
120
+ configurationMd: `### settings
121
+ - \`splitTitle\`: label shown (e.g. “Choose your path”)
122
+ - \`numOutputs\`: number of branches (2–10)
123
+
124
+ ### Graph
125
+ As many outputs as \`numOutputs\`.`,
126
+ },
127
+ {
128
+ id: 'merge',
129
+ category: 'Structure',
130
+ label: 'Merge',
131
+ summary: 'Join multiple branches.',
132
+ configurationMd: `### Role
133
+ Convergence after a \`split\` or parallel paths.
134
+
135
+ ### Specifics
136
+ No dedicated fields in the generic UI; title/description for the designer.`,
137
+ },
138
+ {
139
+ id: 'email',
140
+ category: 'Structure',
141
+ label: 'Email',
142
+ summary: 'Send email (template) in the flow.',
143
+ configurationMd: `### settings
144
+ - \`emailSubject\`: subject line
145
+ - \`emailTemplate\`: HTML body (placeholders e.g. \`{{name}}\`)`,
146
+ },
147
+ {
148
+ id: 'text',
149
+ category: 'Content',
150
+ label: 'Text',
151
+ summary: 'Rich content: article, instructions.',
152
+ configurationMd: `### data.content
153
+ Body HTML (rich editor).
154
+
155
+ ### settings
156
+ Typography / layout per \`TextBlockEditor\` (fonts, line height, etc. when exposed).`,
157
+ },
158
+ {
159
+ id: 'video',
160
+ category: 'Content',
161
+ label: 'Video',
162
+ summary: 'Video player (upload, YouTube, Vimeo, URL).',
163
+ configurationMd: `### Source
164
+ \`settings\` control source type (\`upload\` | \`youtube\` | \`vimeo\` | \`url\`), IDs or URL, file metadata.
165
+
166
+ ### Appearance
167
+ Player theme: \`app\` | \`cinema\` | \`glass\` | \`editorial\`; accent color; ratio \`16:9\` | \`21:9\` | \`4:3\` | \`1:1\`; corners \`soft\` | \`rounded\` | \`square\`; shadow \`soft\` | \`glow\` | \`dramatic\`.
168
+
169
+ ### Subtitles
170
+ Tracks per language (fr, en, es, de, it) per editor.
171
+
172
+ See \`apps/frontend/src/lib/video-block.ts\` for exported keys (\`getVideoBlockConfig\`).`,
173
+ },
174
+ {
175
+ id: 'podcast',
176
+ category: 'Content',
177
+ label: 'Podcast',
178
+ summary: 'Long-form podcast audio (episode / feed).',
179
+ configurationMd: `### Role
180
+ Long audio playback linked to the podcasts module when configured.
181
+
182
+ ### data / settings
183
+ Media URL, episode metadata in \`settings\` per block editor (series title, episode, artwork).`,
184
+ },
185
+ {
186
+ id: 'audio',
187
+ category: 'Content',
188
+ label: 'Audio',
189
+ summary: 'Audio track (outside podcast module).',
190
+ configurationMd: `### Role
191
+ Simple audio playback (uploaded file or URL).
192
+
193
+ ### data / settings
194
+ \`mediaUrl\` or upload; \`AudioBlockEditor\`: default volume, loop, waveform, subtitles/transcript when available.`,
195
+ },
196
+ {
197
+ id: 'image',
198
+ category: 'Content',
199
+ label: 'Image',
200
+ summary: 'Static or responsive image.',
201
+ configurationMd: `### data.mediaUrl or upload
202
+ Image file; \`settings\`: caption, alt, size, clickable link per \`ImageBlockEditor\`.`,
203
+ },
204
+ {
205
+ id: 'pdf',
206
+ category: 'Content',
207
+ label: 'PDF',
208
+ summary: 'Embedded PDF document.',
209
+ configurationMd: `### Role
210
+ Display / download document.
211
+
212
+ ### data / settings
213
+ PDF URL, toolbar options, default page, embed mode (per \`PDFBlockEditor\`).`,
214
+ },
215
+ {
216
+ id: 'presentation',
217
+ category: 'Content',
218
+ label: 'Presentation',
219
+ summary: 'Slide deck.',
220
+ configurationMd: `### settings
221
+ Slides: array of objects (title, content, media) managed by \`PresentationBlockEditor\`.`,
222
+ },
223
+ {
224
+ id: 'browser',
225
+ category: 'Content',
226
+ label: 'Embedded browser',
227
+ summary: 'iframe / webview to an external URL.',
228
+ configurationMd: `### settings
229
+ Allowed URL, sandbox flags, height — via \`BrowserBlockEditor\`.`,
230
+ },
231
+ {
232
+ id: 'chart',
233
+ category: 'Content',
234
+ label: 'Chart',
235
+ summary: 'Chart (bars, line, etc.).',
236
+ configurationMd: `### settings
237
+ Dataset (series, labels), chart type, colors — \`ChartBlockEditor\`.`,
238
+ },
239
+ {
240
+ id: 'timeline',
241
+ category: 'Content',
242
+ label: 'Timeline',
243
+ summary: 'Dated events on a line.',
244
+ configurationMd: `### settings
245
+ Event list: date, title, description, media — \`TimelineBlockEditor\`.`,
246
+ },
247
+ {
248
+ id: 'mindmap',
249
+ category: 'Content',
250
+ label: 'Mind map',
251
+ summary: 'Hierarchical nodes and links.',
252
+ configurationMd: `### settings
253
+ Tree structure (root node, children, styles) — \`MindmapBlockEditor\`.`,
254
+ },
255
+ {
256
+ id: 'visual_ai',
257
+ category: 'Content',
258
+ label: 'AI visual',
259
+ summary: 'AI-generated image or media.',
260
+ configurationMd: `### settings
261
+ Prompt, style, provider, resulting image (URL or media id) — \`VisualAiBlockEditor\` / related modals.`,
262
+ },
263
+ {
264
+ id: 'quiz_mcq',
265
+ category: 'Interactive',
266
+ label: 'MCQ / quiz',
267
+ summary: 'Multiple choice, true/false, or ordering.',
268
+ configurationMd: `### Side panel (\`QuizBlockEditor\`)
269
+ - \`settings.quizType\`: \`mcq\` | \`true_false\` | \`ordering\`
270
+ - \`data.content\`: question stem (HTML/text)
271
+ - \`settings.answers\`: array \`{ id, text, isCorrect }\` (MCQ, max 10 answers)
272
+ - \`settings.orderingItems\`: array \`{ id, text, order }\` (ordering mode)
273
+ - \`settings.shuffleAnswers\`: shuffle options
274
+ - \`settings.correctAnswer\`: boolean (T/F mode)
275
+ - \`settings.pointsPerCorrect\`, \`settings.partialScoring\`
276
+ - \`data.attempts\`: max attempts
277
+ - \`data.feedback\`: correct/incorrect messages, \`showExplanation\`, \`explanation\`
278
+
279
+ ### Canvas panel (extended MCQ)
280
+ - \`settings.optionA\` … \`optionD\`: answer texts
281
+ - \`settings.correctAnswer\`: \`A\` | \`B\` | \`C\` | \`D\`
282
+ - \`settings.branchingMode\`: \`none\` | \`correct_incorrect\` | \`per_answer\` (graph branching)
283
+ - \`settings.feedback\`: message after answer`,
284
+ },
285
+ {
286
+ id: 'quiz_open',
287
+ category: 'Interactive',
288
+ label: 'Open question',
289
+ summary: 'Free-text answer.',
290
+ configurationMd: `### Role
291
+ Learner enters a long answer; manual or AI grading per product settings.
292
+
293
+ ### data
294
+ \`content\`: prompt. \`settings\`: criteria, max length, rubric when present in UI.`,
295
+ },
296
+ {
297
+ id: 'phrase_reveal',
298
+ category: 'Interactive',
299
+ label: 'Phrase reveal',
300
+ summary: 'Words or chunks revealed progressively.',
301
+ configurationMd: `### settings / content
302
+ Base text, splitting, animation — \`PhraseRevealBlockEditor\`.`,
303
+ },
304
+ {
305
+ id: 'self_assessment',
306
+ category: 'Interactive',
307
+ label: 'Self-assessment',
308
+ summary: 'Self-rated criteria grid.',
309
+ configurationMd: `### settings.criteria
310
+ Array \`EduFlowSelfAssessmentCriterion\`: \`id\`, \`label\`, \`description\`, \`badgeOptions\` (if empty → free text per criterion).
311
+
312
+ ### Global response type
313
+ \`settings.responseType\`: \`numeric\` | \`stars\` | \`text\` (see \`eduflow.ts\`).`,
314
+ },
315
+ {
316
+ id: 'voice_assessment',
317
+ category: 'Interactive',
318
+ label: 'Voice assessment',
319
+ summary: 'Pronunciation / recitation vs a reference.',
320
+ configurationMd: `### settings
321
+ - \`referenceText\`: text to recite
322
+ - \`minScore\`: minimum score (0–100%, default 70)`,
323
+ },
324
+ {
325
+ id: 'evaluation',
326
+ category: 'Interactive',
327
+ label: 'Evaluation / grading',
328
+ summary: 'Structured grading rubric.',
329
+ configurationMd: `### data.scoring
330
+ \`maxPoints\`, \`passingScore\`, \`scoringMethod\`, \`rubric\` (criteria, levels, points).
331
+
332
+ ### settings
333
+ Extra fields in \`EvaluationBlockEditor\` (scales, competencies).`,
334
+ },
335
+ {
336
+ id: 'poll',
337
+ category: 'Interactive',
338
+ label: 'Poll',
339
+ summary: 'Ungraded vote or opinion.',
340
+ configurationMd: `### settings
341
+ - \`pollQuestion\`: question
342
+ - \`pollOptions\`: multiline text, **one option per line**
343
+ - \`showLiveResults\`: live results`,
344
+ },
345
+ {
346
+ id: 'dys_reader',
347
+ category: 'Accessibility',
348
+ label: 'DYS reader',
349
+ summary: 'Text with reading aids (syllables, TTS).',
350
+ configurationMd: `### settings
351
+ - \`dysText\`: HTML shown
352
+ - \`ttsEnabled\`: auto audio playback`,
353
+ },
354
+ {
355
+ id: 'dys_reading_practice',
356
+ category: 'Accessibility',
357
+ label: 'DYS reading practice',
358
+ summary: 'Guided reading exercise.',
359
+ configurationMd: `### settings
360
+ Same as dys_reader: \`dysText\`, \`ttsEnabled\`.`,
361
+ },
362
+ {
363
+ id: 'dys_image_zones',
364
+ category: 'Accessibility',
365
+ label: 'DYS image zones',
366
+ summary: 'Clickable zones on an image for reading support.',
367
+ configurationMd: `### settings
368
+ Zone definitions (coordinates, labels, audio) per DYS UI; often JSON in \`settings\`.`,
369
+ },
370
+ {
371
+ id: 'dys_clock',
372
+ category: 'Accessibility',
373
+ label: 'DYS teaching clock',
374
+ summary: 'Adapted time representation.',
375
+ configurationMd: `### settings
376
+ Display format, speed, visual aids (see DYS editor).`,
377
+ },
378
+ {
379
+ id: 'glossary',
380
+ category: 'Accessibility',
381
+ label: 'Glossary',
382
+ summary: 'Terms and definitions in the path.',
383
+ configurationMd: `### settings.termsJson
384
+ JSON string: array \`{ term, definition, quiz?: boolean }\`.`,
385
+ },
386
+ {
387
+ id: 'html',
388
+ category: 'Enterprise',
389
+ label: 'Embedded HTML / JS',
390
+ summary: 'Controlled HTML or script snippet.',
391
+ configurationMd: `### settings.htmlCode
392
+ HTML (and possibly inline JS per security policy). Use with care (XSS).`,
393
+ },
394
+ {
395
+ id: 'certification',
396
+ category: 'Enterprise',
397
+ label: 'Certification (document)',
398
+ summary: 'Generate a certificate from a Word template.',
399
+ configurationMd: `### settings
400
+ - \`templateUrl\`: URL of the .docx template
401
+ - \`variablesJson\`: JSON mapping placeholders → learner fields (\`"{{NAME}}": "learner.lastName"\`, etc.)`,
402
+ },
403
+ {
404
+ id: 'signature',
405
+ category: 'Enterprise',
406
+ label: 'Signature',
407
+ summary: 'Handwritten or electronic signature.',
408
+ configurationMd: `### settings
409
+ Signature canvas config, legal wording, consent text — per block editor.`,
410
+ },
411
+ {
412
+ id: 'facial_recognition',
413
+ category: 'Enterprise',
414
+ label: 'Facial recognition',
415
+ summary: 'Face-based identity check.',
416
+ configurationMd: `### settings
417
+ Confidence threshold, GDPR consent, error messages — per implementation and AWS Rekognition on the backend.`,
418
+ },
419
+ ];
420
+ const byId = new Map(EDUFLOW_BLOCK_DOCS.map((e) => [e.id, e]));
421
+ export function renderEduflowBlockMarkdown(typeId) {
422
+ const e = byId.get(typeId);
423
+ if (!e) {
424
+ return `# Unknown block (\`${typeId}\`)\n\nNo dedicated sheet. See \`lyrra_eduflow_blocks_index\` and \`BlockType\` in the frontend.\n\n${EDUFLOW_BLOCK_COMMON}`;
425
+ }
426
+ return `# ${e.label} — type \`${e.id}\`
427
+
428
+ **Category:** ${e.category}
429
+
430
+ ${e.configurationMd}
431
+
432
+ ---
433
+
434
+ ${EDUFLOW_BLOCK_COMMON}`;
435
+ }
436
+ export function eduflowBlockToolName(typeId) {
437
+ return `lyrra_eduflow_block_${typeId.replace(/[^a-zA-Z0-9_]/g, '_')}`;
438
+ }
package/dist/index.js CHANGED
@@ -1,21 +1,188 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * Lyrra Studio MCP stdio server.
4
+ * Run: `npm run build && npm start` in apps/mcp-server (or `npm run dev`).
5
+ */
6
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
7
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { config } from './config.js';
4
- import { createMcpServer } from './server-factory.js';
5
- // --- Start server (stdio mode) ---
8
+ import { z } from 'zod';
9
+ import { getLyrraRequestAuthHeaders, requestOrigin } from './auth-session.js';
10
+ import { callLyrraOperation } from './lyrra-http.js';
11
+ import { fetchOpenApiJson, parseOpenApiOperations } from './openapi-parse.js';
12
+ import { eduflowBlockDocToolCount, registerEduflowBlockDocumentationTools, } from './register-eduflow-block-tools.js';
13
+ const MAX_DESC_CHARS = 12_000;
14
+ const MAX_TOOLS_ENV = process.env.LYRRA_MCP_MAX_TOOLS;
15
+ function truncateDesc(s) {
16
+ if (s.length <= MAX_DESC_CHARS)
17
+ return s;
18
+ return `${s.slice(0, MAX_DESC_CHARS)}\n\n[… truncated description — see OpenAPI / Swagger]`;
19
+ }
20
+ function zodShapeForOperation(op) {
21
+ const shape = {
22
+ query: z
23
+ .record(z.string(), z.unknown())
24
+ .optional()
25
+ .describe('Query parameters (key → value). Serialize complex values as JSON in a key if needed.'),
26
+ body: z
27
+ .unknown()
28
+ .optional()
29
+ .describe('JSON body (object or array) for POST, PUT, PATCH. Ignored for GET/DELETE.'),
30
+ };
31
+ for (const p of op.pathParamNames) {
32
+ shape[p] = z
33
+ .string()
34
+ .describe(`Value for path segment "${p}" in ${op.path}`);
35
+ }
36
+ return shape;
37
+ }
38
+ function formatToolResult(result) {
39
+ const snippet = result.bodyText.length > 120_000 ? `${result.bodyText.slice(0, 120_000)}\n… [truncated]` : result.bodyText;
40
+ const text = [
41
+ `HTTP ${result.status}`,
42
+ result.headers['content-type'] ? `Content-Type: ${result.headers['content-type']}` : '',
43
+ '',
44
+ snippet || '(empty body)',
45
+ ]
46
+ .filter(Boolean)
47
+ .join('\n');
48
+ return {
49
+ content: [{ type: 'text', text }],
50
+ isError: result.status >= 400,
51
+ };
52
+ }
6
53
  async function main() {
7
- if (!config.clientSecret) {
8
- console.error('⚠️ LYRRA_CLIENT_SECRET non défini. Définissez la variable d\'environnement LYRRA_CLIENT_SECRET.');
54
+ process.stderr.write('[lyrra-mcp] Loading OpenAPI…\n');
55
+ const spec = await fetchOpenApiJson();
56
+ let ops = parseOpenApiOperations(spec);
57
+ const maxTools = MAX_TOOLS_ENV ? parseInt(MAX_TOOLS_ENV, 10) : NaN;
58
+ if (!Number.isNaN(maxTools) && maxTools > 0) {
59
+ ops = ops.slice(0, maxTools);
60
+ process.stderr.write(`[lyrra-mcp] LYRRA_MCP_MAX_TOOLS=${maxTools} — ${ops.length} tools registered.\n`);
61
+ }
62
+ process.stderr.write(`[lyrra-mcp] ${ops.length} operations → MCP tools.\n`);
63
+ const mcp = new McpServer({
64
+ name: 'lyrra-studio',
65
+ version: '1.0.0',
66
+ title: 'Lyrra Studio',
67
+ });
68
+ registerEduflowBlockDocumentationTools(mcp);
69
+ process.stderr.write(`[lyrra-mcp] EduFlow docs: ${eduflowBlockDocToolCount()} tools (index + per-block sheets).\n`);
70
+ mcp.registerTool('lyrra_meta', {
71
+ description: 'Configuration summary: API origin, registered tool count, useful environment variables (no secrets shown).',
72
+ inputSchema: z.object({}),
73
+ }, async () => {
74
+ let tokenMode = 'CLIENT_ID+SECRET';
75
+ if (process.env.LYRRA_ACCESS_TOKEN?.trim())
76
+ tokenMode = 'ACCESS_TOKEN';
77
+ const hn = process.env.LYRRA_MCP_HEADER_NAME?.trim() || process.env.LYRRA_HEADER_AUTH_NAME?.trim();
78
+ const hv = process.env.LYRRA_MCP_HEADER_SECRET?.trim() ||
79
+ process.env.LYRRA_MCP_HEADER_VALUE?.trim() ||
80
+ process.env.LYRRA_HEADER_AUTH_VALUE?.trim();
81
+ if (hn && hv)
82
+ tokenMode = 'HEADER_AUTH';
83
+ const text = [
84
+ `Request origin: ${requestOrigin()}`,
85
+ `OpenAPI tools registered: ${ops.length}`,
86
+ `EduFlow block doc tools: ${eduflowBlockDocToolCount()} (lyrra_eduflow_blocks_index + lyrra_eduflow_block_*)`,
87
+ `Token mode: ${tokenMode}`,
88
+ `LYRRA_API_URL: ${process.env.LYRRA_API_URL ? 'set' : 'missing'}`,
89
+ `LYRRA_OPENAPI_URL: ${process.env.LYRRA_OPENAPI_URL ? 'set' : 'default /api/openapi.json'}`,
90
+ ].join('\n');
91
+ return { content: [{ type: 'text', text }] };
92
+ });
93
+ mcp.registerTool('lyrra_search_operations', {
94
+ description: 'Search operations (operationId, method, path, start of description). Useful when the tool list is long.',
95
+ inputSchema: z.object({
96
+ q: z.string().min(1).describe('Search text (case-insensitive)'),
97
+ limit: z.number().int().min(1).max(80).optional().describe('Max results (default 30)'),
98
+ }),
99
+ }, async ({ q, limit }) => {
100
+ const lim = limit ?? 30;
101
+ const qq = q.toLowerCase();
102
+ const hits = ops
103
+ .filter((o) => o.operationId.toLowerCase().includes(qq) ||
104
+ o.path.toLowerCase().includes(qq) ||
105
+ o.method.toLowerCase().includes(qq) ||
106
+ o.description.toLowerCase().includes(qq))
107
+ .slice(0, lim);
108
+ const text = hits.length === 0
109
+ ? 'No operations found.'
110
+ : hits.map((o) => `${o.method} ${o.path}\n operationId: ${o.operationId}`).join('\n\n');
111
+ return { content: [{ type: 'text', text }] };
112
+ });
113
+ const staticGuide = `# Lyrra Studio — MCP integration
114
+
115
+ - Each tool named after an OpenAPI **operationId** calls **exactly** the documented endpoint.
116
+ - **EduFlow blocks**: tools \`lyrra_eduflow_blocks_index\` then \`lyrra_eduflow_block_<type>\` (e.g. \`lyrra_eduflow_block_quiz_mcq\`) — full sheet: role, \`data\`, \`settings\`, graph.
117
+ - API args: path segments \`{id}\` → required properties of the same name; \`query\`; \`body\` (JSON).
118
+ - Auth: \`LYRRA_CLIENT_ID\` + \`LYRRA_CLIENT_SECRET\` (\`rak_…\`), or \`LYRRA_ACCESS_TOKEN\` (JWT).
119
+ `;
120
+ mcp.registerResource('lyrra-mcp-guide', 'lyrra://flow-construction-guide', {
121
+ description: 'How to use the Lyrra MCP server',
122
+ mimeType: 'text/markdown',
123
+ }, async () => ({
124
+ contents: [{ uri: 'lyrra://flow-construction-guide', mimeType: 'text/markdown', text: staticGuide }],
125
+ }));
126
+ mcp.registerResource('lyrra-block-types', 'lyrra://block-types', {
127
+ description: 'Points to MCP tools lyrra_eduflow_block_* (per-type sheet) + persistence via eduflows API',
128
+ mimeType: 'text/markdown',
129
+ }, async () => ({
130
+ contents: [
131
+ {
132
+ uri: 'lyrra://block-types',
133
+ mimeType: 'text/markdown',
134
+ text: [
135
+ '# EduFlow block types (MCP)',
136
+ '',
137
+ `1. Call **lyrra_eduflow_blocks_index** for documented types (${eduflowBlockDocToolCount() - 1} blocks).`,
138
+ '2. Call **lyrra_eduflow_block_<type>** (e.g. `lyrra_eduflow_block_video`) for the full sheet: pedagogical role, `BlockData`, `settings`, connections.',
139
+ '3. HTTP persistence: OpenAPI routes under `/api/eduflows/{id}/blocks` and flow save.',
140
+ '',
141
+ 'Code reference: `apps/frontend/src/types/eduflow.ts`, palette `eduflow-designer-palette-blueprint.ts`.',
142
+ ].join('\n'),
143
+ },
144
+ ],
145
+ }));
146
+ for (const op of ops) {
147
+ const inputSchema = z.object(zodShapeForOperation(op));
148
+ const desc = truncateDesc(`${op.description}\n\n— HTTP ${op.method} \`${op.path}\``);
149
+ mcp.registerTool(op.operationId, {
150
+ description: desc,
151
+ inputSchema,
152
+ }, async (rawArgs) => {
153
+ const args = rawArgs;
154
+ const pathParams = {};
155
+ for (const name of op.pathParamNames) {
156
+ const v = args[name];
157
+ pathParams[name] = typeof v === 'string' ? v : String(v ?? '');
158
+ }
159
+ const query = args.query;
160
+ const body = args.body;
161
+ try {
162
+ const result = await callLyrraOperation(op.method, op.path, { pathParams, query, body });
163
+ return formatToolResult(result);
164
+ }
165
+ catch (e) {
166
+ const msg = e instanceof Error ? e.message : String(e);
167
+ return {
168
+ content: [{ type: 'text', text: `Error: ${msg}` }],
169
+ isError: true,
170
+ };
171
+ }
172
+ });
173
+ }
174
+ // Resolve credentials early (clear message if .env is incomplete)
175
+ try {
176
+ await getLyrraRequestAuthHeaders();
177
+ process.stderr.write('[lyrra-mcp] API credentials OK.\n');
9
178
  }
10
- else {
11
- console.error('✅ LYRRA Studio MCP connecté (authentification par API Key).');
179
+ catch (e) {
180
+ process.stderr.write(`[lyrra-mcp] Auth warning: ${e instanceof Error ? e.message : e}\n`);
12
181
  }
13
- const server = createMcpServer();
14
182
  const transport = new StdioServerTransport();
15
- await server.connect(transport);
183
+ await mcp.connect(transport);
16
184
  }
17
- main().catch((error) => {
18
- console.error('Erreur fatale:', error);
185
+ main().catch((e) => {
186
+ process.stderr.write(`[lyrra-mcp] Fatal: ${e instanceof Error ? e.stack ?? e.message : e}\n`);
19
187
  process.exit(1);
20
188
  });
21
- //# sourceMappingURL=index.js.map