Et si vous pouviez dire à Claude "crée-moi un article sur Joomla" et le voir apparaître directement sur votre site, sans ouvrir l'administration ? C'est exactement ce que permet le protocole MCP (Model Context Protocol), développé par Anthropic pour connecter des intelligences artificielles à des outils externes.
Dans ce tutoriel, nous allons mettre en place, pas à pas, une connexion entre Claude Desktop (l'application bureau de Claude) et votre site Joomla 5, en passant par l'API REST native de Joomla. À la fin, vous pourrez piloter vos articles, catégories, menus, médias, tags et utilisateurs directement depuis une conversation avec Claude.
- Un site Joomla 5 (ou 4) en ligne
- Claude Desktop installé sur votre ordinateur Windows, Mac ou Linux
- Node.js installé sur votre ordinateur (gratuit)
- Environ 30 minutes
Comprendre le principe en 30 secondes
Joomla 5 intègre nativement une API REST qui permet à n'importe quel logiciel d'interagir avec votre site via des requêtes HTTP. De son côté, Claude Desktop peut se connecter à des serveurs MCP — de petits programmes qui exposent des "outils" que Claude peut utiliser.
Le schéma est simple :
- Claude Desktop lance un petit serveur Node.js sur votre ordinateur
- Ce serveur traduit les demandes de Claude en requêtes vers l'API Joomla
- Joomla répond, et Claude vous présente le résultat
Tout se passe en local sur votre machine — pas besoin d'hébergement supplémentaire.
Étape 1 — Activer l'API REST dans Joomla
1a. Activer le plugin Web Services
Dans votre administration Joomla, allez dans Extensions → Plugins, recherchez "Web Services" et activez le plugin "System – Web Services – API".
1b. Générer votre token API
Allez dans Utilisateurs → Votre compte, puis cliquez sur l'onglet "Authentification API Joomla". Cliquez sur "Créer un token".
Ce token s'utilise comme une clé d'accès : il identifie qui fait la requête et avec quels droits. Il correspond aux droits de votre compte utilisateur Joomla.
Étape 2 — Installer Node.js sur votre ordinateur
Node.js est un environnement d'exécution JavaScript qui va faire tourner notre petit serveur MCP. C'est gratuit et s'installe en quelques clics.
- Rendez-vous sur nodejs.org
- Téléchargez la version LTS (bouton vert à gauche — c'est la version stable recommandée)
- Installez-la avec les options par défaut (Suivant, Suivant, Terminer)
Pour vérifier que l'installation s'est bien passée, ouvrez un terminal (cmd sur Windows, Terminal sur Mac/Linux) et tapez :
node --version
Vous devriez voir s'afficher un numéro de version (ex : v20.x.x ou supérieur). Tout est bon.
Étape 3 — Créer le serveur MCP
( Le zip suivant sert d'exemple à décompresser et installer dans C:\ pour modification, sinon suivez la procédure )
Télécharger les fichiers MCP (zip)
3a. Créer le dossier
Créez un dossier sur votre ordinateur, par exemple C:\joomla-mcp sur Windows, ou ~/joomla-mcp sur Mac/Linux. C'est là que vivront les fichiers de votre serveur MCP.
3b. Créer les fichiers
Dans ce dossier, créez les trois fichiers suivants :
package.json — décrit les dépendances du projet :
{
"name": "joomla-mcp-local",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"node-fetch": "^3.3.2",
"dotenv": "^16.4.5",
"zod": "^3.22.4"
}
}
.env — vos identifiants (ne partagez jamais ce fichier) :
JOOMLA_URL=https://www.votre-site.fr
JOOMLA_TOKEN=votre_token_api_joomla
index.js — le cœur du serveur MCP :
import 'dotenv/config';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import fetch from 'node-fetch';
import fs from 'fs/promises';
import path from 'path';
const JOOMLA_URL = process.env.JOOMLA_URL?.replace(/\/$/, '');
const BASE = JOOMLA_URL + '/api/index.php/v1';
const HEADERS = {
'Authorization': 'Bearer ' + process.env.JOOMLA_TOKEN,
'Content-Type': 'application/json',
'Accept': 'application/vnd.api+json'
};
async function joomla(endpoint, method = 'GET', body = null) {
const opts = { method, headers: HEADERS };
if (body) opts.body = JSON.stringify(body);
const res = await fetch(BASE + endpoint, opts);
if (!res.ok) {
const text = await res.text();
throw new Error(`Joomla API ${res.status}: ${text}`);
}
return res.json();
}
function ok(data) {
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
}
const server = new McpServer({ name: 'joomla-mcp', version: '1.0.0' });
// ── ARTICLES ──────────────────────────────────────────────────────────────────
server.tool('lister_articles', 'Liste les articles Joomla',
{ limite: z.number().optional() },
async ({ limite = 20 }) => ok(await joomla(`/content/articles?page[limit]=${limite}`))
);
server.tool('lire_article', "Lire le contenu complet d'un article",
{ id: z.number() },
async ({ id }) => ok(await joomla(`/content/articles/${id}`))
);
server.tool('creer_article', 'Créer un article',
{
titre: z.string(),
contenu: z.string(),
categorie_id: z.number().optional(),
publie: z.boolean().optional(),
langue: z.string().optional()
},
async ({ titre, contenu, categorie_id = 2, publie = true, langue = '*' }) => {
return ok(await joomla('/content/articles', 'POST', {
title: titre, articletext: contenu,
catid: categorie_id, state: publie ? 1 : 0, language: langue
}));
}
);
server.tool('modifier_article', 'Modifier un article',
{
id: z.number(),
titre: z.string().optional(),
contenu: z.string().optional(),
publie: z.boolean().optional(),
metadesc: z.string().optional(),
metakey: z.string().optional(),
image_intro: z.string().optional(),
image_fulltext: z.string().optional(),
},
async ({ id, titre, contenu, publie, metadesc, metakey, image_intro, image_fulltext }) => {
const body = {};
if (titre !== undefined) body.title = titre;
if (contenu !== undefined) { body.introtext = contenu; body.fulltext = ''; }
if (publie !== undefined) body.state = publie ? 1 : 0;
if (metadesc !== undefined) body.metadesc = metadesc;
if (metakey !== undefined) body.metakey = metakey;
if (image_intro || image_fulltext) {
body.images = {
image_intro: image_intro ?? '',
image_intro_alt: '',
image_intro_caption: '',
float_intro: '',
image_fulltext: image_fulltext ?? image_intro ?? '',
image_fulltext_alt: '',
image_fulltext_caption:'',
float_fulltext: ''
};
}
// ⚠️ Toujours appeler assigner_tags EN DERNIER — le PATCH efface les tags
return ok(await joomla(`/content/articles/${id}`, 'PATCH', body));
}
);
server.tool('supprimer_article', 'Supprimer un article',
{ id: z.number() },
async ({ id }) => { await joomla(`/content/articles/${id}`, 'DELETE'); return ok({ success: true }); }
);
// ── CATÉGORIES ────────────────────────────────────────────────────────────────
server.tool('lister_categories', 'Liste les catégories', {},
async () => ok(await joomla('/content/categories'))
);
// ── UTILISATEURS ──────────────────────────────────────────────────────────────
server.tool('lister_utilisateurs', 'Liste les utilisateurs',
{ limite: z.number().optional() },
async ({ limite = 20 }) => ok(await joomla(`/users?page[limit]=${limite}`))
);
// ── MENUS ─────────────────────────────────────────────────────────────────────
server.tool('lister_menus', 'Liste les menus', {},
async () => ok(await joomla('/menus'))
);
// ── MÉDIAS ────────────────────────────────────────────────────────────────────
server.tool('lister_medias', "Liste les fichiers d'un dossier média",
{ dossier: z.string().optional() },
async ({ dossier = 'images' }) => ok(await joomla(`/media/files?path=${encodeURIComponent(dossier)}`))
);
server.tool('creer_dossier_media', 'Créer un dossier dans la médiathèque',
{ dossier: z.string() },
async ({ dossier }) => {
const result = await joomla('/media/files', 'POST', { path: dossier, type: 'dir' });
return ok({ success: true, dossier });
}
);
server.tool('uploader_image', 'Uploader une image depuis une URL distante',
{
url: z.string(),
nom: z.string(),
dossier: z.string().optional(),
},
async ({ url, nom, dossier = 'articles' }) => {
const imageRes = await fetch(url);
if (!imageRes.ok) throw new Error(`Erreur téléchargement : ${imageRes.status}`);
const ct = imageRes.headers.get('content-type') || 'image/jpeg';
const ext = ct.includes('png') ? 'png' : ct.includes('webp') ? 'webp' : ct.includes('gif') ? 'gif' : 'jpg';
const filename = `${nom}.${ext}`;
const base64 = Buffer.from(await imageRes.arrayBuffer()).toString('base64');
// ✅ path sans préfixe images/ — Joomla l'ajoute automatiquement
const result = await joomla('/media/files', 'POST', { path: `${dossier}/${filename}`, content: base64 });
return ok({ success: true, fichier: filename, url_joomla: `${JOOMLA_URL}/images/${dossier}/${filename}` });
}
);
server.tool('uploader_image_locale', 'Uploader une image depuis votre PC',
{
chemin_local: z.string(),
nom: z.string().optional(),
dossier: z.string().optional(),
},
async ({ chemin_local, nom, dossier = 'articles' }) => {
const buffer = await fs.readFile(chemin_local);
const ext = path.extname(chemin_local).replace('.', '').toLowerCase() || 'jpg';
const basename = nom ?? path.basename(chemin_local, path.extname(chemin_local));
const filename = `${basename}.${ext}`;
const base64 = buffer.toString('base64');
// ✅ path sans préfixe images/ — Joomla l'ajoute automatiquement
const result = await joomla('/media/files', 'POST', { path: `${dossier}/${filename}`, content: base64 });
return ok({ success: true, fichier: filename, url_joomla: `${JOOMLA_URL}/images/${dossier}/${filename}` });
}
);
// ── TAGS ──────────────────────────────────────────────────────────────────────
function normalizeTag(str) {
return str.toLowerCase().trim()
.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
.split(/\s+/).sort().join(' ');
}
server.tool('assigner_tags',
'Assigner des tags à un article par leur nom. Crée les tags manquants. Gère les variantes de casse.',
{
article_id: z.number(),
tags: z.array(z.string()),
},
async ({ article_id, tags }) => {
const res = await joomla('/tags?page[limit]=500');
const existing = res.data ?? [];
const tagIds = [];
const matched = [];
const created = [];
for (const name of tags) {
const normName = normalizeTag(name);
const candidates = existing.filter(t => normalizeTag(t.attributes.title) === normName);
if (candidates.length === 1) {
matched.push(candidates[0].attributes.title);
tagIds.push(parseInt(candidates[0].attributes.id)); // ✅ integer simple
} else {
const alias = normName.replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
const result = await joomla('/tags', 'POST', {
title: name.trim(), alias, language: '*', published: 1,
parent_id: 1, // ✅ obligatoire
description: '' // ✅ obligatoire
});
created.push(name.trim());
tagIds.push(parseInt(result.data.attributes.id)); // ✅ integer simple
}
}
// ✅ Tableau d'integers — seul format accepté par l'API Joomla
if (tagIds.length > 0) {
await joomla(`/content/articles/${article_id}`, 'PATCH', { tags: tagIds });
}
return ok({ success: true, article_id, tags_matches: matched, tags_crees: created });
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
language est obligatoire dans l'API Joomla. La valeur * signifie "toutes les langues" — c'est le bon choix pour un site monolingue. Pour un site multilingue, utilisez fr-FR, en-GB, etc.Étape 4 — Installer les dépendances
Ouvrez un terminal, placez-vous dans votre dossier et lancez :
cd C:\joomla-mcp
npm install
npm (le gestionnaire de paquets de Node.js) va télécharger automatiquement toutes les bibliothèques nécessaires dans un sous-dossier node_modules. L'opération prend environ une minute.
node index.js dans votre terminal. Si rien ne s'affiche (et pas d'erreur), c'est parfait — le serveur stdio attend silencieusement. Stoppez-le avec Ctrl+C.Étape 5 — Configurer Claude Desktop
Claude Desktop se configure via un fichier JSON. Ouvrez le fichier suivant avec un éditeur de texte (Bloc-notes, VS Code...) :
- Windows :
C:\Users\[VotreNom]\AppData\Roaming\Claude\claude_desktop_config.json - Mac :
~/Library/Application Support/Claude/claude_desktop_config.json - Linux :
~/.config/Claude/claude_desktop_config.json
AppData est caché par défaut. Dans l'explorateur de fichiers, allez dans Affichage → Éléments masqués pour le rendre visible. Vous pouvez aussi coller directement le chemin dans la barre d'adresse de l'explorateur.Ajoutez la section mcpServers dans ce fichier :
{
"mcpServers": {
"joomla": {
"command": "node",
"args": ["C:\\joomla-mcp\\index.js"],
"env": {
"JOOMLA_URL": "https://www.votre-site.fr",
"JOOMLA_TOKEN": "votre_token_api_joomla"
}
}
}
}
C:\\joomla-mcp\\index.js et non C:\joomla-mcp\index.js. Sur Mac/Linux, utilisez le chemin normal : /Users/vous/joomla-mcp/index.js.Sauvegardez le fichier, puis redémarrez complètement Claude Desktop (fermez-le entièrement depuis la barre des tâches, puis rouvrez-le).
Étape 6 — Vérifier la connexion
Dans Claude Desktop, allez dans Paramètres → Developer (ou Développeur). Vous devriez voir apparaître un serveur nommé "joomla" avec un statut de connexion.
Testez ensuite directement dans la conversation :
"Liste les articles de mon site Joomla"
Claude va utiliser automatiquement l'outil MCP et vous afficher la liste de vos articles.
- "Crée un article intitulé 'Actualités de mai' dans la catégorie 10, non publié"
- "Liste les catégories de mon site"
- "Uploade cette image dans le dossier articles, nom 'ma-photo' : https://example.com/photo.jpg"
- "Uploade C:\Users\moi\Images\photo.jpg dans le dossier produits"
- "Crée un dossier 'evenements/2026' dans la médiathèque"
- "Sur l'article 42, image intro images/articles/ma-photo.jpg, meta description 'Mon texte SEO'"
- "Ajoute les tags Joomla et IA Claude à l'article 42"
Gérer vos médias
Le serveur MCP expose plusieurs outils dédiés à la gestion de la médiathèque Joomla.
Uploader une image depuis une URL
Claude télécharge l'image, détecte automatiquement l'extension depuis le Content-Type HTTP, et l'enregistre dans votre médiathèque :
"Uploade cette image dans le dossier articles, nom 'ma-photo' : https://example.com/photo.jpg"
Uploader une image depuis votre PC
Fournissez le chemin absolu du fichier. Le dossier destination est créé automatiquement par Joomla s'il n'existe pas :
"Uploade C:\Users\moi\Images\photo.jpg dans le dossier produits"
images/ : L'API Joomla ajoute automatiquement images/ aux chemins médias. Le path envoyé doit être dossier/fichier.jpg et non images/dossier/fichier.jpg. Si vous doublez le préfixe, Joomla crée le fichier dans images/images/dossier/ — invisible dans la médiathèque, sans aucun message d'erreur.Créer un dossier
"Crée un dossier 'evenements/2026' dans la médiathèque"
Gérer les images et la meta description d'un article
L'outil modifier_article permet de définir directement les images d'introduction, d'article complet et la meta description SEO :
"Sur l'article 42, image intro images/mcp/ma-photo.jpg, meta description 'Découvrez comment...'"
tags n'est pas réenvoyé — et l'API ne permet pas de les lire pour les préserver automatiquement.Gérer les tags
L'outil assigner_tags fonctionne en langage naturel — pas besoin de connaître les IDs. Il recherche les tags existants, crée ceux qui manquent, et les associe à l'article. Une normalisation intelligente évite les doublons : "IA Claude", "Claude IA" et "claude ia" sont reconnus comme le même tag.
"Ajoute les tags MCP Joomla et IA Claude à l'article 42"
[11, 12]. Le format [{"id":11}] est accepté sans erreur mais silencieusement ignoré — c'est le piège le plus courant.Résolution des problèmes courants
Le serveur "joomla" n'apparaît pas dans les paramètres
Vérifiez que le fichier claude_desktop_config.json est valide. Collez son contenu sur jsonlint.com pour le valider. Redémarrez Claude Desktop complètement après toute modification.
Erreur "Field 'language' doesn't have a default value"
Votre version du serveur MCP n'envoie pas le champ language obligatoire. Utilisez le code fourni dans ce tutoriel.
La modification d'article ne met pas à jour le contenu
L'API REST Joomla accepte articletext uniquement en création (POST). En modification (PATCH), utilisez introtext + fulltext: ''.
L'image est uploadée mais introuvable en FTP ou dans le backend
Problème du double préfixe images/. N'envoyez que path: "dossier/fichier.jpg" sans le préfixe images/.
Les tags disparaissent après une modification d'article
Le PATCH Joomla est destructif sur les tags. Toujours appeler assigner_tags en dernier, après toutes les autres modifications.
Erreur 401 Unauthorized
Token API incorrect ou expiré. Régénérez-le dans l'administration Joomla et mettez-le à jour dans claude_desktop_config.json.
Erreur 403 Forbidden
Le plugin "System – Web Services – API" n'est pas activé. Vérifiez dans Extensions → Plugins.
Aller plus loin
Ce tutoriel couvre les opérations essentielles. L'API REST de Joomla expose bien d'autres ressources : champs personnalisés, modules, workflow, etc. Vous pouvez étendre le serveur MCP en ajoutant de nouveaux outils dans index.js en suivant le même pattern.
Le code source complet est disponible librement via le zip ci-dessus — n'hésitez pas à l'adapter à vos besoins et à contribuer vos améliorations à la communauté Joomla.
Tutoriel rédigé par Serge Billon — web54.fr | Agence web Joomla en Lorraine
