Drupal + MCP = CMS piloté par IA
create_node, upload_media, publish... depuis Claude ou Cursor
Les CMS sont des silos. Pour créer un article, vous ouvrez le back-office, remplissez les champs, uploadez les images, configurez la taxonomie, puis publiez. Et si votre assistant IA — Claude, ChatGPT ou Cursor — pouvait le faire pour vous, directement depuis une conversation ?
C'est exactement ce que permet le Model Context Protocol (MCP) : un protocole ouvert qui standardise la communication entre les modèles d'IA et les systèmes externes. En transformant Drupal en serveur MCP, on expose des actions (create_node, upload_media, publish_node...) que n'importe quel client MCP peut invoquer.
Chez VOID, nous avons développé un module Drupal qui fait exactement ça. Voici comment.
1MCP en 2 minutes
Model Context Protocol (MCP)
Créé par Anthropic, MCP est un protocole ouvert qui définit comment les modèles d'IA interagissent avec des systèmes externes de manière standardisée. Pensez-y comme une API universelle pour l'IA — au lieu de coder une intégration custom pour chaque outil, un seul protocole les connecte tous.
Tools
Actions que l'IA peut exécuter. Dans notre cas : create_node, upload_media, publish_node...
Resources
Données que l'IA peut lire. Ici : les types de contenu, les taxonomies, la structure des champs Drupal.
Prompts
Instructions contextuelles que le serveur fournit à l'IA pour guider ses actions (ex : "les articles doivent avoir un champ image").
Architecture : Drupal comme serveur MCP
┌─────────────────┐ ┌──────────────────────────────────┐
│ Client MCP │ │ DRUPAL (Serveur MCP) │
│ │ JSON │ │
│ • Claude │ ◄─────► │ ┌──────────────────────────┐ │
│ • ChatGPT │ REST │ │ Module drupal_mcp │ │
│ • Cursor │ │ │ │ │
│ • Continue.dev │ │ │ Tools: │ │
│ │ │ │ • create_node │ │
└─────────────────┘ │ │ • upload_media │ │
│ │ • update_node │ │
│ │ • publish_node │ │
│ │ • list_content_types │ │
│ │ • get_node │ │
│ │ • list_taxonomy_terms │ │
│ │ • delete_node │ │
│ └──────────┬───────────────┘ │
│ │ │
│ ┌──────────▼───────────────┐ │
│ │ Drupal Core APIs │ │
│ │ Node, Media, Taxonomy, │ │
│ │ Field, User, File │ │
│ └──────────────────────────┘ │
└──────────────────────────────────┘2Les Tools MCP exposés par le module
Le module drupal_mcp expose 8 tools couvrant les opérations CRUD essentielles. Chaque tool correspond à une route REST authentifiée.
create_node
Créer un contenu (article, page, landing page)
Params : type, title, body, fields, status
update_node
Modifier un contenu existant
Params : nid, fields, revision_message
upload_media
Uploader une image ou un fichier
Params : file (base64), filename, alt, media_type
list_content_types
Lister les types de contenu disponibles
Params : —
get_node
Récupérer un contenu avec ses champs
Params : nid, include_fields
list_taxonomy_terms
Lister les termes d'un vocabulaire
Params : vocabulary_id
delete_node
Supprimer un contenu
Params : nid, confirm
publish_node
Publier / dépublier un contenu
Params : nid, status
Extensibilité
Le module est conçu pour être extensible. Ajouter un nouveau tool (ex : create_menu_link, manage_blocks, clear_cache) revient à créer un nouveau Controller et déclarer la route dans drupal_mcp.routing.yml. Le protocole MCP gère la découverte automatiquement.
3Implémentation : structure du module
drupal_mcp/
├── drupal_mcp.info.yml # Déclaration du module
├── drupal_mcp.routing.yml # Routes REST (/mcp/tools/*)
├── drupal_mcp.permissions.yml # Permissions granulaires
├── drupal_mcp.services.yml # Services (auth, rate limit)
├── src/
│ ├── Controller/
│ │ ├── McpDiscoveryController.php # GET /mcp — liste des tools
│ │ ├── CreateNodeController.php # POST /mcp/tools/create_node
│ │ ├── UpdateNodeController.php # PATCH /mcp/tools/update_node
│ │ ├── GetNodeController.php # GET /mcp/tools/get_node/{nid}
│ │ ├── DeleteNodeController.php # DELETE /mcp/tools/delete_node/{nid}
│ │ ├── PublishNodeController.php # POST /mcp/tools/publish_node
│ │ ├── UploadMediaController.php # POST /mcp/tools/upload_media
│ │ ├── ListContentTypesController.php # GET /mcp/resources/content_types
│ │ └── ListTaxonomyController.php # GET /mcp/resources/taxonomy/{vid}
│ ├── Authentication/
│ │ └── McpTokenAuth.php # Bearer token validation
│ └── EventSubscriber/
│ └── McpAuditSubscriber.php # Log de toutes les opérations
└── config/
└── install/
└── drupal_mcp.settings.yml # Config par défaut# Découverte MCP — liste tous les tools disponibles
drupal_mcp.discovery:
path: '/mcp'
defaults:
_controller: '\Drupal\drupal_mcp\Controller\McpDiscoveryController::list'
requirements:
_permission: 'access mcp api'
methods: [GET]
# Tool : Créer un node
drupal_mcp.create_node:
path: '/mcp/tools/create_node'
defaults:
_controller: '\Drupal\drupal_mcp\Controller\CreateNodeController::execute'
requirements:
_permission: 'mcp create content'
methods: [POST]
# Tool : Uploader un média
drupal_mcp.upload_media:
path: '/mcp/tools/upload_media'
defaults:
_controller: '\Drupal\drupal_mcp\Controller\UploadMediaController::execute'
requirements:
_permission: 'mcp upload media'
methods: [POST]
# Tool : Publier / dépublier
drupal_mcp.publish_node:
path: '/mcp/tools/publish_node'
defaults:
_controller: '\Drupal\drupal_mcp\Controller\PublishNodeController::execute'
requirements:
_permission: 'mcp publish content'
methods: [POST]<?php
namespace Drupal\drupal_mcp\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
class CreateNodeController extends ControllerBase {
public function execute(Request $request): JsonResponse {
$data = json_decode($request->getContent(), TRUE);
// Validation des paramètres requis
if (empty($data['type']) || empty($data['title'])) {
return new JsonResponse([
'error' => 'Missing required fields: type, title',
], 400);
}
$node = \Drupal\node\Entity\Node::create([
'type' => $data['type'],
'title' => $data['title'],
'body' => [
'value' => $data['body'] ?? '',
'format' => $data['format'] ?? 'full_html',
],
'status' => $data['status'] ?? 0, // brouillon par défaut
]);
// Champs dynamiques (field_image, field_tags, etc.)
if (!empty($data['fields'])) {
foreach ($data['fields'] as $field_name => $value) {
if ($node->hasField($field_name)) {
$node->set($field_name, $value);
}
}
}
$node->save();
return new JsonResponse([
'success' => TRUE,
'nid' => (int) $node->id(),
'uuid' => $node->uuid(),
'url' => $node->toUrl()->toString(),
'status' => $node->isPublished() ? 'published' : 'draft',
], 201);
}
}<?php
namespace Drupal\drupal_mcp\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
class UploadMediaController extends ControllerBase {
public function execute(Request $request): JsonResponse {
$data = json_decode($request->getContent(), TRUE);
if (empty($data['file']) || empty($data['filename'])) {
return new JsonResponse([
'error' => 'Missing required fields: file (base64), filename',
], 400);
}
// Décoder le fichier base64
$file_data = base64_decode($data['file']);
$directory = 'public://mcp-uploads/' . date('Y-m');
\Drupal::service('file_system')->prepareDirectory(
$directory,
\Drupal\Core\File\FileSystemInterface::CREATE_DIRECTORY
);
$file = \Drupal::service('file.repository')->writeData(
$file_data,
$directory . '/' . $data['filename'],
\Drupal\Core\File\FileExists::Rename
);
// Créer l'entité Media
$media = \Drupal\media\Entity\Media::create([
'bundle' => $data['media_type'] ?? 'image',
'name' => $data['alt'] ?? $data['filename'],
'field_media_image' => [
'target_id' => $file->id(),
'alt' => $data['alt'] ?? $data['filename'],
],
'status' => 1,
]);
$media->save();
return new JsonResponse([
'success' => TRUE,
'mid' => (int) $media->id(),
'fid' => (int) $file->id(),
'url' => \Drupal::service('file_url_generator')
->generateAbsoluteString($file->getFileUri()),
], 201);
}
}4Endpoint de découverte : GET /mcp
Le point d'entrée /mcp retourne la liste des tools disponibles au format MCP. C'est ce que le client (Claude, Cursor...) appelle en premier pour savoir ce qu'il peut faire.
{
"name": "drupal-mcp-server",
"version": "1.0.0",
"description": "Drupal CMS — MCP Server",
"tools": [
{
"name": "create_node",
"description": "Create a new content node in Drupal",
"inputSchema": {
"type": "object",
"properties": {
"type": { "type": "string", "description": "Content type machine name" },
"title": { "type": "string", "description": "Node title" },
"body": { "type": "string", "description": "Body content (HTML)" },
"status": { "type": "integer", "description": "1=published, 0=draft" },
"fields": { "type": "object", "description": "Additional fields" }
},
"required": ["type", "title"]
}
},
{
"name": "upload_media",
"description": "Upload an image or file to Drupal media library",
"inputSchema": {
"type": "object",
"properties": {
"file": { "type": "string", "description": "Base64 encoded file" },
"filename": { "type": "string", "description": "File name with extension" },
"alt": { "type": "string", "description": "Alt text for images" },
"media_type": { "type": "string", "description": "Media bundle (image, document)" }
},
"required": ["file", "filename"]
}
}
],
"resources": [
{ "uri": "drupal://content_types", "name": "Content Types", "description": "Available content types" },
{ "uri": "drupal://taxonomy/{vid}", "name": "Taxonomy Terms", "description": "Terms for a vocabulary" }
]
}5Sécurité : ne pas exposer son CMS sans garde-fous
Règle d'or
Un serveur MCP expose des capacités d'écriture sur votre CMS. Sans sécurisation rigoureuse, c'est une porte ouverte. Chaque couche ci-dessous est indispensable.
🔑 Authentification
- • Bearer Token dans le header
Authorization - • Token lié à un utilisateur Drupal (avec ses rôles et permissions)
- • Rotation périodique des tokens
- • Rejet immédiat des requêtes sans token valide
🛡️ Permissions granulaires
- •
access mcp api— accéder à l'API MCP - •
mcp create content— créer du contenu - •
mcp upload media— uploader des médias - •
mcp publish content— publier (séparé de créer) - •
mcp delete content— supprimer (restreint)
⏱️ Rate Limiting
- • Max 60 requêtes/minute par token
- • Max 10 uploads/minute (fichiers volumineux)
- • Taille max fichier : configurable (défaut 10 Mo)
- • Réponse
429 Too Many RequestsavecRetry-After
📋 Audit Log
- • Chaque opération MCP est loguée (action, user, timestamp, payload)
- • Intégration avec le système de log Drupal natif
- • Alertes sur les opérations sensibles (delete, bulk create)
- • Export pour compliance
access mcp api:
title: 'Access MCP API'
description: 'Access the MCP discovery endpoint and tool listing'
mcp create content:
title: 'MCP: Create content'
description: 'Create nodes via MCP tools'
restrict access: true
mcp upload media:
title: 'MCP: Upload media'
description: 'Upload files and create media entities via MCP'
restrict access: true
mcp publish content:
title: 'MCP: Publish content'
description: 'Publish or unpublish nodes via MCP'
restrict access: true
mcp delete content:
title: 'MCP: Delete content'
description: 'Delete nodes via MCP (dangerous)'
restrict access: true6Cas d'usage concrets
Production de contenu assistée par IA
Un rédacteur utilise Claude pour générer un article, puis le publie directement sur Drupal sans quitter la conversation.
Prompt dans Claude Desktop :
"Rédige un article de 800 mots sur les tendances e-commerce 2026 au Maroc, puis publie-le sur notre Drupal en brouillon avec la catégorie 'Digital'."
Migration de contenu par batch
Migrer des centaines d'articles depuis un CSV ou un ancien CMS via un prompt conversationnel, sans écrire un script de migration.
Prompt dans Cursor :
"Lis le fichier articles.csv et pour chaque ligne, crée un node 'article' sur Drupal avec title, body et field_category."
Workflow éditorial automatisé
L'IA crée le contenu en brouillon, ajoute les images, applique les tags, et l'éditeur n'a plus qu'à valider et publier.
Flux type :
IA create_node (draft) → IA upload_media → IA update_node (lier image) → Humain review → Humain publish_node
Développeur + Cursor = productivité x10
Un développeur code dans Cursor et peut tester ses contenus directement sur Drupal sans ouvrir le navigateur.
Dans Cursor :
"Crée 5 articles de test sur Drupal avec des titres lorem et publie-les pour tester la page listing."
7Démo : de la conversation au contenu publié
Crée un article "Les 5 tendances UX 2026" sur notre Drupal avec un body structuré en H2 et une intro percutante. Type : article. Status : brouillon.
Appel MCP : create_node
{
"type": "article",
"title": "Les 5 tendances UX 2026",
"body": "<p>En 2026, l'expérience utilisateur...</p><h2>1. IA conversationnelle</h2>...",
"status": 0,
"fields": {
"field_category": [{"target_id": 42}]
}
}✅ Node #1847 créé en brouillon — voir sur Drupal
Ajoute une image hero et publie l'article.
Appels MCP : upload_media → update_node → publish_node
✅ Image uploadée (mid: 523) — Node #1847 mis à jour — Publié
8Configurer le client MCP
Pour connecter Claude Desktop, Cursor ou tout autre client MCP à votre Drupal, il suffit de déclarer le serveur dans la config du client.
{
"mcpServers": {
"drupal": {
"url": "https://votre-site.com/mcp",
"transport": "rest",
"headers": {
"Authorization": "Bearer YOUR_MCP_TOKEN"
}
}
}
}Votre Drupal, piloté par IA
VOID développe et intègre des serveurs MCP sur Drupal pour connecter votre CMS aux assistants IA. Production de contenu accélérée, workflows automatisés, développement assisté.