🎁 Perplexity PRO offert
Créer votre Premier Outil MCP : L’Outil readFile Expliqué
Vous avez configuré votre environnement TypeScript dans l’article précédent ? Parfait ! Maintenant, place au moment magique où la théorie devient réalité. Nous allons créer ensemble votre tout premier outil MCP : une fonction qui permettra à une IA de lire des fichiers sur votre machine. C’est simple, concret, et surtout : ça fonctionne vraiment.
Introduction
Dans ma carrière de développeur, j’ai toujours adoré ces moments où le code prend vie. Vous savez, quand vous lancez votre application et qu’elle fait exactement ce que vous aviez imaginé ? C’est ce que nous allons vivre ensemble aujourd’hui. Après avoir posé les fondations dans les articles précédents, nous allons construire quelque chose de tangible : un outil MCP qui lit des fichiers.
Imaginez : vous demandez à Claude “Lis-moi le fichier rapport.txt”, et il peut réellement le faire grâce à votre serveur. Ce n’est plus de la théorie, c’est votre code qui rend cela possible. Et le plus beau ? Une fois que vous maîtriserez la création d’un outil, vous pourrez en créer des dizaines d’autres.
Rappel : Qu’est-ce qu’un Outil MCP ?
Avant de coder, rappelons brièvement ce qu’est un outil MCP. C’est essentiellement une fonction que vous exposez à l’IA avec trois informations essentielles :
Le nom de l’outil : Comment l’IA va l’appeler (par exemple “readFile”)
La description : Ce que fait l’outil, pour que l’IA comprenne quand l’utiliser
Les paramètres : Les informations dont l’outil a besoin pour fonctionner
C’est comme créer une fonction dans votre code, mais avec une carte d’identité que l’IA peut lire et comprendre. Simple, non ?
Anatomie d’un Outil MCP
Visualisons la structure complète d’un outil MCP. Voici le squelette que nous allons remplir :
// 1. Interface pour les paramètres d'entrée
interface ToolParams {
// Les données que l'IA nous envoie
}
// 2. Interface pour la réponse
interface ToolResponse {
success: boolean;
content?: string;
error?: string;
}
// 3. La fonction qui fait le travail
async function monOutil(params: ToolParams): Promise<ToolResponse> {
// Logique métier ici
}
// 4. La définition de l'outil (le "menu")
export const monOutilDefinition = {
name: "monOutil",
description: "Ce que fait mon outil",
parameters: {
// Description des paramètres attendus
}
};
Cette structure en quatre parties est votre modèle pour créer n’importe quel outil MCP. Gardons-la en tête pour la suite.
Créer la Structure de Dossiers
Commençons par organiser notre code proprement. Dans votre projet mcp-server, créez la structure suivante :
mkdir -p src/tools
mkdir -p src/types
Cette organisation va nous aider à garder un code maintenable. Le dossier tools contiendra nos outils MCP, et types nos définitions TypeScript réutilisables.
Définir les Types TypeScript
Créons d’abord nos interfaces TypeScript. Créez le fichier src/types/mcp.ts :
// src/types/mcp.ts
// Type générique pour les paramètres d'un outil
export interface ToolParams {
[key: string]: any;
}
// Type pour la réponse standard d'un outil
export interface ToolResponse {
success: boolean;
content?: string;
error?: string;
metadata?: {
[key: string]: any;
};
}
// Type pour la définition d'un outil (le "menu")
export interface ToolDefinition {
name: string;
description: string;
parameters: {
[paramName: string]: {
type: string;
description: string;
required: boolean;
};
};
}
// Type spécifique pour les paramètres de readFile
export interface ReadFileParams extends ToolParams {
chemin_du_fichier: string;
}
Ces types vont nous aider à avoir de l’auto-complétion et à éviter des erreurs stupides. TypeScript devient notre meilleur ami pour ce genre de projet.
Créer l’Outil readFile
Maintenant, le moment que vous attendiez : créons notre outil ! Créez le fichier src/tools/readFile.ts :
// src/tools/readFile.ts
import fs from 'fs/promises';
import path from 'path';
import { ReadFileParams, ToolResponse, ToolDefinition } from '../types/mcp';
/**
* Lit le contenu d'un fichier texte
* @param params - Paramètres contenant le chemin du fichier
* @returns Réponse avec le contenu du fichier ou une erreur
*/
export async function readFile(params: ReadFileParams): Promise<ToolResponse> {
try {
// Étape 1 : Validation des paramètres
if (!params.chemin_du_fichier) {
return {
success: false,
error: "Le paramètre 'chemin_du_fichier' est requis"
};
}
// Étape 2 : Sécurité - Résoudre le chemin absolu
// Cela évite les tentatives d'accès à des chemins relatifs dangereux
const absolutePath = path.resolve(params.chemin_du_fichier);
// Étape 3 : Vérifier que le fichier existe
try {
await fs.access(absolutePath);
} catch {
return {
success: false,
error: `Fichier introuvable : ${params.chemin_du_fichier}`
};
}
// Étape 4 : Obtenir les informations du fichier
const stats = await fs.stat(absolutePath);
// Étape 5 : Vérifier que c'est bien un fichier (pas un dossier)
if (!stats.isFile()) {
return {
success: false,
error: "Le chemin spécifié n'est pas un fichier"
};
}
// Étape 6 : Limiter la taille (sécurité - éviter de lire des fichiers énormes)
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
if (stats.size > MAX_FILE_SIZE) {
return {
success: false,
error: `Fichier trop volumineux (max ${MAX_FILE_SIZE / 1024 / 1024} MB)`
};
}
// Étape 7 : Lire le contenu du fichier
const content = await fs.readFile(absolutePath, 'utf-8');
// Étape 8 : Retourner le succès avec métadonnées
return {
success: true,
content: content,
metadata: {
path: absolutePath,
size: stats.size,
lastModified: stats.mtime
}
};
} catch (error: any) {
// Gestion des erreurs inattendues
return {
success: false,
error: `Erreur lors de la lecture : ${error.message}`
};
}
}
/**
* Définition de l'outil pour le protocole MCP
* C'est ce que l'IA "voit" quand elle découvre nos outils
*/
export const readFileToolDefinition: ToolDefinition = {
name: "readFile",
description: "Lit le contenu d'un fichier texte depuis le système de fichiers local",
parameters: {
chemin_du_fichier: {
type: "string",
description: "Chemin absolu ou relatif vers le fichier à lire",
required: true
}
}
};
Prenons un moment pour comprendre ce code. Chaque étape est numérotée et expliquée :
Validation : On vérifie que le paramètre nécessaire est présent. Toujours valider les entrées !
Sécurité : On résout le chemin absolu pour éviter les chemins relatifs malveillants comme ../../etc/passwd.
Vérification existence : On s’assure que le fichier existe avant d’essayer de le lire.
Vérification type : On confirme que c’est un fichier, pas un dossier.
Limite de taille : On évite de charger un fichier de 2 GB en mémoire par erreur.
Lecture : On lit enfin le contenu en UTF-8.
Réponse enrichie : On retourne non seulement le contenu, mais aussi des métadonnées utiles.
Gestion d’erreurs : On capture toute erreur inattendue proprement.
Créer un Gestionnaire d’Outils
Maintenant, créons un fichier qui va centraliser tous nos outils. Créez src/tools/index.ts :
// src/tools/index.ts
import { ToolDefinition } from '../types/mcp';
import { readFile, readFileToolDefinition } from './readFile';
// Registre de tous nos outils
export const tools = {
readFile
};
// Définitions de tous nos outils (pour le "menu")
export const toolDefinitions: ToolDefinition[] = [
readFileToolDefinition
];
// Fonction helper pour exécuter un outil par son nom
export async function executeTool(toolName: string, params: any) {
const tool = tools[toolName as keyof typeof tools];
if (!tool) {
return {
success: false,
error: `Outil '${toolName}' introuvable`
};
}
return await tool(params);
}
Ce fichier agit comme un registre central. Quand vous créerez de nouveaux outils, vous les ajouterez simplement ici.
Intégrer dans le Serveur Express
Modifions maintenant notre src/index.ts pour exposer nos outils via des routes HTTP :
// src/index.ts
import express, { Request, Response } from 'express';
import { toolDefinitions, executeTool } from './tools';
const app = express();
const PORT = 3000;
// Middleware pour parser le JSON
app.use(express.json());
// Route de test
app.get('/', (req: Request, res: Response) => {
res.json({
message: 'Serveur MCP opérationnel !',
version: '1.0.0'
});
});
// Route pour découvrir les outils disponibles (le "menu")
app.get('/tools', (req: Request, res: Response) => {
res.json({
success: true,
tools: toolDefinitions
});
});
// Route pour exécuter un outil
app.post('/tools/:toolName', async (req: Request, res: Response) => {
const { toolName } = req.params;
const params = req.body;
try {
const result = await executeTool(toolName, params);
res.json(result);
} catch (error: any) {
res.status(500).json({
success: false,
error: `Erreur serveur : ${error.message}`
});
}
});
app.listen(PORT, () => {
console.log(`✅ Serveur MCP lancé sur http://localhost:${PORT}`);
console.log(`📋 Outils disponibles : http://localhost:${PORT}/tools`);
});
Notre serveur expose maintenant deux routes importantes :
GET /tools : Liste tous les outils disponibles (le fameux “menu”)
POST /tools/:toolName : Exécute un outil spécifique avec les paramètres fournis
Tester l’Outil
Moment de vérité ! Testons notre outil. D’abord, créons un fichier de test :
echo "Ceci est un fichier de test pour le MCP !" > test.txt
Lancez votre serveur :
npm run dev
Vous devriez voir :
✅ Serveur MCP lancé sur http://localhost:3000
📋 Outils disponibles : http://localhost:3000/tools
Test 1 : Découvrir les Outils
Ouvrez un nouveau terminal et testez la découverte :
curl http://localhost:3000/tools
Réponse attendue :
{
"success": true,
"tools": [
{
"name": "readFile",
"description": "Lit le contenu d'un fichier texte depuis le système de fichiers local",
"parameters": {
"chemin_du_fichier": {
"type": "string",
"description": "Chemin absolu ou relatif vers le fichier à lire",
"required": true
}
}
}
]
}
Parfait ! L’IA peut maintenant découvrir votre outil.
Test 2 : Utiliser l’Outil
Maintenant, utilisons readFile pour lire notre fichier de test :
curl -X POST http://localhost:3000/tools/readFile \
-H "Content-Type: application/json" \
-d '{"chemin_du_fichier": "test.txt"}'
Réponse attendue :
{
"success": true,
"content": "Ceci est un fichier de test pour le MCP !\n",
"metadata": {
"path": "/chemin/absolu/vers/test.txt",
"size": 42,
"lastModified": "2025-11-12T10:30:00.000Z"
}
}
Ça fonctionne ! Votre serveur MCP peut maintenant lire des fichiers.
Test 3 : Gestion d’Erreurs
Testons avec un fichier inexistant :
curl -X POST http://localhost:3000/tools/readFile \
-H "Content-Type: application/json" \
-d '{"chemin_du_fichier": "fichier_inexistant.txt"}'
Réponse :
{
"success": false,
"error": "Fichier introuvable : fichier_inexistant.txt"
}
Excellent ! Notre gestion d’erreurs fonctionne correctement.
Améliorer l’Outil
Maintenant que la base fonctionne, ajoutons quelques améliorations. Modifions src/tools/readFile.ts :
// Ajout du support d'encodages multiples
export interface ReadFileParams extends ToolParams {
chemin_du_fichier: string;
encoding?: 'utf-8' | 'ascii' | 'base64';
}
export async function readFile(params: ReadFileParams): Promise<ToolResponse> {
try {
// ... validation existante ...
// Support de différents encodages
const encoding = params.encoding || 'utf-8';
const content = await fs.readFile(absolutePath, encoding);
return {
success: true,
content: content,
metadata: {
path: absolutePath,
size: stats.size,
encoding: encoding,
lastModified: stats.mtime
}
};
} catch (error: any) {
return {
success: false,
error: `Erreur lors de la lecture : ${error.message}`
};
}
}
Et mettons à jour la définition :
export const readFileToolDefinition: ToolDefinition = {
name: "readFile",
description: "Lit le contenu d'un fichier texte depuis le système de fichiers local",
parameters: {
chemin_du_fichier: {
type: "string",
description: "Chemin absolu ou relatif vers le fichier à lire",
required: true
},
encoding: {
type: "string",
description: "Encodage du fichier (utf-8, ascii, base64)",
required: false
}
}
};
Créer un Deuxième Outil : listFiles
Maintenant que vous maîtrisez la création d’un outil, créons-en un deuxième rapidement. Créez src/tools/listFiles.ts :
// src/tools/listFiles.ts
import fs from 'fs/promises';
import path from 'path';
import { ToolParams, ToolResponse, ToolDefinition } from '../types/mcp';
export interface ListFilesParams extends ToolParams {
chemin_du_dossier: string;
}
export async function listFiles(params: ListFilesParams): Promise<ToolResponse> {
try {
if (!params.chemin_du_dossier) {
return {
success: false,
error: "Le paramètre 'chemin_du_dossier' est requis"
};
}
const absolutePath = path.resolve(params.chemin_du_dossier);
// Vérifier que c'est un dossier
const stats = await fs.stat(absolutePath);
if (!stats.isDirectory()) {
return {
success: false,
error: "Le chemin spécifié n'est pas un dossier"
};
}
// Lire le contenu du dossier
const files = await fs.readdir(absolutePath);
// Obtenir les détails de chaque fichier
const filesWithDetails = await Promise.all(
files.map(async (file) => {
const filePath = path.join(absolutePath, file);
const fileStats = await fs.stat(filePath);
return {
name: file,
type: fileStats.isDirectory() ? 'directory' : 'file',
size: fileStats.size,
lastModified: fileStats.mtime
};
})
);
return {
success: true,
content: JSON.stringify(filesWithDetails, null, 2),
metadata: {
path: absolutePath,
count: filesWithDetails.length
}
};
} catch (error: any) {
return {
success: false,
error: `Erreur lors de la lecture du dossier : ${error.message}`
};
}
}
export const listFilesToolDefinition: ToolDefinition = {
name: "listFiles",
description: "Liste les fichiers et dossiers dans un répertoire",
parameters: {
chemin_du_dossier: {
type: "string",
description: "Chemin absolu ou relatif vers le dossier",
required: true
}
}
};
Ajoutez-le dans src/tools/index.ts :
import { listFiles, listFilesToolDefinition } from './listFiles';
export const tools = {
readFile,
listFiles
};
export const toolDefinitions: ToolDefinition[] = [
readFileToolDefinition,
listFilesToolDefinition
];
Relancez votre serveur et testez :
curl http://localhost:3000/tools
Vous verrez maintenant deux outils disponibles !
Bonnes Pratiques et Sécurité
Maintenant que vous savez créer des outils, parlons sécurité. Voici les règles d’or :
Toujours Valider les Entrées
Ne faites jamais confiance aux paramètres reçus. Validez tout : type, format, longueur, valeurs autorisées.
Limiter l’Accès aux Fichiers
Créez une liste de dossiers autorisés :
const ALLOWED_DIRECTORIES = [
'/home/user/documents',
'/home/user/projects'
];
function isPathAllowed(filePath: string): boolean {
const absolute = path.resolve(filePath);
return ALLOWED_DIRECTORIES.some(dir => absolute.startsWith(dir));
}
Limiter les Tailles
Toujours définir des limites de taille de fichiers, de nombre de résultats, de profondeur de récursion.
Logger les Accès
Gardez une trace de qui accède à quoi :
console.log(`[${new Date().toISOString()}] readFile: ${params.chemin_du_fichier}`);
Conclusion
Félicitations ! Vous venez de créer votre premier outil MCP fonctionnel. Vous avez appris à :
- Structurer un outil MCP avec TypeScript
- Gérer les paramètres et les réponses
- Valider les entrées et gérer les erreurs
- Exposer vos outils via une API REST
- Tester vos outils avec curl
- Créer plusieurs outils et les enregistrer
La prochaine étape ? Dans le prochain article, nous verrons comment une IA découvre et utilise vos outils automatiquement. Nous implémenterons le protocole complet de découverte et d’exécution, puis nous connecterons votre serveur à Claude Desktop pour voir la magie opérer en conditions réelles.
En attendant, expérimentez ! Créez vos propres outils. Que diriez-vous d’un outil qui recherche dans des fichiers ? Ou qui renomme des fichiers en masse ? Ou qui analyse des données JSON ? Les possibilités sont infinies.
Article publié le 12 novembre 2025 par Nicolas Dabène - Expert PHP & PrestaShop avec 15+ ans d’expérience dans l’architecture logicielle et l’intégration d’IA
À lire aussi :
- Comprendre le Model Context Protocol (MCP) : Une Conversation Simple
- Créer son Premier Serveur MCP : Setup du Projet TypeScript
- Le Menu MCP : Comment l’IA Découvre et Utilise vos Outils
- Sécuriser votre Serveur MCP : Permissions, Validation et Protection
- Connecter votre Serveur MCP à Claude Desktop : L’Intégration Complète
Questions Fréquentes
Puis-je créer un outil qui écrit des fichiers ?
Absolument ! La structure est la même. Utilisez fs.writeFile() au lieu de fs.readFile(). Mais attention à la sécurité : l’écriture est plus risquée que la lecture.
Comment gérer les fichiers binaires ?
Utilisez l’encoding base64 pour lire les fichiers binaires et les transmettre en tant que texte. L’IA pourra ensuite les manipuler.
Mes outils peuvent-ils appeler d'autres outils ?
Techniquement oui, mais c’est généralement le rôle de l’IA d’orchestrer les appels entre outils. Gardez vos outils simples et atomiques.
Articles Liés
Évolution des Compétences des Développeurs : de l'Expertise Technique à l'Hybride Visionnaire
En quelques mois seulement, l'intelligence artificielle a redéfini le métier de développeur : moins de technique répé...
Automatiser vos Publications Facebook et Instagram avec n8n : Le Guide Salvateur
Si vous pensiez que l'intégration Meta serait un jeu d'enfant, ce guide détaillé va vous éviter des heures de frustra...
Vous laisseriez un Dev Junior coder sans supervision ? Alors pourquoi l'IA ?
84% des développeurs utilisent l'IA, mais 45% du code généré contient des vulnérabilités. Découvrez pourquoi l'IA néc...
Le Guide Définitif pour Mesurer le GEO : Du Classement SEO à l'Influence IA
L'émergence des moteurs génératifs a catalysé une transformation fondamentale du marketing numérique. Découvrez le ca...
Créer son Premier Serveur MCP : Setup du Projet TypeScript
Passons à la pratique ! Découvrez comment initialiser un projet TypeScript pour créer votre premier serveur MCP en qu...
Comprendre le Model Context Protocol (MCP) : Une Conversation Simple
Découvrez comment les IA peuvent accéder à vos fichiers et données grâce au MCP, expliqué à travers une conversation ...
Découvrez mes autres articles
Guides e-commerce, tutoriels PrestaShop et bonnes pratiques pour développeurs
Voir tous les articles