@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
package/README.md CHANGED
@@ -1,34 +1,21 @@
1
- # LYRRA Studio MCP Server
1
+ # Lyrra Studio MCP server
2
2
 
3
- Serveur MCP (Model Context Protocol) pour piloter **LYRRA Studio** depuis un outil IA externe (Claude Desktop, Claude.ai, Cursor, Copilot CLI, etc.).
3
+ **stdio** process (Model Context Protocol): one tool per backend **OpenAPI** operation, plus `lyrra_meta`, `lyrra_search_operations`, and `lyrra://…` resources.
4
4
 
5
- ## Modes de transport
5
+ ## Prerequisites
6
6
 
7
- ### Mode stdio (Claude Desktop, Cursor)
8
- Transport stdio classique le serveur MCP tourne en processus local.
7
+ - Node.js 20
8
+ - Reachable Lyrra backend with generated `openapi/openapi.json` (`npm run openapi:generate` in `apps/backend`)
9
9
 
10
- ### Mode HTTP (Claude.ai, applications web)
11
- Transport HTTP Streamable avec OAuth 2.0 — le serveur MCP tourne comme service web accessible via `https://lyrrastudio.com/mcp`.
10
+ ## Install from npm (Claude / Cursor)
12
11
 
13
- ## Installation
12
+ Published as **`@lyrra/mcp-server`**. No local clone required:
14
13
 
15
14
  ```bash
16
- npm install -g @lyrra/mcp-server
15
+ npx -y @lyrra/mcp-server
17
16
  ```
18
17
 
19
- Ou utilisez directement avec `npx` (aucune installation requise) — voir la configuration ci-dessous.
20
-
21
- ## Configuration
22
-
23
- ### 1. Obtenir vos identifiants
24
-
25
- Connectez-vous à LYRRA Studio → Dashboard Entreprise → Serveur MCP → Onglet "Identifiants" → Créer un nouveau client.
26
-
27
- Vous obtiendrez un **Client ID** et un **Client Secret** (affiché une seule fois).
28
-
29
- ### 2. Claude Desktop (mode stdio)
30
-
31
- Ajoutez dans `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) ou `%APPDATA%\Claude\claude_desktop_config.json` (Windows) :
18
+ In **Claude Desktop** (`claude_desktop_config.json`), prefer:
32
19
 
33
20
  ```json
34
21
  {
@@ -37,257 +24,87 @@ Ajoutez dans `~/Library/Application Support/Claude/claude_desktop_config.json` (
37
24
  "command": "npx",
38
25
  "args": ["-y", "@lyrra/mcp-server"],
39
26
  "env": {
40
- "LYRRA_CLIENT_ID": "votre_client_id",
41
- "LYRRA_CLIENT_SECRET": "rak_votre_client_secret",
42
- "LYRRA_API_URL": "https://lyrrastudio.com/api",
43
- "LYRRA_EDUFLOW_API_URL": "https://lyrrastudio.com/api/eduflow"
27
+ "LYRRA_API_URL": "https://your-domain.com/api",
28
+ "LYRRA_CLIENT_ID": "rak_xxxxxxxx",
29
+ "LYRRA_CLIENT_SECRET": "rak_xxxxxxxx_yyyyyyyy_zzzzzzzzzzzzzzzz"
44
30
  }
45
31
  }
46
32
  }
47
33
  }
48
34
  ```
49
35
 
50
- > Après modification, quittez complètement Claude Desktop (Cmd+Q / Ctrl+Q) et relancez-le.
51
-
52
- ### 3. Claude.ai (mode HTTP)
53
-
54
- Ajoutez le MCP serveur dans Claude.ai Settings → Integrations → Add MCP Server :
55
-
56
- - **URL** : `https://lyrrastudio.com/mcp`
57
-
58
- Claude.ai lancera automatiquement le flux OAuth :
59
- 1. Vous serez redirigé vers la page d'autorisation LYRRA Studio
60
- 2. Entrez votre **Client Secret** (clé API `rak_...`)
61
- 3. L'accès est autorisé, Claude.ai peut utiliser vos outils MCP
62
-
63
- ### 4. Cursor (mode stdio)
64
-
65
- Ajoutez dans `.cursor/mcp.json` :
36
+ Use **Header Auth** keys from the institution dashboard instead of client id/secret:
66
37
 
