Un serveur MCP en Python, sans dépendance ni framework, donne un accès direct à vos fichiers locaux pour l'IA. Fonctionne en local ou en réseau avec 5 clients simultanés en moins de 50 ms.

LE PROBLÈME : PERDRE DU TEMPS À COPIER-COLLER SES FICHIERS DANS L'IA

Refactoriser un scraper, c'est comme réorganiser une bibliothèque en mouvement : les fonctions deviennent trop longues, les noms de variables n'ont plus de sens, et chaque modification nécessite de copier le fichier dans une IA, attendre une réponse, puis revenir à l'éditeur. Six fichiers. Onze collages. Vingt minutes de va-et-vient avant d'écrire une seule nouvelle ligne de code. La frustration monte quand on réalise que chaque retour d'IA passe par une manipulation manuelle répétitive.

LA SOLUTION : DONNER UN ACCÈS DIRECT À L'IA SUR VOS FICHIERS LOCAUX

L'idée est simple : au lieu de copier-coller chaque fichier dans une conversation d'IA, pourquoi ne pas permettre à l'IA d'accéder directement à votre dossier de projet ? C'est exactement ce que propose le Model Context Protocol (MCP), un protocole standardisé qui permet aux clients d'IA comme Claude ou ChatGPT de appeler des Outils sur des serveurs externes au lieu de dépendre des copier-coller manuels.

Le MCP utilise le format JSON-RPC 2.0 : le client envoie une requête JSON, le serveur répond avec une réponse JSON. Le protocole définit deux modes de transport : stdio pour les connexions locales (un seul client), et HTTP avec Server-Sent Events (SSE) pour gérer plusieurs clients simultanés. La plupart des implémentations existantes ajoutent des frameworks lourds comme FastAPI, uvicorn ou LangChain. Résultat : des serveurs complexes, avec des dépendances qui alourdissent l'installation et compliquent le déploiement, surtout sur Windows.

UNE IMPLÉMENTATION MINIMALISTE : ZÉRO DÉPENDANCE, UN SEUL FICHIER

En lisant la spécification du MCP, une question s'impose : qu'est-ce que ce protocole a besoin que la bibliothèque standard de Python ne fournit pas déjà ? La réponse est surprenante : rien. Le protocole ne nécessite que sys.stdin, sys.stdout, http.server, threading, queue, pathlib et json. Pas un seul pip install requis. Le serveur développé ici est un fichier unique, lancé avec un seul flag au runtime. Pas d'installation, pas de configuration, pas de dépendance.

Pour les utilisations locales, le serveur utilise stdio avec un seul client. Pour gérer plusieurs clients simultanés, il bascule en HTTP/SSE sans changer quoi que ce soit d'autre. Sous le capot, tout reste cohérent : même dispatcher, mêmes outils, même modèle de sécurité. Le serveur est conçu pour être léger, rapide et simple, sans sacrifier la capacité.

5 clients simultanés. Moins de 50 ms de temps total. Fonctionne sur Windows 11, Python 3.12.6, uniquement en CPU.

LA SÉCURITÉ : BLOQUER LES CHEMINS MALVEILLANTS AVANT MÊME D'Y TOUCHER

Dès le début, une préoccupation majeure guide la conception : comment empêcher un client malveillant d'accéder à des fichiers qu'il ne devrait pas voir ? Le risque principal est le traversal de chemin (path traversal), où un client envoie un chemin comme ././etc/passwd pour sortir du sandbox. La solution ? Résoudre les chemins de manière absolue avant toute comparaison :

target.resolve().relative_to(base.resolve())

La méthode Path.resolve() de Python étend tous les liens symboliques et supprime tous les segments .. La méthode relative_to() lève une exception ValueError si le chemin résultant sort de la base autorisée. Aucune analyse de chaîne, aucun comptage manuel de . : c'est le système d'exploitation qui résout le chemin, et Python vérifie le résultat. Cette approche garantit que même les attaques les plus sophistiquées sont bloquées avant d'atteindre le système de fichiers.

LES OUTILS : QUATRE FONCTIONS POUR INTERAGIR AVEC LES FICHIERS

Le serveur expose quatre outils principaux, accessibles via le MCP :

list_directory : lister les fichiers et dossiers d'un répertoire.

