Model Context Protocol (MCP) : Révolution de l'Intégration IA avec Symfony
Imaginez pouvoir connecter n'importe quel modèle d'Intelligence Artificielle à n'importe quel outil externe avec un simple "plug-and-play". C'est exactement ce que propose le Model Context Protocol (MCP), introduit par Anthropic en novembre 2024. Ce protocole open-source révolutionne la façon dont les modèles d'IA accèdent aux données externes et interagissent avec les outils.
🤔 Qu'est-ce que le Model Context Protocol ?
Le problème fondamental
Les Modèles de Langage comme ChatGPT, Claude ou Gemini sont incroyablement puissants, mais ils souffrent d'une limitation majeure : ils n'ont pas accès au monde extérieur. Ils ne peuvent pas :
- 📂 Accéder à vos fichiers locaux
- 🌐 Naviguer sur Internet en temps réel
- 💾 Interroger vos bases de données
- 📧 Envoyer des emails
- 🔧 Utiliser des APIs externes
La solution MCP
Le Model Context Protocol agit comme un "USB-C universel pour l'IA". Au lieu de créer des intégrations personnalisées pour chaque outil, MCP fournit un protocole standardisé qui permet à n'importe quel modèle d'IA de se connecter à n'importe quel service externe.
[Modèle IA] ←→ [Client MCP] ←→ [Serveur MCP] ←→ [Outil Externe]
🏗️ Architecture du MCP
Les composants clés
Le MCP suit une architecture client-serveur avec trois éléments principaux :
1. MCP Host (Hôte)
L'application qui contient le modèle d'IA (Claude Desktop, VS Code, etc.)
2. MCP Client (Client)
Le pont entre le modèle d'IA et les serveurs MCP. Il :
- Traduit les demandes du modèle en requêtes MCP
- Gère l'authentification
- Maintient les connexions
3. MCP Server (Serveur)
Expose les outils et données externes via l'interface MCP standardisée
Les trois primitives fondamentales
Le MCP expose ses capacités via trois types de primitives :
🔧 Tools (Outils)
Fonctions exécutables que l'IA peut appeler :
{
"name": "send_email",
"description": "Envoie un email à un destinataire",
"inputSchema": {
"type": "object",
"properties": {
"to": { "type": "string", "format": "email" },
"subject": { "type": "string" },
"body": { "type": "string" }
}
}
}
📊 Resources (Ressources)
Données structurées ou non que l'IA peut consulter :
{
"uri": "file:///home/user/project/README.md",
"name": "Documentation du projet",
"description": "Fichier README principal",
"mimeType": "text/markdown"
}
💡 Prompts (Invites)
Templates prédéfinis pour guider le comportement de l'IA :
{
"name": "debug_application",
"description": "Assistant de débogage d'application",
"arguments": [
{
"name": "error_message",
"description": "Message d'erreur à analyser"
}
]
}
🚀 Avantages du MCP
✅ Standardisation
- Un seul protocole pour toutes les intégrations
- Réduction du problème "M×N" vers "M+N"
- Interopérabilité entre différents modèles et outils
🔒 Sécurité
- Contrôle granulaire des accès
- Isolation des serveurs
- Authentification standardisée
⚡ Productivité
- Développement accéléré
- Réutilisabilité des connecteurs
- Maintenance simplifiée
🔮 Extensibilité
- Ajout facile de nouvelles capacités
- Évolution sans rupture de compatibilité
- Écosystème ouvert
🐘 MCP avec Symfony : Implementation Pratique
Symfony a embrassé le MCP avec l'initiative Symfony AI et le composant MCP SDK. Voyons comment implémenter un serveur MCP avec Symfony.
Installation du MCP Server Bundle
composer require ecourty/mcp-server-bundle
Configuration du routing
Créez config/routes/mcp.yaml
:
mcp_controller:
path: /mcp
controller: mcp_server.entrypoint_controller
Création d'un outil simple
1. Schéma d'entrée
<?php
namespace App\Schema;
use OpenApi\Attributes as OA;
use Symfony\Component\Validator\Constraints as Assert;
class GetWeatherSchema
{
#[Assert\NotBlank]
#[OA\Property(type: 'string', description: 'Nom de la ville')]
public string $city;
#[Assert\Choice(['metric', 'imperial'])]
#[OA\Property(type: 'string', enum: ['metric', 'imperial'])]
public string $unit = 'metric';
}
2. Implémentation de l'outil
<?php
namespace App\Tool;
use App\Schema\GetWeatherSchema;
use Ecourty\McpServerBundle\Attribute\AsTool;
use Ecourty\McpServerBundle\Attribute\ToolAnnotations;
use Ecourty\McpServerBundle\IO\TextToolResult;
use Ecourty\McpServerBundle\IO\ToolResult;
use Symfony\Contracts\HttpClient\HttpClientInterface;
#[AsTool(
name: 'get_weather',
description: 'Récupère les informations météo pour une ville donnée',
annotations: new ToolAnnotations(
title: 'Service Météo',
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
)
)]
class GetWeatherTool
{
public function __construct(
private HttpClientInterface $httpClient,
private string $weatherApiKey
) {}
public function __invoke(GetWeatherSchema $payload): ToolResult
{
try {
$response = $this->httpClient->request('GET',
'https://api.openweathermap.org/data/2.5/weather', [
'query' => [
'q' => $payload->city,
'appid' => $this->weatherApiKey,
'units' => $payload->unit,
'lang' => 'fr'
]
]);
$data = $response->toArray();
$result = sprintf(
"🌡️ Météo à %s:\n" .
"Température: %s°%s\n" .
"Description: %s\n" .
"Humidité: %s%%\n" .
"Vent: %s m/s",
$data['name'],
round($data['main']['temp']),
$payload->unit === 'metric' ? 'C' : 'F',
$data['weather'][0]['description'],
$data['main']['humidity'],
$data['wind']['speed']
);
return new ToolResult([
new TextToolResult($result),
]);
} catch (\Exception $e) {
return new ToolResult(
elements: [
new TextToolResult('❌ Erreur lors de la récupération de la météo: ' . $e->getMessage())
],
isError: true,
);
}
}
}
Outil plus avancé : Système de notifications intelligent
1. Schéma pour les notifications
<?php
namespace App\Schema;
use OpenApi\Attributes as OA;
use Symfony\Component\Validator\Constraints as Assert;
class SendNotificationSchema
{
#[Assert\Choice(['email', 'slack', 'both'])]
#[OA\Property(type: 'string', enum: ['email', 'slack', 'both'])]
public string $channel;
#[Assert\NotBlank]
#[OA\Property(type: 'string', description: 'Destinataire (email ou @username Slack)')]
public string $recipient;
#[Assert\NotBlank]
#[OA\Property(type: 'string', description: 'Titre de la notification')]
public string $title;
#[Assert\NotBlank]
#[OA\Property(type: 'string', description: 'Contenu du message')]
public string $message;
#[Assert\Choice(['low', 'normal', 'high', 'urgent'])]
#[OA\Property(type: 'string', enum: ['low', 'normal', 'high', 'urgent'])]
public string $priority = 'normal';
#[OA\Property(type: 'boolean', description: 'Envoyer immédiatement ou programmer')]
public bool $immediate = true;
}
2. Implémentation de l'outil de notification
<?php
namespace App\Tool;
use App\Schema\SendNotificationSchema;
use App\Service\SlackService;
use Ecourty\McpServerBundle\Attribute\AsTool;
use Ecourty\McpServerBundle\Attribute\ToolAnnotations;
use Ecourty\McpServerBundle\IO\TextToolResult;
use Ecourty\McpServerBundle\IO\ToolResult;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Psr\Log\LoggerInterface;
#[AsTool(
name: 'send_notification',
description: 'Envoie des notifications intelligentes via email et/ou Slack selon le contexte',
annotations: new ToolAnnotations(
title: 'Système de Notifications',
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
)
)]
class NotificationTool
{
public function __construct(
private MailerInterface $mailer,
private SlackService $slackService,
private LoggerInterface $logger,
private string $fromEmail = 'noreply@epickone.com'
) {}
public function __invoke(SendNotificationSchema $payload): ToolResult
{
$results = [];
$hasError = false;
// Formatage du message selon la priorité
$formattedMessage = $this->formatMessage($payload);
try {
// Envoi par email
if (in_array($payload->channel, ['email', 'both'])) {
$emailResult = $this->sendEmail($payload, $formattedMessage);
$results[] = $emailResult;
if (str_contains($emailResult, '❌')) {
$hasError = true;
}
}
// Envoi via Slack
if (in_array($payload->channel, ['slack', 'both'])) {
$slackResult = $this->sendSlack($payload, $formattedMessage);
$results[] = $slackResult;
if (str_contains($slackResult, '❌')) {
$hasError = true;
}
}
// Log de l'activité
$this->logger->info('MCP Notification sent', [
'channel' => $payload->channel,
'recipient' => $payload->recipient,
'priority' => $payload->priority,
'success' => !$hasError
]);
$finalMessage = implode("\n", $results);
return new ToolResult(
elements: [new TextToolResult($finalMessage)],
isError: $hasError
);
} catch (\Exception $e) {
$this->logger->error('MCP Notification failed', [
'error' => $e->getMessage(),
'payload' => $payload
]);
return new ToolResult(
elements: [new TextToolResult('❌ Erreur système: ' . $e->getMessage())],
isError: true,
);
}
}
private function formatMessage(SendNotificationSchema $payload): string
{
$emoji = match($payload->priority) {
'urgent' => '🚨',
'high' => '⚠️',
'normal' => 'ℹ️',
'low' => '💬',
};
return "$emoji **{$payload->title}**\n\n{$payload->message}";
}
private function sendEmail(SendNotificationSchema $payload, string $formattedMessage): string
{
try {
// Validation de l'email
if (!filter_var($payload->recipient, FILTER_VALIDATE_EMAIL)) {
return "❌ Email invalide: {$payload->recipient}";
}
$email = (new Email())
->from($this->fromEmail)
->to($payload->recipient)
->subject("[{$payload->priority}] {$payload->title}")
->html($this->convertToHtml($formattedMessage));
$this->mailer->send($email);
return "✅ Email envoyé à {$payload->recipient}";
} catch (\Exception $e) {
return "❌ Échec email: " . $e->getMessage();
}
}
private function sendSlack(SendNotificationSchema $payload, string $formattedMessage): string
{
try {
// Formatage spécial pour Slack
$slackMessage = [
'text' => $payload->title,
'blocks' => [
[
'type' => 'section',
'text' => [
'type' => 'mrkdwn',
'text' => $formattedMessage
]
]
],
'channel' => $this->resolveSlackChannel($payload->recipient)
];
$response = $this->slackService->sendMessage($slackMessage);
if ($response['ok']) {
return "✅ Message Slack envoyé à {$payload->recipient}";
} else {
return "❌ Erreur Slack: " . ($response['error'] ?? 'Inconnue');
}
} catch (\Exception $e) {
return "❌ Échec Slack: " . $e->getMessage();
}
}
private function convertToHtml(string $markdown): string
{
// Conversion simple Markdown → HTML
$html = preg_replace('/\*\*(.*?)\*\*/', '<strong>$1</strong>', $markdown);
$html = nl2br($html);
return "
<html>
<body style='font-family: Arial, sans-serif;'>
<div style='padding: 20px;'>
{$html}
</div>
</body>
</html>
";
}
private function resolveSlackChannel(string $recipient): string
{
// Si c'est un @username, on le convertit en channel
if (str_starts_with($recipient, '@')) {
return $recipient;
}
// Si c'est un #channel
if (str_starts_with($recipient, '#')) {
return $recipient;
}
// Par défaut, on assume que c'est un username
return "@{$recipient}";
}
}
Test et débogage
Utilisez la commande de debug pour vérifier vos outils :
bin/console debug:mcp-tools
Résultat attendu :
MCP Tools Debug Information
===========================
Name Description Input Schema
------------------|--------------------------------------------|-------------------------------------------
get_weather | Récupère les informations météo | App\Schema\GetWeatherSchema
send_notification | Envoie des notifications intelligentes | App\Schema\SendNotificationSchema
Test via API
Test de l'outil météo
curl -X POST http://localhost:8000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"city": "Paris",
"unit": "metric"
}
}
}'
Test de l'outil de notifications
curl -X POST http://localhost:8000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "send_notification",
"arguments": {
"channel": "email",
"recipient": "admin@epickone.com",
"title": "Test MCP",
"message": "Ceci est un test du système MCP",
"priority": "normal"
}
}
}'
🌟 Cas d'usage concrets
1. Assistant de développement
- Outils : Analyse de code, génération de tests, déploiement
- Ressources : Documentation, logs d'erreur, métriques
- Prompts : Templates de debugging, revue de code
2. Support client automatisé
- Outils : Recherche commandes, mise à jour statut, envoi notifications
- Ressources : Base de connaissances, historique client
- Prompts : Réponses standardisées, escalade
3. Analyse de données
- Outils : Requêtes SQL, génération graphiques, export rapports
- Ressources : Datasets, tableaux de bord
- Prompts : Templates d'analyse, interprétation données
🔮 L'écosystème MCP en expansion
Adoption massive
- Anthropic : Intégration native dans Claude
- OpenAI : Support annoncé pour ChatGPT
- Google : Intégration dans Vertex AI et Gemini
- Microsoft : Support via .NET SDK
Outils disponibles
Plus de 200 serveurs MCP disponibles sur GitHub :
- 📊 Databases : PostgreSQL, MySQL, SQLite
- 🔧 DevOps : Git, Docker, Kubernetes
- 📱 Services : Slack, GitHub, Jira
- 💰 E-commerce : Stripe, Shopify
Future du MCP
- Registry API : Découverte centralisée des serveurs
- OAuth 2.0 : Authentification standardisée
- Serveurs distants : Déploiement cloud natif
- Multi-agents : Orchestration d'agents autonomes
⚡ Meilleures pratiques
Sécurité
// Validation stricte des entrées
#[Assert\Choice(['read', 'write'])]
public string $operation;
// Limitation des permissions
if ($operation === 'write' && !$this->isAuthorized()) {
throw new UnauthorizedException();
}
Performance
// Cache des résultats fréquents
#[Cache(maxAge: 300)]
public function getExpensiveData(): array
{
return $this->expensiveOperation();
}
Observabilité
// Logging détaillé
$this->logger->info('MCP tool called', [
'tool' => 'get_weather',
'city' => $payload->city,
'duration' => $stopwatch->stop()->getDuration()
]);
🎯 Conclusion
Le Model Context Protocol représente une évolution majeure dans l'intégration des IA avec le monde réel. En standardisant ces interactions, MCP :
- ✅ Simplifie le développement d'applications IA
- ✅ Accélère l'innovation dans l'écosystème
- ✅ Sécurise les interactions IA-outils
- ✅ Démocratise l'accès aux capacités avancées
Avec Symfony et le MCP Server Bundle, créer des serveurs MCP robustes et sécurisés n'a jamais été aussi simple. L'écosystème PHP peut ainsi participer pleinement à cette révolution de l'IA.
Le futur de l'IA ne sera pas déterminé par le prochain grand modèle, mais par la qualité des intégrations avec nos données et outils métier. MCP nous donne les clés de ce futur.
🚀 Prochaines étapes
- Explorez le MCP Server Bundle
- Créez votre premier serveur MCP avec Symfony
- Contribuez à l'écosystème open-source
- Partagez vos créations avec la communauté
L'ère de l'IA contextualisée commence maintenant. Ne ratez pas le train ! 🚂