@lyrra/mcp-server 1.1.3 → 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.
- package/README.md +59 -242
- package/dist/auth-session.js +160 -0
- package/dist/eduflow-block-docs.js +438 -0
- package/dist/index.js +179 -12
- package/dist/lyrra-http.js +80 -0
- package/dist/openapi-parse.js +61 -0
- package/dist/register-eduflow-block-tools.js +31 -0
- package/package.json +36 -13
- package/Dockerfile +0 -16
- package/dist/client.d.ts +0 -23
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -92
- package/dist/client.js.map +0 -1
- package/dist/config.d.ts +0 -8
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -8
- package/dist/config.js.map +0 -1
- package/dist/http-server.d.ts +0 -8
- package/dist/http-server.d.ts.map +0 -1
- package/dist/http-server.js +0 -481
- package/dist/http-server.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/resources/block-types.d.ts +0 -318
- package/dist/resources/block-types.d.ts.map +0 -1
- package/dist/resources/block-types.js +0 -297
- package/dist/resources/block-types.js.map +0 -1
- package/dist/resources/flow-schema.d.ts +0 -147
- package/dist/resources/flow-schema.d.ts.map +0 -1
- package/dist/resources/flow-schema.js +0 -143
- package/dist/resources/flow-schema.js.map +0 -1
- package/dist/server-factory.d.ts +0 -8
- package/dist/server-factory.d.ts.map +0 -1
- package/dist/server-factory.js +0 -82
- package/dist/server-factory.js.map +0 -1
- package/dist/tools/admin.d.ts +0 -265
- package/dist/tools/admin.d.ts.map +0 -1
- package/dist/tools/admin.js +0 -118
- package/dist/tools/admin.js.map +0 -1
- package/dist/tools/ai-designer.d.ts +0 -297
- package/dist/tools/ai-designer.d.ts.map +0 -1
- package/dist/tools/ai-designer.js +0 -89
- package/dist/tools/ai-designer.js.map +0 -1
- package/dist/tools/analytics.d.ts +0 -95
- package/dist/tools/analytics.d.ts.map +0 -1
- package/dist/tools/analytics.js +0 -44
- package/dist/tools/analytics.js.map +0 -1
- package/dist/tools/auth.d.ts +0 -61
- package/dist/tools/auth.d.ts.map +0 -1
- package/dist/tools/auth.js +0 -36
- package/dist/tools/auth.js.map +0 -1
- package/dist/tools/blocks.d.ts +0 -457
- package/dist/tools/blocks.d.ts.map +0 -1
- package/dist/tools/blocks.js +0 -173
- package/dist/tools/blocks.js.map +0 -1
- package/dist/tools/connections.d.ts +0 -173
- package/dist/tools/connections.d.ts.map +0 -1
- package/dist/tools/connections.js +0 -81
- package/dist/tools/connections.js.map +0 -1
- package/dist/tools/eduflow.d.ts +0 -409
- package/dist/tools/eduflow.d.ts.map +0 -1
- package/dist/tools/eduflow.js +0 -139
- package/dist/tools/eduflow.js.map +0 -1
- package/dist/tools/participants.d.ts +0 -221
- package/dist/tools/participants.d.ts.map +0 -1
- package/dist/tools/participants.js +0 -70
- package/dist/tools/participants.js.map +0 -1
- package/dist/tools/presentation.d.ts +0 -233
- package/dist/tools/presentation.d.ts.map +0 -1
- package/dist/tools/presentation.js +0 -57
- package/dist/tools/presentation.js.map +0 -1
- package/dist/tools/projects.d.ts +0 -131
- package/dist/tools/projects.d.ts.map +0 -1
- package/dist/tools/projects.js +0 -55
- package/dist/tools/projects.js.map +0 -1
- package/dist/tools/resources.d.ts +0 -93
- package/dist/tools/resources.d.ts.map +0 -1
- package/dist/tools/resources.js +0 -37
- package/dist/tools/resources.js.map +0 -1
- package/dist/tools/store.d.ts +0 -125
- package/dist/tools/store.d.ts.map +0 -1
- package/dist/tools/store.js +0 -66
- package/dist/tools/store.js.map +0 -1
- package/mcp-config.example.json +0 -14
- package/src/client.ts +0 -106
- package/src/config.ts +0 -7
- package/src/http-server.ts +0 -591
- package/src/index.ts +0 -23
- package/src/resources/block-types.ts +0 -298
- package/src/resources/flow-schema.ts +0 -148
- package/src/server-factory.ts +0 -109
- package/src/tools/admin.ts +0 -128
- package/src/tools/ai-designer.ts +0 -97
- package/src/tools/analytics.ts +0 -49
- package/src/tools/auth.ts +0 -39
- package/src/tools/blocks.ts +0 -186
- package/src/tools/connections.ts +0 -83
- package/src/tools/eduflow.ts +0 -150
- package/src/tools/participants.ts +0 -77
- package/src/tools/presentation.ts +0 -61
- package/src/tools/projects.ts +0 -61
- package/src/tools/resources.ts +0 -41
- package/src/tools/store.ts +0 -67
- package/tsconfig.json +0 -19
package/README.md
CHANGED
|
@@ -1,34 +1,21 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Lyrra Studio MCP server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**stdio** process (Model Context Protocol): one tool per backend **OpenAPI** operation, plus `lyrra_meta`, `lyrra_search_operations`, and `lyrra://…` resources.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Prerequisites
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
- Node.js ≥ 20
|
|
8
|
+
- Reachable Lyrra backend with generated `openapi/openapi.json` (`npm run openapi:generate` in `apps/backend`)
|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
|
|
12
|
+
Published as **`@lyrra/mcp-server`**. No local clone required:
|
|
14
13
|
|
|
15
14
|
```bash
|
|
16
|
-
|
|
15
|
+
npx -y @lyrra/mcp-server
|
|
17
16
|
```
|
|
18
17
|
|
|
19
|
-
|
|
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
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
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
|
-
|
|
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
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
46
|
+
Global install (optional): `npm install -g @lyrra/mcp-server` then run **`lyrra-mcp`** (binary name on `PATH`).
|
|
85
47
|
|
|
86
|
-
|
|
48
|
+
## Develop from this monorepo
|
|
87
49
|
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
| `
|
|
204
|
-
| `
|
|
205
|
-
| `
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
+
Development (no pre-build): `npm run dev`
|
|
269
97
|
|
|
270
|
-
|
|
98
|
+
## Automated test (no Lyrra backend)
|
|
271
99
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
106
|
+
## MCP client (local build vs npm)
|
|
287
107
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
+
}
|