Brancher les serveurs avec McpToolset et filtrer les outils
McpToolset : un ensemble d'outils venu d'un serveur
Un toolset ADK est un objet qui fournit une liste d'outils au lieu d'un seul. McpToolset se connecte à un serveur MCP, appelle tools/list et transforme chaque outil MCP en outil ADK. Dans ADK 2.10, son constructeur accepte notamment :
| Paramètre | Rôle |
|---|---|
connection_params | comment joindre le serveur : StdioConnectionParams, SseConnectionParams ou StreamableHTTPConnectionParams |
tool_filter | liste de noms à garder, ou fonction (outil, contexte) -> bool |
tool_name_prefix | préfixe ajouté aux noms vus par le modèle (docs donne docs_lire_document) |
require_confirmation | demande une confirmation avant chaque appel (booléen ou fonction) |
def serveur(script: str, **env: str) -> StdioConnectionParams:
"""Paramètres pour lancer un serveur MCP Python comme sous-processus (même interpréteur)."""
return StdioConnectionParams(
# env : ajouté aux seules variables « sûres » (PATH, HOME...) ; la clé d'API n'est PAS transmise.
server_params=StdioServerParameters(command=sys.executable, args=[str(ICI / script)], env=env),
timeout=20, # secondes pour démarrer le serveur et répondre
)
commandes = McpToolset(
connection_params=serveur("commandes_server.py",
COMMANDES_DB=os.environ.get("COMMANDES_DB", str(ICI / "commandes.db"))),
tool_filter=["lister_tables", "executer_select"], # annuler_commande n'est PAS exposé au modèle
)
fichiers = McpToolset(
connection_params=serveur("fichiers_server.py", DOCS_ROOT=os.environ.get("DOCS_ROOT", str(ICI / "docs"))),
tool_name_prefix="docs", # les outils deviennent docs_lister_documents, docs_lire_document
)sys.executable lance le serveur avec le même Python que l'agent, donc avec le même environnement virtuel : pas de surprise du type « module mcp introuvable ».
flowchart LR
S1["commandes_server : 3 outils"] --> F1{"tool_filter"}
F1 -->|"gardés"| O1["lister_tables, executer_select"]
F1 -->|"écarté"| X["annuler_commande"]
S2["fichiers_server : 2 outils"] --> P["tool_name_prefix = docs"]
P --> O2["docs_lister_documents, docs_lire_document"]
O1 --> M["Déclarations envoyées à Gemini"]
O2 --> M
Liste de noms ou prédicat ?
Une liste est explicite et lisible en revue de code : un outil ajouté plus tard au serveur n'apparaîtra pas sans modification de l'agent. Un prédicat (une fonction qui renvoie vrai ou faux) s'adapte automatiquement, par exemple « tous les outils annotés en lecture seule » :
def lecture_seule(outil, contexte=None) -> bool:
annotations = outil.raw_mcp_tool.annotations
return bool(annotations and annotations.read_only_hint)
commandes = McpToolset(connection_params=..., tool_filter=lecture_seule)Le serveur de commandes gagne demain un outil exporter_clients (lecture seule, annoté readOnlyHint). Que voit l'agent sans modification de agent.py ?
Avec une liste de noms, tout nouvel outil est exclu par défaut. Avec le prédicat lecture_seule, il serait apparu automatiquement : pratique, mais c'est une décision à prendre consciemment.
L'agent et son instruction
root_agent = Agent(
name="analyste_commandes",
model=os.environ.get("MODEL", "gemini-3.5-flash"),
instruction="""Tu es l'analyste des commandes d'une boutique de vélos. Réponds en français.
- Pour les chiffres : appelle d'abord lister_tables pour connaître le schéma, puis executer_select.
N'invente jamais un chiffre : chaque nombre cité vient d'une requête.
- Pour les règles (retours, livraison, promotions) : docs_lister_documents puis docs_lire_document.
- Tu ne peux rien modifier. Si on te demande une action (annuler, rembourser), explique ce que
disent les règles et indique qu'un humain doit la faire.
- Termine par une ligne « Sources : » listant les tables et documents utilisés.""",
tools=[commandes, fichiers],
)Fermer proprement
Les serveurs stdio sont des sous-processus : il faut les arrêter. runner.close() ferme les toolsets de l'agent, donc les sessions MCP :
runner = InMemoryRunner(agent=root_agent, app_name="mcp_lab")
try:
for q in sys.argv[1:] or DEMO:
...
finally:
await runner.close() # ferme proprement les sous-processus MCPPourquoi le préfixe docs est-il utile, même si les noms ne se chevauchent pas aujourd'hui ?
Deux serveurs peuvent très bien exposer chacun un outil lister ou lire. Le préfixe rend les noms uniques et parlants pour le modèle (docs_lire_document).
Exercice : un troisième serveur
Vous voulez brancher le serveur de fichiers une seconde fois, sur un dossier archives/, avec uniquement l'outil de lecture et le préfixe archives. Écrivez le McpToolset correspondant. Quel nom d'outil faut-il mettre dans tool_filter : avec ou sans préfixe ?
Voir la solution
archives = McpToolset(
connection_params=serveur("fichiers_server.py", DOCS_ROOT=str(ICI / "archives")),
tool_filter=["lire_document"],
tool_name_prefix="archives",
)
root_agent = Agent(..., tools=[commandes, fichiers, archives])Le filtre s'applique aux noms d'origine du serveur (lire_document) : dans ADK 2.10, get_tools() filtre, puis get_tools_with_prefix() ajoute le préfixe. Le modèle verra archives_lire_document. C'est exactement ce que vérifie test_filtrage_des_outils_mcp pour docs.
Résultat obtenu
Test du 2026-10-15 qui démarre les vrais serveurs en sous-processus et liste ce que voit le modèle (sans clé d'API) :
test_mcp.py::test_filtrage_des_outils_mcp PASSED
test_mcp.py::test_appel_outil_mcp_de_bout_en_bout PASSEDLe premier vérifie ["executer_select", "lister_tables"] pour les commandes et ["docs_lire_document", "docs_lister_documents"] pour les fichiers. Pendant l'écriture du lab, une première version de ce test appelait get_tools() et obtenait lire_document sans préfixe : c'est ainsi que nous avons vérifié l'ordre « filtre puis préfixe ».
Je sais faire
- Déclarer un
McpToolsetavecStdioConnectionParams - Filtrer les outils par liste de noms ou par prédicat, et savoir lequel choisir
- Préfixer les outils d'un serveur
- Fermer les sous-processus MCP avec
runner.close()