Skip to content

scraping_browser_* tools in Bright Data mcp hang for 5 minutes in Docker/Render but return instantly locally #11

Closed

Description

@olgazju

I'm experiencing a reproducible issue when using your MCP browser tools with the pydantic-ai framework.

Specifically, when using any scraping_browser_* tool (like scraping_browser_navigate), the call to agent.run() returns a model output quickly, but the underlying run_mcp_servers() context (provided by pydantic-ai) hangs for exactly 5 minutes before exiting — only in Docker or on Render.com. Locally, everything works instantly.

Setup:

  • I’m using pydantic-ai with the MCP server launched via MCPServerStdio.
  • My MCP command runs your brightdata/mcp package.
  • The app is a minimal FastAPI wrapper with a single endpoint that sends a basic prompt to navigate to a static article.
  • I’ve included a Dockerfile and docker-compose setup that reproduces the issue.
  • The prompt is minimal: navigate to a given article URL and return immediately - previously I had more complecated prompt to scrap last 5 reviews from glassdoor company page but then I found then 1 browser tool command is enough to reproduce this issue.
from fastapi import FastAPI, HTTPException
from pydantic_ai.mcp import MCPServerStdio
from pydantic_ai import Agent
from dotenv import load_dotenv
import os
from pydantic_ai.settings import ModelSettings

load_dotenv()

app = FastAPI()
model = "openai:gpt-4.1-mini"

glassdoor_server = MCPServerStdio(
    command="npx",
    args=["@brightdata/mcp"],
    env={
        "API_TOKEN": os.getenv("BRIGHTDATA_API_TOKEN"),
        "WEB_UNLOCKER_ZONE": os.getenv("BRIGHTDATA_GLASSDOOR_UNLOCKER_ZONE"),
        "BROWSER_AUTH": os.getenv("BROWSER_AUTH_GLASSDOOR", ""),
    },
    log_level="debug"
)

system_prompt = (
    "You are a tool-using agent connected to Bright Data's MCP server."
)

glassdoor_agent = Agent(
    model,
    mcp_servers=[glassdoor_server],
    retries=1,
    system_prompt=system_prompt,
    model_settings=ModelSettings(
        request_timeout=120,
        max_tokens=5000
    )
)

@app.get("/glassdoor")
async def analyze_company(name: str):
    prompt = (
        "Using scraping_browser_navigate - navigate to this article "
        "https://dev.to/olgabraginskaya/when-small-parquet-files-become-a-big-problem-and-how-i-ended-up-writing-a-compactor-in-pyarrow-20b6 "
        "and return null immediately without timeout, don't hung out"
    )
    try:
        async with glassdoor_agent.run_mcp_servers():
            result = await glassdoor_agent.run(prompt)
            print(result)
            return {"result": str(result.output)}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

What works:

  • The model returns a correct response like:

    I have navigated to the article "When Small Parquet Files Become a Big Problem (and How I Ended Up Writing a Compactor in PyArrow)" on dev.to as requested and returned immediately without timeout. 
    
    
  • Non-browser tools like search_engine work without issue.

  • Running the exact same code locally on macOS (Apple M2 Max, 32 GB RAM, macOS 14.5) works perfectly — response is instant and clean.

What fails:

  • In Docker (or Render), run_mcp_servers() always blocks for 5 minutes, despite the model returning.
  • If I don’t install Playwright (RUN npx -y playwright@^1.51.1 install --with-deps), the container hangs indefinitely instead.

To debug this, I cloned your MCP repo and added a log inside the get_page() method in Browser_session.js:

session.browserClosed = false;
session.page.once('close', () => {
    console.error(`[Browser_session] Page closed for domain ${domain}`);
    session.page = null;
});

Then I built the Docker image and called my endpoint:

http://0.0.0.0:8000/glassdoor?name=Google

Here's what I see in logs (from Docker):

2025-05-14 17:21:24 Logfire project URL: https://logfire-us.pydantic.dev/olgazjuzju/starter-project
2025-05-14 17:21:31 14:21:31.800 MCP request: initialize
2025-05-14 17:21:32 Checking for required zones...
2025-05-14 17:21:33 Required zone "reputato_glassdoor_unlocker" already exists
2025-05-14 17:21:33 Starting server...
2025-05-14 17:21:33 14:21:33.287 MCP request: logging/setLevel
2025-05-14 17:21:33 14:21:33.291 glassdoor_agent run
2025-05-14 17:21:33 14:21:33.293   MCP request: tools/list
2025-05-14 17:21:33 14:21:33.315   chat gpt-4.1-mini
2025-05-14 17:21:33 [FastMCP error] received error listing roots.
2025-05-14 17:21:33 McpError: MCP error -32600: List roots not supported
2025-05-14 17:21:35 14:21:35.374   MCP request: tools/list
2025-05-14 17:21:35 14:21:35.384   running 1 tool
2025-05-14 17:21:35 14:21:35.384     running tool: scraping_browser_navigate
2025-05-14 17:21:35 14:21:35.385       MCP request: tools/call scraping_browser_navigate
2025-05-14 17:21:40 14:21:40.838   MCP request: tools/list
2025-05-14 17:21:40 14:21:40.847   chat gpt-4.1-mini
2025-05-14 17:21:43 AgentRunResult(output='I have navigated to the article "When Small Parquet Files Become a Big Problem (and How I Ended Up Writing a Compactor in PyArrow)" on dev.to as requested and returned immediately without timeout. How would you like to proceed?')
2025-05-14 17:26:41 [Browser_session] Page closed for domain dev.to
2025-05-14 17:26:41 INFO:     192.168.65.1:36346 - "GET /glassdoor?name=Firebolt HTTP/1.1" 200 OK

So:

  • The model returns a valid answer at 17:21:43
  • But run_mcp_servers() exits only at 17:26:41, right when the Bright Data browser auto-closes the page

When I run the exact same setup locally, I never see [Browser_session] Page closed for domain dev.to — the session ends immediately.

I also tried this with:

  • Anthropic model instead of OpenAI → same behavior
  • Other MCP server types → no problem at all

Archive.zip

Activity

  1. meirk-brd commented on May 19, 2025

    @meirk-brd
    Contributor

    Hi,

    Thank you for reaching out!

    Ive looked into this issue and I have a direction on why this is happening on Docker and not on Mac, to clarify, the issue here is not related to our MCP, we have many different use cases on Docker, non of them faced this issue.

    After diving a bit into Pydantic AI library, there is what I observed:

    run_mcp_servers() leaves its async with, so AsyncExitStack.aclose() awaits each server’s aexit

    In Docker the child gets SIGTERM (graceful), stays alive while the WebSocket is open and After 300 s the watchdog fires → tab closes → Node exits → aclose() unblocks.

    The difference is that locally, the MCP child is force-killed on shutdown (macOS sends SIGKILL), so you never notice the timer.

    The fix: launch the MCP server once at app start-up and keep it alive across requests; close (or time-out) the browser only after your final multi-step tool call.

    Here is an example, using your code snippet and adding the relevant context manager to handle this issue:

    
    from fastapi import FastAPI
    from pydantic_ai.mcp import MCPServerStdio
    from pydantic_ai import Agent
    from dotenv import load_dotenv
    import os
    from pydantic_ai.settings import ModelSettings
    from contextlib import asynccontextmanager
    
    load_dotenv()
    
    app = FastAPI()
    model = "openai:gpt-4.1-mini"
    
    glassdoor_server = MCPServerStdio(
        command="npx",
        args=["@brightdata/mcp"],
        env={
            "API_TOKEN": os.getenv("BRIGHTDATA_API_TOKEN"),
            "WEB_UNLOCKER_ZONE": os.getenv("BRIGHTDATA_GLASSDOOR_UNLOCKER_ZONE"),
            "BROWSER_AUTH": os.getenv("BROWSER_AUTH_GLASSDOOR", ""),
        },
        log_level="debug"
    )
    
    system_prompt = (
        "You are a tool-using agent connected to Bright Data's MCP server."
    )
    
    glassdoor_agent = Agent(
        model,
        mcp_servers=[glassdoor_server],
        retries=1,
        system_prompt=system_prompt,
        model_settings=ModelSettings(
            request_timeout=120,
            max_tokens=5000
        )
    )
    
    @asynccontextmanager
    async def lifespan(app: FastAPI):
        servers_ctx = glassdoor_agent.run_mcp_servers()
        await servers_ctx.__aenter__()          # startup: launch MCP once
        try:
            yield                                # << app serves requests >>
        finally:
            await servers_ctx.__aexit__(None, None, None)   # shutdown
    
    app = FastAPI(lifespan=lifespan)             # FastAPI handles cleanup
    
    
    @app.get("/glassdoor")
    async def analyze_company(name: str):
        prompt = (
            "Using scraping_browser_navigate - navigate to this article "
            "https://dev.to/olgabraginskaya/when-small-parquet-files-become-a-big-problem-and-how-i-ended-up-writing-a-compactor-in-pyarrow-20b6 "
            "and return null immediately without timeout, don't hung out"
        )
    
        result = await glassdoor_agent.run(prompt)
        print(result)
        return {"result": str(result.output)}
    
    

    Let me know if that works for you, or if not - if you are seeing any issues with the MCP implementation that can be solved to make it work, feel free to contribute if needed!

  2. olgazju commented on May 21, 2025

    @olgazju
    Author

    Thank you so much for the detailed explanation and the fix - I’m genuinely impressed by the level of involvement and how quickly you dove into the root cause.

    I’ve tested the solution in Docker and so far it seems to be working well! I really appreciate the support and if I run into anything else or find something useful to contribute back, I definitely will. Thanks again!

  3. meirk-brd commented on May 22, 2025

    @meirk-brd
    Contributor

    Glad I could help-thanks for confirming!

    I’ll go ahead and close this issue now. If you need anything else, don’t hesitate to reach out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions