Aller au contenu principal

Symfony AI Mate : un troisième port d'entrée pour votre app Symfony.

Découvrez Symfony AI Mate, la solution innovante qui ouvre votre application Symfony aux agents via JSON-RPC sur stdio. Exposez vos services à des IDE ou chatbots en toute sécurité, sans port TCP ouvert. Simplifiez le développement avec cette approche unique.

Pierre 7 min de lecture
Sommaire · 8 0%

Une application Symfony a deux ports d'entrée : le HttpKernel, qui parle HTTP avec les navigateurs, et la Console, qui parle argv avec les humains en terminal : deux façades, un seul container, un seul jeu de services métier. symfony/ai-mate, ajoute un port qui parle JSON-RPC avec des agents.

C'est l'angle qui rend Mate intéressant et pas le fait qu'il soit estampillé « IA ». Mate ne sert pas à brancher GPT sur votre site, il fait l'inverse : il expose votre application — son container DI, ses services, ses logs, son profiler — à n'importe quel client qui parle [MCP](https://modelcontextprotocol.io/). Le consommateur visible aujourd'hui, c'est l'IDE (Claude Code, Junie, Codex). Le port reste ouvert à tout le reste : agent de support interne, chatbot d'admin, script CI qui interroge le domaine en langage naturel, tâche planifiée qui pose une question hebdomadaire et logue la réponse.

Mate ne tourne pas en serveur, il tourne en sous-process

Mate ne s'expose pas sur un port TCP. Pas de EXPOSE 8000 dans le Dockerfile, pas de service à ajouter dans compose.override.yaml, pas de worker FrankenPHP à démarrer. Le transport est stdio — stdin et stdout du process — et c'est tout.Le code source le dit en une ligne. Dans vendor/symfony/ai-mate/src/Command/ServeCommand.php, en plein execute() :

PHP
$server->run(new StdioTransport());

Pas de TCP, pas de HTTP, aucun port ouvert. Mate parle à son client par les pipes du process — comme git parle au shell qui l'a invoqué.

Conséquence : c'est le client MCP qui spawn vendor/bin/mate serve à la demande. Le client lit la config MCP du projet, fait un fork-exec du binaire, et ils discutent en JSON-RPC ligne par ligne sur leurs pipes partagés. Quand le client se ferme, le sous-process meurt avec lui.

Mate sans client est inerte : aucune écoute réseau, aucun loop de polling, juste un process qui attend une ligne sur son entrée standard.

Une installation en trois lignes et un fichier

L'installation tient en quelques actions :

Terminal
composer require --dev symfony/ai-mate
vendor/bin/mate init
composer dump-autoload

mate init crée trois fichiers utiles :

  • mate/config.php — la config DI où s'enregistrent les services custom et les paramètres des extensions.
  • mate/extensions.php — la liste des extensions activées (par défaut, juste symfony/ai-mate).
  • mate/AGENT_INSTRUCTIONS.md — la doc agrégée à destination de l'agent, pour qu'il sache quels outils il a sous la main.

Et un bloc managé apparaît dans AGENTS.md (ou CLAUDE.md) :

Markdown
<!-- BEGIN AI_MATE_INSTRUCTIONS -->
AI Mate Summary:
- Role: MCP-powered, project-aware coding guidance and tools.
- Required action: Read and follow `mate/AGENT_INSTRUCTIONS.md` before taking any action in this project.
- Installed extensions: symfony/ai-mate.
<!-- END AI_MATE_INSTRUCTIONS -->

