> Discover all available pages from the documentation index: https://mastra.zisheng.pro/fr/llms.txt # MCPClient La classe `MCPClient` permet de gérer plusieurs connexions à des serveurs MCP et leurs Tools dans une application Mastra. Elle prend en charge le cycle de vie des connexions et les espaces de noms des Tools, et donne accès aux Tools de tous les serveurs configurés. ## Constructeur Crée une instance de la classe MCPClient. ```typescript constructor({ id?: string; servers: Record; timeout?: number; }: MCPClientOptions) ``` ### MCPClientOptions **id** (`string`): Identifiant unique facultatif de l’instance de configuration. Utilisez-le pour éviter les fuites de mémoire lors de la création de plusieurs instances ayant des configurations identiques. **servers** (`Record`): Une table de configurations de serveur, dans laquelle chaque clé est un identifiant de serveur unique et chaque valeur la configuration correspondante. **timeout** (`number`): Délai d’expiration global, en millisecondes, pour tous les serveurs, sauf remplacement dans la configuration d’un serveur. (Default: `60000`) ### `MastraMCPServerDefinition` Chaque serveur de la table `servers` est configuré avec le type `MastraMCPServerDefinition`. Le type de transport est détecté d’après les paramètres fournis : - Si `command` est fourni, le transport Stdio est utilisé. - Si `url` est fourni, le client tente d’abord d’utiliser le transport Streamable HTTP, puis se replie sur l’ancien transport SSE si la connexion initiale échoue. **command** (`string`): Pour les serveurs Stdio : commande à exécuter. **args** (`string[]`): Pour les serveurs Stdio : arguments à transmettre à la commande. **env** (`Record`): Pour les serveurs Stdio : variables d’environnement à définir pour la commande. **inheritDefaultEnv** (`boolean`): Pour les serveurs Stdio : indique si l’environnement du sous-processus part de l’environnement hérité par défaut du SDK MCP. Par défaut, il s’agit d’une liste blanche sélectionnée, et non de l’environnement complet du processus : sous POSIX, HOME, LOGNAME, PATH, SHELL, TERM et USER sont héritées ; sous Windows, APPDATA, HOMEDRIVE, HOMEPATH, LOCALAPPDATA, PATH, PROCESSOR\_ARCHITECTURE, SYSTEMDRIVE, SYSTEMROOT, TEMP, USERNAME et USERPROFILE le sont. Lorsque cette option vaut false, seules les variables explicitement répertoriées dans env sont transmises au sous-processus. Notez qu’un sous-processus dépourvu de PATH peut ne pas parvenir à lancer des commandes dont le chemin n’est pas absolu. (Default: `true`) **url** (`URL`): Pour les serveurs HTTP (Streamable HTTP ou SSE) : URL du serveur. **requestInit** (`RequestInit`): Pour les serveurs HTTP : configuration des requêtes de l’API fetch. **eventSourceInit** (`EventSourceInit`): Pour le repli SSE : configuration fetch personnalisée des connexions SSE. Requise avec des en-têtes personnalisés en SSE. **fetch** (`MastraFetchLike`): Pour les serveurs HTTP : implémentation fetch personnalisée utilisée pour toutes les requêtes réseau. Elle reçoit un troisième paramètre requestContext facultatif contenant les données propres à la requête entrante (par exemple, cookies d’authentification ou jetons bearer). Lorsqu’elle est fournie, cette fonction est utilisée pour toutes les requêtes HTTP. Elle permet d’ajouter des en-têtes d’authentification dynamiques, de transmettre au serveur MCP des identifiants propres à la requête, de personnaliser le comportement de chaque requête, ou encore d’intercepter et de modifier les requêtes et réponses. Lorsque fetch est fourni, requestInit, eventSourceInit et authProvider deviennent facultatifs, car votre fonction fetch personnalisée peut prendre en charge ces aspects. **allowedHosts** (`string[]`): Pour les serveurs HTTP : liste d’autorisation facultative des hôtes que le client peut contacter pour le compte de ce serveur. Chaque entrée est comparée à l’hôte de l’URL (nom d’hôte et port lorsque l’URL utilise un port non standard), par exemple "api.example.com" ou "localhost:8080". La correspondance est exacte et insensible à la casse pour le nom d’hôte ; les caractères génériques ne sont pas pris en charge et le schéma de l’URL n’est pas vérifié. Un tableau vide refuse toutes les requêtes. En l’absence de valeur, aucune restriction ne s’applique. Consultez la section Sécurité ci-dessous pour les détails d’application. **logger** (`LogHandler`): Gestionnaire supplémentaire facultatif pour la journalisation. **timeout** (`number`): Délai d’expiration propre au serveur, en millisecondes. **capabilities** (`ClientCapabilities`): Configuration des capacités propre au serveur. **authProvider** (`OAuthClientProvider`): Pour les serveurs HTTP : Provider d’authentification OAuth assurant l’actualisation automatique des jetons et la gestion du flux OAuth. Utilisez MCPOAuthClientProvider pour une implémentation prête à l’emploi. **enableServerLogs** (`boolean`): Indique si la journalisation doit être activée pour ce serveur. (Default: `true`) **forwardInstructions** (`boolean`): Indique si les instructions annoncées par ce serveur MCP doivent être ajoutées au prompt système d’un Agent lorsque celui-ci utilise les Tools du serveur. Cette option est désactivée par défaut ; ne l’activez que pour les serveurs auxquels vous faites confiance, car les instructions sont injectées dans le prompt système de l’Agent. (Default: `false`) **instructionsMaxLength** (`number`): Nombre maximal de caractères d’instructions du serveur à ajouter au prompt système d’un Agent. (Default: `512`) **requireToolApproval** (`boolean | (params: RequireToolApprovalContext) => boolean | Promise`): Exige une approbation humaine avant d’exécuter les Tools de ce serveur. Avec la valeur true, tous les Tools nécessitent une approbation. Lorsqu’une fonction est fournie, elle reçoit le nom du Tool, ses arguments, le contexte de requête et les éventuelles annotations annoncées par le serveur afin de déterminer dynamiquement si une approbation est nécessaire. ## Approbation des Tools Utilisez `requireToolApproval` dans la définition d’un serveur pour exiger une approbation humaine avant l’exécution de tout Tool de ce serveur. Cette option s’intègre au flux d’approbation [human-in-the-loop](https://mastra.zisheng.pro/fr/docs/workflows/human-in-the-loop) existant. ### Exiger une approbation pour tous les Tools Définissez `requireToolApproval` sur `true` pour exiger une approbation pour chaque Tool du serveur : ```typescript const mcp = new MCPClient({ servers: { github: { url: new URL('http://localhost:3000/mcp'), requireToolApproval: true, }, }, }) ``` ### Approbation dynamique avec une fonction Transmettez une fonction pour décider, à chaque appel, si une approbation est nécessaire. La fonction reçoit le nom du Tool, les arguments transmis par le modèle, tout contexte issu de la requête entrante et les `annotations` MCP du Tool (lorsque le serveur les annonce) : ```typescript const mcp = new MCPClient({ servers: { github: { url: new URL('http://localhost:3000/mcp'), requireToolApproval: ({ toolName, args, requestContext }) => { // Read-only tools don't need approval if (toolName === 'list_repos') return false // Destructive tools with force flag always need approval if (toolName === 'delete_repo') return args.force === true // Non-admin users need approval for everything else return requestContext?.userRole !== 'admin' }, }, }, }) ``` La fonction peut également être asynchrone. Elle reçoit le `requestContext` de la requête entrante, que vous pouvez utiliser pour les contrôles d’authentification ou toute autre logique propre à la requête. ### Utiliser les annotations de Tool d’un serveur de confiance Si vous faites confiance au serveur MCP, vous pouvez utiliser ses [annotations de Tool](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-annotations) (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`, `title`) pour orienter les décisions d’approbation : ```typescript const mcp = new MCPClient({ servers: { github: { url: new URL('http://localhost:3000/mcp'), requireToolApproval: ({ annotations }) => { // Skip approval for tools the server has marked read-only if (annotations?.readOnlyHint) return false // Always require approval for destructive tools if (annotations?.destructiveHint) return true return true }, }, }, }) ``` Selon la spécification MCP, **les clients DOIVENT considérer les annotations de Tool comme non fiables, sauf si elles proviennent de serveurs de confiance**. Ces annotations ne sont que des indications et ne constituent aucune frontière de sécurité. Un serveur malveillant ou défectueux peut prétendre qu’un Tool est en lecture seule alors que ce n’est pas le cas. N’utilisez les annotations pour assouplir les exigences d’approbation que pour les serveurs auxquels vous faites confiance. Ces mêmes annotations sont également exposées sur les Tools renvoyés par `listTools()` et `listToolsets()`, sous `tool.mcp.annotations`. Vous pouvez ainsi les examiner lorsque vous associez les Tools à un Agent. ## Instructions du serveur Lorsqu’un serveur MCP annonce des instructions pendant l’initialisation, `MCPClient` les conserve pour ce serveur. Leur transmission au prompt système d’un Agent est **facultative** : définissez `forwardInstructions: true` sur un serveur pour que les Agents qui utilisent ses Tools (via `listTools()` ou `listToolsets()`) reçoivent automatiquement ses instructions. Les instructions sont regroupées par nom de serveur et limitées à `instructionsMaxLength` caractères par serveur. ```typescript const mcp = new MCPClient({ servers: { db: { url: new URL('http://localhost:3000/mcp'), forwardInstructions: true, instructionsMaxLength: 512, }, }, }) const agent = new Agent({ id: 'db-agent', name: 'DB Agent', instructions: 'Help with database changes.', model, tools: await mcp.listTools(), }) ``` Lorsque `forwardInstructions` est omis (comportement par défaut), les instructions restent mises en cache et peuvent être consultées avec [`getServerInstructions()`](#getserverinstructions), mais ne sont ajoutées au prompt système d’aucun Agent. > **Note de sécurité :** les instructions du serveur sont transmises telles quelles au prompt système de l’Agent (sous réserve uniquement de leur troncature). Un serveur MCP malveillant ou compromis peut s’en servir pour injecter des instructions que l’Agent considérera comme des directives système fiables. N’activez `forwardInstructions` que pour les serveurs auxquels vous faites confiance et examinez de préférence les instructions avec `getServerInstructions()` avant de transmettre celles de serveurs tiers. ## Sécurité ### Environnement des sous-processus pour les serveurs Stdio Les sous-processus Stdio n’héritent pas de l’intégralité de l’environnement du processus parent. Par défaut, leur environnement part de la liste blanche sélectionnée par le SDK MCP (POSIX : `HOME`, `LOGNAME`, `PATH`, `SHELL`, `TERM`, `USER` ; Windows : `APPDATA`, `HOMEDRIVE`, `HOMEPATH`, `LOCALAPPDATA`, `PATH`, `PROCESSOR_ARCHITECTURE`, `SYSTEMDRIVE`, `SYSTEMROOT`, `TEMP`, `USERNAME`, `USERPROFILE`), à laquelle s’ajoutent les variables définies dans `env`. Les variables sensibles, comme les clés d’API, ne sont pas héritées à moins d’être transmises explicitement. Pour une isolation plus stricte, définissez `inheritDefaultEnv: false` afin que seules les entrées configurées dans `env` atteignent le sous-processus : ```typescript const mcp = new MCPClient({ servers: { myTool: { command: '/usr/local/bin/my-mcp-server', inheritDefaultEnv: false, env: { MY_TOOL_API_KEY: process.env.MY_TOOL_API_KEY! }, }, }, }) ``` Les variables placées dans `env` sont transmises telles quelles. Considérez donc comme des entrées non fiables les configurations de serveur provenant de sources non fiables, par exemple des fichiers de configuration fournis par l’utilisateur. ### Restreindre les hôtes sortants avec `allowedHosts` Lorsque les URL des serveurs HTTP proviennent d’une configuration non fiable, une URL contrôlée par un attaquant peut diriger le client vers des services internes (falsification de requête côté serveur). Définissez `allowedHosts` sur ces serveurs afin de limiter les hôtes que le client peut contacter : ```typescript const mcp = new MCPClient({ servers: { remote: { url: new URL(untrustedConfig.serverUrl), allowedHosts: ['api.example.com'], }, }, }) ``` Détails d’application : - Avec le chemin fetch par défaut, les requêtes vers des hôtes non autorisés, y compris chaque étape d’une redirection, sont bloquées **avant** leur envoi. Les redirections sont suivies manuellement (jusqu’à 5 étapes) afin de valider chacune d’elles. L’en-tête `Authorization` n’est pas transmis lorsqu’une étape change d’origine : toute modification du schéma, de l’hôte ou du port le supprime, conformément au comportement fetch standard. - Lorsque vous fournissez un `fetch` personnalisé (ou un `eventSourceInit.fetch` personnalisé), l’URL initiale est toujours vérifiée avant la requête, mais les étapes de redirection sont validées **a posteriori** à l’aide de `response.url` : la requête sortante peut avoir lieu, puis la réponse est rejetée si son URL finale pointe vers un hôte non autorisé. Une `Response` construite manuellement avec un `response.url` vide contourne cette vérification a posteriori. - Les requêtes OAuth effectuées via `authProvider` (découverte des métadonnées du serveur d’autorisation, échange et actualisation des jetons) sont également validées. Si votre serveur d’autorisation s’exécute sur un hôte différent de celui du serveur MCP, ajoutez aussi cet hôte à `allowedHosts`. - Un hôte bloqué fait échouer la connexion avec une erreur explicite et la logique de reconnexion ne réessaie jamais. `allowedHosts` est volontairement minimal : il compare les hôtes exactement et ne prend en charge ni caractères génériques ni contrôle du schéma. Pour une politique plus riche (contrôle du schéma ou règles de plages IP), fournissez une implémentation `fetch` personnalisée, appelée pour chaque requête du client. ### Considérer les réponses des Tools comme des entrées non fiables Les résultats de Tools renvoyés par les serveurs MCP sont intégrés au contexte de votre Agent comme entrées du modèle. Un serveur malveillant ou compromis peut utiliser la sortie d’un Tool pour injecter un prompt. Le client de transport n’assainit pas les réponses des Tools : cette politique relève de la couche Agent, où les [processeurs d’entrée et de sortie](https://mastra.zisheng.pro/fr/docs/agents/processors) de Mastra permettent d’examiner, de transformer ou de bloquer le contenu avant et après son passage dans le modèle. Lorsque vous utilisez des serveurs tiers, combinez cette protection avec `requireToolApproval` et les précautions relatives à `forwardInstructions` ci-dessus. ## Méthodes ### `listTools()` Récupère tous les Tools de tous les serveurs configurés, en préfixant leurs noms par l’espace de noms du serveur (au format `serverName_toolName`) afin d’éviter les conflits. Cette valeur est destinée à être transmise à la définition d’un Agent. ```ts new Agent({ id: 'agent', tools: await mcp.listTools() }) ``` ### `listToolsWithErrors()` Récupère tous les Tools de tous les serveurs configurés, avec des noms placés dans l’espace de noms de leur serveur. Renvoie également les erreurs propres aux serveurs qui n’ont pas pu se connecter ou répertorier leurs Tools. ```typescript const { tools, errors } = await mcp.listToolsWithErrors() new Agent({ id: 'agent', tools }) console.log(errors) ``` ### `listToolsets()` Renvoie un objet qui associe les noms de Tools placés dans un espace de noms (au format `serverName.toolName`) à leurs implémentations. Cette valeur est destinée à être transmise à l’exécution à la méthode generate ou stream. ```typescript const res = await agent.stream(prompt, { toolsets: await mcp.listToolsets(), }) ``` ### `getServerInstructions()` Renvoie les instructions actuellement connues pour chaque serveur MCP configuré. Les serveurs qui ne se sont pas encore connectés ou qui n’annoncent aucune instruction renvoient `undefined`. ```typescript getServerInstructions(): Record ``` Exemple : ```typescript await mcp.listTools() const instructionsByServer = mcp.getServerInstructions() console.log(instructionsByServer.db) ``` ### `authenticate()` Exécute le flux interactif de code d’autorisation OAuth pour un serveur configuré avec un `MCPOAuthClientProvider` dont l’URL de redirection pointe vers une adresse de bouclage. La méthode démarre un serveur de rappel local, transmet l’URL d’autorisation via le callback `onRedirectToAuthorization` du Provider, attend que le navigateur renvoie le code d’autorisation, échange celui-ci contre des jetons, puis se reconnecte. Consultez [Authentification interactive dans le navigateur](#interactive-browser-authentication). Le paramètre facultatif `timeoutMs` limite la durée pendant laquelle le flux attend que le navigateur renvoie le code d’autorisation avant d’échouer. Sa valeur par défaut est de 5 minutes. ```typescript async authenticate(serverName: string, options?: { timeoutMs?: number }): Promise ``` ### `getServerAuthState()` Renvoie l’état d’autorisation OAuth d’un serveur configuré : `'needs-auth'` après le rejet d’une tentative de connexion pour erreur d’autorisation, `'authorized'` dès que le serveur a accepté les identifiants du Provider, ou `undefined` pour les serveurs sans `authProvider` ou qui n’ont encore tenté aucune connexion. ```typescript getServerAuthState(serverName: string): 'needs-auth' | 'authorized' | undefined ``` ### `cancelAuthentication()` Annule un flux `authenticate()` en cours pour un serveur, afin qu’une autorisation abandonnée dans le navigateur ne laisse pas le client en attente indéfiniment. La méthode interrompt le flux, y compris sa phase de préparation avant la liaison du serveur de rappel, ferme le serveur de rappel local s’il est en écoute et fait échouer l’appel `authenticate()` en attente. Elle renvoie `true` si un flux a été annulé, ou `false` si aucun flux n’était en cours. La valeur de `getServerAuthState()` qui en résulte dépend de l’avancement du flux. Un flux annulé après un rejet `401` reste à l’état `'needs-auth'` et peut être relancé immédiatement. Une annulation pendant la préparation laisse l’état inchangé (généralement `undefined`) si aucune connexion n’a été tentée. ```typescript async cancelAuthentication(serverName: string): Promise ``` ### `disconnect()` Se déconnecte de tous les serveurs MCP et libère les ressources. ```typescript async disconnect(): Promise ``` ### `toMCPServerProxies()` Renvoie une table d’instances `MCPClientServerProxy`, une par serveur configuré. Chaque proxy encapsule la connexion cliente sous-jacente dans une instance `MCPServerBase`, ce qui permet d’enregistrer des serveurs MCP externes à Mastra dans `mcpServers` et de les afficher dans Studio. ```typescript async toMCPServerProxies(): Promise> ``` Décomposez le résultat dans la configuration `mcpServers` de `Mastra` : ```typescript import { Mastra } from '@mastra/core/mastra' import { MCPClient } from '@mastra/mcp' const mcpClient = new MCPClient({ servers: { 'color-mixer': { command: 'node', args: ['path/to/color-mixer-server.js'], }, }, }) export const mastra = new Mastra({ mcpServers: { ...(await mcpClient.toMCPServerProxies()), }, }) ``` Cette méthode permet de connecter à Studio des serveurs MCP externes qui implémentent l’extension MCP Apps ou d’autres fonctionnalités, sans les encapsuler dans un `MCPServer` Mastra. ### Propriété `resources` L’instance `MCPClient` possède une propriété `resources` qui donne accès aux opérations liées aux ressources. ```typescript const mcpClient = new MCPClient({/* ...servers configuration... */}) // Access resource methods via mcpClient.resources const allResourcesByServer = await mcpClient.resources.list() const templatesByServer = await mcpClient.resources.templates() // ... and so on for other resource methods. ``` #### `resources.list()` Récupère toutes les ressources disponibles sur tous les serveurs MCP connectés, regroupées par nom de serveur. ```typescript async list(): Promise> ``` Exemple : ```typescript const resourcesByServer = await mcpClient.resources.list() for (const serverName in resourcesByServer) { console.log(`Resources from ${serverName}:`, resourcesByServer[serverName]) } ``` #### `resources.templates()` Récupère tous les modèles de ressource disponibles sur tous les serveurs MCP connectés, regroupés par nom de serveur. ```typescript async templates(): Promise> ``` Exemple : ```typescript const templatesByServer = await mcpClient.resources.templates() for (const serverName in templatesByServer) { console.log(`Templates from ${serverName}:`, templatesByServer[serverName]) } ``` #### `resources.read(serverName: string, uri: string)` Lit le contenu d’une ressource précise sur un serveur. ```typescript async read(serverName: string, uri: string): Promise ``` - `serverName` : identifiant du serveur (clé utilisée dans l’option `servers` du constructeur). - `uri` : URI de la ressource à lire. Exemple : ```typescript const content = await mcpClient.resources.read('myWeatherServer', 'weather://current') console.log('Current weather:', content.contents[0].text) ``` #### `resources.subscribe(serverName: string, uri: string)` S’abonne aux mises à jour d’une ressource précise sur un serveur. ```typescript async subscribe(serverName: string, uri: string): Promise ``` Exemple : ```typescript await mcpClient.resources.subscribe('myWeatherServer', 'weather://current') ``` #### `resources.unsubscribe(serverName: string, uri: string)` Se désabonne des mises à jour d’une ressource précise sur un serveur. ```typescript async unsubscribe(serverName: string, uri: string): Promise ``` Exemple : ```typescript await mcpClient.resources.unsubscribe('myWeatherServer', 'weather://current') ``` #### `resources.onUpdated(serverName: string, handler: (params: { uri: string }) => void)` Définit un gestionnaire de notification appelé lorsqu’une ressource suivie sur un serveur précis est mise à jour. ```typescript async onUpdated(serverName: string, handler: (params: { uri: string }) => void): Promise ``` Exemple : ```typescript mcpClient.resources.onUpdated('myWeatherServer', params => { console.log(`Resource updated on myWeatherServer: ${params.uri}`) // You might want to re-fetch the resource content here // await mcpClient.resources.read("myWeatherServer", params.uri); }) ``` #### `resources.onListChanged(serverName: string, handler: () => void)` Définit un gestionnaire de notification appelé lorsque la liste des ressources disponibles change sur un serveur précis. ```typescript async onListChanged(serverName: string, handler: () => void): Promise ``` Exemple : ```typescript mcpClient.resources.onListChanged('myWeatherServer', () => { console.log('Resource list changed on myWeatherServer.') // You should re-fetch the list of resources // await mcpClient.resources.list(); }) ``` ### Propriété `elicitation` L’instance `MCPClient` possède une propriété `elicitation` qui donne accès aux opérations de sollicitation. La sollicitation permet aux serveurs MCP de demander des informations structurées aux utilisateurs. ```typescript const mcpClient = new MCPClient({/* ...servers configuration... */}) // Set up elicitation handler mcpClient.elicitation.onRequest('serverName', async request => { // Handle elicitation request from server console.log('Server requests:', request.message) console.log('Schema:', request.requestedSchema) // Return user response return { action: 'accept', content: { name: 'John Doe', email: 'john@example.com' }, } }) ``` #### `elicitation.onRequest(serverName: string, handler: ElicitationHandler)` Configure une fonction de gestion appelée lorsqu’un serveur MCP connecté envoie une demande de sollicitation. Le gestionnaire reçoit la demande et doit renvoyer une réponse. ##### Fonction `ElicitationHandler` La fonction de gestion reçoit un objet de requête comprenant : - `message` : message lisible décrivant les informations nécessaires - `requestedSchema` : schéma JSON définissant la structure de la réponse attendue Le gestionnaire doit renvoyer un `ElicitResult` comprenant : - `action` : l’une des valeurs `'accept'`, `'decline'` ou `'cancel'` - `content` : données de l’utilisateur (uniquement lorsque l’action vaut `'accept'`) **Exemple :** ```typescript mcpClient.elicitation.onRequest('serverName', async request => { console.log(`Server requests: ${request.message}`) // Example: Simple user input collection if (request.requestedSchema.properties.name) { // Simulate user accepting and providing data return { action: 'accept', content: { name: 'Alice Smith', email: 'alice@example.com', }, } } // Simulate user declining the request return { action: 'decline' } }) ``` **Exemple interactif complet :** ```typescript import { MCPClient } from '@mastra/mcp' import { createInterface } from 'readline' const readline = createInterface({ input: process.stdin, output: process.stdout, }) function askQuestion(question: string): Promise { return new Promise(resolve => { readline.question(question, answer => resolve(answer.trim())) }) } const mcpClient = new MCPClient({ servers: { interactiveServer: { url: new URL('http://localhost:3000/mcp'), }, }, }) // Set up interactive elicitation handler await mcpClient.elicitation.onRequest('interactiveServer', async request => { console.log(`\n📋 Server Request: ${request.message}`) console.log('Required information:') const schema = request.requestedSchema const properties = schema.properties || {} const required = schema.required || [] const content: Record = {} // Collect input for each field for (const [fieldName, fieldSchema] of Object.entries(properties)) { const field = fieldSchema as any const isRequired = required.includes(fieldName) let prompt = `${field.title || fieldName}` if (field.description) prompt += ` (${field.description})` if (isRequired) prompt += ' *required*' prompt += ': ' const answer = await askQuestion(prompt) // Handle cancellation if (answer.toLowerCase() === 'cancel') { return { action: 'cancel' } } // Validate required fields if (answer === '' && isRequired) { console.log(`❌ ${fieldName} is required`) return { action: 'decline' } } if (answer !== '') { content[fieldName] = answer } } // Confirm submission console.log('\n📝 You provided:') console.log(JSON.stringify(content, null, 2)) const confirm = await askQuestion('\nSubmit this information? (yes/no/cancel): ') if (confirm.toLowerCase() === 'yes' || confirm.toLowerCase() === 'y') { return { action: 'accept', content } } else if (confirm.toLowerCase() === 'cancel') { return { action: 'cancel' } } else { return { action: 'decline' } } }) ``` ### Propriété `prompts` L’instance `MCPClient` possède une propriété `prompts` qui donne accès aux opérations liées aux prompts. ```typescript const mcpClient = new MCPClient({/* ...servers configuration... */}) // Access prompt methods via mcpClient.prompts const allPromptsByServer = await mcpClient.prompts.list() const { prompt, messages } = await mcpClient.prompts.get({ serverName: 'myWeatherServer', name: 'current', }) ``` #### `prompts.list()` Récupère tous les prompts disponibles sur tous les serveurs MCP connectés, regroupés par nom de serveur. ```typescript async list(): Promise> ``` Exemple : ```typescript const promptsByServer = await mcpClient.prompts.list() for (const serverName in promptsByServer) { console.log(`Prompts from ${serverName}:`, promptsByServer[serverName]) } ``` #### `prompts.get({ serverName, name, args?, version? })` Récupère un prompt précis et ses messages depuis un serveur. ```typescript async get({ serverName, name, args?, version?, }: { serverName: string; name: string; args?: Record; version?: string; }): Promise<{ prompt: Prompt; messages: PromptMessage[] }> ``` Exemple : ```typescript const { prompt, messages } = await mcpClient.prompts.get({ serverName: 'myWeatherServer', name: 'current', args: { location: 'London' }, }) console.log(prompt) console.log(messages) ``` #### `prompts.onListChanged(serverName: string, handler: () => void)` Définit un gestionnaire de notification appelé lorsque la liste des prompts disponibles change sur un serveur précis. ```typescript async onListChanged(serverName: string, handler: () => void): Promise ``` Exemple : ```typescript mcpClient.prompts.onListChanged('myWeatherServer', () => { console.log('Prompt list changed on myWeatherServer.') // You should re-fetch the list of prompts // await mcpClient.prompts.list(); }) ``` ### Propriété `tools` L’instance `MCPClient` possède une propriété `tools` permettant de s’abonner aux notifications de modification de la liste des Tools. Pour récupérer les Tools, utilisez `listTools()` ou `listToolsets()`. #### `tools.onListChanged(serverName: string, handler: () => void)` Définit un gestionnaire de notification appelé lorsque la liste des Tools disponibles change sur un serveur précis, par exemple lorsque le serveur ajoute ou supprime des Tools à l’exécution. ```typescript async onListChanged(serverName: string, handler: () => void): Promise ``` Exemple : ```typescript await mcpClient.tools.onListChanged('myWeatherServer', async () => { console.log('Tool list changed on myWeatherServer.') // You should re-fetch the tools // const tools = await mcpClient.listTools(); }) ``` ### Propriété `progress` L’instance `MCPClient` possède une propriété `progress` permettant de s’abonner aux notifications de progression émises par les serveurs MCP pendant l’exécution des Tools. ```typescript const mcpClient = new MCPClient({ servers: { myServer: { url: new URL('http://localhost:4111/api/mcp/myServer/mcp'), // Enabled by default; set to false to disable enableProgressTracking: true, }, }, }) // Subscribe to progress updates for a specific server await mcpClient.progress.onUpdate('myServer', params => { console.log('📊 Progress:', params.progress, '/', params.total) if (params.message) console.log('Message:', params.message) if (params.progressToken) console.log('Token:', params.progressToken) }) ``` #### `progress.onUpdate(serverName: string, handler)` Enregistre une fonction de gestion qui reçoit les mises à jour de progression du serveur indiqué. ```typescript async onUpdate( serverName: string, handler: (params: { progressToken: string; progress: number; total?: number; message?: string; }) => void, ): Promise ``` Remarques : - Lorsque `enableProgressTracking` vaut true (valeur par défaut), les appels de Tool comprennent un `progressToken` qui permet d’associer les mises à jour à une exécution précise. - Si vous transmettez un `runId` lors de l’exécution d’un Tool, il sera utilisé comme `progressToken`. Pour désactiver le suivi de progression d’un serveur : ```typescript const mcpClient = new MCPClient({ servers: { myServer: { url: new URL('http://localhost:4111/api/mcp/myServer/mcp'), enableProgressTracking: false, }, }, }) ``` ## Sollicitation La sollicitation est une fonctionnalité qui permet aux serveurs MCP de demander des informations structurées aux utilisateurs. Lorsqu’un serveur a besoin de données supplémentaires, il peut envoyer une demande de sollicitation que le client traite en interrogeant l’utilisateur. Un appel de Tool en est un exemple courant. ### Fonctionnement de la sollicitation 1. **Demande du serveur** : un Tool du serveur MCP appelle `server.elicitation.sendRequest()` avec un message et un schéma 2. **Gestionnaire du client** : votre fonction de gestion de la sollicitation est appelée avec la demande 3. **Interaction utilisateur** : votre gestionnaire recueille la saisie de l’utilisateur (via une interface graphique, la CLI, etc.) 4. **Réponse** : votre gestionnaire renvoie la réponse de l’utilisateur (acceptation, refus ou annulation) 5. **Poursuite du Tool** : le Tool du serveur reçoit la réponse et poursuit son exécution ### Configurer la sollicitation Vous devez configurer un gestionnaire de sollicitation avant d’appeler les Tools qui utilisent cette fonctionnalité : ```typescript import { MCPClient } from '@mastra/mcp' const mcpClient = new MCPClient({ servers: { interactiveServer: { url: new URL('http://localhost:3000/mcp'), }, }, }) // Set up elicitation handler mcpClient.elicitation.onRequest('interactiveServer', async request => { // Handle the server's request for user input console.log(`Server needs: ${request.message}`) // Your logic to collect user input const userData = await collectUserInput(request.requestedSchema) return { action: 'accept', content: userData, } }) ``` ### Types de réponse Votre gestionnaire de sollicitation doit renvoyer l’un des trois types de réponse suivants : - **Accepter** : l’utilisateur a fourni des données et confirmé leur envoi ```typescript return { action: 'accept', content: { name: 'John Doe', email: 'john@example.com' }, } ``` - **Refuser** : l’utilisateur a explicitement refusé de fournir les informations ```typescript return { action: 'decline' } ``` - **Annuler** : l’utilisateur a ignoré ou annulé la demande ```typescript return { action: 'cancel' } ``` ### Collecte des saisies fondée sur un schéma Le `requestedSchema` définit la structure des données dont le serveur a besoin : ```typescript await mcpClient.elicitation.onRequest('interactiveServer', async request => { const { properties, required = [] } = request.requestedSchema const content: Record = {} for (const [fieldName, fieldSchema] of Object.entries(properties || {})) { const field = fieldSchema as any const isRequired = required.includes(fieldName) // Collect input based on field type and requirements const value = await promptUser({ name: fieldName, title: field.title, description: field.description, type: field.type, required: isRequired, format: field.format, enum: field.enum, }) if (value !== null) { content[fieldName] = value } } return { action: 'accept', content } }) ``` ### Bonnes pratiques - **Toujours gérer la sollicitation** : configurez votre gestionnaire avant d’appeler des Tools susceptibles d’utiliser la sollicitation - **Valider les saisies** : vérifiez que les champs obligatoires sont renseignés - **Respecter le choix de l’utilisateur** : traitez correctement les réponses de refus et d’annulation - **Clarifier l’interface** : indiquez clairement quelles informations sont demandées et pourquoi - **Sécuriser les demandes** : n’acceptez jamais automatiquement une demande d’informations sensibles ## Authentification OAuth Pour vous connecter à des serveurs MCP qui exigent une authentification OAuth conformément à la [spécification d’authentification MCP](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization), utilisez `MCPOAuthClientProvider` : ```typescript import { MCPClient, MCPOAuthClientProvider } from '@mastra/mcp' // Create an OAuth provider const oauthProvider = new MCPOAuthClientProvider({ redirectUrl: 'http://localhost:3000/oauth/callback', clientMetadata: { redirect_uris: ['http://localhost:3000/oauth/callback'], client_name: 'My MCP Client', grant_types: ['authorization_code', 'refresh_token'], response_types: ['code'], }, onRedirectToAuthorization: url => { // Handle authorization redirect (open browser, redirect response, etc.) console.log(`Please visit: ${url}`) }, }) // Use the provider with MCPClient const client = new MCPClient({ servers: { protectedServer: { url: new URL('https://mcp.example.com/mcp'), authProvider: oauthProvider, }, }, }) ``` Attribuez à chaque serveur sa propre instance `MCPOAuthClientProvider`. Pendant l’autorisation, un Provider conserve l’état de session et les identifiants propres au serveur. Partager une même instance entre plusieurs serveurs permettrait donc à leurs flux de s’écraser mutuellement. Lorsque vous configurez plusieurs serveurs protégés, créez un Provider distinct pour chacun. ### Authentification interactive dans le navigateur Lorsqu’un serveur rejette une connexion parce qu’une autorisation est requise, le client enregistre l’état `'needs-auth'` au lieu d’échouer immédiatement. L’appel de `authenticate()` termine le flux. Il démarre un serveur de rappel à usage unique sur l’URL de redirection en boucle locale du Provider et essaie les ports suivants dans l’ordre si le port est occupé. Le SDK effectue ensuite la découverte et l’enregistrement du client à l’exécution. `onRedirectToAuthorization` reçoit l’URL d’autorisation afin que votre application puisse l’ouvrir dans le navigateur de l’utilisateur. L’échange de jetons se termine lorsque le navigateur renvoie le code d’autorisation : ```typescript import { MCPClient, MCPOAuthClientProvider } from '@mastra/mcp' const oauthProvider = new MCPOAuthClientProvider({ redirectUrl: 'http://127.0.0.1:5533/oauth/callback', clientMetadata: { redirect_uris: ['http://127.0.0.1:5533/oauth/callback'], client_name: 'My MCP Client', grant_types: ['authorization_code', 'refresh_token'], response_types: ['code'], }, onRedirectToAuthorization: url => { // Open the user's browser at the consent page console.log(`Please visit: ${url}`) }, }) const mcp = new MCPClient({ servers: { protectedServer: { url: new URL('https://mcp.example.com/mcp'), authProvider: oauthProvider, }, }, }) try { await mcp.listTools() } catch { if (mcp.getServerAuthState('protectedServer') === 'needs-auth') { await mcp.authenticate('protectedServer') } } ``` Les appels simultanés de `authenticate()` pour un même serveur rejoignent le flux en attente. Les différents serveurs s’authentifient indépendamment. Si des jetons valides sont enregistrés, l’appel se reconnecte sans ouvrir de navigateur. Les hôtes qui pilotent eux-mêmes le flux peuvent récupérer le code d’autorisation avec l’utilitaire exporté `createOAuthCallbackServer`. Celui-ci lie un serveur de bouclage à usage unique, valide le paramètre OAuth `state`, puis renvoie le code. Comme il crée un simple serveur HTTP, il est exclusivement destiné aux redirections locales en boucle. Les applications web qui utilisent une URL de redirection HTTPS doivent héberger leur propre point de terminaison de rappel et piloter directement le Provider au lieu d’utiliser cet utilitaire : ```typescript import { createOAuthCallbackServer, getCallbackUrlCandidates } from '@mastra/mcp' // getCallbackUrlCandidates() lists every URL the helper may bind, so register // all of them as redirect_uris during client registration to cover port fallback. const redirectUris = getCallbackUrlCandidates('http://127.0.0.1:5533/oauth/callback').map(url => url.toString(), ) const server = await createOAuthCallbackServer({ redirectUrl: 'http://127.0.0.1:5533/oauth/callback', state: expectedState, }) // server.url reflects the port actually bound — use it as the redirect_uri. try { const { code } = await server.waitForCode() // Exchange the code here. } finally { await server.close() } ``` ### Provider de jeton rapide Pour effectuer des tests ou lorsque vous disposez déjà d’un jeton d’accès valide : ```typescript import { MCPClient, createSimpleTokenProvider } from '@mastra/mcp' const provider = createSimpleTokenProvider('your-access-token', { redirectUrl: 'http://localhost:3000/callback', clientMetadata: { redirect_uris: ['http://localhost:3000/callback'], client_name: 'Test Client', }, }) const client = new MCPClient({ servers: { testServer: { url: new URL('https://mcp.example.com/mcp'), authProvider: provider, }, }, }) ``` ### Stockage personnalisé des jetons Pour conserver les jetons entre les sessions, implémentez l’interface `OAuthStorage` : ```typescript import { MCPOAuthClientProvider, OAuthStorage } from '@mastra/mcp' class DatabaseOAuthStorage implements OAuthStorage { constructor( private db: Database, private userId: string, ) {} async set(key: string, value: string): Promise { await this.db.query( 'INSERT INTO oauth_tokens (user_id, key, value) VALUES (?, ?, ?) ON CONFLICT DO UPDATE SET value = ?', [this.userId, key, value, value], ) } async get(key: string): Promise { const result = await this.db.query( 'SELECT value FROM oauth_tokens WHERE user_id = ? AND key = ?', [this.userId, key], ) return result?.[0]?.value } async delete(key: string): Promise { await this.db.query('DELETE FROM oauth_tokens WHERE user_id = ? AND key = ?', [ this.userId, key, ]) } } const provider = new MCPOAuthClientProvider({ redirectUrl: 'http://localhost:3000/callback', clientMetadata: {/* ... */}, storage: new DatabaseOAuthStorage(db, 'user-123'), }) ``` ## Exemples ### Configuration statique des Tools Pour les Tools qui utilisent une seule connexion au serveur MCP dans toute votre application, appelez `listTools()` et transmettez les Tools à votre Agent : ```typescript import { MCPClient } from '@mastra/mcp' import { Agent } from '@mastra/core/agent' const mcp = new MCPClient({ servers: { stockPrice: { command: 'npx', args: ['tsx', 'stock-price.ts'], env: { API_KEY: 'your-api-key', }, log: logMessage => { console.log(`[${logMessage.level}] ${logMessage.message}`) }, }, weather: { url: new URL('http://localhost:8080/sse'), }, }, timeout: 30000, // Global 30s timeout }) // Create an agent with access to all tools const agent = new Agent({ id: 'multi-tool-agent', name: 'Multi-tool Agent', instructions: 'You have access to multiple tool servers.', model: 'openai/gpt-5.6-sol', tools: await mcp.listTools(), }) // Example of using resource methods async function checkWeatherResource() { try { const weatherResources = await mcp.resources.list() if (weatherResources.weather && weatherResources.weather.length > 0) { const currentWeatherURI = weatherResources.weather[0].uri const weatherData = await mcp.resources.read('weather', currentWeatherURI) console.log('Weather data:', weatherData.contents[0].text) } } catch (error) { console.error('Error fetching weather resource:', error) } } checkWeatherResource() // Example of using prompt methods async function checkWeatherPrompt() { try { const weatherPrompts = await mcp.prompts.list() if (weatherPrompts.weather && weatherPrompts.weather.length > 0) { const currentWeatherPrompt = weatherPrompts.weather.find(p => p.name === 'current') if (currentWeatherPrompt) { console.log('Weather prompt:', currentWeatherPrompt) } else { console.log('Current weather prompt not found') } } } catch (error) { console.error('Error fetching weather prompt:', error) } } checkWeatherPrompt() ``` ### Toolsets dynamiques Lorsque chaque utilisateur nécessite une nouvelle connexion MCP, utilisez `listToolsets()` et ajoutez les Tools lors de l’appel de stream ou generate : ```typescript import { Agent } from '@mastra/core/agent' import { MCPClient } from '@mastra/mcp' // Create the agent first, without any tools const agent = new Agent({ id: 'multi-tool-agent', name: 'Multi-tool Agent', instructions: 'You help users check stocks and weather.', model: 'openai/gpt-5.6-sol', }) // Later, configure MCP with user-specific settings const mcp = new MCPClient({ servers: { stockPrice: { command: 'npx', args: ['tsx', 'stock-price.ts'], env: { API_KEY: 'user-123-api-key', }, timeout: 20000, // Server-specific timeout }, weather: { url: new URL('http://localhost:8080/sse'), requestInit: { headers: { Authorization: `Bearer user-123-token`, }, }, }, }, }) // Pass all toolsets to stream() or generate() const response = await agent.stream('How is AAPL doing and what is the weather?', { toolsets: await mcp.listToolsets(), }) ``` ## Gestion des instances La classe `MCPClient` intègre un mécanisme de prévention des fuites de mémoire pour gérer plusieurs instances : 1. Créer plusieurs instances avec des configurations identiques sans `id` provoque une erreur afin d’éviter les fuites de mémoire 2. Si plusieurs instances avec des configurations identiques sont nécessaires, fournissez un `id` unique à chacune 3. Appelez `await configuration.disconnect()` avant de recréer une instance avec la même configuration 4. Si une seule instance suffit, placez la configuration dans une portée supérieure afin d’éviter de la recréer Par exemple, si vous tentez de créer plusieurs instances avec la même configuration sans `id` : ```typescript // First instance - OK const mcp1 = new MCPClient({ servers: {/* ... */}, }) // Second instance with same config - Will throw an error const mcp2 = new MCPClient({ servers: {/* ... */}, }) // To fix, either: // 1. Add unique IDs const mcp3 = new MCPClient({ id: 'instance-1', servers: {/* ... */}, }) // 2. Or disconnect before recreating await mcp1.disconnect() const mcp4 = new MCPClient({ servers: {/* ... */}, }) ``` ## Cycle de vie des serveurs MCPClient gère proprement les connexions aux serveurs : 1. Gestion automatique des connexions à plusieurs serveurs 2. Arrêt propre des serveurs pour éviter les messages d’erreur pendant le développement 3. Libération correcte des ressources lors de la déconnexion ## Utiliser un fetch personnalisé pour l’authentification définie à l’exécution Pour les serveurs HTTP, vous pouvez fournir une fonction `fetch` personnalisée afin de gérer une authentification définie à l’exécution ou d’intercepter les requêtes. Elle peut aussi prendre en charge d’autres comportements personnalisés. Cette approche est particulièrement utile lorsque vous devez actualiser les jetons à chaque requête ou transmettre au serveur MCP les identifiants utilisateur issus de la requête entrante. La fonction `fetch` personnalisée reçoit un troisième paramètre facultatif `requestContext`, qui donne accès aux données propres à la requête (par exemple, cookies d’authentification ou jetons bearer) définies par un middleware ou transmises lors de l’exécution d’un Agent ou d’un Tool. Le `requestContext` vaut `null` pendant l’établissement initial de la connexion. Lorsque `fetch` est fourni, `requestInit`, `eventSourceInit` et `authProvider` deviennent facultatifs, car votre fonction fetch personnalisée peut prendre en charge ces aspects. ```typescript const mcpClient = new MCPClient({ servers: { apiServer: { url: new URL('https://api.example.com/mcp'), fetch: async (url, init, requestContext) => { const headers = new Headers(init?.headers) // Forward auth cookie from the incoming request const cookie = requestContext?.get('cookie') if (cookie) { headers.set('cookie', cookie) } return fetch(url, { ...init, headers }) }, }, }, }) // Use with an agent — requestContext is automatically forwarded const agent = new Agent({ id: 'my-agent', name: 'My Agent', instructions: 'You are a helpful assistant.', model: openai('gpt-5.4'), tools: await mcpClient.listTools(), }) await agent.generate('Hello!', { requestContext: myRequestContext, // forwarded to the custom fetch }) ``` ## Gérer les échecs d’authentification dans un fetch personnalisé Un `fetch` personnalisé ne doit pas utiliser `throw` lorsque l’authentification est indisponible. Le transport Streamable HTTP du SDK MCP ouvre en arrière-plan un flux « écouteur autonome » `GET /mcp` de longue durée pour recevoir les notifications envoyées par le serveur. Les erreurs de ce flux font l’objet de nouvelles tentatives avec temporisation exponentielle ; un `fetch` qui lève une exception ou un flux fermé proprement peut entraîner une boucle de reconnexion indéfinie, à raison d’environ une tentative par seconde. Renvoyez plutôt une `Response` synthétique. La [spécification MCP Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports) définit `405 Method Not Allowed` comme le signal renvoyé par un serveur qui ne propose pas le flux GET SSE. Le SDK le traite comme un état final qui arrête proprement l’écouteur. Utilisez ce mécanisme pour désactiver l’écouteur lorsque votre serveur n’envoie pas de notifications. Le modèle suivant attend un jeton d’authentification pour les requêtes POST, l’ajoute aux en-têtes sortants et court-circuite l’écouteur GET avec une réponse 405 synthétique : ```typescript async function waitForToken(timeoutMs = 5000): Promise { // Replace with your token lookup. Return null if no token is available. return getAuthToken({ timeoutMs }) } const mcpClient = new MCPClient({ servers: { apiServer: { url: new URL('https://api.example.com/mcp'), fetch: async (url, init) => { const method = (init?.method || 'GET').toUpperCase() // The SDK opens a background GET stream for server-pushed notifications. // If your server does not use it, short-circuit with 405 to stop reconnect attempts. if (method === 'GET') { return new Response(null, { status: 405, statusText: 'Method Not Allowed' }) } // POST: wait for the token, then forward the request with an Authorization header. const token = await waitForToken() if (!token) { // Forward the request without a token and let the server reject it. // The SDK surfaces non-2xx POST responses as errors to the caller of // tools/list, tools/call, etc., which is the desired behavior here. return fetch(url, init) } const headers = new Headers(init?.headers) headers.set('authorization', `Bearer ${token}`) return fetch(url, { ...init, headers }) }, }, }, }) ``` Ne renvoyez `405` pour l’écouteur GET que si votre serveur n’envoie pas de notifications au client. Si votre serveur utilise le flux GET autonome, ajoutez également le jeton d’authentification aux requêtes `GET` et laissez-les aboutir. ## Utiliser des en-têtes de requête SSE Lorsque vous utilisez l’ancien transport MCP SSE, vous devez configurer à la fois `requestInit` et `eventSourceInit` en raison d’un bug du SDK MCP. Vous pouvez aussi employer une fonction `fetch` personnalisée, qui sera automatiquement utilisée pour les requêtes POST comme pour les connexions SSE : ```ts // Option 1: Using requestInit and eventSourceInit (required for SSE) const sseClient = new MCPClient({ servers: { exampleServer: { url: new URL('https://your-mcp-server.com/sse'), // Note: requestInit alone isn't enough for SSE requestInit: { headers: { Authorization: 'Bearer your-token', }, }, // This is also required for SSE connections with custom headers eventSourceInit: { fetch(input: Request | URL | string, init?: RequestInit) { const headers = new Headers(init?.headers || {}) headers.set('Authorization', 'Bearer your-token') return fetch(input, { ...init, headers, }) }, }, }, }, }) // Option 2: Using custom fetch (simpler, works for both Streamable HTTP and SSE) const sseClientWithFetch = new MCPClient({ servers: { exampleServer: { url: new URL('https://your-mcp-server.com/sse'), fetch: async (url, init) => { const headers = new Headers(init?.headers || {}) headers.set('Authorization', 'Bearer your-token') return fetch(url, { ...init, headers, }) }, }, }, }) ``` ## Informations connexes - Pour créer des serveurs MCP, consultez la [documentation de MCPServer](https://mastra.zisheng.pro/fr/reference/tools/mcp-server). - Pour en savoir plus sur Model Context Protocol, consultez la [documentation de @modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk).