Skip to content

Investigate: reuse a single @playwright/mcp connection per browser context instead of one per step #12

@gurvinder-dhillon

Description

@gurvinder-dhillon

Background

Currently, every runAgent() call (i.e. every BDD step) creates a brand new set of resources:

  1. createConnection() — a new in-process @playwright/mcp server
  2. A new TCP server (net.createServer) on a random ephemeral port
  3. A new temp directory /tmp/openqa-mcp-<uuid>/ with a unique .mcp.json and bridge script

These are all torn down when the subprocess exits (in the child.on('close') handler).

The UUID temp dir exists purely to give the Claude Code CLI a path to an .mcp.json pointing at the right TCP port. The bridge script content never changes across steps — it is always the same few lines pointing to the same port. So the entire bundle (TCP server + temp dir + .mcp.json + bridge script) is recreated per step even though it does not need to be.

What IS shared across steps in a scenario today

  • The Playwright BrowserContext / Page — same browser object throughout
  • The Claude Code session ID — each step resumes the same conversation via --resume <sessionId>

What is NOT shared (but could be)

  • The @playwright/mcp server instance — new one per step
  • The TCP server — new random port per step
  • The temp dir, .mcp.json, and bridge script — new per step

Proposed architecture

If createConnection() supports multiple sequential client connections, the ideal lifecycle would be scoped to the browser context rather than the step:

browserContext created by Playwright fixture
    └── one createConnection() per browserContext  ← created on first runAgent() call
    └── one TCP server per browserContext          ← same port reused across all steps
    └── one temp dir + .mcp.json per browserContext ← written once, reused across steps
            └── step 1: Claude Code subprocess connects → runs → disconnects
            └── step 2: Claude Code subprocess reconnects → runs → disconnects
            └── ...
    └── teardown: mcpServer.close(), tcpServer.close(), temp dir deleted
        when the browserContext is closed by the Playwright fixture

SessionManager already maps browserContext → sessionId and could be extended to also hold the MCP server, TCP server, and temp dir references.

What to investigate

1. Read the @playwright/mcp source code

The bundled source is at:

node_modules/playwright-core/lib/coreBundle.js

Search for the following symbols/concepts — do not rely on line numbers as they change with every version:

  • createConnection — what does it return, and does it support multiple sequential StdioServerTransport connections after each client disconnects?
  • isolated config field — creates a new browser context per connection vs reusing an existing one. In our setup we pass our own contextGetter, so verify whether this flag even applies to the createConnection path.
  • sharedBrowserContext / shared-browser-context — reuses the same browser context across multiple HTTP clients. We use stdio transport, not HTTP, but the internal behaviour may be relevant.
  • Session lifecycle — when does the MCP server need to be torn down vs when is it safe to keep alive between client connections?

2. Check the TypeScript config types

node_modules/@playwright/mcp/config.d.ts

Lists all valid fields for the createConnection config object. Cross-reference with the CLI flags in the source to understand which flags are available via the API vs CLI only.

3. Confirm parallel safety

Each browser context must still get its own TCP port and .mcp.json. Parallel test workers must never share an MCP server or temp dir. The SessionManager already enforces per-context isolation for session IDs — the same pattern applies here.

Key files

File Relevance
src/agent/Orchestrator.js Current per-step setup/teardown — the code to change
src/agent/SessionManager.js Already scoped per BrowserContext — good place to hold MCP server references
node_modules/playwright-core/lib/coreBundle.js @playwright/mcp bundled source
node_modules/@playwright/mcp/config.d.ts Config type definitions

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions