Un serveur MCP avec PHP
Comment interfacer vos outils locaux avec l'intelligence artificielle ?
Notions théoriques
Le Model Context Protocol (MCP) est un standard ouvert révolutionnaire qui permet aux modèles de langage (LLM), comme Claude ou ceux intégrés dans vos environnements de développement, d'accéder à des données et à des outils locaux de manière sécurisée et structurée. Jusqu'à présent, pour qu'une IA puisse lire vos fichiers ou interagir avec une base de données, il fallait souvent uploader des documents ou créer des API complexes. Avec MCP, vous créez un "pont" direct.
L'architecture du protocole
Le fonctionnement du MCP repose sur une relation entre trois entités distinctes :
- L'Hôte (Host) : C'est l'application avec laquelle vous interagissez (par exemple, Claude Desktop ou VS Code via une extension).
- Le Client : Une couche logicielle intégrée à l'hôte qui gère la connexion.
- Le Serveur : C'est votre programme, ici écrit en PHP, qui expose des capacités spécifiques (lire des fichiers, exécuter des commandes, etc.).
Contrairement aux API Web traditionnelles qui utilisent souvent le protocole HTTP sur le port 80 ou 443, un serveur MCP local communique généralement via les flux standards de votre système d'exploitation : stdin (entrée standard) et stdout (sortie standard).
La communication en JSON-RPC
Les échanges entre le client et votre serveur PHP se font au format JSON-RPC 2.0. C'est un protocole léger où chaque message est un objet JSON contenant :
- Une version (
jsonrpc: "2.0") - Une méthode (ce que l'on veut faire)
- Des paramètres
- Un identifiant unique (
id) pour réassocier la réponse à la requête.
Pourquoi utiliser PHP pour MCP ?
Bien que beaucoup d'exemples MCP soient en Python ou TypeScript, PHP est un candidat excellent pour les raisons suivantes :
- Exécution CLI : PHP dispose d'un moteur de ligne de commande très performant.
- Traitement JSON : Les fonctions
json_encode()etjson_decode()sont natives et ultra-rapides. - Accès système : PHP permet de manipuler les fichiers et les processus Windows avec une grande facilité.
- Gratuité : Aucun coût de licence, et pas besoin de serveur web (Apache/Nginx) pour faire fonctionner un serveur MCP.
Le cycle de vie d'une connexion
Lorsqu'un client lance votre script PHP, il suit toujours ces étapes :
- Initialisation : Le client envoie une requête
initialize. Votre serveur répond avec ses capacités. - Découverte : Le client demande la liste des outils disponibles via
tools/list. - Exécution : Lorsque l'utilisateur demande à l'IA d'agir, le client envoie
tools/callavec les arguments nécessaires.
Le serveur MCP doit rester en exécution constante. Il utilise une boucle infinie pour écouter stdin et répondre instantanément sur stdout.
Il est crucial de ne jamais utiliser echo ou var_dump de manière sauvage dans votre code PHP. Toute sortie texte qui n'est pas un JSON valide corrompra la communication et fera planter le client.
Exemple pratique
Nous allons mettre en place un serveur MCP minimaliste sous Windows qui permet à une IA de connaître la version de PHP installée et de lister les fichiers d'un dossier.
1. Préparation de l'environnement
Assurez-vous que PHP est accessible dans votre terminal Windows. Ouvrez une invite de commande et tapez :
php -v
Si la commande n'est pas reconnue, vous devez ajouter le chemin de votre dossier PHP (ex: C:\php) dans les Variables d'environnement de Windows.
2. Création du serveur PHP
Créez un dossier pour votre projet et ouvrez-le dans VS Code. Créez un fichier nommé mcp_server.php. Copiez le code suivant, qui constitue la structure de base d'un serveur MCP :
<?php
// On empêche PHP d'envoyer des erreurs au format texte sur la sortie standard
ini_set('display_errors', 0);
error_reporting(E_ALL);
// Ouverture des flux
$stdin = fopen("php://stdin", "r");
$stdout = fopen("php://stdout", "w");
// Journalisation pour le débogage (dans un fichier, pas sur stdout !)
function log_message($message) {
file_put_contents("debug.log", $message . PHP_EOL, FILE_APPEND);
}
log_message("Serveur MCP PHP démarré.");
while ($line = fgets($stdin)) {
$request = json_decode($line, true);
if (!$request) continue;
$method = $request['method'] ?? '';
$id = $request['id'] ?? null;
if ($method === 'initialize') {
$response = [
"jsonrpc" => "2.0",
"id" => $id,
"result" => [
"protocolVersion" => "2024-11-05",
"capabilities" => [
"tools" => (object)[]
],
"serverInfo" => ["name" => "MonServeurPHP", "version" => "1.0.0"]
]
];
} elseif ($method === 'tools/list') {
$response = [
"jsonrpc" => "2.0",
"id" => $id,
"result" => [
"tools" => [
[
"name" => "get_php_version",
"description" => "Récupère la version actuelle de PHP sur le système.",
"inputSchema" => ["type" => "object", "properties" => (object)[]]
]
]
]
];
} elseif ($method === 'tools/call') {
$toolName = $request['params']['name'] ?? '';
if ($toolName === 'get_php_version') {
$response = [
"jsonrpc" => "2.0",
"id" => $id,
"result" => [
"content" => [
["type" => "text", "text" => "La version de PHP est : " . PHP_VERSION]
]
]
];
}
}
if (isset($response)) {
fwrite($stdout, json_encode($response) . "\n");
fflush($stdout);
unset($response);
}
}
3. Test du serveur
Pour vérifier que votre serveur répond correctement, vous pouvez simuler une requête d'initialisation. Créez un fichier test_input.json contenant :
{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "test", "version": "1.0"}}}
Lancez ensuite la commande suivante dans votre terminal :
type test_input.json | php mcp_server.php
Utilisez toujours fflush($stdout) après un fwrite pour forcer l'envoi immédiat des données au client sans attendre que le tampon mémoire soit plein.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Objectif : créer un serveur MCP, avec Mistral qui expose 3 outils
lister_fichiers→ liste les fichiers du dossierlire_fichier→ lit le contenu d’un fichier donnéécrire_fichier→ écrit du texte dans un fichier
et qui communique via HTTP.
1. Préparation de l’environnement
-
Vérifie que PHP est bien dans le PATH
Ouvre PowerShell ou le terminal intégré de VS Code et tape :php -vTu dois voir quelque chose comme PHP 8.2.x ou 8.3.x
-
Crée le dossier de travail et ouvre-le dans VS Code :
mkdir "$HOME\Desktop\mcp-tp-http-php"
cd "$HOME\Desktop\mcp-tp-http-php"
code . -
Crée ces deux fichiers dans le dossier (clic droit → New File dans VS Code) :
server.phphttp-server.php
2. Code principal (server.php)
Ouvre server.php et colle ce code complet :
<?php
// server.php ────────────────────────────────────────────────────────────────
ini_set('display_errors', 0);
ini_set('html_errors', 0);
error_reporting(E_ALL);
function debug_log($message) {
$log = date('[Y-m-d H:i:s] ') . $message . "\n";
file_put_contents(__DIR__ . '/trace.log', $log, FILE_APPEND);
}
// ── Lecture de la requête entrante ─────────────────────────────────────────
$rawInput = file_get_contents('php://input');
$request = json_decode($rawInput, true);
if (!$request || !isset($request['jsonrpc']) || $request['jsonrpc'] !== '2.0') {
header('Content-Type: application/json', true, 400);
echo json_encode(['jsonrpc' => '2.0', 'error' => ['code' => -32600, 'message' => 'Invalid Request']]);
exit;
}
$id = $request['id'] ?? null;
$method = $request['method'] ?? '';
$params = $request['params'] ?? [];
$response = null;
debug_log("Reçu → method = $method | id = " . ($id ?? 'null'));
// ── initialize ─────────────────────────────────────────────────────────────
if ($method === 'initialize') {
$response = [
'jsonrpc' => '2.0',
'id' => $id,
'result' => [
'protocolVersion' => '2024-11-05', // version connue en 2025-2026
'capabilities' => ['tools' => new stdClass()],
'serverInfo' => [
'name' => 'TP-HTTP-PHP-MCP',
'version' => '1.1.0'
]
]
];
}
// ── tools/list ─────────────────────────────────────────────────────────────
elseif ($method === 'tools/list') {
$response = [
'jsonrpc' => '2.0',
'id' => $id,
'result' => [
'tools' => [
[
'name' => 'lister_fichiers',
'description' => 'Retourne la liste des fichiers et dossiers présents dans le répertoire du script PHP.',
'inputSchema' => [
'type' => 'object',
'properties' => new stdClass(),
'additionalProperties' => false
]
],
[
'name' => 'lire_fichier',
'description' => 'Lit et retourne le contenu texte d’un fichier situé dans le même dossier que le script.',
'inputSchema' => [
'type' => 'object',
'properties' => [
'nom' => [
'type' => 'string',
'description' => 'Nom du fichier (pas de chemin, uniquement le nom ou sous-dossier simple)',
'minLength' => 1
]
],
'required' => ['nom'],
'additionalProperties' => false
]
]
]
]
];
}
// ── tools/call ─────────────────────────────────────────────────────────────
elseif ($method === 'tools/call') {
$toolName = $params['name'] ?? '';
$args = $params['arguments'] ?? [];
if ($toolName === 'lister_fichiers') {
$items = scandir(__DIR__);
$items = array_diff($items, ['.', '..']);
natcasesort($items);
$liste = "Contenu du dossier :\n" . implode("\n• ", $items);
$response = [
'jsonrpc' => '2.0',
'id' => $id,
'result' => [
'content' => [
['type' => 'text', 'text' => $liste]
]
]
];
}
elseif ($toolName === 'lire_fichier') {
$nom = trim($args['nom'] ?? '');
// Sécurité minimale : pas de .. ni de / absolu
if (preg_match('#(^|/)\.\.?#', $nom) || strpos($nom, '/') !== false || strlen($nom) < 1) {
$response = [
'jsonrpc' => '2.0',
'id' => $id,
'error' => ['code' => -32001, 'message' => 'Nom de fichier invalide']
];
} else {
$chemin = __DIR__ . DIRECTORY_SEPARATOR . $nom;
if (!file_exists($chemin) || is_dir($chemin)) {
$response = [
'jsonrpc' => '2.0',
'id' => $id,
'error' => ['code' => -32002, 'message' => 'Fichier non trouvé']
];
} else {
$contenu = @file_get_contents($chemin);
if ($contenu === false) {
$response = [
'jsonrpc' => '2.0',
'id' => $id,
'error' => ['code' => -32003, 'message' => 'Impossible de lire le fichier']
];
} else {
$response = [
'jsonrpc' => '2.0',
'id' => $id,
'result' => [
'content' => [
['type' => 'text', 'text' => "Contenu du fichier $nom :\n\n" . $contenu]
]
]
];
}
}
}
}
}
// ── Envoi de la réponse ────────────────────────────────────────────────────
header('Content-Type: application/json; charset=utf-8');
if ($response) {
echo json_encode($response, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
debug_log("Réponse envoyée");
} else {
http_response_code(404);
echo json_encode(['jsonrpc' => '2.0', 'error' => ['code' => -32601, 'message' => 'Method not found']]);
debug_log("Méthode inconnue : $method");
}
echo "\n"; // petite fin de ligne demandée par certains clients
3. Lanceur HTTP (http-server.php)
Crée http-server.php avec ce contenu très court :
<?php
// http-server.php
echo "Démarrage du serveur MCP HTTP...\n";
echo "URL → http://127.0.0.1:8765\n";
echo "Laissez cette fenêtre ouverte.\n\n";
require __DIR__ . '/server.php';
4. Lancement du serveur
Dans le terminal intégré de VS Code (ou PowerShell) :
cd "$HOME\Desktop\mcp-tp-http-php"
# Option 1 : le plus simple (recommandé pour le TP)
php -S 127.0.0.1:8765 http-server.php
Tu devrais voir :
Démarrage du serveur MCP HTTP...
URL → http://127.0.0.1:8765
Laissez cette fenêtre ouverte.
Laisse cette fenêtre ouverte pendant tout le test.
5. Connexion dans Le Chat de Mistral
Nous allons nous connecter à
chat.mistral.aipour tester notre serveur MCP.
-
Ouvre https://chat.mistral.ai dans ton navigateur
-
Connecte-toi si nécessaire
-
Clique sur ton profil (coin supérieur droit) → Connectors (ou Outils / Connectors selon la langue et la version)
-
Clique sur + Add custom connector ou Custom MCP server
-
Remplis les champs :
Champ Valeur suggérée Name Mon serveur PHP local Description Liste et lit les fichiers du dossier TP Transport HTTP URL http://127.0.0.1:8765Authentication None (Autres options) Laisse par défaut -
Clique sur Save ou Test connection (si l’option existe)
Est ce que la connexion dans Le Chat de Mistral est gratuite ?
La connexion et l'utilisation des serveurs MCP sur Mistral Le Chat (chat.mistral.ai) sont actuellement totalement gratuites.
Contrairement à d'autres plateformes qui réservent parfois les fonctionnalités avancées (comme l'utilisation d'outils externes ou d'agents) aux abonnés "Pro" ou "Premium", Mistral a choisi de laisser ces options accessibles à tous les utilisateurs disposant d'un compte gratuit.
Il n'est pas nécessaire de sortir votre carte bleue ou de souscrire à une API payante (la "Console" Mistral) pour ce TP. Le site Le Chat suffit largement.
Voici quelques précisions importantes pour votre usage en tant qu'étudiant :
Pourquoi est-ce gratuit ?
- Stratégie d'ouverture : Mistral AI encourage les développeurs à bâtir des outils sur leur plateforme pour enrichir leur écosystème.
- Exécution locale : C'est votre ordinateur qui fait le "travail" (calcul PHP, lecture de fichiers). Mistral ne fait qu'envoyer une requête JSON à votre adresse
127.0.0.1. Cela ne leur coûte donc presque rien en ressources serveur.
Les points de vigilance
Bien que l'accès soit gratuit, gardez en tête ces deux éléments techniques :
- L'exposition locale : Pour que Mistral (qui est sur le Web) puisse parler à votre serveur PHP (qui est sur votre PC), l'interface de Mistral utilise votre navigateur comme passerelle. Tant que votre serveur tourne sur
127.0.0.1:8765, cela fonctionnera sans frais. - Limites de requêtes : Bien que l'usage du MCP soit gratuit, les modèles de langage (comme Mistral Large ou Pixtral) peuvent avoir des quotas de messages par heure sur le plan gratuit. Si vous atteignez la limite, vous devrez simplement attendre un peu pour continuer vos tests.
Il faut configurer le pare-feu (Firewall) pour autoriser le navigateur à communiquer avec le port 8765.
6. Test dans une conversation
-
Ouvre une nouvelle conversation dans Le Chat
-
Écris (en français de préférence) :
Peux-tu me dire quels fichiers se trouvent dans mon dossier de TP PHP ? -
Normalement, une popup ou une bannière apparaît pour demander l’autorisation d’utiliser l’outil
lister_fichiers→ Accepte
-
Puis essaie :
Maintenant peux-tu afficher le contenu du fichier server.php ?→ Accepte encore l’exécution
Tu devrais voir Mistral répondre avec la liste des fichiers, puis avec le contenu du script.
7. Dépannage rapide
| Symptôme | Que vérifier / faire |
|---|---|
| Pas de popup d’autorisation du tout | → Le serveur n’est pas lancé OU l’URL est mal tapée (vérifie 127.0.0.1:8765) |
| Erreur “timeout” ou “connection refused” | → php -S est-il toujours lancé ? Fenêtre fermée par erreur ? |
| Outil visible mais erreur à l’exécution | → Ouvre trace.log dans le dossier (avec VS Code) et regarde les dernières lignes |
| “Method not found” ou 404 | → Vérifie que http-server.php contient bien require 'server.php' |
| Le contenu du fichier est vide/truncaté | → Le fichier contient des caractères spéciaux → ajoute UTF-8 dans la réponse |
Voici une proposition réaliste et cohérente pour la suite du TP (version HTTP), en restant dans le même style que les étapes précédentes.
8. Un outil pour lire un fichier
Maintenant, nous allons ajouter un 2ᵉ outil à ce serveur pour permettre à l’IA de lire le contenu d’un fichier spécifique.
Nous allons créer un outil nommé lire_fichier qui accepte un paramètre obligatoire :
nom: le nom du fichier (situé dans le même dossier queserver.php)
L’outil renverra le contenu du fichier sous forme de texte.
9. Modifier le bloc tools/list
Ouvrez server.php et ajoutez le deuxième outil dans le tableau tools de la réponse à tools/list.
Localisez cette partie :
'tools' => [
[ /* lister_fichiers */ ],
// ← ajouter ici
]
Et remplacez (ou ajoutez juste après le premier outil) :
[
'name' => 'lire_fichier',
'description' => 'Lit le contenu texte d’un fichier situé dans le même dossier que le script PHP.',
'inputSchema' => [
'type' => 'object',
'properties' => [
'nom' => [
'type' => 'string',
'description' => 'Nom exact du fichier à lire (ex: notes.txt, server.php)',
'minLength' => 1
]
],
'required' => ['nom'],
'additionalProperties' => false
]
]
→ Résultat attendu après modification :
'tools' => [
[
'name' => 'lister_fichiers',
...
],
[
'name' => 'lire_fichier',
'description' => 'Lit le contenu texte d’un fichier situé dans le même dossier que le script PHP.',
'inputSchema' => [
'type' => 'object',
'properties' => [
'nom' => [
'type' => 'string',
'description' => 'Nom exact du fichier à lire (ex: notes.txt, server.php)',
'minLength' => 1
]
],
'required' => ['nom'],
'additionalProperties' => false
]
]
]
10. Ajouter la logique d’exécution
Ajouter la logique d’exécution dans
tools/call
Toujours dans server.php, descendez dans le bloc :
elseif ($method === 'tools/call') {
$toolName = $params['name'] ?? '';
$args = $params['arguments'] ?? [];
Et ajoutez (juste après le bloc lister_fichiers) :
elseif ($toolName === 'lire_fichier') {
$nom = trim($args['nom'] ?? '');
// Sécurité de base : interdit les chemins relatifs vers le haut et les slashes absolus
if ($nom === '' || preg_match('#(\.\.|/|^\\\\)#', $nom)) {
$response = [
'jsonrpc' => '2.0',
'id' => $id,
'error' => [
'code' => -32001,
'message' => 'Nom de fichier invalide (chemins interdits)'
]
];
} else {
$chemin = __DIR__ . DIRECTORY_SEPARATOR . $nom;
if (!file_exists($chemin)) {
$response = [
'jsonrpc' => '2.0',
'id' => $id,
'error' => [
'code' => -32002,
'message' => "Le fichier '$nom' n'existe pas dans le dossier du serveur"
]
];
} elseif (is_dir($chemin)) {
$response = [
'jsonrpc' => '2.0',
'id' => $id,
'error' => [
'code' => -32003,
'message' => "'$nom' est un dossier, pas un fichier"
]
];
} else {
$contenu = @file_get_contents($chemin);
if ($contenu === false) {
$response = [
'jsonrpc' => '2.0',
'id' => $id,
'error' => [
'code' => -32004,
'message' => "Impossible de lire le fichier '$nom' (droits insuffisants ?)"
]
];
} else {
// On limite un peu la taille pour éviter de saturer la réponse
if (strlen($contenu) > 200_000) {
$contenu = substr($contenu, 0, 200_000) . "\n\n… (contenu tronqué – plus de 200 ko)";
}
$response = [
'jsonrpc' => '2.0',
'id' => $id,
'result' => [
'content' => [
[
'type' => 'text',
'text' => "Contenu du fichier « $nom » :\n\n" . $contenu
]
]
]
];
}
}
}
}
11. Redémarrer le serveur et tester
-
Arrêtez le serveur actuel (Ctrl+C dans le terminal où tourne
php -S) -
Relancez-le :
php -S 127.0.0.1:8765 http-server.php -
Retournez dans Le Chat (chat.mistral.ai)
-
Créez une nouvelle conversation (important : les outils sont souvent mis en cache par conversation)
-
Essayez successivement :
Liste-moi les fichiers présents dans ton dossier de projet→ devrait appeler
lister_fichiersPuis :
Maintenant peux-tu me montrer ce qu’il y a dans le fichier server.php ?→ devrait appeler
lire_fichieravec{"nom": "server.php"}
Ce que vous devriez observer dans Mistral :
- Une demande d’autorisation pour chaque nouvel outil (la première fois)
- Le modèle Mistral affiche le contenu du fichier
server.php(ou échoue proprement si le fichier n’existe pas / n’est pas lisible)
12. Amélioration de l'affichage
Ajoutez cette ligne juste avant de renvoyer le contenu, pour améliorer l'affichage des fichiers dans le cas où le fichier contient des retours à la ligne au format Windows (\r\n) ou Mac (\r) :
$contenu = str_replace(["\r\n", "\r"], "\n", $contenu); // normalise les fins de ligne
Cela rend l’affichage plus propre quand le fichier vient de Windows.
Voici une proposition réaliste et pédagogique pour la suite logique du TP, en restant dans le même style et la même tonalité que les étapes précédentes.
Attention : cet outil ecrire_fichier est extrêmement sensible d’un point de vue sécurité.
Nous allons donc le faire de manière très encadrée et avec de multiples garde-fous pédagogiques.
13. Un outil pour écrire dans un fichier
Noous allons maintenant ajouter un 3ème outil nommé ecrire_fichier qui permettra à l’IA d’écrire du texte dans un fichier du dossier courant.
Pourquoi cet outil est risqué et comment nous allons le sécuriser :
| Risque | Mesure de protection dans ce TP |
|---|---|
| Écraser des fichiers système | Interdiction stricte des chemins en dehors de __DIR__ |
Injection de chemin (../, /etc/) | Filtrage agressif du nom de fichier |
| Écriture massive / boucle infinie | Limite stricte de taille (max 64 Ko) |
| Écraser accidentellement server.php | Interdiction explicite d’écrire dans certains noms sensibles |
| Perte de données | Mode « append » par défaut au lieu d’écraser (configurable) |
14. Déclaration de l’outil
Déclaration de l’outil dans
tools/list
Dans server.php, dans la réponse à la méthode tools/list, ajoutez un troisième outil dans le tableau tools :
[
'name' => 'ecrire_fichier',
'description' => 'Crée ou ajoute du contenu texte dans un fichier du dossier courant. Par défaut : mode append (ajout). Taille max : 64 Ko.',
'inputSchema' => [
'type' => 'object',
'properties' => [
'nom' => [
'type' => 'string',
'description' => 'Nom du fichier (ex: notes.txt). Pas de chemin, pas de ..',
'minLength' => 1,
'maxLength' => 120
],
'contenu' => [
'type' => 'string',
'description' => 'Texte à écrire ou à ajouter',
'minLength' => 1,
'maxLength' => 65536 // 64 Ko
],
'mode' => [
'type' => 'string',
'description' => 'Écraser (overwrite) ou ajouter (append) ?',
'enum' => ['append', 'overwrite'],
'default' => 'append'
]
],
'required' => ['nom', 'contenu'],
'additionalProperties' => false
]
]
15. Logique d’exécution
Logique d’exécution dans
tools/call
Ajoutez ce bloc après les conditions lister_fichiers et lire_fichier :
elseif ($toolName === 'ecrire_fichier') {
$nom = trim($args['nom'] ?? '');
$contenu = $args['contenu'] ?? '';
$mode = $args['mode'] ?? 'append';
// ── 1. Validation très stricte du nom ──────────────────────────────
if ($nom === '' || strlen($nom) > 120) {
$response = error_response($id, -32010, 'Nom de fichier invalide (vide ou trop long)');
}
elseif (preg_match('#(\.\.|/|\\\|[\x00-\x1F])#', $nom)) {
$response = error_response($id, -32011, 'Chemin interdit (sécurité)');
}
elseif (in_array(strtolower($nom), ['server.php', 'http-server.php', '.htaccess', 'trace.log'], true)) {
$response = error_response($id, -32012, 'Écriture interdite sur ce fichier protégé');
}
// ── 2. Vérification du contenu ─────────────────────────────────────
elseif (strlen($contenu) > 65536) {
$response = error_response($id, -32013, 'Contenu trop volumineux (> 64 Ko)');
}
elseif ($contenu === '') {
$response = error_response($id, -32014, 'Contenu vide');
}
else {
$chemin = __DIR__ . DIRECTORY_SEPARATOR . $nom;
$flags = ($mode === 'overwrite') ? 'w' : 'a';
$f = @fopen($chemin, $flags);
if ($f === false) {
$response = error_response($id, -32015, "Impossible d'ouvrir ou créer le fichier '$nom'");
} else {
$ecrit = @fwrite($f, $contenu . "\n"); // on ajoute un saut de ligne final
@fclose($f);
if ($ecrit === false) {
$response = error_response($id, -32016, 'Échec de l’écriture (droits insuffisants ?)');
} else {
$action = ($mode === 'overwrite') ? 'écrasé' : 'mis à jour (ajout)';
$response = [
'jsonrpc' => '2.0',
'id' => $id,
'result' => [
'content' => [
[
'type' => 'text',
'text' => "Succès : le fichier « $nom » a été $action.\n" .
"Octets écrits : $ecrit"
]
]
]
];
debug_log("Écriture réussie : $nom ($mode) - $ecrit octets");
}
}
}
}
Petite fonction utilitaire à ajouter (par exemple en haut du fichier, après debug_log) :
function error_response($id, $code, $message) {
return [
'jsonrpc' => '2.0',
'id' => $id,
'error' => ['code' => $code, 'message' => $message]
];
}
16. Test de l’outil
-
Relancez le serveur
Ctrl+Cpuisphp -S 127.0.0.1:8765 http-server.php -
Nouvelle conversation dans Mistral Le Chat
-
Essayez dans cet ordre :
Liste les fichiersCrée un fichier appelé mes_notes.txt et écris-y la phrase "Ceci est un test depuis Mistral"Ajoute la ligne "Deuxième ligne ajoutée" dans mes_notes.txtMaintenant lis-moi le contenu de mes_notes.txt→ Vous devriez voir apparaître le fichier dans la liste, puis pouvoir le lire.
-
Test d’erreur volontaire :
Écrase le fichier server.php avec "hack"→ devrait être refusé (protection explicite)
17. Sécurisation
Vous pouvez remplacer la condition sur les noms protégés par :
$interdits = ['server.php', 'http-server.php', 'trace.log', '.php', '.ini', '.json', '.log'];
foreach ($interdits as $ext) {
if (str_ends_with(strtolower($nom), $ext)) {
return error_response($id, -32017, 'Écriture interdite sur les fichiers de ce type');
}
}
Cela protège tous les fichiers .php, .log, etc.
Vous avez maintenant un serveur MCP avec 3 outils :
- lecture de répertoire
- lecture de fichier
- écriture contrôlée (très encadrée)
Une solution complète pour le serveur HTTP (server.php)
Vous devez être connecté pour voir le contenu.
Commandes dans le terminal
- Lancer le serveur (assurez-vous d'être dans le bon dossier) :
php -S 127.0.0.1:8765 http-server.php
- Tester dans Mistral Chat :
Une fois le connecteur configuré sur
http://127.0.0.1:8765, demandez simplement : "Crée un fichier test.txt avec la liste de mes courses, puis affiche-le."