« Ability not found » — le message apparaît alors que la commande de listing confirme pourtant que l’ability est bien enregistrée côté serveur. Ce symptôme, déroutant au premier abord, revient assez souvent pour mériter une méthode de diagnostic dédiée plutôt qu’une recherche au hasard dans le code.
Le point commun à la plupart des cas rencontrés : l’ability existe bel et bien dans le registre global, mais sa disponibilité effective dépend d’une condition évaluée au moment de l’appel — un contexte utilisateur, une capacité WordPress, un état de requête — qui ne correspond pas à ce que l’agent fournit réellement quand il tente l’exécution.
Distinguer enregistrement et disponibilité
L’enregistrement d’une ability via wp_register_ability() se produit généralement au chargement du plugin, sur un hook comme init, sans connaissance de qui appellera la fonction ni dans quel contexte. La disponibilité effective, elle, dépend souvent d’un rappel de permission distinct, exécuté à chaque tentative d’appel, qui peut retourner un refus silencieux transformé en « ability not found » plutôt qu’en message d’erreur explicite.
Cette confusion entre les deux étapes explique pourquoi une simple commande de listing des abilities enregistrées ne suffit jamais à diagnostiquer le problème : elle confirme l’existence de l’ability, pas son accessibilité dans le contexte précis de l’appel qui échoue.
Reproduire l’appel exact qui échoue
La première étape consiste à isoler le contexte d’exécution réel au moment où l’agent tente l’appel : quel utilisateur WordPress est associé au jeton utilisé, quelles capacités cet utilisateur possède, et surtout dans quel processus la vérification se produit (une requête REST classique, un appel synchrone, ou un contexte de tâche planifiée sans session utilisateur).
add_action('wp_ability_execution_denied', function ($ability_name, $raison) {
error_log(sprintf(
'Ability refusée : %s | Raison : %s | Utilisateur courant : %d',
$ability_name,
$raison,
get_current_user_id()
));
}, 10, 2);
Ce type de journalisation temporaire, ajouté sur le filtre ou l’action pertinent exposé par l’implémentation utilisée, révèle presque toujours la cause réelle : un identifiant utilisateur à zéro alors que l’ability attend un utilisateur authentifié, ou une capacité manquante que l’agent ne peut évidemment pas deviner depuis l’extérieur.

Le cas fréquent des tâches asynchrones
Un serveur MCP qui exécute une ability depuis un contexte de tâche différée (une file de traitement, un appel déclenché par une tâche planifiée) perd parfois le contexte utilisateur associé à la session initiale. L’ability, enregistrée en supposant un utilisateur connecté avec une capacité précise, se retrouve exécutée depuis un contexte anonyme ou depuis un utilisateur système générique qui ne possède pas la capacité attendue.
Vérifier la chaîne complète
- Le jeton fourni par l’agent correspond-il à un utilisateur WordPress valide au moment précis de l’appel
- Cet utilisateur possède-t-il la capacité exigée par le rappel de permission de l’ability
- Le contexte d’exécution (synchrone ou différé) préserve-t-il bien cette identité utilisateur jusqu’à l’appel final
Un cas particulier : la portée de site en environnement multisite
Sur une installation multisite, une ability enregistrée sur un site du réseau n’est pas automatiquement disponible sur un autre site du même réseau, même pour un utilisateur qui possède les droits nécessaires sur les deux. Un agent qui bascule de contexte (changement de blog_id en cours de session) sans que le serveur MCP ne recharge la liste des abilities disponibles pour ce nouveau contexte tombera sur la même erreur, pour une raison différente de celle d’un simple défaut de capacité.
Une erreur « ability not found » qui survient uniquement pour certains utilisateurs ou certains contextes de requête n’est jamais un problème d’enregistrement : c’est systématiquement un problème de portée.
Prévenir la confusion pour les développeurs suivants
Retourner un message d’erreur distinct entre « ability inexistante » et « ability existante mais refusée dans ce contexte » facilite grandement le diagnostic pour quiconque reprend le projet plus tard. Cette distinction demande simplement de ne pas fusionner les deux cas dans un même message générique au niveau du rappel de permission, une pratique qui économise des heures de recherche à la première régression.
En résumé
Face à une ability introuvable qui pourtant figure bien dans le registre, la piste à privilégier n’est presque jamais l’enregistrement lui-même, mais la correspondance entre le contexte d’appel réel (utilisateur, capacités, synchronicité, portée de site) et les conditions attendues par le rappel de permission. Journaliser temporairement les refus avec leur raison précise transforme un diagnostic qui pourrait prendre des heures en une vérification de quelques minutes.