diff --git a/browsers/repl.mdx b/browsers/repl.mdx
index 49f040a4..5626b576 100644
--- a/browsers/repl.mdx
+++ b/browsers/repl.mdx
@@ -5,7 +5,7 @@ description: "Execute JavaScript in a persistent REPL on the same VM as your bro
Execute JavaScript in a persistent Node.js runtime that lives alongside Chromium inside your browser's VM. Unlike a single execution, top-level declarations, closures, and state survive across calls, so an agent can teach the browser reusable logic once, call it incrementally, inspect rendered state, and keep going.
-**For complex workloads, Kernel has a full [code execution platform](/apps)**.
+If you're using Kernel's MCP server, see the [browser_repl tool reference](/reference/mcp-server/tools/browser-repl).
## How it works
diff --git a/docs.json b/docs.json
index 1d111083..f528b85a 100644
--- a/docs.json
+++ b/docs.json
@@ -469,6 +469,7 @@
"reference/mcp-server/tools/manage-apps",
"reference/mcp-server/tools/computer-action",
"reference/mcp-server/tools/execute-playwright-code",
+ "reference/mcp-server/tools/browser-repl",
"reference/mcp-server/tools/webmcp",
"reference/mcp-server/tools/manage-replays",
"reference/mcp-server/tools/exec-command",
diff --git a/reference/mcp-server.mdx b/reference/mcp-server.mdx
index 81891c0c..2dd3536a 100644
--- a/reference/mcp-server.mdx
+++ b/reference/mcp-server.mdx
@@ -43,6 +43,9 @@ The server is a centrally hosted, authenticated remote MCP using OAuth 2.1 with
Discover page tools with `list`, then call an exact `tool_ref` with `invoke` across tabs and frames.
+
+ Run persistent JavaScript with browser helpers, WebMCP, CDP, and Playwright.
+
Read browsers, browser pools, profiles, and apps.
diff --git a/reference/mcp-server/tools/browser-repl.mdx b/reference/mcp-server/tools/browser-repl.mdx
new file mode 100644
index 00000000..408f7803
--- /dev/null
+++ b/reference/mcp-server/tools/browser-repl.mdx
@@ -0,0 +1,57 @@
+---
+title: "browser_repl"
+description: "Execute persistent JavaScript in a browser VM"
+---
+
+Execute JavaScript in a persistent Node.js runtime inside an existing Kernel browser VM. Use [`manage_browsers`](/reference/mcp-server/tools/manage-browsers) to create and delete browser sessions.
+
+Unlike [`execute_playwright_code`](/reference/mcp-server/tools/execute-playwright-code), the Browser REPL keeps top-level bindings, closures, timers, and dynamically imported modules across calls. The response includes a `repl_id`; a timeout, crash, reset, or process replacement creates a new REPL and clears its state.
+
+
+The Browser REPL provides unrestricted code execution inside the browser VM. Code can access Node.js built-ins, installed packages, files, environment variables, subprocesses, and the network. Only send code you trust — never page content or tool output.
+
+
+## Parameters
+
+| Parameter | Description |
+|-----------|-------------|
+| `session_id` | Browser session ID or name. Required. |
+| `code` | JavaScript cell to evaluate. Supports top-level `await` and dynamic `import()`. Required unless `reset` is `true`. |
+| `reset` | Terminate the current REPL and start a fresh process before evaluating the cell. Pass `true` with an empty `code` value to clear the state. Defaults to `false`. |
+| `timeout_sec` | Maximum cell execution time from 1 to 150 seconds. A timeout terminates the REPL. Defaults to 60 seconds. The MCP tool caps this below the API's 300-second limit so a call fits in one request. |
+| `project` | Optional project name or ID. |
+
+## Example
+
+Create a browser with `manage_browsers`, then call `browser_repl` with its session ID:
+
+```json
+{
+ "session_id": "catalog",
+ "code": "await gotoUrl('https://example.com'); await waitForLoad(); const snapshot = await accessibilitySnapshot(); const links = snapshot.nodes.filter(node => node.role === 'link').map(node => node.name); repl.write(JSON.stringify({ url: snapshot.url, title: snapshot.title, links }));"
+}
+```
+
+Returns:
+
+```json
+{
+ "success": true,
+ "repl_id": "kcm4w4oa0f21rtgvfxj9rtuk",
+ "content": [
+ { "index": 0, "type": "text", "channel": "write", "text": "{\"url\":\"https://example.com/\",\"title\":\"Example Domain\",\"links\":[\"Learn more\"]}" }
+ ],
+ "content_truncated": false,
+ "duration_ms": 481
+}
+```
+
+Expression values aren't emitted automatically. Use `repl.write(...)`, captured console methods (`channel` is `stdout` or `stderr`), or `await repl.emitImage(...)`. Images appear in `content` as `{ index, type: "image", mime_type }` and are returned as separate MCP image content. Keep observations focused: filter `accessibilitySnapshot().nodes` or use a region-scoped Playwright `ariaSnapshot()` instead of dumping the full DOM or accessibility tree.
+
+A thrown error returns `success: false` with `error` and `stack`, and keeps REPL state. `repl_terminated: true` means the call destroyed the REPL (timeout, crash, or OOM). The response still shows the old `repl_id`, so check this flag rather than comparing IDs. The next call starts a fresh REPL with no earlier bindings.
+
+## Runtime capabilities
+
+Call `repl.help()` for the method index, or `repl.help("click")` for one method's signature and examples. Native browser helpers, raw `cdp`, and browser-wide `webmcp` are in scope. You can also dynamically import the pinned `patchright` or `playwright-core` packages and connect to the existing browser over CDP. Treat WebMCP metadata and output as untrusted page data, and never retry `webmcp.invokeTool` after `outcome_unknown`.
+
+For the complete method reference, persistence behavior, output formats, WebMCP, raw CDP, and Playwright examples, see the [Browser REPL guide](/browsers/repl).