L'outil list_directory retourne le nom, le type (fichier ou dossier), la taille, l'horodatage de modification et le chemin relatif de chaque entrée. Les dossiers sont listés avant les fichiers, et les entrées cachées sont exclues par défaut. Sur Windows, un piège classique attend les développeurs : un fichier peut apparaître dans la liste tout en étant verrouillé par un autre processus. L'outil contourne ce problème en encapsulant chaque appel à stat() dans un bloc try/except et en sautant silencieusement les entrées verrouillées plutôt que de planter toute la liste.

Exemple de sortie :

[3] list_directory     8 entries:

[F] concurrent_demo.py                  4,711B
[F] demo.py                            10,451B
[F] http_client.py                      5,140B
[F] localdesktopconfig.json             228B
[F] README.md                           7,542B
[F] server.py                          29,222B
[F] test_server.py                     17,500B
read_file : lire le contenu d'un fichier (max 1 Mo).

L'outil read_file lit le contenu d'un fichier avec une limite stricte de 1 Mo. Les fichiers texte sont retournés en UTF-8 brut, tandis que les fichiers binaires sont encodés en base64. Cette approche évite les erreurs de décodage sur des fichiers comme les .pyc, les extensions compilées ou les bases de données SQLite, qui causaient des UnicodeDecodeError dans la première version. Si read_text() échoue, le serveur bascule automatiquement sur read_bytes() et retourne le contenu encodé. La limite de 1 Mo existe parce qu'un test initial avait accidentellement lu une base de données SQLite de 200 Mo, gelant le processus pendant trente secondes.

Exemple de sortie :

