Vista previa alfa de langchain.mcp: adaptador MCP para LangChain

imagem 65

Versión preliminar alfa de langchain.mcp: un adaptador propio que convierte cualquier servidor MCP en herramientas de LangChain listas para pasar directamente a create_agent.

El manejo de las conexiones corre a cargo de FastMCP, así que las capacidades de su cliente están disponibles tal cual, en lugar de reimplementarse detrás de una interfaz más limitada.

pip install "langchain[mcp]==1.4.0a2"

Conexión

MCPAdapter acepta cualquier objetivo que fastmcp.Client admita: el transporte se infiere automáticamente, de modo que hay un único punto de entrada en lugar de uno por protocolo.

from langchain.agents import create_agent
from langchain.mcp import MCPAdapter

async with MCPAdapter("https://example.com/mcp") as adapter:
    agent = create_agent("anthropic:claude-sonnet-5", await adapter.get_tools())
    result = await agent.ainvoke({"messages": [{"role": "user", "content": "..."}]})

Objetivos válidos: una URL, la ruta de un script local (lanzado a través de stdio), un servidor FastMCP en el mismo proceso, una configuración que nombre varios servidores a la vez, o un fastmcp.Client que tú mismo hayas construido.

Las herramientas devueltas por get_tools() conservan el cliente del adaptador, por lo que siguen siendo invocables después de salir del contexto: el bloque async with delimita el descubrimiento, no la vida útil de las herramientas.

Autenticación, caché, tiempos de espera: construye el cliente

MCPAdapter recibe dos argumentos: el objetivo y elicitation. Todo lo demás que FastMCP soporta se configura en un fastmcp.Client que tú construyes y entregas como objetivo. Este es el patrón a seguir siempre que necesites algo más que una conexión básica:

from fastmcp.client import Client
from langchain.mcp import MCPAdapter

client = Client(
    "https://example.com/mcp",
    auth="oauth",       # o una cadena de token portador, o cualquier auth de httpx
    cache=True,         # caché de respuestas opcional
    timeout=30,
)

async with MCPAdapter(client) as adapter:
    tools = await adapter.get_tools()

Autenticación acepta "oauth" para ejecutar el flujo OAuth, una cadena de token para autenticación portador, o una instancia de httpx.Auth para cualquier cosa personalizada; consulta la documentación de autenticación de FastMCP. Los encabezados y la autenticación por servidor también se pueden establecer en una configuración multiserver (más abajo).

Caché es opcional y está desactivada por defecto: cache=True la activa con los valores predeterminados, respetando las sugerencias ttlMs y cacheScope del propio servidor; un CacheConfig permite personalizarla. La caché es por cliente y reside en memoria.

Todo lo demás de fastmcp.Clienttimeout, log_handler, progress_handler, message_handler, roots, sampling_handler — funciona de la misma manera. El adaptador pasa tu cliente sin modificarlo, por lo que el comportamiento de FastMCP no se reimplementa ni se restringe.

Una advertencia: con elicitation="interrupt", el adaptador clona tu cliente para no sobrescribir una devolución de llamada que hayas configurado. La configuración (autenticación, ajustes de caché, manejadores) se transfiere al clon; las entradas en caché no, ya que el clon obtiene su propio almacén.

adapter.client expone el cliente subyacente para prompts, recursos y cualquier otra cosa que el adaptador no envuelva.

Varios servidores

Apunta el adaptador a una configuración y se extiende a todos los servidores a través de una sola conexión, presentando una única lista de herramientas a tu agente.

config = {
    "mcpServers": {
        "weather": {"url": "https://weather.example.com/mcp"},
        "calendar": {
            "url": "https://calendar.example.com/mcp",
            "headers": {"Authorization": "Bearer ..."},
        },
    }
}

async with MCPAdapter(config) as adapter:
    agent = create_agent("anthropic:claude-sonnet-5", await adapter.get_tools())

Con más de un servidor, las herramientas se agrupan por nombre de servidor — weather_get_forecast, calendar_create_event — de modo que las colisiones entre servidores resultan imposibles. Con exactamente un servidor, el adaptador se conecta directamente y los nombres no llevan prefijo. Cada entrada admite sus propios headers, auth, transport y timeout, así servidores con credenciales distintas pueden combinarse en un mismo agente. Un servidor local usa command/args en lugar de url y se lanza a través de stdio. La configuración sigue el esquema JSON MCP de FastMCP, por lo que una configuración que ya uses en otro lugar funciona aquí sin cambios.

Servidores de protocolo antiguo y nuevo, lado a lado

MCP ha pasado del handshake initialize a server/discover, y los servidores en producción se sitúan a ambos lados de esa línea. FastMCP negocia la era en cada conexión, de modo que el adaptador llega a cualquiera de los dos sin que tengas que seleccionar uno:

# servidor de la era handshake sobre SSE
legacy = MCPAdapter("https://legacy.example.com/sse")

# servidor de era moderna sobre HTTP transmitible
modern = MCPAdapter("https://modern.example.com/mcp")

Los adaptadores separados negocian de forma independiente y pueden ejecutarse simultáneamente, cada uno en su propia era. Esto está cubierto por pruebas de integración que levantan un servidor de cada era y llaman a ambos.

La única regla que vale la pena conocer: una configuración multiserver expone una sola era, por lo que el backend más antiguo fija la era para todo el conjunto. Mezclar un servidor de la era handshake en una configuración hace retroceder a los backends modernos a la era handshake — siguen funcionando, pero las características limitadas por era se pierden con ello. Mantén un servidor heredado en su propio adaptador cuando quieras que los demás usen el protocolo moderno:

async with (
    MCPAdapter({"mcpServers": {...modern servers...}}) as modern,
    MCPAdapter("https://legacy.example.com/sse") as legacy,
):
    tools = await modern.get_tools() + await legacy.get_tools()

Resultados

Cada herramienta es asíncrona. Una herramienta MCP que se ejecuta y reporta un fallo devuelve un ToolMessage con status="error" que lleva el texto de error del propio servidor, para que el agente pueda corregirse y reintentar. Los fallos de transporte y el contenido inconvertible generan una excepción; un modelo no puede actuar sobre ellos.

La salida estructurada viaja en el artefacto del mensaje de herramienta:

from langchain.mcp import MCPToolArtifact

artifact: MCPToolArtifact | None = tool_message.artifact  # None cuando no hay contenido estructurado
artifact["structured_content"]

Elicitación: servidores que preguntan a mitad de llamada

Algunas herramientas MCP necesitan datos antes de poder terminar. Si optas por ello, la solicitud aparece como una interrupt() de LangGraph, de modo que la persona que ya está revisando el trabajo del agente también responde al servidor.

adapter = MCPAdapter(target, elicitation="interrupt")

La capacidad es optativa y no viene por defecto porque declararla es una promesa que se hace en la comunicación: un agente sin acceso a un humano no puede cumplirla. Si se deja sin configurar, no se declara nada, y un servidor cuya herramienta requiera una respuesta rechazará la llamada antes que ejecutarla sin ella.

La ejecución se detiene con una carga útil tipada y se reanuda con una respuesta por cada clave de solicitud:

from langgraph.types import Command

result = await agent.ainvoke({"messages": [...]}, config)

[pause] = result["__interrupt__"]
pause.value["type"]       # "mcp_elicitation"
pause.value["tool_name"]  # la herramienta que está esperando
pause.value["requests"]   # cada pregunta, en el orden en que se deben hacer

answer = {"responses": {key: {"action": "accept", "content": {"guests": 4}}}}
result = await agent.ainvoke(Command(resume=answer), config)

Las solicitudes se concretan con mode: una solicitud de tipo "form" incluye un requested_schema que la respuesta debe satisfacer; una de tipo "url" lleva una dirección para que el humano la visite. Las respuestas se concretan con action: "accept" (con content), "decline" (saltar la pregunta, dejar que la llamada continúe) o "cancel" (abandonar la llamada a la herramienta). Requiere un checkpointer, como cualquier interrupción.

El muestreo y los roots no se responden a través de interrupciones; déjalos en manos de los manejadores propios de tu cliente.

Los tipos para los manejadores — MCPElicitationInterrupt, MCPElicitationRequest, MCPElicitationResponse, MCPElicitationResume y el discriminador ELICITATION_INTERRUPT_TYPE — viven en langchain.mcp.elicitation.

Más información


Esto es una versión alfa: la interfaz puede cambiar antes de que la 1.4.0 sea definitiva. Precisamente buscamos opiniones sobre la forma de la API.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *