WordPress
26 novembre 2025

MCP PrestaShop : le vrai contrat d’intégration, et les trois pièges que personne ne documente

MCP Prestashop : ce que cette nouveauté va changer pour votre boutique

Le MCP Server PrestaShop a été annoncé à la Developer Conference du 19 novembre 2025, à Paris. Depuis, le module est sorti de bêta et son contrat d’intégration a changé. Résultat : à peu près tout ce qui a été publié sur le sujet, y compris la première version de cet article, donne aujourd’hui des noms d’attributs qui n’existent plus.

Cet article a donc été repris à partir de la seule source publique qui fasse autorité : les stubs officiels publiés par PrestaShop SA, qui décrivent la surface exacte du module. On y ajoute ce qu’on a appris en exploitant notre propre serveur MCP en production, sur un tout autre CMS, parce que les problèmes rencontrés ne sont pas spécifiques à PrestaShop.

Ce que vous allez trouver ici :

  • Le nom réel des attributs PHP, et pourquoi ceux qu’on lit partout sont faux
  • Une exception du module qui n’est pas une exception, et dont le catch ne se déclenche jamais
  • Pourquoi « l’IA ne fait que ce que vous autorisez » est une formulation dangereuse
  • Ce que le caractère propriétaire du module change concrètement pour un développeur
  • Les deux pannes silencieuses qu’on a rencontrées en exploitant un serveur MCP au quotidien

Le contrat d’intégration a changé, et la documentation communautaire n’a pas suivi

Le principe reste celui annoncé : un module tiers peut exposer ses propres outils à l’agent IA. La déclaration se fait en deux temps, et le premier n’a pas bougé. Une méthode sur la classe principale du module signale au serveur qu’il y a des outils à découvrir :

/**
 * Tell ps_mcp_server that this module provides MCP tools.
 */
public function isMcpCompliant(): bool
{
    return true;
}

C’est le second temps qui a changé. Les premiers modules tiers, écrits fin 2025, déclaraient leurs outils avec les attributs de la bibliothèque php-mcp/server :

// Génération 2025, encore visible dans les modules publiés à l'époque
use PhpMcp\Server\Attributes\McpTool;
use PhpMcp\Server\Attributes\Schema;

#[McpTool(name: 'get_morning_briefing', description: '...')]
#[Schema(properties: ['startDate' => ['type' => 'string']])]
public function getMorningBriefing(?string $startDate = null): array

Les stubs officiels, eux, exposent un jeu d’attributs préfixés, dans un espace de noms PrestaShop :

// Contrat actuel, namespace PrestaShop\Module\PsMcpServer\Server\Attributes
use PrestaShop\Module\PsMcpServer\Server\Attributes\PsMcpTool;
use PrestaShop\Module\PsMcpServer\Server\Attributes\PsMcpSchema;
use PrestaShop\Module\PsMcpServer\Server\Attributes\PsMcpToolAnnotations;

#[PsMcpTool(
    name: 'get_low_stock_alerts',
    description: 'Liste les produits sous un seuil de stock donné.',
    annotations: new PsMcpToolAnnotations(readOnlyHint: true, openWorldHint: false)
)]
#[PsMcpSchema(
    properties: ['threshold' => ['type' => 'integer', 'description' => 'Seuil, défaut 10']],
    required: []
)]
public function getLowStockAlerts(int $threshold = 10): array

Trois noms circulent aujourd’hui dans les articles et les tutoriels : #[AsMcpTool], #[McpTool] et #[PsMcpTool]. Seul le troisième correspond aux stubs publiés par l’éditeur. Les deux autres produisent un attribut simplement ignoré au moment de la découverte, ce qui veut dire un outil absent de la liste, sans erreur de compilation ni message au démarrage.

Détail passé sous silence à peu près partout : le module ne se limite pas aux outils. Les stubs déclarent aussi PsMcpPrompt, PsMcpResource et PsMcpResourceTemplate. Un module peut donc exposer des ressources adressables par URI et des prompts réutilisables, pas seulement des actions.

L’exception qui n’est pas une exception

Voici le genre de détail qui coûte une demi-journée si on ne l’a pas lu avant. Le module fournit trois classes d’erreur, dont PsMcpToolCallException. Son commentaire officiel est explicite :

/**
 * Wrapper for the MCP SDK ToolCallException.
 *
 * The SDK class is declared `final` and cannot be extended.
 * This class delegates to the SDK exception via its constructor,
 * so the idiomatic usage is: `throw new PsMcpToolCallException($message, $exitCode);`
 *
 * @throws \Mcp\Exception\ToolCallException
 */
class PsMcpToolCallException
{
    public function __construct(string $message, int $exitCode, ?\Throwable $previous = null)

Lisez la signature attentivement : cette classe n’étend pas Exception et n’implémente pas Throwable. La classe du SDK étant final, PrestaShop ne pouvait pas en hériter. Le contournement retenu est un objet dont le constructeur lève l’exception réelle, un \Mcp\Exception\ToolCallException.

La conséquence est un piège propre :

try {
    $this->doSomething();
} catch (PsMcpToolCallException $e) {
    // ne se déclenchera jamais : l'objet levé est un \Mcp\Exception\ToolCallException
}

Le catch ne remonte aucune erreur, il ne matche simplement jamais, et l’exception poursuit sa route. Pour intercepter, il faut viser \Mcp\Exception\ToolCallException. Pour lever, on écrit bien throw new PsMcpToolCallException($message, $exitCode);, ce qui est syntaxiquement acceptable puisque le constructeur lève avant que l’instruction throw ne s’exécute. C’est inhabituel, c’est documenté, et il n’y a aucune chance de le deviner.

Le second paramètre, $exitCode, est un entier transmis à l’agent IA avec le message. C’est le canal par lequel l’agent comprend la nature de l’échec plutôt que de le paraphraser. À ne pas négliger : un outil qui échoue sans code d’erreur lisible pousse le modèle à réessayer ou à improviser une réponse.

Les annotations sont des indices, pas des garde-fous

C’est le point sur lequel la communication autour du sujet, la nôtre comprise dans sa version précédente, a été trop optimiste. On lit régulièrement que « l’IA ne fait que ce que vous l’autorisez à faire » et que « vous gardez le contrôle à 100 % ». La réalité mérite plus de nuance.

Les stubs exposent PsMcpToolAnnotations, qui reprend les hints de la spécification MCP :

public function __construct(
    public readonly ?string $title = null,
    public readonly ?bool $readOnlyHint = null,      // n'altère pas son environnement
    public readonly ?bool $destructiveHint = null,   // peut détruire des données
    public readonly ?bool $idempotentHint = null,    // rejouable sans effet supplémentaire
    public readonly ?bool $openWorldHint = null      // interagit avec des entités externes
)

Le mot important est dans les noms eux-mêmes : ce sont des hints. Ils décrivent au client ce que l’outil prétend faire, pour qu’une interface puisse par exemple demander une confirmation avant un appel marqué destructiveHint: true. Ils ne contraignent rien du tout côté serveur. Un outil déclaré readOnlyHint: true qui contient un DELETE supprimera très bien vos données.

La frontière de sécurité réelle, c’est le corps de la méthode, plus le compte utilisateur sous lequel le serveur exécute la requête. Les annotations relèvent de l’ergonomie du client, pas du contrôle d’accès. La formulation juste est donc : l’agent ne peut appeler que les outils déclarés, et chaque outil ne fait que ce que son code fait. Ce qui déplace la question de « puis-je faire confiance à l’IA » vers « ai-je relu le code de mes outils », qui est une bien meilleure question.

En pratique, on applique la même règle que sur n’importe quel point d’extension PrestaShop : un outil en écriture est du code de production, il se relit et se teste comme tel.

Un module propriétaire, et ce que ça implique

Point rarement mentionné alors qu’il structure tout le reste : le MCP Server n’est pas open source, contrairement à l’essentiel du cœur PrestaShop. L’en-tête des stubs est sans ambiguïté, et le composer.json déclare "license": "proprietary". La licence est décrite comme personnelle, non exclusive, non transférable, et limitée à l’exploitation de la boutique de l’utilisateur.

Trois conséquences concrètes pour qui développe autour :

  • Vous ne pouvez pas lire le code du serveur. Les stubs ne contiennent que des signatures, les corps de méthodes sont vides. Quand un outil se comporte différemment de ce que vous attendez, il n’y a pas de source à ouvrir. Le débogage passe par l’observation des échanges, pas par la lecture.
  • Les stubs entrent en conflit avec le SDK. Leur composer.json déclare "conflict": {"mcp/sdk": "*"}. Si votre module tire mcp/sdk par ailleurs, l’installation des stubs échoue. Ils sont d’ailleurs prévus en --dev, uniquement pour l’autocomplétion et l’analyse statique.
  • Le versionnage suit celui du module. Les stubs sont alignés sur les releases de ps_mcp_server, ce qui veut dire qu’une montée de version du module peut déplacer le contrat de vos outils, comme cela vient d’arriver avec le renommage des attributs.

Deux pannes silencieuses qu’on a rencontrées en exploitation

Nous exploitons notre propre serveur MCP en production depuis plusieurs mois, sur un autre CMS. Le protocole est le même, et les deux problèmes qui nous ont coûté le plus cher n’ont rien de spécifique à PrestaShop. Ils méritent d’être connus avant de brancher un agent sur une boutique.

Un outil qui renvoie une réponse incomplète, et personne ne le sait

C’est de loin le pire, parce qu’il n’y a aucun signal. Un de nos outils de lecture renvoyait le contenu d’une page, découpé en blocs éditables. Il en renvoyait la majorité, pas la totalité : certains textes vivaient dans une structure que l’outil ne parcourait pas.

Rien dans la réponse ne le disait. Le modèle a donc travaillé sur ce qu’il avait reçu, en toute confiance, et conclu que la réécriture était complète. Elle ne l’était pas : après une passe de réécriture sur 36 blocs, 24 paragraphes de l’ancienne version subsistaient sur la page publiée, totalement invisibles depuis l’outil.

La leçon vaut pour n’importe quel outil MCP en lecture : une réponse partielle est indiscernable d’une réponse complète. Un agent ne peut pas savoir ce qu’on ne lui a pas envoyé. Depuis, notre règle est de vérifier la couverture d’un outil de lecture contre le rendu réel, et non contre ce que l’outil prétend renvoyer. Sur une boutique, l’équivalent serait un outil catalogue qui ignore silencieusement les déclinaisons ou une langue.

Un outil d’écriture pointe sur la production

Cela paraît évident écrit noir sur blanc, et ça l’est beaucoup moins au moment où on enchaîne les appels dans une conversation. Un outil de modification branché sur la boutique écrit en ligne, immédiatement, sans étape de validation et sans historique autre que celui du CMS.

Notre parade tient en une convention : tout outil d’écriture accepte un paramètre dry_run, et on ne déclenche jamais une écriture sans avoir lu le diff produit par l’appel en dry_run. C’est exactement l’usage prévu par idempotentHint et destructiveHint, à ceci près que le paramètre, lui, contraint réellement le comportement au lieu de le décrire.

Si vous développez vos propres outils MCP sur PrestaShop, c’est la première chose à mettre en place, avant même le premier outil métier.

Les prérequis, vérifiés

Les chiffres qui circulent sur les versions minimales sont contradictoires. Voici ce qui est vérifiable dans les fichiers publics plutôt que dans les articles :

