src/main/mcpServers/browser/README.md
A Model Context Protocol (MCP) server for controlling browser windows via Chrome DevTools Protocol (CDP).
Note: Normal mode uses a global
persist:defaultpartition shared by all clients. This means login sessions and stored data are accessible to any code using the MCP server.
Normal Mode (BrowserWindow)
├─ Persistent Storage (partition: persist:default) ← Global, shared across all clients
└─ Tabs (BrowserView) ← created via newTab or automatically
Private Mode (BrowserWindow)
├─ Ephemeral Storage (partition: private) ← No disk persistence
└─ Tabs (BrowserView) ← created via newTab or automatically
newTab: true for parallel URL requestsopenOpen a URL in a browser window. Optionally return page content.
{
"url": "https://example.com",
"format": "markdown",
"timeout": 10000,
"privateMode": false,
"newTab": false,
"showWindow": false
}
format: If set (html, txt, markdown, json), returns page content in that format along with tabId. If not set, just opens the page and returns navigation info.newTab: Set to true to open in a new tab (required for parallel requests)showWindow: Set to true to display the browser window (useful for debugging){ currentUrl, title, tabId }{ tabId, content } where content is in the specified formatexecuteExecute JavaScript code in the page context.
{
"code": "document.title",
"timeout": 5000,
"privateMode": false,
"tabId": "optional-tab-id"
}
tabId: Target a specific tab (from open response)resetReset browser windows and tabs.
{
"privateMode": false,
"tabId": "optional-tab-id"
}
privateMode to close a specific windowprivateMode and tabId to close a specific tab only// Open a URL in normal mode (data persists)
await controller.open('https://example.com')
// Open URL and get content as markdown
await open({ url: 'https://example.com', format: 'markdown' })
// Open URL and get raw HTML
await open({ url: 'https://example.com', format: 'html' })
// Open multiple URLs in parallel using newTab
const [page1, page2] = await Promise.all([
controller.open('https://site1.com', 10000, false, true), // newTab: true
controller.open('https://site2.com', 10000, false, true) // newTab: true
])
// Execute on specific tab
await controller.execute('document.title', 5000, false, page1.tabId)
// Close specific tab when done
await controller.reset(false, page1.tabId)
// Open a URL in private mode (no data persistence)
await controller.open('https://example.com', 10000, true)
// Cookies and localStorage won't persist after reset
// Set data
await controller.open('https://example.com', 10000, false)
await controller.execute('localStorage.setItem("key", "value")', 5000, false)
// Close window
await controller.reset(false)
// Reopen - data persists!
await controller.open('https://example.com', 10000, false)
const value = await controller.execute('localStorage.getItem("key")', 5000, false)
// Returns: "value"
// Set data in private mode
await controller.open('https://example.com', 10000, true)
await controller.execute('localStorage.setItem("key", "value")', 5000, true)
// Close private window
await controller.reset(true)
// Reopen - data is gone!
await controller.open('https://example.com', 10000, true)
const value = await controller.execute('localStorage.getItem("key")', 5000, true)
// Returns: null
const controller = new CdpBrowserController({
maxWindows: 5, // Maximum concurrent windows
idleTimeoutMs: 5 * 60 * 1000 // 5 minutes idle timeout (lazy cleanup)
})
Note on Idle Timeout: Idle windows are cleaned up lazily when the next window is created or accessed, not on a background timer.
newTab: true for Parallel Requests: Avoid race conditions when fetching multiple URLsreset() when done, or reset(privateMode, tabId) to close specific tabspersist:default (disk-persisted, global)private (memory only)showWindow: true to display)