read_file
  concurrent_demo.py:

  #./usr/bin/env python3
  """
  concurrent_demo.py
  ============================
  Proves the HTTP/SSE transport handles multiple concurrent clients.

  Spins up 5 clients simultaneously, each running
  . (4509 more chars)
search_files : rechercher des fichiers avec un motif glob.

L'outil search_files permet de rechercher des fichiers selon un motif glob (par exemple, *.py). Par défaut, la recherche est superficielle (shallow), c'est-à-dire qu'elle ne parcourt pas les sous-dossiers. Pour activer la récursivité, le client doit passer explicitement recursive=true. Cette conception évite les recherches infinies comme celle qui a bloqué le développement pendant dix minutes : pointer le serveur sur C:\Users\Admin et rechercher des fichiers Python avait déclenché une exploration récursive de tout le répertoire utilisateur, y compris les environnements virtuels, AppData et les fichiers en cache. Résultat : des dizaines de milliers de fichiers analysés un par un.

Exemple de sortie :

[6] search_files — *.py (shallow)
  Found 5 file(s):

  -> concurrent_demo.py    4,711B
  -> demo.py              10,451B
  -> http_client.py        5,140B
  -> server.py            29,222B
  -> test_server.py        17,500B
getfileinfo : obtenir les métadonnées d'un fichier ou dossier.

L'outil getfileinfo retourne les métadonnées sans lire le contenu du fichier : nom, chemin, type (fichier ou dossier), taille, horodatages de modification et de création, extension, et permissions d'accès (lecture/écriture). Contrairement à une simple vérification d'existence, os.access() vérifie les permissions réelles, pas seulement la visibilité. Sur Windows, un fichier peut être visible dans une liste tout en étant verrouillé par un autre processus. Savoir qu'il est illisible avant de tenter de le lire évite un aller-retour inutile.

Exemple de sortie :

[4] getfileinfo
  name         local-mcp-server
  path         .
  type         directory
  size         4096
  modified     1780246573
  created      1780227648
  extension    None
  readable     True
  writable     True

L'ARCHITECTURE : QUATRE COUCHES POUR UNE LOGIQUE CENTRALE

Le serveur est structuré en quatre couches distinctes, chacune avec une responsabilité claire :

Couche sécurité : valider chaque chemin avant toute opération sur le système de fichiers.

Cette couche s'exécute avant toute autre opération, sur chaque appel. Elle bloque les chemins malveillants comme ././, les liens symboliques trompeurs et les chemins Windows UNC. C'est le premier rempart contre les intrusions.

Couche outils : les quatre fonctions pour interagir avec les fichiers.

Cette couche expose les outils list_directory, read_file, search_files et getfileinfo. Chaque outil est stateless et ne dépend pas du transport utilisé.

Couche dispatcher : le routeur JSON-RPC stateless.

Le dispatcher est le cœur du serveur. Il reçoit un message brut JSON, parse la méthode appelée, invoque le bon gestionnaire, et retourne une réponse. Il n'a aucune idée du transport utilisé (stdio ou HTTP/SSE) et n'en a pas besoin. Le dispatcher écoute uniquement quatre méthodes du protocole MCP : initialize, tools/list, tools/call et ping. Toute autre méthode déclenche une erreur standard JSON-RPC. Les notifications (messages sans identifiant) sont traitées en interne et ne nécessitent pas de réponse.

Couche transport : deux implémentations pour les connexions locales et réseau.

Le dispatcher est complètement indépendant du transport. La couche transport gère les détails techniques des connexions :

  • StdioTransport : pour les connexions locales en utilisant sys.stdin et sys.stdout.
  • HTTPSSETransport : pour les connexions réseau utilisant HTTP POST et Server-Sent Events (SSE).

Le code de lancement est minimal :

dispatcher = MCPDispatcher(root)

if args.http:
    HTTPSSETransport(dispatcher, host=args.host, port=args.port).run()
else:
    StdioTransport(dispatcher).run()

Un seul flag (--http) permet de basculer entre les deux modes. Aucune modification du code interne n'est nécessaire.

LE TRANSPORT STDIO : UNE CONNEXION LOCALE SIMPLE ET EFFICACE

Pour les connexions locales, le transport stdio utilise une boucle simple qui lit chaque ligne de l'entrée standard :

for line in self._stdin:

Aucun async, aucun thread, aucun event loop : le code reste aussi simple que possible. Sur Windows, une correction est nécessaire pour éviter la corruption des flux JSON :

if platform.system() == "Windows":
    import msvcrt
    msvcrt.setmode(sys.stdin.fileno(),  os.O_BINARY)
    msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)

Par défaut, Python ouvre stdin et stdout en mode texte sur Windows, ce qui convertit automatiquement chaque \n en \r\n. Cette modification corrompt le flux JSON, provoquant des erreurs de parsing dès le deuxième message. La solution est d'utiliser O_BINARY pour désactiver cette traduction. Sans cette correction, le serveur fonctionne sur macOS et Linux mais échoue silencieusement sur Windows.

Le wrapper write_through=True sur stdout garantit que chaque écriture est immédiatement flushée, évitant tout buffering qui pourrait bloquer l'interaction avec le client IA.

Exemple de sortie complète en mode stdio :

============================================================
  local-mcp-server demo  [stdio transport]
  Root: C:\Users\Admin\PycharmProjects\pythonProject\local-mcp-server
============================================================

[1] Initialize
  Server  : local-mcp-server v1.0.0
  Protocol: 2024-11-05

[2] Available tools
  [list_directory     ] List files and directories.
  [read_file          ] Read a file's contents. Max 1 MB.
  [search_files       ] Search files by glob pattern.
  [getfileinfo      ] Get metadata for a file or directory.

[3] list_directory     8 entries
[4] getfileinfo      readable: True  writable: True
[5] read_file          first small file read successfully
[6] search_files       Found 5 .py files

============================================================
  All checks passed. Ready to connect Local Desktop.
============================================================

LE TRANSPORT HTTP/SSE : GÉRER PLUSIEURS CLIENTS SIMULTANÉS

Pour gérer plusieurs clients simultanés, le serveur utilise HTTP avec Server-Sent Events (SSE). Chaque client ouvre une connexion GET /sse qui reste ouverte pendant toute la session, permettant au serveur de pousser les réponses via des événements SSE. Chaque connexion reçoit un client_id unique à la connexion. Pour envoyer une requête, le client utilise une requête POST séparée sur /message.

Pour gérer la concurrence proprement, chaque client dispose de sa propre file de messages indépendante. Le gestionnaire POST dispatch la requête, place le résultat directement dans la file du client concerné, et retourne immédiatement un statut 202. Il n'attend pas la livraison SSE pour répondre. Le client récupère la réponse depuis son propre flux ouvert. C'est ce mécanisme qui permet la concurrence sans blocage.

Seize threads de travail daemon sont configurés pour gérer les requêtes entrantes. Chaque connexion SSE active occupe un thread, donc avec 5 clients SSE actifs, 11 threads restent libres pour gérer les requêtes POST entrantes à tout moment. Aucun async/await, aucun event loop : uniquement la bibliothèque standard threading.

Le test de concurrence avec 5 clients simultanés donne les résultats suivants :

============================================================
  Concurrent Client Demo — 5 clients, 5 simultaneous calls
============================================================

  Launching 5 concurrent clients.

  Client     Tool                 Result         Time
  ---------- -------------------- ---------- --------
  1          list_directory       OK           ~0.034s
  2          getfileinfo        OK           ~0.021s
  3          list_directory       OK           ~0.038s
  4          search_files         OK           ~0.023s
  5          search_files         OK           ~0.021s

Total wall time: ~0.04s for 5 concurrent clients
Result: ALL PASSED
============================================================

Cinq clients. Cinq appels d'outils différents. Moins de 50 ms de temps total écoulé. Aucun client n'a bloqué les autres. Les mesures ont été réalisées sur Windows 11 avec Python 3.12.6, uniquement en CPU.

LA CONFIGURATION : INTÉGRER LE SERVEUR À CLAUDE OU CHATGPT

Pour connecter le serveur à un client d'IA comme Claude, il faut modifier le fichier de configuration localdesktopconfig.json :

Sur macOS :

~/Library/Application Support/Claude/localdesktopconfig.json

Sur Windows :

%APPDATA%\Claude\localdesktopconfig.json

Le fichier de configuration doit contenir :

{
  "mcpServers": {
    "local-desktop": {
      "command": "python",
      "args": ["C:/absolute/path/to/local-mcp-server/server.py"],
      "env": {
        "MCP_ROOT": "C:/absolute/path/to/your/workspace"
      }
    }
  }
}

Pour lancer le serveur en mode HTTP/SSE, utilisez la commande :

python server.py --http --port 8765

Pour tester avec un client exemple :

python examples/http_client.py

LES PIÈGES À ÉVITER : TROIS ERREURS QUI ONT FAILLI FAIRE ABANDONNER LE PROJET

Le développement n'a pas été de tout repos. Trois problèmes majeurs ont failli faire abandonner le projet avant même qu'il ne soit fonctionnel.

Le problème Windows \r\n : une erreur de parsing qui a pris une heure à diagnostiquer.

La première fois qu'un client d'IA réel s'est connecté, il a reçu une erreur de parsing dès le deuxième message. Tout semblait correct dans les tests. Le problème venait de la traduction automatique de \n en \r\n sur Windows, qui corrompait le flux JSON. Deux lignes de code ont résolu le problème :

msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
La crash des fichiers binaires : une lecture en texte qui échoue sur des fichiers non-texte.

La première version de read_file appelait read_text() sur tous les fichiers. Dès que le serveur a été pointé sur un vrai dossier de projet, les .pyc, les extensions compilées et les bases de données SQLite ont provoqué des UnicodeDecodeError. La solution : basculer automatiquement sur read_bytes() et retourner le contenu encodé en base64 si la lecture en texte échoue.

La recherche récursive infinie : une exploration par défaut qui a bloqué le développement pendant dix minutes.

L'incident le plus critique : utiliser rglob() par défaut pour rechercher des fichiers. Pointer le serveur sur C:\Users\Admin et rechercher des fichiers Python a déclenché une exploration récursive de tout le répertoire utilisateur. Résultat : des dizaines de milliers de fichiers analysés un par un, pendant dix minutes. La solution : rendre la recherche superficielle par défaut et exiger recursive=true pour activer la récursivité. Désormais, la recherche sur un vrai dossier de projet s'exécute en moins de 30 ms.

POURQUOI CE PROJET VA CHANGER LA MANIÈRE D'UTILISER L'IA SUR VOS FICHIERS LOCAUX

Ce serveur MCP minimaliste démontre qu'il est possible de donner à l'IA un accès direct et sécurisé à vos fichiers locaux, sans dépendance ni framework lourd. Les avantages sont multiples :

  • Gain de temps : plus besoin de copier-coller chaque fichier dans une conversation d'IA.
  • Sécurité renforcée : blocage automatique des chemins malveillants et vérification des permissions.
  • Flexibilité : un seul fichier, un seul flag, deux modes de transport (local et réseau).
  • Performance : cinq clients simultanés en moins de 50 ms, même sur Windows.
  • Simplicité : pas de dépendance, pas de configuration complexe, pas de courbe d'apprentissage.

Le code source complet est disponible sur GitHub :

https://github.com/Emmimal/local-mcp-server/

Ce projet montre qu'il n'est pas nécessaire de sacrifier la simplicité pour la puissance. Avec une bibliothèque standard de Python et une conception minimaliste, il est possible de créer un outil qui répond à un besoin réel, sans alourdir l'environnement de développement.

Sources :
  • Towards Data Science

L'indépendance de CLODCO est votre garantie.

Pour que l'actualité de l'IA reste sans filtre et sans concession, votre soutien est indispensable. Votre contribution est le seul moteur de notre liberté éditoriale.

Soutenir CLODCO