Skip to content

datarobot_genai.langgraph.mcp

mcp

mcp_tools_context async

mcp_tools_context(mcp_config: MCPConfig) -> AsyncGenerator[list[BaseTool], None]

Yield a list of LangChain BaseTool instances loaded via MCP.

If no configuration or loading fails, yields an empty list without raising.

Parameters

authorization_context : dict[str, Any] | None Authorization context to use for MCP connections forwarded_headers : dict[str, str] | None Forwarded headers, e.g. x-datarobot-api-key to use for MCP authentication

Source code in datarobot_genai/langgraph/mcp.py
@asynccontextmanager
async def mcp_tools_context(
    mcp_config: MCPConfig,
) -> AsyncGenerator[list[BaseTool], None]:
    """Yield a list of LangChain BaseTool instances loaded via MCP.

    If no configuration or loading fails, yields an empty list without raising.

    Parameters
    ----------
    authorization_context : dict[str, Any] | None
        Authorization context to use for MCP connections
    forwarded_headers : dict[str, str] | None
        Forwarded headers, e.g. x-datarobot-api-key to use for MCP authentication
    """
    server_config = mcp_config.server_config

    if not server_config:
        logger.info("No MCP server configured, using empty tools list")
        yield []
        return

    # Prevent mutation of the original server_config
    server_config = copy.deepcopy(server_config)

    url = server_config["url"]
    logger.info("Connecting to MCP server: %s", url)

    # Pop transport from server_config to avoid passing it twice
    # Use .pop() with default to never error
    transport = server_config.pop("transport", "streamable-http")

    if transport in ["streamable-http", "streamable_http"]:
        connection = StreamableHttpConnection(transport="streamable_http", **server_config)
    elif transport == "sse":
        connection = SSEConnection(transport="sse", **server_config)
    else:
        raise RuntimeError("Unsupported MCP transport specified.")

    # Graceful fallback: if we can't connect to the MCP server, yield empty tools
    # instead of crashing.
    #
    # The `connected` flag distinguishes two cases for the except clause:
    #   - Exception raised during setup (before yield): log a warning and yield [].
    #   - Exception thrown back in from the consumer (after yield, via athrow()): re-raise.
    # Without this guard, a consumer exception of a caught type would hit `yield []` as a
    # second yield, causing `RuntimeError: generator didn't stop after athrow()`.
    connected = False
    try:
        async with create_session(connection=connection) as session:
            raw_tools = await load_mcp_tools(session=session)
            tools = [_wrap_mcp_tool_for_langgraph(t) for t in raw_tools]
            logger.info("Successfully loaded %d MCP tools", len(tools))
            connected = True
            yield tools
    except (ConnectionError, OSError, TimeoutError, ExceptionGroup) as exc:
        if connected:
            raise
        logger.warning(
            "Failed to connect to MCP server at %s: %s. Continuing without MCP tools.",
            url,
            exc,
        )
        yield []