  • PHP 8.1 minimum, déclaré par le composer.json des stubs officiels ("php": ">=8.1"). C’est cohérent avec l’usage d’attributs PHP, disponibles depuis 8.0, et de readonly, apparu en 8.1.
  • PrestaShop 8.0 minimum côté modules tiers déjà publiés, qui déclarent ps_versions_compliancy = ['min' => '8.0', 'max' => _PS_VERSION_].
  • Le module s’obtient sur Addons, pas sur GitHub. Seuls les stubs sont publics, et ils s’installent en dépendance de développement.

Si vous êtes encore sur une version antérieure, le sujet n’est pas le MCP mais la migration elle-même, et il vaut mieux traiter celle-ci d’abord.

Notre avis

Le MCP est une bonne nouvelle, et le choix d’un standard plutôt que d’une intégration maison est le bon. Ce qui nous gêne, c’est la manière dont le sujet est présenté, y compris par nous dans la version précédente de cet article : une liste de choses qu’on va pouvoir demander en langage naturel, et une promesse de contrôle total.

Ce qui compte réellement au moment de brancher un agent sur une boutique tient en trois questions. Qu’est-ce que chacun de mes outils fait exactement, ligne par ligne. Que se passe-t-il quand un outil renvoie une réponse partielle. Et comment j’annule une écriture faite par un agent à 23 h un vendredi.

Aucune de ces trois questions n’est résolue par le protocole. Elles se traitent dans le code de vos outils, et c’est la vraie charge de travail derrière une intégration MCP réussie.

Questions fréquentes

Quel est le nom exact de l’attribut pour déclarer un outil ?
#[PsMcpTool], dans l’espace de noms PrestaShop\Module\PsMcpServer\Server\Attributes. Les formes #[McpTool] et #[AsMcpTool] qu’on trouve en ligne correspondent soit à une génération antérieure basée sur php-mcp/server, soit à une confusion. Un attribut inconnu n’échoue pas bruyamment, il produit un outil qui n’apparaît jamais.

Comment un module signale-t-il qu’il fournit des outils ?
En implémentant isMcpCompliant(): bool sur sa classe principale et en renvoyant true. Le serveur découvre ensuite les outils par lecture des attributs.

Le module est-il open source ?
Non. ps_mcp_server est propriétaire, propriété de PrestaShop SA, et se récupère sur Addons. Seuls les stubs d’autocomplétion sont publics, sous licence propriétaire également.

Puis-je empêcher l’IA de modifier mes données ?
Oui, mais pas via les annotations. readOnlyHint et destructiveHint sont des indications destinées au client, sans effet contraignant côté serveur. Le contrôle réel vient de ce que vous exposez comme outils et de ce que leur code autorise.

Que se passe-t-il si mon outil échoue ?
Vous levez new PsMcpToolCallException($message, $exitCode). Attention, cette classe n’est pas un Throwable : elle lève depuis son constructeur un \Mcp\Exception\ToolCallException. Un catch (PsMcpToolCallException $e) ne se déclenchera jamais.

Peut-on exposer autre chose que des outils ?
Oui. Les stubs déclarent également PsMcpPrompt, PsMcpResource et PsMcpResourceTemplate, ce qui permet d’exposer des ressources adressables par URI et des prompts réutilisables.

Faut-il installer mcp/sdk à côté des stubs ?
Non, les deux sont incompatibles : les stubs déclarent explicitement un conflit avec mcp/sdk. Ils s’installent en --dev, uniquement pour l’autocomplétion et l’analyse statique.

Vous envisagez d’exposer vos modules métier à un agent IA et vous voulez le faire sans ouvrir de porte de sortie sur vos données ? Parlons-en.

Nous vous recommandons aussi