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() :
$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 :
composer require --dev symfony/ai-mate
vendor/bin/mate init
composer dump-autoloadmate 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, justesymfony/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) :
<!-- 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) :
{
"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 :
$ ./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 :
composer require --dev symfony/ai-symfony-mate-extension
composer require --dev symfony/ai-monolog-mate-extensionAprès un vendor/bin/mate discover, mate/extensions.php ressemble à ça :
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 mappingservice_id → class_name. C'estbin/console debug:containeren 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=1renvoie le dernier profil.symfony-profiler-get: Récupère un profil complet par token. Inclut unresource_uriqui permet ensuite d'accéder à un collector via le template MCPsymfony-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. Couvregrep,grep -Eet le filtrage par niveau dans un seul tool.monolog-context-search: Recherche par champ structuré du contexte. Exemple : entrées oùcontext.user_idcontient 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 (app,security,doctrine…) 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.