@rgaaudit/mcp-server 0.2.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 ADDED
@@ -0,0 +1,73 @@
1
+ # @rgaaudit/mcp-server
2
+
3
+ Serveur MCP (Model Context Protocol) pour auditer l'accessibilite web selon le referentiel francais **RGAA 4.1**, directement depuis votre IDE.
4
+
5
+ Compatible **Claude Code**, **Cursor**, **Copilot**, **Windsurf**.
6
+
7
+ ## Installation rapide
8
+
9
+ ```bash
10
+ npx @rgaaudit/mcp-server
11
+ ```
12
+
13
+ ## Configuration
14
+
15
+ ### 1. Obtenir une cle API
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)
20
+
21
+ ### 2. Configurer Claude Code
22
+
23
+ Ajoutez dans `.mcp.json` a la racine de votre projet :
24
+
25
+ ```json
26
+ {
27
+ "mcpServers": {
28
+ "rgaaudit": {
29
+ "command": "npx",
30
+ "args": ["-y", "@rgaaudit/mcp-server"],
31
+ "env": {
32
+ "RGAA_API_KEY": "rga_votre_cle_ici"
33
+ }
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ ### 3. Configurer Cursor / Copilot
40
+
41
+ Ajoutez la meme configuration dans les parametres MCP de votre IDE.
42
+
43
+ ## Outils disponibles
44
+
45
+ | Outil | Description |
46
+ |-------|-------------|
47
+ | `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 |
52
+
53
+ ## Exemples d'utilisation
54
+
55
+ Dans Claude Code, demandez simplement :
56
+
57
+ ```
58
+ > Audite l'accessibilite de https://monsite.fr
59
+ > 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
62
+ ```
63
+
64
+ ## Variables d'environnement
65
+
66
+ | Variable | Requis | Description |
67
+ |----------|--------|-------------|
68
+ | `RGAA_API_KEY` | Oui | Cle API RGAAudit (format `rga_...`) |
69
+ | `RGAA_API_URL` | Non | URL de l'API (defaut: `https://app.rgaaudit.fr`) |
70
+
71
+ ## Licence
72
+
73
+ MIT
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * RGAAudit MCP Server — Audit d'accessibilité RGAA 4.1
4
+ *
5
+ * Se connecte à l'API RGAAudit pour auditer l'accessibilité web selon le
6
+ * référentiel français RGAA 4.1, directement depuis Claude Code, Cursor, Copilot.
7
+ *
8
+ * Configuration requise :
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)
11
+ *
12
+ * Outils disponibles :
13
+ * - rgaa_audit : Lancer un audit complet sur une URL
14
+ * - rgaa_score : Score de conformité rapide
15
+ * - rgaa_check : Vérifier un critère RGAA spécifique
16
+ * - rgaa_explain : Expliquer un critère RGAA avec exemples
17
+ * - rgaa_report : Rapport structuré JSON
18
+ */
19
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * RGAAudit MCP Server — Audit d'accessibilité RGAA 4.1
4
+ *
5
+ * Se connecte à l'API RGAAudit pour auditer l'accessibilité web selon le
6
+ * référentiel français RGAA 4.1, directement depuis Claude Code, Cursor, Copilot.
7
+ *
8
+ * Configuration requise :
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)
11
+ *
12
+ * Outils disponibles :
13
+ * - rgaa_audit : Lancer un audit complet sur une URL
14
+ * - rgaa_score : Score de conformité rapide
15
+ * - rgaa_check : Vérifier un critère RGAA spécifique
16
+ * - rgaa_explain : Expliquer un critère RGAA avec exemples
17
+ * - rgaa_report : Rapport structuré JSON
18
+ */
19
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
20
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
21
+ import { z } from 'zod';
22
+ const API_URL = process.env.RGAA_API_URL || 'https://app.rgaaudit.fr';
23
+ const API_KEY = process.env.RGAA_API_KEY || '';
24
+ const server = new McpServer({
25
+ name: 'rgaaudit',
26
+ version: '0.2.0',
27
+ });
28
+ /**
29
+ * Appelle POST /api/ci/scan
30
+ */
31
+ async function callScan(url, options) {
32
+ if (!API_KEY) {
33
+ throw new Error('RGAA_API_KEY non configurée.\n\n' +
34
+ '1. Créez un compte sur https://app.rgaaudit.fr\n' +
35
+ '2. Allez dans Paramètres > API\n' +
36
+ '3. Générez une clé API\n' +
37
+ '4. Configurez : RGAA_API_KEY=rga_...');
38
+ }
39
+ const res = await fetch(`${API_URL}/api/ci/scan`, {
40
+ method: 'POST',
41
+ headers: {
42
+ 'Content-Type': 'application/json',
43
+ 'X-API-Key': API_KEY,
44
+ },
45
+ body: JSON.stringify({
46
+ url,
47
+ details: options?.details ?? false,
48
+ threshold: options?.threshold ?? 0,
49
+ }),
50
+ });
51
+ if (!res.ok) {
52
+ const body = await res.json().catch(() => ({}));
53
+ throw new Error(`API erreur ${res.status}: ${body.message || res.statusText}`);
54
+ }
55
+ return res.json();
56
+ }
57
+ // ── Outil 1 : rgaa_audit ──
58
+ server.tool('rgaa_audit', 'Lancer un audit d\'accessibilité RGAA 4.1 complet sur une URL. Retourne les violations avec critères RGAA, impact, et suggestions de correction.', {
59
+ url: z.string().url().describe('URL du site web à auditer'),
60
+ }, async ({ url }) => {
61
+ try {
62
+ const result = await callScan(url, { details: true });
63
+ const violations = result.violationDetails || [];
64
+ const critique = violations.filter(v => v.impact === 'critical').length;
65
+ const serieux = violations.filter(v => v.impact === 'serious').length;
66
+ const modere = violations.filter(v => v.impact === 'moderate').length;
67
+ let text = `# Audit RGAA 4.1 — ${url}\n\n` +
68
+ `**Score** : ${result.score}% — ${result.conformity}\n` +
69
+ `**${result.violations} non-conformités** sur ${result.pages} pages\n` +
70
+ `- ${critique} critique(s), ${serieux} sérieuse(s), ${modere} modérée(s)\n\n`;
71
+ if (result.patterns && result.patterns.length > 0) {
72
+ text += `## Patterns de correction (${result.patterns.length})\n\n`;
73
+ for (const p of result.patterns) {
74
+ text += `- **${p.title}** (${p.type}) — ${p.violationsCount} violations, ${p.pagesAffected} pages\n`;
75
+ }
76
+ text += '\n';
77
+ }
78
+ if (violations.length > 0) {
79
+ text += `## Détail des violations\n\n`;
80
+ for (const [i, v] of violations.entries()) {
81
+ text += `### ${i + 1}. ${v.criteriaTitle || v.description}\n`;
82
+ text += `- **Critère RGAA** : ${v.criteria || 'N/A'}${v.theme ? ` (${v.theme})` : ''}\n`;
83
+ text += `- **Impact** : ${v.impact}\n`;
84
+ text += `- **Éléments** : ${v.nodesCount} nœuds sur ${v.pagesCount} pages\n`;
85
+ if (v.zone)
86
+ text += `- **Zone** : ${v.zone}\n`;
87
+ if (v.suggestion)
88
+ text += `- **Suggestion** : ${v.suggestion}\n`;
89
+ if (v.nodes.length > 0) {
90
+ text += `- **Exemple** : \`${v.nodes[0].target}\`\n`;
91
+ }
92
+ text += '\n';
93
+ }
94
+ }
95
+ text += `\n🔗 Rapport complet : ${result.auditUrl}`;
96
+ return { content: [{ type: 'text', text }] };
97
+ }
98
+ catch (error) {
99
+ return { content: [{ type: 'text', text: `Erreur : ${error instanceof Error ? error.message : String(error)}` }], isError: true };
100
+ }
101
+ });
102
+ // ── Outil 2 : rgaa_score ──
103
+ server.tool('rgaa_score', 'Score de conformité RGAA rapide pour une URL. Retourne le score (0-100%), le nombre de violations, et le niveau de conformité.', {
104
+ url: z.string().url().describe('URL du site web'),
105
+ }, async ({ url }) => {
106
+ try {
107
+ const result = await callScan(url);
108
+ const text = `**Score RGAA** : ${result.score}%\n` +
109
+ `**Conformité** : ${result.conformity}\n` +
110
+ `**Violations** : ${result.violations}\n` +
111
+ `**Pages analysées** : ${result.pages}\n` +
112
+ `**URL** : ${url}\n\n` +
113
+ `🔗 Détails : ${result.auditUrl}`;
114
+ return { content: [{ type: 'text', text }] };
115
+ }
116
+ catch (error) {
117
+ return { content: [{ type: 'text', text: `Erreur : ${error instanceof Error ? error.message : String(error)}` }], isError: true };
118
+ }
119
+ });
120
+ // ── Outil 3 : rgaa_check ──
121
+ server.tool('rgaa_check', 'Vérifier un critère RGAA spécifique sur une URL. Par exemple : critère 1.1 (images), 3.1 (couleurs), 8.1 (langue).', {
122
+ url: z.string().url().describe('URL du site web'),
123
+ criteria: z.string().describe('Code du critère RGAA (ex: 1.1, 3.1, 8.1, 11.1)'),
124
+ }, async ({ url, criteria }) => {
125
+ try {
126
+ const result = await callScan(url, { details: true });
127
+ const matching = (result.violationDetails || []).filter(v => v.criteria && v.criteria.startsWith(criteria));
128
+ if (matching.length === 0) {
129
+ return { content: [{ type: 'text', text: `✅ Critère RGAA ${criteria} : **Aucune violation détectée** sur ${url}\n\nScore global : ${result.score}%` }] };
130
+ }
131
+ const text = `❌ Critère RGAA ${criteria} : **${matching.length} violation(s)** sur ${url}\n\n` +
132
+ matching.map(v => `- **${v.criteriaTitle || v.description}** (${v.impact})\n` +
133
+ ` ${v.nodesCount} nœuds sur ${v.pagesCount} pages\n` +
134
+ (v.suggestion ? ` 💡 ${v.suggestion}\n` : '')).join('\n') +
135
+ `\n🔗 Détails : ${result.auditUrl}`;
136
+ return { content: [{ type: 'text', text }] };
137
+ }
138
+ catch (error) {
139
+ return { content: [{ type: 'text', text: `Erreur : ${error instanceof Error ? error.message : String(error)}` }], isError: true };
140
+ }
141
+ });
142
+ // ── Outil 4 : rgaa_explain (local, pas d'API) ──
143
+ const RGAA_CRITERES = {
144
+ '1.1': { numero: '1.1', thematique: 'Images', titre: 'Chaque image porteuse d\'information a-t-elle une alternative textuelle ?', niveau: 'A', description: 'Toute image qui apporte une information doit avoir un attribut alt décrivant cette information.', exemple: '```html\n<!-- Conforme -->\n<img src="logo.png" alt="Logo de l\'entreprise">\n\n<!-- Non conforme -->\n<img src="logo.png">\n```' },
145
+ '3.1': { numero: '3.1', thematique: 'Couleurs', titre: 'L\'information ne doit pas être donnée uniquement par la couleur.', niveau: 'A', description: 'Utiliser un second indicateur visuel (icône, texte, forme) en plus de la couleur.' },
146
+ '3.2': { numero: '3.2', thematique: 'Couleurs', titre: 'Le contraste entre le texte et le fond est-il suffisamment élevé ?', niveau: 'AA', description: 'Ratio minimum de 4.5:1 pour le texte normal, 3:1 pour le texte agrandi.', exemple: '```css\n/* Conforme — ratio 7.4:1 */\ncolor: #333; background: #fff;\n\n/* Non conforme — ratio 2.1:1 */\ncolor: #999; background: #fff;\n```' },
147
+ '6.1': { numero: '6.1', thematique: 'Liens', titre: 'Chaque lien est-il explicite ?', niveau: 'A', description: 'Le texte du lien doit permettre de comprendre sa destination.', exemple: '```html\n<!-- Conforme -->\n<a href="/tarifs">Voir nos tarifs</a>\n\n<!-- Non conforme -->\n<a href="/tarifs">Cliquez ici</a>\n```' },
148
+ '8.1': { numero: '8.1', thematique: 'Éléments obligatoires', titre: 'Chaque page est-elle définie par un type de document ?', niveau: 'A', description: 'La page doit commencer par <!DOCTYPE html>.' },
149
+ '8.3': { numero: '8.3', thematique: 'Éléments obligatoires', titre: 'La langue par défaut est-elle indiquée ?', niveau: 'A', description: 'L\'attribut lang doit être présent sur <html>.', exemple: '```html\n<html lang="fr">\n```' },
150
+ '8.5': { numero: '8.5', thematique: 'Éléments obligatoires', titre: 'Chaque page a-t-elle un titre ?', niveau: 'A', description: 'L\'élément <title> doit être présent et pertinent.' },
151
+ '9.1': { numero: '9.1', thematique: 'Structuration', titre: 'La hiérarchie de titres est-elle pertinente ?', niveau: 'A', description: 'Les titres h1-h6 doivent suivre une hiérarchie logique.' },
152
+ '9.2': { numero: '9.2', thematique: 'Structuration', titre: 'La structure utilise-t-elle des landmarks ?', niveau: 'A', description: 'Utiliser header, nav, main, footer.' },
153
+ '11.1': { numero: '11.1', thematique: 'Formulaires', titre: 'Chaque champ de formulaire a-t-il une étiquette ?', niveau: 'A', description: 'Chaque input doit être associé à un label.', exemple: '```html\n<!-- Conforme -->\n<label for="email">Email</label>\n<input id="email" type="email">\n\n<!-- Non conforme -->\n<input type="email" placeholder="Email">\n```' },
154
+ '11.2': { numero: '11.2', thematique: 'Formulaires', titre: 'Chaque étiquette est-elle pertinente ?', niveau: 'A', description: 'Le label doit décrire clairement la finalité du champ.' },
155
+ '12.7': { numero: '12.7', thematique: 'Navigation', titre: 'Un lien d\'accès rapide au contenu principal est-il présent ?', niveau: 'A', description: 'Un skip-link doit être le premier élément focusable.' },
156
+ '12.8': { numero: '12.8', thematique: 'Navigation', titre: 'L\'ordre de tabulation est-il cohérent ?', niveau: 'A', description: 'L\'ordre de focus doit suivre l\'ordre logique du contenu.' },
157
+ '13.1': { numero: '13.1', thematique: 'Consultation', titre: 'L\'utilisateur est-il averti de l\'ouverture d\'une nouvelle fenêtre ?', niveau: 'A', description: 'Indiquer visuellement ou textuellement quand un lien ouvre un nouvel onglet.' },
158
+ };
159
+ server.tool('rgaa_explain', 'Explique un critère RGAA 4.1 avec sa description, son niveau WCAG, et des exemples de code.', {
160
+ criteria: z.string().describe('Code du critère RGAA (ex: 1.1, 3.1, 8.1, 11.1)'),
161
+ }, async ({ criteria }) => {
162
+ const info = RGAA_CRITERES[criteria];
163
+ if (!info) {
164
+ return { content: [{ type: 'text', text: `Critère RGAA ${criteria} non trouvé dans la base locale. Les critères vont de 1.1 à 13.12.\n\nCritères disponibles : ${Object.keys(RGAA_CRITERES).join(', ')}` }] };
165
+ }
166
+ const text = `# Critère RGAA ${info.numero}\n\n` +
167
+ `**Thématique** : ${info.thematique}\n` +
168
+ `**Titre** : ${info.titre}\n` +
169
+ `**Niveau** : ${info.niveau}\n\n` +
170
+ (info.description ? `## Description\n${info.description}\n\n` : '') +
171
+ (info.exemple ? `## Exemple\n${info.exemple}\n` : '');
172
+ return { content: [{ type: 'text', text }] };
173
+ });
174
+ // ── Outil 5 : rgaa_report ──
175
+ server.tool('rgaa_report', 'Génère un rapport d\'audit RGAA structuré en JSON pour intégration CI/CD ou export.', {
176
+ url: z.string().url().describe('URL du site web'),
177
+ threshold: z.number().min(0).max(100).default(0).describe('Score minimum pour pass/fail (0 = pas de seuil)'),
178
+ }, async ({ url, threshold }) => {
179
+ try {
180
+ const result = await callScan(url, { details: true, threshold });
181
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
182
+ }
183
+ catch (error) {
184
+ return { content: [{ type: 'text', text: `Erreur : ${error instanceof Error ? error.message : String(error)}` }], isError: true };
185
+ }
186
+ });
187
+ // ── Démarrage ──
188
+ async function main() {
189
+ if (!API_KEY) {
190
+ console.error('⚠️ RGAA_API_KEY non configurée. Les outils d\'audit ne fonctionneront pas.');
191
+ console.error(' Générez une clé sur https://app.rgaaudit.fr > Paramètres > API');
192
+ }
193
+ const transport = new StdioServerTransport();
194
+ await server.connect(transport);
195
+ }
196
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@rgaaudit/mcp-server",
3
+ "version": "0.2.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": ["mcp", "rgaa", "accessibility", "a11y", "wcag", "audit", "france", "rgaa4"],
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/rgaaudit/mcp-server"
36
+ },
37
+ "homepage": "https://rgaaudit.fr/docs/mcp",
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
41
+ }