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.
ConstructeurLien direct vers Constructeur
Crée une instance de la classe MCPClient.
constructor({
id?: string;
servers: Record<string, MastraMCPServerDefinition>;
timeout?: number;
}: MCPClientOptions)
MCPClientOptionsLien direct vers MCPClientOptions
id?:
servers:
timeout?:
MastraMCPServerDefinitionLien direct vers 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
commandest fourni, le transport Stdio est utilisé. - Si
urlest 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?:
args?:
env?:
inheritDefaultEnv?:
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.url?:
requestInit?:
eventSourceInit?:
fetch?:
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?:
"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?:
timeout?:
capabilities?:
authProvider?:
enableServerLogs?:
forwardInstructions?:
instructionsMaxLength?:
requireToolApproval?:
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 ToolsLien direct vers 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 existant.
Exiger une approbation pour tous les ToolsLien direct vers Exiger une approbation pour tous les Tools
Définissez requireToolApproval sur true pour exiger une approbation pour chaque Tool du serveur :
const mcp = new MCPClient({
servers: {
github: {
url: new URL('http://localhost:3000/mcp'),
requireToolApproval: true,
},
},
})
Approbation dynamique avec une fonctionLien direct vers 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) :
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 confianceLien direct vers Utiliser les annotations de Tool d’un serveur de confiance
Si vous faites confiance au serveur MCP, vous pouvez utiliser ses annotations de Tool (readOnlyHint, destructiveHint, idempotentHint, openWorldHint, title) pour orienter les décisions d’approbation :
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 serveurLien direct vers 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.
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(), 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
forwardInstructionsque pour les serveurs auxquels vous faites confiance et examinez de préférence les instructions avecgetServerInstructions()avant de transmettre celles de serveurs tiers.
SécuritéLien direct vers Sécurité
Environnement des sous-processus pour les serveurs StdioLien direct vers 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 :
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 allowedHostsLien direct vers restricting-outbound-hosts-with-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 :
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
Authorizationn’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
fetchpersonnalisé (ou uneventSourceInit.fetchpersonnalisé), l’URL initiale est toujours vérifiée avant la requête, mais les étapes de redirection sont validées a posteriori à l’aide deresponse.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é. UneResponseconstruite manuellement avec unresponse.urlvide 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 fiablesLien direct vers 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 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éthodesLien direct vers Méthodes
listTools()Lien direct vers 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.
new Agent({ id: 'agent', tools: await mcp.listTools() })
listToolsWithErrors()Lien direct vers 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.
const { tools, errors } = await mcp.listToolsWithErrors()
new Agent({ id: 'agent', tools })
console.log(errors)
listToolsets()Lien direct vers 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.
const res = await agent.stream(prompt, {
toolsets: await mcp.listToolsets(),
})
getServerInstructions()Lien direct vers 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.
getServerInstructions(): Record<string, string | undefined>
Exemple :
await mcp.listTools()
const instructionsByServer = mcp.getServerInstructions()
console.log(instructionsByServer.db)
authenticate()Lien direct vers 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.
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.
async authenticate(serverName: string, options?: { timeoutMs?: number }): Promise<void>
getServerAuthState()Lien direct vers 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.
getServerAuthState(serverName: string): 'needs-auth' | 'authorized' | undefined
cancelAuthentication()Lien direct vers 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.
async cancelAuthentication(serverName: string): Promise<boolean>
disconnect()Lien direct vers disconnect
Se déconnecte de tous les serveurs MCP et libère les ressources.
async disconnect(): Promise<void>
toMCPServerProxies()Lien direct vers 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.
async toMCPServerProxies(): Promise<Record<string, MCPClientServerProxy>>
Décomposez le résultat dans la configuration mcpServers de Mastra :
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é resourcesLien direct vers resources-property
L’instance MCPClient possède une propriété resources qui donne accès aux opérations liées aux ressources.
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()Lien direct vers resourceslist
Récupère toutes les ressources disponibles sur tous les serveurs MCP connectés, regroupées par nom de serveur.
async list(): Promise<Record<string, Resource[]>>
Exemple :
const resourcesByServer = await mcpClient.resources.list()
for (const serverName in resourcesByServer) {
console.log(`Resources from ${serverName}:`, resourcesByServer[serverName])
}
resources.templates()Lien direct vers resourcestemplates
Récupère tous les modèles de ressource disponibles sur tous les serveurs MCP connectés, regroupés par nom de serveur.
async templates(): Promise<Record<string, ResourceTemplate[]>>
Exemple :
const templatesByServer = await mcpClient.resources.templates()
for (const serverName in templatesByServer) {
console.log(`Templates from ${serverName}:`, templatesByServer[serverName])
}
resources.read(serverName: string, uri: string)Lien direct vers resourcesreadservername-string-uri-string
Lit le contenu d’une ressource précise sur un serveur.
async read(serverName: string, uri: string): Promise<ReadResourceResult>
serverName: identifiant du serveur (clé utilisée dans l’optionserversdu constructeur).uri: URI de la ressource à lire.
Exemple :
const content = await mcpClient.resources.read('myWeatherServer', 'weather://current')
console.log('Current weather:', content.contents[0].text)
resources.subscribe(serverName: string, uri: string)Lien direct vers resourcessubscribeservername-string-uri-string
S’abonne aux mises à jour d’une ressource précise sur un serveur.
async subscribe(serverName: string, uri: string): Promise<object>
Exemple :
await mcpClient.resources.subscribe('myWeatherServer', 'weather://current')
resources.unsubscribe(serverName: string, uri: string)Lien direct vers resourcesunsubscribeservername-string-uri-string
Se désabonne des mises à jour d’une ressource précise sur un serveur.
async unsubscribe(serverName: string, uri: string): Promise<object>
Exemple :
await mcpClient.resources.unsubscribe('myWeatherServer', 'weather://current')
resources.onUpdated(serverName: string, handler: (params: { uri: string }) => void)Lien direct vers resourcesonupdatedservername-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.
async onUpdated(serverName: string, handler: (params: { uri: string }) => void): Promise<void>
Exemple :
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)Lien direct vers resourcesonlistchangedservername-string-handler---void
Définit un gestionnaire de notification appelé lorsque la liste des ressources disponibles change sur un serveur précis.
async onListChanged(serverName: string, handler: () => void): Promise<void>
Exemple :
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é elicitationLien direct vers elicitation-property
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.
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)Lien direct vers elicitationonrequestservername-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 ElicitationHandlerLien direct vers elicitationhandler-function
La fonction de gestion reçoit un objet de requête comprenant :
message: message lisible décrivant les informations nécessairesrequestedSchema: 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 :
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 :
import { MCPClient } from '@mastra/mcp'
import { createInterface } from 'readline'
const readline = createInterface({
input: process.stdin,
output: process.stdout,
})
function askQuestion(question: string): Promise<string> {
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<string, any> = {}
// 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é promptsLien direct vers prompts-property
L’instance MCPClient possède une propriété prompts qui donne accès aux opérations liées aux prompts.
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()Lien direct vers promptslist
Récupère tous les prompts disponibles sur tous les serveurs MCP connectés, regroupés par nom de serveur.
async list(): Promise<Record<string, Prompt[]>>
Exemple :
const promptsByServer = await mcpClient.prompts.list()
for (const serverName in promptsByServer) {
console.log(`Prompts from ${serverName}:`, promptsByServer[serverName])
}
prompts.get({ serverName, name, args?, version? })Lien direct vers promptsget-servername-name-args-version-
Récupère un prompt précis et ses messages depuis un serveur.
async get({
serverName,
name,
args?,
version?,
}: {
serverName: string;
name: string;
args?: Record<string, any>;
version?: string;
}): Promise<{ prompt: Prompt; messages: PromptMessage[] }>
Exemple :
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)Lien direct vers promptsonlistchangedservername-string-handler---void
Définit un gestionnaire de notification appelé lorsque la liste des prompts disponibles change sur un serveur précis.
async onListChanged(serverName: string, handler: () => void): Promise<void>
Exemple :
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é toolsLien direct vers tools-property
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)Lien direct vers toolsonlistchangedservername-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.
async onListChanged(serverName: string, handler: () => void): Promise<void>
Exemple :
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é progressLien direct vers progress-property
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.
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)Lien direct vers progressonupdateservername-string-handler
Enregistre une fonction de gestion qui reçoit les mises à jour de progression du serveur indiqué.
async onUpdate(
serverName: string,
handler: (params: {
progressToken: string;
progress: number;
total?: number;
message?: string;
}) => void,
): Promise<void>
Remarques :
- Lorsque
enableProgressTrackingvaut true (valeur par défaut), les appels de Tool comprennent unprogressTokenqui permet d’associer les mises à jour à une exécution précise. - Si vous transmettez un
runIdlors de l’exécution d’un Tool, il sera utilisé commeprogressToken.
Pour désactiver le suivi de progression d’un serveur :
const mcpClient = new MCPClient({
servers: {
myServer: {
url: new URL('http://localhost:4111/api/mcp/myServer/mcp'),
enableProgressTracking: false,
},
},
})
SollicitationLien direct vers 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 sollicitationLien direct vers Fonctionnement de la sollicitation
- Demande du serveur : un Tool du serveur MCP appelle
server.elicitation.sendRequest()avec un message et un schéma - Gestionnaire du client : votre fonction de gestion de la sollicitation est appelée avec la demande
- Interaction utilisateur : votre gestionnaire recueille la saisie de l’utilisateur (via une interface graphique, la CLI, etc.)
- Réponse : votre gestionnaire renvoie la réponse de l’utilisateur (acceptation, refus ou annulation)
- Poursuite du Tool : le Tool du serveur reçoit la réponse et poursuit son exécution
Configurer la sollicitationLien direct vers Configurer la sollicitation
Vous devez configurer un gestionnaire de sollicitation avant d’appeler les Tools qui utilisent cette fonctionnalité :
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éponseLien direct vers 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
return {action: 'accept',content: { name: 'John Doe', email: 'john@example.com' },} -
Refuser : l’utilisateur a explicitement refusé de fournir les informations
return { action: 'decline' } -
Annuler : l’utilisateur a ignoré ou annulé la demande
return { action: 'cancel' }
Collecte des saisies fondée sur un schémaLien direct vers Collecte des saisies fondée sur un schéma
Le requestedSchema définit la structure des données dont le serveur a besoin :
await mcpClient.elicitation.onRequest('interactiveServer', async request => {
const { properties, required = [] } = request.requestedSchema
const content: Record<string, any> = {}
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 pratiquesLien direct vers 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 OAuthLien direct vers Authentification OAuth
Pour vous connecter à des serveurs MCP qui exigent une authentification OAuth conformément à la spécification d’authentification MCP, utilisez MCPOAuthClientProvider :
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 navigateurLien direct vers 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 :
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 :
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 rapideLien direct vers Provider de jeton rapide
Pour effectuer des tests ou lorsque vous disposez déjà d’un jeton d’accès valide :
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 jetonsLien direct vers Stockage personnalisé des jetons
Pour conserver les jetons entre les sessions, implémentez l’interface OAuthStorage :
import { MCPOAuthClientProvider, OAuthStorage } from '@mastra/mcp'
class DatabaseOAuthStorage implements OAuthStorage {
constructor(
private db: Database,
private userId: string,
) {}
async set(key: string, value: string): Promise<void> {
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<string | undefined> {
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<void> {
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'),
})
ExemplesLien direct vers Exemples
Configuration statique des ToolsLien direct vers 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 :
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 dynamiquesLien direct vers 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 :
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 instancesLien direct vers Gestion des instances
La classe MCPClient intègre un mécanisme de prévention des fuites de mémoire pour gérer plusieurs instances :
- Créer plusieurs instances avec des configurations identiques sans
idprovoque une erreur afin d’éviter les fuites de mémoire - Si plusieurs instances avec des configurations identiques sont nécessaires, fournissez un
idunique à chacune - Appelez
await configuration.disconnect()avant de recréer une instance avec la même configuration - 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 :
// 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 serveursLien direct vers Cycle de vie des serveurs
MCPClient gère proprement les connexions aux serveurs :
- Gestion automatique des connexions à plusieurs serveurs
- Arrêt propre des serveurs pour éviter les messages d’erreur pendant le développement
- Libération correcte des ressources lors de la déconnexion
Utiliser un fetch personnalisé pour l’authentification définie à l’exécutionLien direct vers 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.
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éLien direct vers 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 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 :
async function waitForToken(timeoutMs = 5000): Promise<string | null> {
// 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 SSELien direct vers 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 :
// 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 connexesLien direct vers Informations connexes
- Pour créer des serveurs MCP, consultez la documentation de MCPServer.
- Pour en savoir plus sur Model Context Protocol, consultez la documentation de @modelcontextprotocol/sdk.