67
38
  ```json
68
- {
69
- "mcpServers": {
70
- "lyrra-studio": {
71
- "command": "npx",
72
- "args": ["-y", "@lyrra/mcp-server"],
73
- "env": {
74
- "LYRRA_CLIENT_ID": "votre_client_id",
75
- "LYRRA_CLIENT_SECRET": "rak_votre_client_secret",
76
- "LYRRA_API_URL": "https://lyrrastudio.com/api",
77
- "LYRRA_EDUFLOW_API_URL": "https://lyrrastudio.com/api/eduflow"
78
- }
79
- }
80
- }
39
+ "env": {
40
+ "LYRRA_API_URL": "https://your-domain.com/api",
41
+ "LYRRA_MCP_HEADER_NAME": "X-Lyrra-Api-Key",
42
+ "LYRRA_MCP_HEADER_VALUE": "rak_…full secret…"
81
43
  }
82
44
  ```
83
45
 
84
- ### 5. GitHub Copilot CLI / VS Code
46
+ Global install (optional): `npm install -g @lyrra/mcp-server` then run **`lyrra-mcp`** (binary name on `PATH`).
85
47
 
86
- Ajoutez dans `.vscode/mcp.json` ou les settings Copilot :
48
+ ## Develop from this monorepo
87
49
 
88
- ```json
89
- {
90
- "mcpServers": {
91
- "lyrra-studio": {
92
- "command": "npx",
93
- "args": ["-y", "@lyrra/mcp-server"],
94
- "env": {
95
- "LYRRA_CLIENT_ID": "votre_client_id",
96
- "LYRRA_CLIENT_SECRET": "rak_votre_client_secret",
97
- "LYRRA_API_URL": "https://lyrrastudio.com/api",
98
- "LYRRA_EDUFLOW_API_URL": "https://lyrrastudio.com/api/eduflow"
99
- }
100
- }
101
- }
102
- }
50
+ ```bash
51
+ cd lyrra-studio-app/apps/mcp-server
52
+ npm ci
53
+ npm run build
103
54
  ```
104
55
 
105
- ## Variables d'environnement
106
-
107
- | Variable | Défaut | Description |
108
- |---|---|---|
109
- | `LYRRA_CLIENT_ID` | - | Client ID (nom du client) |
110
- | `LYRRA_CLIENT_SECRET` | - | Client Secret (clé secrète, préfixe `rak_`) |
111
- | `LYRRA_API_URL` | `http://localhost:3001/api` | URL de l'API REST |
112
- | `LYRRA_EDUFLOW_API_URL` | `http://localhost:3001/api/eduflow` | URL de l'API EduFlow |
113
-
114
- ## Tools disponibles (58)
115
-
116
- ### 🔐 Authentification
117
- | Tool | Description |
118
- |---|---|
119
- | `auth_login` | Se connecter avec email/mot de passe |
120
- | `auth_get_profile` | Profil utilisateur connecté |
121
- | `auth_list_api_keys` | Lister les clés API |
122
-
123
- ### 📚 Parcours EduFlow
124
- | Tool | Description |
125
- |---|---|
126
- | `eduflow_list` | Lister tous les parcours |
127
- | `eduflow_get` | Détails d'un parcours |
128
- | `eduflow_create` | Créer un parcours |
129
- | `eduflow_update` | Mettre à jour un parcours |
130
- | `eduflow_delete` | Supprimer un parcours |
131
- | `eduflow_duplicate` | Dupliquer un parcours |
132
- | `eduflow_change_status` | Changer le statut (draft/published/archived) |
133
- | `eduflow_export` | Exporter un parcours |
134
- | `eduflow_get_public` | Infos publiques d'un parcours |
135
- | `eduflow_get_urls` | Obtenir les liens (prévisualisation, édition, analytics, etc.) |
136
-
137
- ### 🧱 Blocs
138
- | Tool | Description |
139
- |---|---|
140
- | `block_list_types` | Documentation des 29 types de blocs |
141
- | `block_get` | Détails d'un bloc |
142
- | `block_create` | Créer un bloc |
143
- | `block_update` | Mettre à jour un bloc |
144
- | `block_batch_update` | Mise à jour en lot |
145
- | `block_delete` | Supprimer un bloc |
146
- | `block_generate_tts` | Générer audio TTS |
147
-
148
- ### 🔗 Connexions
149
- | Tool | Description |
150
- |---|---|
151
- | `connection_list` | Lister les connexions |
152
- | `connection_add` | Ajouter une connexion |
153
- | `connection_remove` | Supprimer une connexion |
56
+ ### Publish a new version (maintainers)
154
57
 
155
- ### 👥 Participants
156
- | Tool | Description |
157
- |---|---|
158
- | `participant_list` | Lister les participants |
159
- | `participant_add` | Ajouter des participants |
160
- | `participant_remove` | Retirer un participant |
161
- | `participant_get_progress` | Progression d'un participant |
162
- | `participant_get_overview` | Vue d'ensemble participant |
163
- | `participant_get_flow_stats` | Stats de tous les participants |
58
+ 1. Create an **npm** organization or user scope **`@lyrra`** and log in: `npm login`.
59
+ 2. Bump **`version`** in `package.json` (semver).
60
+ 3. From `apps/mcp-server`: `npm publish`
61
+ (`publishConfig.access` is `public` for the scoped package.)
164
62
 
165
- ### 📊 Analytics
166
- | Tool | Description |
167
- |---|---|
168
- | `analytics_overview` | Dashboard global |
169
- | `analytics_flow_learners` | Stats apprenants par parcours |
170
- | `analytics_flow_dashboard` | Dashboard analytics parcours |
171
- | `analytics_my_stats` | Mes statistiques |
63
+ Ensure `dist/` is built (`prepublishOnly` runs `npm run build` automatically).
172
64
 
173
- ### 🤖 AI Designer
174
- | Tool | Description |
175
- |---|---|
176
- | `ai_generate_plan` | Générer un plan de parcours |
177
- | `ai_generate_block` | Générer le contenu d'un bloc |
178
- | `ai_generate_presentation` | Générer la page de présentation |
179
- | `ai_generate_objectives` | Générer les objectifs pédagogiques |
180
- | `ai_save_objectives` | Sauvegarder les objectifs |
181
- | `ai_chat_message` | Discuter avec le designer IA |
182
- | `ai_get_conversation` | Historique de conversation IA |
65
+ **Publish error:** `Cannot implicitly apply the "latest" tag because previously published version … is higher` — the registry already has a **newer** semver on `latest` (e.g. `1.1.3`). Bump `package.json` to something **greater** (e.g. `1.1.4`), not `1.0.x`. Alternatively: `npm publish --tag maintenance` to publish an older line without moving `latest`.
183
66
 
184
- ### 🎭 Présentation
185
- | Tool | Description |
186
- |---|---|
187
- | `presentation_get` | Page de présentation |
188
- | `presentation_update` | Mettre à jour la présentation |
189
- | `presentation_toggle` | Activer/désactiver |
67
+ Run `npm pkg fix` in this folder to apply npm’s suggested `package.json` fixes (`bin`, `repository`, …).
190
68
 
191
- ### 🏪 Store
192
- | Tool | Description |
193
- |---|---|
194
- | `store_list_audiobooks` | Lister les audiobooks |
195
- | `store_get_audiobook` | Détails d'un audiobook |
196
- | `store_list_authors` | Auteurs en vedette |
197
- | `store_my_library` | Ma bibliothèque |
198
- | `store_search` | Rechercher dans le store |
69
+ ## Environment variables
199
70
 
200
- ### 🎵 Projets Audio
201
- | Tool | Description |
202
- |---|---|
203
- | `project_list` | Lister mes projets |
204
- | `project_get` | Détails d'un projet |
205
- | `project_create` | Créer un projet |
206
- | `project_update` | Mettre à jour un projet |
207
- | `project_delete` | Supprimer un projet |
71
+ | Variable | Purpose |
72
+ |----------|---------|
73
+ | `LYRRA_API_URL` | API base, e.g. `https://yourdomain/api` or `http://localhost:3001/api` |
74
+ | `LYRRA_CLIENT_ID` | Key prefix (`keyPrefix`) |
75
+ | `LYRRA_CLIENT_SECRET` | Full secret `rak_…` |
76
+ | `LYRRA_ACCESS_TOKEN` | *(optional)* Bearer JWT if not using key exchange |
77
+ | `LYRRA_MCP_HEADER_NAME` | *(optional)* HTTP header name for **Header Auth** keys (e.g. `X-Lyrra-Api-Key`; alias `LYRRA_HEADER_AUTH_NAME`) |
78
+ | `LYRRA_MCP_HEADER_VALUE` | *(optional)* Same secret as shown once at key creation (alias `LYRRA_MCP_HEADER_SECRET` or `LYRRA_HEADER_AUTH_VALUE`) |
79
+ | `LYRRA_OPENAPI_URL` | *(optional)* OpenAPI JSON URL |
80
+ | `LYRRA_MCP_MAX_TOOLS` | *(optional)* Max number of tools (integer) |
208
81
 