Reste à déclarer le serveur côté client. Le format est standardisé — un mcp.json à la racine pour les clients qui le lisent (Claude Code, Junie, l'inspecteur MCP officiel) :

JSON
{
    "mcpServers": {
        "symfony-ai-mate": {
            "command": "./vendor/bin/mate",
            "args": ["serve", "--force-keep-alive"]
        }
    }
}

--force-keep-alive : un wrapper, pas un daemon

Le flag a un nom qui sonne « daemon en boucle infinie », ce qu'il n'est pas. La logique vit dans vendor/symfony/ai-mate/bin/mate.

Le wrapper est un parent qui spawn un enfant et lui passe les mêmes stdin/stdout que ceux qu'il a reçus du client. Lecture précise du contrat : si l'enfant termine avec exit 0 (« j'ai fini, tout va bien »), le parent le relance ; si l'enfant meurt sur un signal système (codes 129-192) ou avec une erreur (code non nul), le parent respecte et sort avec le même code.

Pourquoi relancer sur une sortie « propre » ? Parce qu'un serveur MCP en stdio termine normalement quand le client ferme son pipe — typiquement lors d'un reload de configuration côté IDE. Sans --force-keep-alive, la session est perdue jusqu'à un redémarrage manuel du client. Avec, le sous-process est immédiatement relancé et la prochaine connexion le retrouve disponible. Le quai reste armé entre deux escales, sans intervention humaine.

Catalogue de base : un seul tool

Quand un client MCP ouvre la session, il appelle tools/list, et Mate lui retourne un catalogue. Sur un projet vierge, c'est volontairement minimal :

Terminal
$ ./vendor/bin/mate mcp:tools:list
+--------------+-----------------------------------------------------------+
| Name         | Description                                               |
+--------------+-----------------------------------------------------------+
| server-info  | Get PHP runtime environment details: version, OS,         |
|              | OS family, and loaded extensions                          |
+--------------+-----------------------------------------------------------+

Un seul tool, et c'est assumé. Mate suit la philosophie « bring your own capabilities » : la valeur vient des extensions activées et des tools écrits maison, pas d'un buffet préchargé. La table de Rosette de Mate, dans vendor/symfony/ai-mate/INSTRUCTIONS.md, illustre le parti pris :

  • Au lieu de php -v : server-info
  • Au lieu de php -m : server-info
  • Au lieu de uname -s : server-info

Trois commandes shell remplacées par un appel MCP qui renvoie un payload typé. Le client ne parse plus une sortie texte, et il a la garantie que l'info vient du process PHP du projet, pas du php du PATH qui peut être un autre binaire. Sur le devcontainer du blog, le php du système est en 8.1 et celui du projet en 8.5 ; un agent qui croit raisonner sur du 8.5 alors qu'il a tapé une commande exécutée par le 8.1 produit du conseil faux. server-info ferme cette fuite sans qu'on ait à y penser.

Les deux extensions à activer

Le catalogue à un tool ne sert à rien en pratique. La densité réelle arrive avec les deux extensions officielles du monorepo symfony/ai, en dépendances de dev :

Terminal
composer require --dev symfony/ai-symfony-mate-extension
composer require --dev symfony/ai-monolog-mate-extension

Après un vendor/bin/mate discovermate/extensions.php ressemble à ça :

PHP
return [
    'symfony/ai-mate' => ['enabled' => true],
    'symfony/ai-symfony-mate-extension' => ['enabled' => true],
    'symfony/ai-monolog-mate-extension' => ['enabled' => true],
];

Le catalogue passe d'un tool à neuf. Voici, classe par classe, ce que chaque tool apporte concrètement.

Extension Symfony — trois tools

  • symfony-services : Recherche dans le container DI. Filtres par ID ou par classe. Renvoie un mapping service_id → class_name. C'est bin/console debug:container en réponse JSON typée.
  • symfony-profiler-list : Liste et filtre les profils du Web Profiler. Filtres : méthode HTTP, URL, IP, status code, contexte, plage de dates. Tri DESC sur la date — limit=1 renvoie le dernier profil.
  • symfony-profiler-get : Récupère un profil complet par token. Inclut un resource_uri qui permet ensuite d'accéder à un collector via le template MCP symfony-profiler://profile/{token}/{collector}.

Côté sécurité, cookies, données de session, en-têtes d'auth et variables d'environnement sensibles sont redacted côté extension (logique dans Service/Formatter/). Le profiler est exposable à un agent sans risque de fuiter un Authorization: Bearer … dans la conversation. Détail à connaître avant la première session.

Extension Monolog — cinq tools

  • monolog-search : Recherche par texte ou par regex (regex: true). Filtres : niveau, channel, environment, plage de dates. Couvre grepgrep -E et le filtrage par niveau dans un seul tool.
  • monolog-context-search : Recherche par champ structuré du contexte. Exemple : entrées où context.user_id contient une valeur donnée. Imparable pour suivre un order_id sans grep manuel.
  • monolog-tail : Les N dernières entrées, optionnellement filtrées par niveau et environment.
  • monolog-list-files : Liste les fichiers de log avec métadonnées (nom, path, taille, date de modification). À appeler avant une recherche pour savoir ce qui existe.
  • monolog-list-channels : Énumère les channels (appsecuritydoctrine…) trouvés dans les logs. Utile pour cibler une recherche sans connaître la nomenclature interne.

Le mot de la fin

Mate n'a pas la prestance d'un service web. Pas de port à ouvrir, pas de ligne supplémentaire dans var/log/. Il vit dans vendor/, dort tant qu'aucun client ne l'invoque, et s'efface dès la session fermée. La surface d'attaque est réduite à ce qui est explicitement déposé dans mate/src/.

Le seul investissement réel, c'est d'écrire les tools custom, et c'est une bonne nouvelle parce que c'est précisément l'investissement qui transforme l'agent en collaborateur du domaine plutôt qu'en lecteur de fichiers. Tant qu'un agent ne parle qu'au filesystem, il fait de la divination sur du grep. Quand il parle à des services typés, il raisonne sur le métier qu'on a passé des années à modéliser.

Cet article vous a-t-il aidé ?

Vos réactions ne sont pas encore enregistrées — bientôt disponible.

Activez uniquement ce que vous souhaitez. Vos choix sont conservés 6 mois.

Strictement nécessaires

Indispensables au fonctionnement du site (session, sécurité, préférence d'affichage). Aucune donnée n'est partagée à des tiers et aucun consentement n'est requis.

Toujours actif

Mesure d'audience

Statistiques anonymes via Umami Cloud (hébergement UE) : pages vues, source du trafic, navigateur. Pas de cookie tiers, pas de profilage, pas de partage commercial.