For AI agents: a documentation index is available at /llms.txt
Skip to main content

Connecting LlamaIndex agents to Browserless

LlamaIndex is a Python framework for building agents and retrieval pipelines over your data. This guide gives a LlamaIndex FunctionAgent web access through Browserless, first with the hosted Browserless MCP server and then with LlamaIndex's Playwright tool spec for multi-step page interaction.

Prerequisites
  • A Browserless API token from your account dashboard, set as BROWSERLESS_TOKEN
  • Python 3.10 or higher
  • An OpenAI API key, set as OPENAI_API_KEY. Any LlamaIndex LLM that supports function calling works
  • Familiarity with LlamaIndex agents

Which approach to use​

ApproachUse it whenWhat the agent gets
Browserless MCP serverThe agent needs page content, search, site maps, screenshots, or PDFsBrowserless MCP tools through McpToolSpec, with no browser code to write
Playwright tool specThe agent needs to click, fill forms, and move between pages in one sessionLlamaIndex's PlaywrightToolSpec tools, running on a Browserless session via connect_over_cdp

Start with MCP. Switch to the Playwright tool spec when the agent has to interact with a page rather than just read it.

Connect LlamaIndex to the Browserless MCP server​

  1. Install the packages

    python -m venv .venv
    source .venv/bin/activate # On Windows: .venv\Scripts\activate
    pip install llama-index-core llama-index-tools-mcp llama-index-llms-openai
  2. Set your keys

    export BROWSERLESS_TOKEN=YOUR_API_TOKEN_HERE
    export OPENAI_API_KEY=your-openai-api-key
  3. Create the agent

    Create agent.py. BasicMCPClient connects to the hosted server over Streamable HTTP with your token in an Authorization header, and McpToolSpec turns the server's tools into LlamaIndex tools:

    import asyncio
    import os

    from llama_index.core.agent.workflow import FunctionAgent
    from llama_index.llms.openai import OpenAI
    from llama_index.tools.mcp import BasicMCPClient, McpToolSpec

    TOKEN = os.environ["BROWSERLESS_TOKEN"]


    class BrowserlessToolSpec(McpToolSpec):
    """McpToolSpec that skips underscore-prefixed schema fields.

    Browserless tools accept an optional `_prompt` field. Pydantic rejects field
    names with a leading underscore, so drop them before building each tool's schema.
    """

    def create_model_from_json_schema(self, schema, model_name="DynamicModel"):
    properties = {
    name: spec
    for name, spec in schema.get("properties", {}).items()
    if not name.startswith("_")
    }
    return super().create_model_from_json_schema(
    {**schema, "properties": properties}, model_name
    )


    async def main():
    client = BasicMCPClient(
    "https://mcp.browserless.io/mcp",
    headers={"Authorization": f"Bearer {TOKEN}"},
    # Scrapes can outlast the 30-second default, so allow 5 minutes.
    timeout=300,
    )
    tool_spec = BrowserlessToolSpec(
    client=client,
    # Keep the tool list short so the model picks the right tool more often.
    allowed_tools=["browserless_smartscraper", "browserless_search", "browserless_map"],
    )
    tools = await tool_spec.to_tool_list_async()

    agent = FunctionAgent(
    tools=tools,
    llm=OpenAI(model="gpt-5-mini"),
    system_prompt=(
    "You answer questions about the web. Use the Browserless tools to "
    "search and scrape pages, and cite the URLs you used."
    ),
    )
    response = await agent.run(
    "Scrape https://news.ycombinator.com and list the top 3 stories."
    )
    print(str(response))


    if __name__ == "__main__":
    asyncio.run(main())

    Without the BrowserlessToolSpec subclass, to_tool_list_async() fails with Fields must not use names with leading underscores, because every Browserless MCP tool declares the optional _prompt field. The subclass only removes that field from the schema the model sees. Required fields such as url are unchanged.

  4. Run the agent

    python agent.py

    Response:

    Here are the top 3 stories on Hacker News right now:

    1. <story title> (https://...)
    2. <story title> (https://...)
    3. <story title> (https://...)

BasicMCPClient opens a new MCP session for each tool call. That's a good fit for the stateless REST API tools. For multi-step work in one browser, use the stateful browserless_agent tool, which keeps a session alive across calls when the model passes back its sessionId, or use the Playwright tool spec below.

Drive a Browserless session with PlaywrightToolSpec​

PlaywrightToolSpec from llama-index-tools-playwright gives the agent eight tools that act on one shared browser: navigate_to, navigate_back, click, fill, get_current_page, extract_text, extract_hyperlinks, and get_elements. Pass it a browser connected to Browserless instead of a local one, and the whole agent run happens in a single cloud session.

pip install llama-index-core llama-index-tools-playwright llama-index-llms-openai

You don't need playwright install, since the browser runs on Browserless.

import asyncio
import os

from llama_index.core.agent.workflow import FunctionAgent
from llama_index.llms.openai import OpenAI
from llama_index.tools.playwright import PlaywrightToolSpec
from playwright.async_api import async_playwright

TOKEN = os.environ["BROWSERLESS_TOKEN"]
BROWSERLESS_URL = f"wss://production-sfo.browserless.io?token={TOKEN}"


async def main():
async with async_playwright() as p:
# connect_over_cdp attaches to the remote browser instead of launching a local one.
browser = await p.chromium.connect_over_cdp(BROWSERLESS_URL)
try:
tools = PlaywrightToolSpec(async_browser=browser).to_tool_list()
agent = FunctionAgent(
tools=tools,
llm=OpenAI(model="gpt-5-mini"),
system_prompt="Use the browser tools to navigate pages and read their content.",
)
response = await agent.run(
"Go to https://news.ycombinator.com, open the comments for the top story, "
"and summarize the discussion in three bullet points."
)
print(str(response))
finally:
# Close in finally so a failed run doesn't leave the session running.
await browser.close()


if __name__ == "__main__":
asyncio.run(main())

Response:

The top story is "<story title>". The discussion covers:

- <point 1>
- <point 2>
- <point 3>

The tools reuse the browser's current page between calls, so navigate_to, click, and extract_text all operate on the same tab. The Browserless session stays open until browser.close() runs or the session timeout is reached.

Configure the browser session​

For the Playwright tool spec, set every Browserless session option on the connect_over_cdp URL. Routes such as /stealth go in the path, and options such as proxies and timeouts go in the query string.

Stealth​

Use the stealth route for sites with bot detection:

BROWSERLESS_URL = f"wss://production-sfo.browserless.io/stealth?token={TOKEN}"

Residential proxies​

Route traffic through residential IPs for geo-targeted pages or sites that rate-limit datacenter IPs:

BROWSERLESS_URL = f"wss://production-sfo.browserless.io?token={TOKEN}&proxy=residential&proxyCountry=us"

Regional and self-hosted endpoints​

DeploymentEndpoint
US West (San Francisco)wss://production-sfo.browserless.io?token={TOKEN}
Europe (London)wss://production-lon.browserless.io?token={TOKEN}
Europe (Amsterdam)wss://production-ams.browserless.io?token={TOKEN}
Self-hosted Dockerws://localhost:3000?token={TOKEN}

For the MCP tools, pick the region on the MCP server instead. See regional endpoints.

FAQ & Troubleshooting​

to_tool_list_async() fails with Fields must not use names with leading underscores

You're using McpToolSpec directly. Browserless MCP tools declare an optional _prompt field, and Pydantic can't build a model with that field name. Use the BrowserlessToolSpec subclass from the MCP example, which drops underscore-prefixed fields before building each schema.

Tool calls return Server error 401: Invalid API key

The token isn't reaching Browserless. Listing tools doesn't need a valid token, so the agent starts fine and only fails when it calls a tool. Check that BROWSERLESS_TOKEN is set in the shell that runs the script and that the header value reads Bearer <token> with a space.

MCP tool calls time out

BasicMCPClient waits 30 seconds per request by default. Raise timeout (in seconds) as in the example, or ask the agent to work on fewer pages. browserless_crawl is in beta and only available on Cloud plans, so keep crawls small while you test.

The click tool times out on elements that exist

PlaywrightToolSpec waits 1 second for clicks by default. Pass a longer playwright_timeout in milliseconds, for example PlaywrightToolSpec(async_browser=browser, playwright_timeout=10_000), for pages that render slowly.

The page loads but the content is empty or blocked

Switch to the stealth route and add a residential proxy. If the site still blocks you, use the browserless_smartscraper MCP tool, which cascades through HTTP fetch, proxies, a headless browser, and CAPTCHA solving on its own.

Next steps​

Was this page helpful?