209
- ### 📁 Ressources
210
- | Tool | Description |
211
- |---|---|
212
- | `resource_list` | Lister les ressources |
213
- | `resource_delete` | Supprimer une ressource |
214
- | `resource_list_categories` | Catégories disponibles |
82
+ Client ID + Secret → JWT exchange uses `POST /api/auth/api-key/token`. **Header Auth** keys do not use that route: send the header on each API request instead (same as n8n MCP « Header Auth »).
215
83
 
216
- ### 🔧 Versions, Gamification & Webhooks
217
- | Tool | Description |
218
- |---|---|
219
- | `version_list` | Versions d'un parcours |
220
- | `version_create` | Créer une version |
221
- | `version_activate` | Activer une version |
222
- | `gamification_stats` | Stats de gamification |
223
- | `gamification_objectives` | Objectifs à atteindre |
224
- | `activity_history` | Historique d'activité |
225
- | `activity_stats` | Stats d'utilisation |
226
- | `webhook_list` | Lister les webhooks |
227
- | `webhook_create` | Créer un webhook |
228
- | `webhook_delete` | Supprimer un webhook |
229
- | `webhook_test` | Tester un webhook |
84
+ ## EduFlow block documentation
230
85
 
231
- ## Resources MCP
86
+ - Tool **`lyrra_eduflow_blocks_index`**: list of documented types.
87
+ - One tool per type: **`lyrra_eduflow_block_<type>`** (e.g. `lyrra_eduflow_block_quiz_mcq`) — role, `data` / `settings` fields, graph, persistence.
88
+ - Sheet source: `src/eduflow-block-docs.ts` (keep aligned with the React designer).
232
89
 
233
- | URI | Description |
234
- |---|---|
235
- | `lyrra://block-types` | Documentation complète des 29 types de blocs |
236
- | `lyrra://flow-construction-guide` | Guide de construction de parcours |
237
-
238
- ## Exemples d'utilisation avec Claude
239
-
240
- ### Créer un parcours complet
241
- > "Crée un parcours EduFlow sur les fractions pour des élèves de CM2, avec 3 leçons en texte, un quiz après chaque leçon, et un certificat de réussite à la fin."
242
-
243
- ### Analyser les statistiques
244
- > "Montre-moi les statistiques de mon parcours 'Introduction au Python'. Quels sont les blocs où les étudiants passent le plus de temps ?"
245
-
246
- ### Modifier un parcours existant
247
- > "Dans mon parcours sur la photosynthèse, ajoute un bloc vidéo après le texte d'introduction et connecte-le au quiz."
248
-
249
- ## Développement
90
+ ## Run
250
91
 
251
92
  ```bash
252
- # Lancer en mode stdio (développement local)
253
- npm run dev
254
-
255
- # Lancer en mode HTTP (développement local)
256
- npm run dev:http
257
-
258
- # Rebuild après modifications
259
- npm run build
260
-
261
- # Lancer en production (stdio)
262
93
  npm start
263
-
264
- # Lancer en production (HTTP)
265
- npm run start:http
266
94
  ```
267
95
 
268
- ## Architecture HTTP
96
+ Development (no pre-build): `npm run dev`
269
97
 
270
- Le mode HTTP expose les endpoints suivants :
98
+ ## Automated test (no Lyrra backend)
271
99
 
272
- | Endpoint | Méthode | Description |
273
- |---|---|---|
274
- | `/.well-known/oauth-protected-resource/mcp` | GET | Métadonnées RFC 9728 |
275
- | `/mcp/.well-known/oauth-authorization-server` | GET | Métadonnées OAuth AS |
276
- | `/mcp/register` | POST | Enregistrement dynamique de client OAuth |
277
- | `/mcp/authorize` | GET | Page d'autorisation OAuth |
278
- | `/mcp/approve` | POST | Validation des identifiants |
279
- | `/mcp/token` | POST | Échange de code → token |
280
- | `/mcp/revoke` | POST | Révocation de token |
281
- | `/mcp` | POST | Protocole MCP (messages) |
282
- | `/mcp` | GET | SSE stream (notifications serveur) |
283
- | `/mcp` | DELETE | Fermeture de session |
284
- | `/mcp/health` | GET | Health check |
100
+ A minimal OpenAPI is served locally; the script checks `initialize`, `tools/list`, and a `tools/call` on a block sheet:
101
+
102
+ ```bash
103
+ npm run test:mcp
104
+ ```
285
105
 
286
- ### Variables d'environnement (mode HTTP)
106
+ ## MCP client (local build vs npm)
287
107
 
288
- | Variable | Défaut | Description |
289
- |---|---|---|
290
- | `MCP_HTTP_PORT` | `3002` | Port du serveur HTTP |
291
- | `MCP_BASE_URL` | `https://lyrrastudio.com` | URL publique de base |
292
- | `LYRRA_API_URL` | `http://localhost:3001/api` | URL de l'API backend |
293
- | `LYRRA_EDUFLOW_API_URL` | `http://localhost:3001/api/eduflow` | URL de l'API EduFlow |
108
+ - **Recommended:** `command` `npx`, `args` `["-y", "@lyrra/mcp-server"]` (see above).
109
+ - **Local monorepo:** `command` `node`, `args`: **absolute** path to `apps/mcp-server/dist/index.js`
110
+ `env`: `LYRRA_API_URL`, `LYRRA_CLIENT_ID`, `LYRRA_CLIENT_SECRET` (or `LYRRA_ACCESS_TOKEN` / header variables).
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Access token for Lyrra API calls.
3
+ * - LYRRA_ACCESS_TOKEN: JWT directly
4
+ * - or LYRRA_CLIENT_ID (keyPrefix) + LYRRA_CLIENT_SECRET (rak_… secret) → POST /api/auth/api-key/token
5
+ * - or clé institution « header_auth » : LYRRA_MCP_HEADER_NAME + LYRRA_MCP_HEADER_VALUE
6
+ * (alias : LYRRA_HEADER_AUTH_NAME / LYRRA_HEADER_AUTH_VALUE) — même en-tête que n8n « Header Auth »
7
+ */
8
+ let cached = null;
9
+ function apiBase() {
10
+ const u = process.env.LYRRA_API_URL?.trim();
11
+ if (!u) {
12
+ throw new Error('LYRRA_API_URL is required (e.g. https://lyrrastudio.com/api or http://localhost:3001/api)');
13
+ }
14
+ return u.replace(/\/+$/, '');
15
+ }
16
+ /** HTTP origin without /api suffix, for /api/auth/… and /api/openapi.json */
17
+ export function requestOrigin() {
18
+ const base = apiBase();
19
+ if (base.endsWith('/api')) {
20
+ const o = base.slice(0, -4);
21
+ return o.length > 0 ? o : base;
22
+ }
23
+ return base;
24
+ }
25
+ function parseJsonEnvelope(json) {
26
+ if (!json || typeof json !== 'object')
27
+ return {};
28
+ const o = json;
29
+ const data = o.data;
30
+ if (data && typeof data === 'object')
31
+ return data;
32
+ return o;
33
+ }
34
+ async function exchangeApiKey(prefix, secret) {
35
+ const origin = requestOrigin();
36
+ const url = `${origin}/api/auth/api-key/token`;
37
+ const r = await fetch(url, {
38
+ method: 'POST',
39
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
40
+ body: JSON.stringify({ keyPrefix: prefix, secret }),
41
+ });
42
+ const text = await r.text();
43
+ if (!r.ok) {
44
+ throw new Error(`API key exchange failed (${r.status}): ${text.slice(0, 800)}`);
45
+ }
46
+ let parsed;
47
+ try {
48
+ parsed = JSON.parse(text);
49
+ }
50
+ catch {
51
+ throw new Error('Invalid auth response (expected JSON)');
52
+ }
53
+ const body = parseJsonEnvelope(parsed);
54
+ const access = typeof body.accessToken === 'string' ? body.accessToken : '';
55
+ const refresh = typeof body.refreshToken === 'string' ? body.refreshToken : '';
56
+ if (!access) {
57
+ throw new Error('Auth response missing accessToken');
58
+ }
59
+ let expMs = Date.now() + 10 * 60_000;
60
+ try {
61
+ const part = access.split('.')[1];
62
+ if (part) {
63
+ const payload = JSON.parse(Buffer.from(part, 'base64url').toString('utf8'));
64
+ if (typeof payload.exp === 'number') {
65
+ expMs = payload.exp * 1000;
66
+ }
67
+ }
68
+ }
69
+ catch {
70
+ /* keep default expMs */
71
+ }
72
+ cached = { access, refresh, expMs };
73
+ }
74
+ async function refreshAccess() {
75
+ if (!cached?.refresh)
76
+ return false;
77
+ const origin = requestOrigin();
78
+ const r = await fetch(`${origin}/api/auth/refresh`, {
79
+ method: 'POST',
80
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
81
+ body: JSON.stringify({ refreshToken: cached.refresh }),
82
+ });
83
+ const text = await r.text();
84
+ if (!r.ok)
85
+ return false;
86
+ try {
87
+ const parsed = JSON.parse(text);
88
+ const body = parseJsonEnvelope(parsed);
89
+ const access = typeof body.accessToken === 'string' ? body.accessToken : '';
90
+ if (!access)
91
+ return false;
92
+ let expMs = Date.now() + 10 * 60_000;
93
+ try {
94
+ const part = access.split('.')[1];
95
+ if (part) {
96
+ const payload = JSON.parse(Buffer.from(part, 'base64url').toString('utf8'));
97
+ if (typeof payload.exp === 'number')
98
+ expMs = payload.exp * 1000;
99
+ }
100
+ }
101
+ catch {
102
+ /* ignore */
103
+ }
104
+ cached = { ...cached, access, expMs };
105
+ return true;
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ }
111
+ function headerAuthFromEnv() {
112
+ const name = process.env.LYRRA_MCP_HEADER_NAME?.trim() ||
113
+ process.env.LYRRA_HEADER_AUTH_NAME?.trim() ||
114
+ '';
115
+ const value = process.env.LYRRA_MCP_HEADER_SECRET?.trim() ||
116
+ process.env.LYRRA_MCP_HEADER_VALUE?.trim() ||
117
+ process.env.LYRRA_HEADER_AUTH_VALUE?.trim() ||
118
+ '';
119
+ if (!name || !value)
120
+ return null;
121
+ return { name, value };
122
+ }
123
+ /**
124
+ * En-têtes à envoyer sur chaque appel API Lyrra (Bearer ou clé header_auth).
125
+ */
126
+ export async function getLyrraRequestAuthHeaders() {
127
+ const headerPair = headerAuthFromEnv();
128
+ if (headerPair) {
129
+ return { [headerPair.name]: headerPair.value };
130
+ }
131
+ const token = await getAccessToken();
132
+ return { Authorization: `Bearer ${token}` };
133
+ }
134
+ export async function getAccessToken() {
135
+ if (headerAuthFromEnv()) {
136
+ throw new Error('Header auth is configured (LYRRA_MCP_HEADER_*); use getLyrraRequestAuthHeaders for API calls');
137
+ }
138
+ const direct = process.env.LYRRA_ACCESS_TOKEN?.trim();
139
+ if (direct)
140
+ return direct;
141
+ const prefix = process.env.LYRRA_CLIENT_ID?.trim();
142
+ const secret = process.env.LYRRA_CLIENT_SECRET?.trim();
143
+ if (!prefix || !secret) {
144
+ throw new Error('Set LYRRA_ACCESS_TOKEN (JWT), LYRRA_CLIENT_ID + LYRRA_CLIENT_SECRET, or LYRRA_MCP_HEADER_NAME + LYRRA_MCP_HEADER_VALUE');
145
+ }
146
+ const margin = 90_000;
147
+ if (cached && cached.expMs > Date.now() + margin) {
148
+ return cached.access;
149
+ }
150
+ if (cached?.refresh && (await refreshAccess())) {
151
+ return cached.access;
152
+ }
153
+ cached = null;
154
+ await exchangeApiKey(prefix, secret);
155
+ return cached.access;
156
+ }
157
+ /** Clear cache (e.g. after persistent 401) */
158
+ export function clearTokenCache() {
159
+ cached = null;
160
+ }