@lyrra/mcp-server 1.1.3 → 1.1.7

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 (108) hide show
  1. package/README.md +80 -250
  2. package/dist/auth-session.js +171 -0
  3. package/dist/eduflow-block-docs.js +438 -0
  4. package/dist/http-incoming-auth.js +48 -0
  5. package/dist/http-main.js +104 -0
  6. package/dist/index.js +16 -12
  7. package/dist/lyrra-http.js +80 -0
  8. package/dist/lyrra-mcp-core.js +174 -0
  9. package/dist/openapi-parse.js +61 -0
  10. package/dist/register-eduflow-block-tools.js +31 -0
  11. package/package.json +41 -13
  12. package/Dockerfile +0 -16
  13. package/dist/client.d.ts +0 -23
  14. package/dist/client.d.ts.map +0 -1
  15. package/dist/client.js +0 -92
  16. package/dist/client.js.map +0 -1
  17. package/dist/config.d.ts +0 -8
  18. package/dist/config.d.ts.map +0 -1
  19. package/dist/config.js +0 -8
  20. package/dist/config.js.map +0 -1
  21. package/dist/http-server.d.ts +0 -8
  22. package/dist/http-server.d.ts.map +0 -1
  23. package/dist/http-server.js +0 -481
  24. package/dist/http-server.js.map +0 -1
  25. package/dist/index.d.ts +0 -3
  26. package/dist/index.d.ts.map +0 -1
  27. package/dist/index.js.map +0 -1
  28. package/dist/resources/block-types.d.ts +0 -318
  29. package/dist/resources/block-types.d.ts.map +0 -1
  30. package/dist/resources/block-types.js +0 -297
  31. package/dist/resources/block-types.js.map +0 -1
  32. package/dist/resources/flow-schema.d.ts +0 -147
  33. package/dist/resources/flow-schema.d.ts.map +0 -1
  34. package/dist/resources/flow-schema.js +0 -143
  35. package/dist/resources/flow-schema.js.map +0 -1
  36. package/dist/server-factory.d.ts +0 -8
  37. package/dist/server-factory.d.ts.map +0 -1
  38. package/dist/server-factory.js +0 -82
  39. package/dist/server-factory.js.map +0 -1
  40. package/dist/tools/admin.d.ts +0 -265
  41. package/dist/tools/admin.d.ts.map +0 -1
  42. package/dist/tools/admin.js +0 -118
  43. package/dist/tools/admin.js.map +0 -1
  44. package/dist/tools/ai-designer.d.ts +0 -297
  45. package/dist/tools/ai-designer.d.ts.map +0 -1
  46. package/dist/tools/ai-designer.js +0 -89
  47. package/dist/tools/ai-designer.js.map +0 -1
  48. package/dist/tools/analytics.d.ts +0 -95
  49. package/dist/tools/analytics.d.ts.map +0 -1
  50. package/dist/tools/analytics.js +0 -44
  51. package/dist/tools/analytics.js.map +0 -1
  52. package/dist/tools/auth.d.ts +0 -61
  53. package/dist/tools/auth.d.ts.map +0 -1
  54. package/dist/tools/auth.js +0 -36
  55. package/dist/tools/auth.js.map +0 -1
  56. package/dist/tools/blocks.d.ts +0 -457
  57. package/dist/tools/blocks.d.ts.map +0 -1
  58. package/dist/tools/blocks.js +0 -173
  59. package/dist/tools/blocks.js.map +0 -1
  60. package/dist/tools/connections.d.ts +0 -173
  61. package/dist/tools/connections.d.ts.map +0 -1
  62. package/dist/tools/connections.js +0 -81
  63. package/dist/tools/connections.js.map +0 -1
  64. package/dist/tools/eduflow.d.ts +0 -409
  65. package/dist/tools/eduflow.d.ts.map +0 -1
  66. package/dist/tools/eduflow.js +0 -139
  67. package/dist/tools/eduflow.js.map +0 -1
  68. package/dist/tools/participants.d.ts +0 -221
  69. package/dist/tools/participants.d.ts.map +0 -1
  70. package/dist/tools/participants.js +0 -70
  71. package/dist/tools/participants.js.map +0 -1
  72. package/dist/tools/presentation.d.ts +0 -233
  73. package/dist/tools/presentation.d.ts.map +0 -1
  74. package/dist/tools/presentation.js +0 -57
  75. package/dist/tools/presentation.js.map +0 -1
  76. package/dist/tools/projects.d.ts +0 -131
  77. package/dist/tools/projects.d.ts.map +0 -1
  78. package/dist/tools/projects.js +0 -55
  79. package/dist/tools/projects.js.map +0 -1
  80. package/dist/tools/resources.d.ts +0 -93
  81. package/dist/tools/resources.d.ts.map +0 -1
  82. package/dist/tools/resources.js +0 -37
  83. package/dist/tools/resources.js.map +0 -1
  84. package/dist/tools/store.d.ts +0 -125
  85. package/dist/tools/store.d.ts.map +0 -1
  86. package/dist/tools/store.js +0 -66
  87. package/dist/tools/store.js.map +0 -1
  88. package/mcp-config.example.json +0 -14
  89. package/src/client.ts +0 -106
  90. package/src/config.ts +0 -7
  91. package/src/http-server.ts +0 -591
  92. package/src/index.ts +0 -23
  93. package/src/resources/block-types.ts +0 -298
  94. package/src/resources/flow-schema.ts +0 -148
  95. package/src/server-factory.ts +0 -109
  96. package/src/tools/admin.ts +0 -128
  97. package/src/tools/ai-designer.ts +0 -97
  98. package/src/tools/analytics.ts +0 -49
  99. package/src/tools/auth.ts +0 -39
  100. package/src/tools/blocks.ts +0 -186
  101. package/src/tools/connections.ts +0 -83
  102. package/src/tools/eduflow.ts +0 -150
  103. package/src/tools/participants.ts +0 -77
  104. package/src/tools/presentation.ts +0 -61
  105. package/src/tools/projects.ts +0 -61
  106. package/src/tools/resources.ts +0 -41
  107. package/src/tools/store.ts +0 -67
  108. 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
+ }
@@ -0,0 +1,48 @@
1
+ function single(h) {
2
+ if (h === undefined)
3
+ return undefined;
4
+ return Array.isArray(h) ? h[0] : h;
5
+ }
6
+ const HOP_BY_HOP = new Set([
7
+ 'connection',
8
+ 'keep-alive',
9
+ 'proxy-authenticate',
10
+ 'proxy-authorization',
11
+ 'te',
12
+ 'trailers',
13
+ 'transfer-encoding',
14
+ 'upgrade',
15
+ 'host',
16
+ 'content-length',
17
+ ]);
18
+ /**
19
+ * En-têtes d’auth reçus sur le MCP HTTP à réutiliser vers l’API Lyrra.
20
+ * n8n « Header Auth » envoie en général X-Lyrra-Api-Key (ou Authorization).
21
+ */
22
+ export function pickIncomingAuthHeaders(headers) {
23
+ const out = {};
24
+ const auth = single(headers['authorization']);
25
+ if (auth)
26
+ out.Authorization = auth;
27
+ const lyrra = single(headers['x-lyrra-api-key']);
28
+ if (lyrra)
29
+ out['X-Lyrra-Api-Key'] = lyrra;
30
+ const xak = single(headers['x-api-key']);
31
+ if (xak)
32
+ out['X-Api-Key'] = xak;
33
+ const extra = process.env.LYRRA_MCP_EXTRA_INBOUND_HEADERS?.split(',') ?? [];
34
+ for (const raw of extra) {
35
+ const display = raw.trim();
36
+ if (!display)
37
+ continue;
38
+ const lower = display.toLowerCase();
39
+ if (HOP_BY_HOP.has(lower))
40
+ continue;
41
+ const val = single(headers[lower]);
42
+ if (val)
43
+ out[display] = val;
44
+ }
45
+ if (Object.keys(out).length === 0)
46
+ return null;
47
+ return out;
48
+ }
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Streamable HTTP — pour n8n / clients qui attendent une URL https://…/mcp
4
+ * (Header Auth : X-Lyrra-Api-Key ou Authorization, aligné tableau de bord institution).
5
+ */
6
+ import express from 'express';
7
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
8
+ import { createLyrraMcpServer } from './lyrra-mcp-core.js';
9
+ import { runWithInboundAuthHeaders, requestOrigin } from './auth-session.js';
10
+ import { pickIncomingAuthHeaders } from './http-incoming-auth.js';
11
+ const PORT = parseInt(process.env.LYRRA_MCP_HTTP_PORT ?? '3457', 10);
12
+ let mcpSingleton = null;
13
+ async function getMcp() {
14
+ mcpSingleton ??= createLyrraMcpServer().then((r) => r.mcp);
15
+ return mcpSingleton;
16
+ }
17
+ /** Sérialise les requêtes MCP : un seul transport à la fois sur l’instance McpServer. */
18
+ let mutexChain = Promise.resolve();
19
+ function enqueueMcp(fn) {
20
+ const next = mutexChain.then(() => fn());
21
+ mutexChain = next.then(() => undefined, () => undefined);
22
+ return next;
23
+ }
24
+ async function validateAuth(headers) {
25
+ const origin = requestOrigin();
26
+ const url = `${origin}/api/auth/me`;
27
+ const r = await fetch(url, {
28
+ headers: { Accept: 'application/json', ...headers },
29
+ });
30
+ return r.ok;
31
+ }
32
+ const app = express();
33
+ app.disable('x-powered-by');
34
+ app.use('/mcp', express.json({ limit: '50mb' }));
35
+ app.all('/mcp', async (req, res) => {
36
+ const auth = pickIncomingAuthHeaders(req.headers);
37
+ if (!auth) {
38
+ res.status(401).json({
39
+ success: false,
40
+ error: 'Missing auth: use n8n Header Auth with X-Lyrra-Api-Key (institution key) or Authorization Bearer.',
41
+ });
42
+ return;
43
+ }
44
+ const skipValidate = process.env.LYRRA_MCP_SKIP_AUTH_VALIDATE === '1';
45
+ if (!skipValidate) {
46
+ const ok = await validateAuth(auth);
47
+ if (!ok) {
48
+ res.status(401).json({
49
+ success: false,
50
+ error: 'Invalid Lyrra credentials (GET /api/auth/me failed). Check your Header Auth value.',
51
+ });
52
+ return;
53
+ }
54
+ }
55
+ try {
56
+ await runWithInboundAuthHeaders(auth, async () => enqueueMcp(async () => {
57
+ const mcp = await getMcp();
58
+ const transport = new StreamableHTTPServerTransport({
59
+ sessionIdGenerator: undefined,
60
+ });
61
+ await mcp.connect(transport);
62
+ try {
63
+ await transport.handleRequest(req, res, req.body);
64
+ }
65
+ finally {
66
+ try {
67
+ await transport.close();
68
+ }
69
+ catch {
70
+ /* ignore */
71
+ }
72
+ try {
73
+ await mcp.close();
74
+ }
75
+ catch {
76
+ /* ignore */
77
+ }
78
+ }
79
+ }));
80
+ }
81
+ catch (e) {
82
+ process.stderr.write(`[lyrra-mcp-http] ${e instanceof Error ? e.stack ?? e.message : e}\n`);
83
+ if (!res.headersSent) {
84
+ res.status(500).json({
85
+ jsonrpc: '2.0',
86
+ error: { code: -32603, message: 'Internal server error' },
87
+ id: null,
88
+ });
89
+ }
90
+ }
91
+ });
92
+ app.get('/health', (_req, res) => {
93
+ res.status(200).type('text/plain').send('ok');
94
+ });
95
+ async function main() {
96
+ await getMcp();
97
+ app.listen(PORT, '0.0.0.0', () => {
98
+ process.stderr.write(`[lyrra-mcp-http] listening 0.0.0.0:${PORT} path /mcp health /health\n`);
99
+ });
100
+ }
101
+ main().catch((e) => {
102
+ process.stderr.write(`[lyrra-mcp-http] Fatal: ${e instanceof Error ? e.stack ?? e.message : e}\n`);
103
+ process.exit(1);
104
+ });
package/dist/index.js CHANGED
@@ -1,21 +1,25 @@
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
+ * HTTP (n8n URL) : `npm run start:http` → voir http-main.ts
6
+ */
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 { getLyrraRequestAuthHeaders } from './auth-session.js';
9
+ import { createLyrraMcpServer } from './lyrra-mcp-core.js';
6
10
  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.');
11
+ const { mcp } = await createLyrraMcpServer();
12
+ try {
13
+ await getLyrraRequestAuthHeaders();
14
+ process.stderr.write('[lyrra-mcp] API credentials OK.\n');
9
15
  }
10
- else {
11
- console.error('✅ LYRRA Studio MCP connecté (authentification par API Key).');
16
+ catch (e) {
17
+ process.stderr.write(`[lyrra-mcp] Auth warning: ${e instanceof Error ? e.message : e}\n`);
12
18
  }
13
- const server = createMcpServer();
14
19
  const transport = new StdioServerTransport();
15
- await server.connect(transport);
20
+ await mcp.connect(transport);
16
21
  }
17
- main().catch((error) => {
18
- console.error('Erreur fatale:', error);
22
+ main().catch((e) => {
23
+ process.stderr.write(`[lyrra-mcp] Fatal: ${e instanceof Error ? e.stack ?? e.message : e}\n`);
19
24
  process.exit(1);
20
25
  });
21
- //# sourceMappingURL=index.js.map