@rgaaudit/mcp-server 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @rgaaudit/mcp-server
2
2
 
3
- Serveur MCP (Model Context Protocol) pour auditer l'accessibilite web selon le referentiel francais **RGAA 4.1**, directement depuis votre IDE.
3
+ Serveur MCP (Model Context Protocol) pour auditer l'accessibilité web selon le référentiel français **RGAA 4.1**, directement depuis votre IDE.
4
4
 
5
5
  Compatible **Claude Code**, **Cursor**, **Copilot**, **Windsurf**.
6
6
 
@@ -12,15 +12,16 @@ npx @rgaaudit/mcp-server
12
12
 
13
13
  ## Configuration
14
14
 
15
- ### 1. Obtenir une cle API
15
+ ### 1. Obtenir une clé API
16
16
 
17
- 1. Creez un compte sur [rgaaudit.fr](https://rgaaudit.fr)
18
- 2. Allez dans **Parametres > API**
19
- 3. Generez une cle API (plan Freelance ou superieur)
17
+ 1. Créez un compte sur [rgaaudit.fr](https://rgaaudit.fr)
18
+ 2. Choisissez un plan **Freelance** ou supérieur (le plan Découverte ne donne pas accès à l'API)
19
+ 3. Allez dans **Paramètres > API**
20
+ 4. Générez une clé API (préfixe `rga_`)
20
21
 
21
22
  ### 2. Configurer Claude Code
22
23
 
23
- Ajoutez dans `.mcp.json` a la racine de votre projet :
24
+ Ajoutez dans `.mcp.json` à la racine de votre projet :
24
25
 
25
26
  ```json
26
27
  {
@@ -38,35 +39,35 @@ Ajoutez dans `.mcp.json` a la racine de votre projet :
38
39
 
39
40
  ### 3. Configurer Cursor / Copilot
40
41
 
41
- Ajoutez la meme configuration dans les parametres MCP de votre IDE.
42
+ Ajoutez la même configuration dans les paramètres MCP de votre IDE.
42
43
 
43
44
  ## Outils disponibles
44
45
 
45
46
  | Outil | Description |
46
47
  |-------|-------------|
47
48
  | `rgaa_audit` | Audit complet RGAA 4.1 avec violations, patterns et suggestions |
48
- | `rgaa_score` | Score de conformite rapide (0-100%) |
49
- | `rgaa_check` | Verifier un critere RGAA specifique (ex: 1.1, 3.2, 11.1) |
50
- | `rgaa_explain` | Expliquer un critere RGAA avec exemples de code |
51
- | `rgaa_report` | Rapport JSON structure pour CI/CD |
49
+ | `rgaa_score` | Score de conformité rapide (0-100%) |
50
+ | `rgaa_check` | Vérifier un critère RGAA spécifique (ex: 1.1, 3.2, 11.1) |
51
+ | `rgaa_explain` | Expliquer un critère RGAA avec exemples de code |
52
+ | `rgaa_report` | Rapport JSON structuré pour CI/CD |
52
53
 
53
54
  ## Exemples d'utilisation
54
55
 
55
56
  Dans Claude Code, demandez simplement :
56
57
 
57
58
  ```
58
- > Audite l'accessibilite de https://monsite.fr
59
+ > Audite l'accessibilité de https://monsite.fr
59
60
  > Quel est le score RGAA de https://monsite.fr ?
60
- > Verifie le critere 3.2 (contrastes) sur https://monsite.fr
61
- > Explique le critere RGAA 11.1
61
+ > Vérifie le critère 3.2 (contrastes) sur https://monsite.fr
62
+ > Explique le critère RGAA 11.1
62
63
  ```
63
64
 
64
65
  ## Variables d'environnement
65
66
 
66
67
  | Variable | Requis | Description |
67
68
  |----------|--------|-------------|
68
- | `RGAA_API_KEY` | Oui | Cle API RGAAudit (format `rga_...`) |
69
- | `RGAA_API_URL` | Non | URL de l'API (defaut: `https://app.rgaaudit.fr`) |
69
+ | `RGAA_API_KEY` | Oui | Clé API RGAAudit (format `rga_...`) |
70
+ | `RGAA_API_URL` | Non | URL de l'API (défaut: `https://rgaaudit.fr`) |
70
71
 
71
72
  ## Licence
72
73
 
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Configuration requise :
9
9
  * RGAA_API_KEY — Clé API (générée dans Paramètres > API)
10
- * RGAA_API_URL — URL de l'API (optionnel, défaut: https://app.rgaaudit.fr)
10
+ * RGAA_API_URL — URL de l'API (optionnel, défaut: https://rgaaudit.fr)
11
11
  *
12
12
  * Outils disponibles :
13
13
  * - rgaa_audit : Lancer un audit complet sur une URL
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Configuration requise :
9
9
  * RGAA_API_KEY — Clé API (générée dans Paramètres > API)
10
- * RGAA_API_URL — URL de l'API (optionnel, défaut: https://app.rgaaudit.fr)
10
+ * RGAA_API_URL — URL de l'API (optionnel, défaut: https://rgaaudit.fr)
11
11
  *
12
12
  * Outils disponibles :
13
13
  * - rgaa_audit : Lancer un audit complet sur une URL
@@ -19,7 +19,7 @@
19
19
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
20
20
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
21
21
  import { z } from 'zod';
22
- const API_URL = process.env.RGAA_API_URL || 'https://app.rgaaudit.fr';
22
+ const API_URL = process.env.RGAA_API_URL || 'https://rgaaudit.fr';
23
23
  const API_KEY = process.env.RGAA_API_KEY || '';
24
24
  const server = new McpServer({
25
25
  name: 'rgaaudit',
@@ -48,7 +48,7 @@ function cleanCache() {
48
48
  async function callScan(url, options) {
49
49
  if (!API_KEY) {
50
50
  throw new Error('RGAA_API_KEY non configurée.\n\n' +
51
- '1. Créez un compte sur https://app.rgaaudit.fr\n' +
51
+ '1. Créez un compte sur https://rgaaudit.fr\n' +
52
52
  '2. Allez dans Paramètres > API\n' +
53
53
  '3. Générez une clé API\n' +
54
54
  '4. Configurez : RGAA_API_KEY=rga_...');
@@ -68,6 +68,7 @@ async function callScan(url, options) {
68
68
  headers: {
69
69
  'Content-Type': 'application/json',
70
70
  'X-API-Key': API_KEY,
71
+ 'X-Source': 'mcp',
71
72
  },
72
73
  body: JSON.stringify({
73
74
  url,
@@ -101,6 +102,7 @@ async function callRecheck(auditId) {
101
102
  headers: {
102
103
  'Content-Type': 'application/json',
103
104
  'X-API-Key': API_KEY,
105
+ 'X-Source': 'mcp',
104
106
  },
105
107
  body: JSON.stringify({ auditId }),
106
108
  });
@@ -117,80 +119,126 @@ server.tool('rgaa_audit', 'Lancer un audit d\'accessibilité RGAA 4.1 complet su
117
119
  try {
118
120
  const result = await callScan(url, { details: true });
119
121
  const violations = result.violationDetails || [];
120
- const critique = violations.filter(v => v.impact === 'critical').length;
121
- const serieux = violations.filter(v => v.impact === 'serious').length;
122
- const modere = violations.filter(v => v.impact === 'moderate').length;
123
- const uniqueViolations = result.violationsInSharedZones
124
- ? result.violations - result.violationsInSharedZones
125
- : result.violations;
122
+ const impactLabels = { critical: 'critique', serious: 'sérieux', moderate: 'modéré', minor: 'mineur', critique: 'critique', serieux: 'sérieux', modere: 'modéré' };
123
+ const impactOrder = { critical: 0, critique: 0, serious: 1, serieux: 1, moderate: 2, modere: 2, minor: 3, mineur: 3 };
124
+ const bySeverity = {};
125
+ for (const v of violations) {
126
+ const label = impactLabels[v.impact] || v.impact;
127
+ bySeverity[label] = (bySeverity[label] || 0) + 1;
128
+ }
126
129
  const sharedInfo = result.violationsInSharedZones
127
130
  ? ` (dont ${result.violationsInSharedZones} dans des zones partagées nav/header/footer)`
128
131
  : '';
129
- let text = `# Audit RGAA 4.1 — ${url}\n\n` +
130
- `**Score** : ${result.score}% — ${result.conformity}\n` +
131
- `**${result.violations} non-conformités**${sharedInfo} sur ${result.pages} pages\n` +
132
- `- ${critique} critique(s), ${serieux} sérieuse(s), ${modere} modérée(s)\n`;
132
+ // ── En-tête ──
133
+ let text = `# Audit RGAA 4.1 — ${url}\n\n`;
134
+ text += `| Métrique | Valeur |\n|----------|--------|\n`;
135
+ text += `| Score | **${result.score}%** ${result.conformity} |\n`;
136
+ text += `| Non-conformités | ${result.violations}${sharedInfo} |\n`;
137
+ text += `| Par impact | ${Object.entries(bySeverity).map(([k, v]) => `${v} ${k}`).join(', ') || 'aucune'} |\n`;
138
+ text += `| Pages analysées | ${result.pages} auditées |\n`;
133
139
  if (result.coverage) {
134
- text += `- **Couverture RGAA** : ${result.coverage.percentage}% (${result.coverage.mappedCriteria}/${result.coverage.totalCriteria} critères testés)\n`;
140
+ text += `| Couverture RGAA | ${result.coverage.percentage}% (${result.coverage.mappedCriteria}/${result.coverage.totalCriteria} critères) |\n`;
141
+ }
142
+ if (result.techStack?.framework || result.techStack?.cms) {
143
+ text += `| Technologies | ${[result.techStack.framework, result.techStack.cms].filter(Boolean).join(', ')} |\n`;
135
144
  }
145
+ text += `| Source | ${result.source || 'ci'} |\n`;
146
+ text += `| Audit ID | \`${result.auditId}\` |\n`;
136
147
  text += '\n';
137
- if (result.patterns && result.patterns.length > 0) {
138
- text += `## Patterns de correction (${result.patterns.length})\n\n`;
139
- for (const p of result.patterns) {
140
- text += `- **${p.title}** (${p.type}) ${p.violationsCount} violations, ${p.pagesAffected} pages\n`;
148
+ // ── Pages auditées ──
149
+ if (result.pagesAudited && result.pagesAudited.length > 0) {
150
+ const audited = result.pagesAudited.filter(p => p.status === 'COMPLETED');
151
+ const extrapolated = result.pagesAudited.filter(p => p.status === 'EXTRAPOLATED');
152
+ const errors = result.pagesAudited.filter(p => !['COMPLETED', 'EXTRAPOLATED'].includes(p.status));
153
+ text += `## Pages (${result.pagesAudited.length} total : ${audited.length} auditées, ${extrapolated.length} extrapolées${errors.length ? `, ${errors.length} erreurs` : ''})\n\n`;
154
+ if (audited.length > 0) {
155
+ text += `### Pages auditées (axe-core)\n`;
156
+ for (const p of audited) {
157
+ text += `- **${p.title || p.url}** — ${p.url}${p.score != null ? ` — **${p.score}%**` : ''}\n`;
158
+ }
159
+ text += '\n';
160
+ }
161
+ if (extrapolated.length > 0) {
162
+ text += `### Pages extrapolées (Template Detection — mêmes violations que la page source)\n`;
163
+ for (const p of extrapolated) {
164
+ text += `- ${p.title || p.url} — ${p.url}\n`;
165
+ }
166
+ text += '\n';
141
167
  }
142
- text += '\n';
143
168
  }
169
+ // ── Violations ──
144
170
  if (violations.length > 0) {
145
- // Regrouper les violations par zone partagée
146
- const sharedZones = ['header', 'nav', 'footer'];
147
- const inSharedZone = violations.filter(v => v.zone && sharedZones.includes(v.zone));
148
- const inContent = violations.filter(v => !v.zone || !sharedZones.includes(v.zone));
149
- if (inSharedZone.length > 0) {
150
- text += `## Violations en zones partagées (${inSharedZone.length})\n\n`;
151
- text += `> Ces violations sont dans des zones répétées sur toutes les pages (nav, header, footer).\n`;
152
- text += `> **Corrigez le composant layout une seule fois** pour résoudre toutes les pages.\n\n`;
153
- // Grouper par zone
154
- const byZone = new Map();
155
- for (const v of inSharedZone) {
156
- const zone = v.zone;
157
- if (!byZone.has(zone))
158
- byZone.set(zone, []);
159
- byZone.get(zone).push(v);
171
+ const real = violations.filter(v => !v.isExtrapolated);
172
+ const extrapolatedV = violations.filter(v => v.isExtrapolated);
173
+ // Trier par impact (critique d'abord)
174
+ real.sort((a, b) => (impactOrder[a.impact] ?? 4) - (impactOrder[b.impact] ?? 4));
175
+ text += `## Non-conformités (${real.length} réelles${extrapolatedV.length > 0 ? `, ${extrapolatedV.length} extrapolées` : ''})\n\n`;
176
+ for (const [i, v] of real.entries()) {
177
+ const impact = impactLabels[v.impact] || v.impact;
178
+ text += `### ${i + 1}. [${impact.toUpperCase()}] ${v.criteria || ''} ${v.criteriaTitle || v.description}\n\n`;
179
+ text += `| | |\n|---|---|\n`;
180
+ text += `| Critère RGAA | ${v.criteria || 'N/A'} |\n`;
181
+ if (v.theme)
182
+ text += `| Thématique | ${v.theme} |\n`;
183
+ text += `| Impact | ${impact} |\n`;
184
+ text += `| Nœuds affectés | ${v.nodesCount} |\n`;
185
+ if (v.zone) {
186
+ const isShared = ['header', 'nav', 'footer'].includes(v.zone);
187
+ text += `| Zone | ${v.zone}${isShared ? ' **(partagée — corrigez le layout, pas chaque page)**' : ''} |\n`;
160
188
  }
161
- for (const [zone, zoneViolations] of byZone) {
162
- text += `### Zone : ${zone}\n`;
163
- text += `Fichier probable : \`layouts/*.vue\` ou \`components/${zone === 'nav' ? 'AppNav' : zone === 'footer' ? 'AppFooter' : 'AppHeader'}.vue\`\n\n`;
164
- for (const v of zoneViolations) {
165
- text += `- **${v.criteriaTitle || v.description}** (${v.impact}, ${v.nodesCount} nœuds)`;
166
- if (v.nodes.length > 0)
167
- text += ` \`${v.nodes[0].target}\``;
168
- text += '\n';
169
- if (v.suggestion)
170
- text += ` ${v.suggestion}\n`;
171
- }
172
- text += '\n';
189
+ if (v.pageUrls && v.pageUrls.length > 0) {
190
+ text += `| Pages | ${v.pageUrls.join(', ')} |\n`;
191
+ }
192
+ text += '\n';
193
+ // Suggestion de correction
194
+ if (v.suggestion) {
195
+ text += `**Suggestion** : ${v.suggestion}\n\n`;
173
196
  }
197
+ // Nœuds détaillés avec HTML
198
+ text += `**Nœuds affectés :**\n\n`;
199
+ for (const n of v.nodes.slice(0, 5)) {
200
+ const selector = n.contextPath || n.target;
201
+ text += `- **Sélecteur** : \`${selector}\`\n`;
202
+ if (n.pageUrl)
203
+ text += ` - Page : ${n.pageUrl}\n`;
204
+ if (n.html)
205
+ text += ` - HTML : \`${n.html.replace(/\n/g, ' ').trim()}\`\n`;
206
+ if (n.failureSummary)
207
+ text += ` - Problème : ${n.failureSummary}\n`;
208
+ if (n.zone)
209
+ text += ` - Zone : ${n.zone}\n`;
210
+ }
211
+ if (v.nodes.length > 5)
212
+ text += `- ... et ${v.nodes.length - 5} autres nœuds\n`;
213
+ text += '\n';
174
214
  }
175
- if (inContent.length > 0) {
176
- text += `## Violations de contenu (${inContent.length})\n\n`;
177
- for (const [i, v] of inContent.entries()) {
178
- text += `### ${i + 1}. ${v.criteriaTitle || v.description}\n`;
179
- text += `- **Critère RGAA** : ${v.criteria || 'N/A'}${v.theme ? ` (${v.theme})` : ''}\n`;
180
- text += `- **Impact** : ${v.impact}\n`;
181
- text += `- **Éléments** : ${v.nodesCount} nœuds\n`;
182
- if (v.zone)
183
- text += `- **Zone** : ${v.zone}\n`;
184
- if (v.suggestion)
185
- text += `- **Suggestion** : ${v.suggestion}\n`;
186
- if (v.nodes.length > 0) {
187
- text += `- **Exemple** : \`${v.nodes[0].target}\`\n`;
188
- }
215
+ if (extrapolatedV.length > 0) {
216
+ text += `## Violations extrapolées (${extrapolatedV.length})\n\n`;
217
+ text += `> Template Detection : ces violations sont héritées d'une page source. Corrigez la page source, toutes les pages extrapolées seront corrigées.\n\n`;
218
+ for (const v of extrapolatedV) {
219
+ const impact = impactLabels[v.impact] || v.impact;
220
+ text += `- **[${impact}] ${v.criteria || ''}** ${v.criteriaTitle || v.description} — ${v.nodesCount} nœuds`;
221
+ if (v.pageUrls && v.pageUrls.length > 0)
222
+ text += ` (${v.pageUrls.join(', ')})`;
189
223
  text += '\n';
190
224
  }
225
+ text += '\n';
226
+ }
227
+ }
228
+ // ── Patterns / Corrections groupées ──
229
+ if (result.patterns && result.patterns.length > 0) {
230
+ text += `## Patterns de correction groupée (${result.patterns.length})\n\n`;
231
+ text += `> Un pattern = plusieurs nœuds identiques corrigeables en une seule modification.\n\n`;
232
+ text += `| Pattern | Type | Occurrences |\n|---------|------|-------------|\n`;
233
+ for (const p of result.patterns) {
234
+ text += `| ${p.title} | ${p.type} | ${p.occurrences} |\n`;
191
235
  }
236
+ text += '\n';
192
237
  }
193
- text += `\n🔗 Rapport complet : ${result.auditUrl}`;
238
+ // ── Lien rapport ──
239
+ text += `---\n\n`;
240
+ text += `**Rapport complet** : ${result.auditUrl}\n`;
241
+ text += `**Audit ID** : \`${result.auditId}\` (utilisable avec rgaa_recheck pour mesurer les améliorations)\n`;
194
242
  return { content: [{ type: 'text', text }] };
195
243
  }
196
244
  catch (error) {
@@ -723,7 +771,7 @@ server.tool('rgaa_report', 'Génère un rapport d\'audit RGAA structuré en JSON
723
771
  async function main() {
724
772
  if (!API_KEY) {
725
773
  console.error('⚠️ RGAA_API_KEY non configurée. Les outils d\'audit ne fonctionneront pas.');
726
- console.error(' Générez une clé sur https://app.rgaaudit.fr > Paramètres > API');
774
+ console.error(' Générez une clé sur https://rgaaudit.fr > Paramètres > API');
727
775
  }
728
776
  const transport = new StdioServerTransport();
729
777
  await server.connect(transport);
package/package.json CHANGED
@@ -1,50 +1,50 @@
1
- {
2
- "name": "@rgaaudit/mcp-server",
3
- "version": "0.3.1",
4
- "description": "MCP Server RGAA 4.1 — Audit d'accessibilité pour Claude Code, Cursor, Copilot",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "bin": {
8
- "rgaaudit-mcp": "dist/index.js"
9
- },
10
- "files": [
11
- "dist",
12
- "README.md"
13
- ],
14
- "scripts": {
15
- "build": "tsc",
16
- "dev": "tsx src/index.ts",
17
- "start": "node dist/index.js",
18
- "prepublishOnly": "npm run build"
19
- },
20
- "dependencies": {
21
- "@modelcontextprotocol/sdk": "^1.12.1",
22
- "zod": "^3.25.3"
23
- },
24
- "devDependencies": {
25
- "tsx": "^4.19.4",
26
- "typescript": "^5.8.3"
27
- },
28
- "engines": {
29
- "node": ">=18.0.0"
30
- },
31
- "keywords": [
32
- "mcp",
33
- "rgaa",
34
- "accessibility",
35
- "a11y",
36
- "wcag",
37
- "audit",
38
- "france",
39
- "rgaa4"
40
- ],
41
- "license": "MIT",
42
- "repository": {
43
- "type": "git",
44
- "url": "https://github.com/rgaaudit/mcp-server"
45
- },
46
- "homepage": "https://rgaaudit.fr/docs/mcp",
47
- "publishConfig": {
48
- "access": "public"
49
- }
50
- }
1
+ {
2
+ "name": "@rgaaudit/mcp-server",
3
+ "version": "0.4.0",
4
+ "description": "MCP Server RGAA 4.1 — Audit d'accessibilité pour Claude Code, Cursor, Copilot",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "rgaaudit-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "tsx src/index.ts",
17
+ "start": "node dist/index.js",
18
+ "prepublishOnly": "npm run build"
19
+ },
20
+ "dependencies": {
21
+ "@modelcontextprotocol/sdk": "^1.12.1",
22
+ "zod": "^3.25.3"
23
+ },
24
+ "devDependencies": {
25
+ "tsx": "^4.19.4",
26
+ "typescript": "^5.8.3"
27
+ },
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ },
31
+ "keywords": [
32
+ "mcp",
33
+ "rgaa",
34
+ "accessibility",
35
+ "a11y",
36
+ "wcag",
37
+ "audit",
38
+ "france",
39
+ "rgaa4"
40
+ ],
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/Foxbane-c/RGAAudit"
45
+ },
46
+ "homepage": "https://rgaaudit.fr/docs/mcp",
47
+ "publishConfig": {
48
+ "access": "public"
49
+ }
50
+ }