Background
Currently, every runAgent() call (i.e. every BDD step) creates a brand new set of resources:
createConnection() — a new in-process @playwright/mcp server
- A new TCP server (
net.createServer) on a random ephemeral port
- 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
Background
Currently, every
runAgent()call (i.e. every BDD step) creates a brand new set of resources:createConnection()— a new in-process@playwright/mcpservernet.createServer) on a random ephemeral port/tmp/openqa-mcp-<uuid>/with a unique.mcp.jsonand bridge scriptThese 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.jsonpointing 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
BrowserContext/Page— same browser object throughout--resume <sessionId>What is NOT shared (but could be)
@playwright/mcpserver instance — new one per step.mcp.json, and bridge script — new per stepProposed architecture
If
createConnection()supports multiple sequential client connections, the ideal lifecycle would be scoped to the browser context rather than the step:SessionManageralready mapsbrowserContext → sessionIdand could be extended to also hold the MCP server, TCP server, and temp dir references.What to investigate
1. Read the
@playwright/mcpsource codeThe bundled source is at:
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 sequentialStdioServerTransportconnections after each client disconnects?isolatedconfig field — creates a new browser context per connection vs reusing an existing one. In our setup we pass our owncontextGetter, so verify whether this flag even applies to thecreateConnectionpath.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.2. Check the TypeScript config types
Lists all valid fields for the
createConnectionconfig 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. TheSessionManageralready enforces per-context isolation for session IDs — the same pattern applies here.Key files
src/agent/Orchestrator.jssrc/agent/SessionManager.jsBrowserContext— good place to hold MCP server referencesnode_modules/playwright-core/lib/coreBundle.js@playwright/mcpbundled sourcenode_modules/@playwright/mcp/config.d.tsReferences
@playwright/mcpGitHub: https://github.com/microsoft/playwright-mcpStdioServerTransport: https://github.com/modelcontextprotocol/typescript-sdk