{
  "markdown": "# BrowserMesh\n\n**Run many browser sessions at once, fully isolated from each other, from one MCP server.**\n\nEvery other browser MCP server gives your AI client one browser with a current tab. BrowserMesh\ngives it as many independent sessions as the task needs — each with its own cookies, storage, and\nauthentication — running in parallel. Test checkout as a customer while an admin session verifies\nthe order, in one conversation, without either seeing the other's state.\n\n- [Documentation](https://scrolldynasty.github.io/BrowserMesh/)\n- [Getting started](https://scrolldynasty.github.io/BrowserMesh/guide/getting-started)\n- [MCP tool reference](https://scrolldynasty.github.io/BrowserMesh/reference/tools)\n\n```text\nExternal AI client\n        |\n     MCP stdio\n        |\nBrowserMesh runtime\n   |         |         |\nSession A  Session B  Session C\nContext A  Context B  Context C\n```\n\nThe external client reasons and plans. BrowserMesh executes browser operations, enforces isolation,\norders work within each session, and returns structured results.\n\nWorks with Claude Code, Claude Desktop, Codex, Cursor, Windsurf, Qwen, and any other MCP-compatible\nclient.\n\n## Quick start\n\nClaude Code:\n\n```sh\nclaude mcp add browsermesh -- npx -y browsermesh\n```\n\nAny other client, in its MCP configuration file:\n\n```json\n{\n  \"mcpServers\": {\n    \"browsermesh\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"browsermesh\"]\n    }\n  }\n}\n```\n\nThat is the whole setup. On its first start BrowserMesh downloads the Chromium build it uses, so\nthere is no separate install step. Pass `--no-auto-install` to manage the browser yourself, in\nwhich case MCP discovery still works and `browser_session_create` returns an actionable\n`BROWSER_ERROR` explaining what to run.\n\nThen ask for the work in plain language:\n\n> Test the checkout flow as a buyer and confirm the order appeared, as an admin, at the same time.\n\nThe client creates one session per role on its own. BrowserMesh also publishes a `parallel_roles`\nprompt that spells the workflow out, so a client can offer it directly.\n\nCheck an installation without starting the protocol:\n\n```sh\nnpx -y browsermesh --doctor\n```\n\nChromium and BrowserMesh remain on your machine. There is no hosted BrowserMesh service.\n\n> **Renamed in 0.2.** The npm package was `multi-agent-browser-mcp` and is now `browsermesh`,\n> matching the name everything else already used. Change `args` to `[\"-y\", \"browsermesh\"]`;\n> nothing else moves.\n\n## v0.1 architecture\n\nBrowserMesh v0.1 is intentionally small: one local Node.js process, one Chromium process, and one\nnon-persistent `BrowserContext` for every ready session.\n\nIt provides:\n\n- explicit session and page addressing;\n- isolated browser contexts and page ownership checks;\n- navigation, inspection, interaction, waits, capture, and persistence;\n- per-session serialization with parallel execution across independent sessions;\n- bounded timeouts, structured errors, graceful shutdown, and resource cleanup;\n- MCP stdio integration with deterministic integration and end-to-end tests.\n\nBrowserMesh is a browser runtime, not an internal agent framework, LLM orchestrator, message bus,\nPlaywright fork, browser GUI, or interactive shell.\n\n## How it is normally used\n\n1. Configure BrowserMesh once in an MCP-compatible AI client.\n2. The client starts BrowserMesh over stdio and discovers its tools.\n3. Describe the browser task in natural language.\n4. The client creates the required sessions and chooses the tools to invoke.\n5. BrowserMesh executes the operations and returns structured results.\n\nUse a separate session for each user, account, role, authentication state, or independent parallel\nworkflow. BrowserMesh tools are normally selected by the external client rather than called by hand.\n\n## Build from source\n\nBrowserMesh v0.1 targets Node.js 24 and supports Node.js 22 as its minimum supported major version.\n\nClone the repository and run:\n\n```sh\nnpm install\nnpx playwright install chromium\nnpm run build\n```\n\nThen configure an MCP client to launch the locally built server:\n\n```json\n{\n  \"mcpServers\": {\n    \"browsermesh\": {\n      \"command\": \"node\",\n      \"args\": [\"/absolute/path/to/browsermesh/dist/cli.js\"]\n    }\n  }\n}\n```\n\nFor development:\n\n```sh\nnpm run verify\nnpm run verify:package\n```\n\n## Session model\n\nThere is no global:\n\n- current session;\n- active session;\n- current page;\n- active page;\n- current tab.\n\nEvery browser operation explicitly identifies its session.\n\nEvery page-specific operation explicitly identifies its page.\n\nConceptually:\n\n```text\nbrowser_session_create\n        │\n        ▼\n{\n  sessionId,\n  pageId\n}\n        │\n        ▼\nbrowser_navigate({\n  sessionId,\n  pageId,\n  ...\n})\n```\n\nA newly created session contains one deterministic initial page.\n\n`browser_session_create` returns the initial `pageId` immediately so an AI client does not need an additional `browser_page_list` call before its first browser action.\n\nThe page also appears in `browser_page_list` and is marked `isDefault`.\n\nSession views consistently expose `sessionId`; page views consistently expose `pageId` and their owning `sessionId`.\n\nThe `isDefault` marker is informational only. Browser operations still use explicit `pageId` addressing.\n\n## Isolation\n\nEach ready BrowserMesh session maps to its own non-persistent Chromium `BrowserContext`.\n\nTherefore independent sessions must not accidentally share:\n\n- cookies;\n- browser storage/authentication state;\n- pages;\n- page references;\n- current URLs;\n- DOM snapshots;\n- screenshots;\n- form state.\n\nA `pageId` belonging to one session cannot be used through another session.\n\nCross-session page addressing is rejected.\n\n## Concurrency model\n\nEvery live session has an independent serial operation queue.\n\nOperations targeting the same session execute deterministically in accepted order.\n\nFor example:\n\n```text\nSession A\n\nnavigate\n   ↓\nsnapshot\n   ↓\nclick\n   ↓\nget_url\n```\n\nA read-style operation does not bypass an in-progress navigation or interaction.\n\nA failed or timed-out operation must not poison the queue. Later accepted operations continue normally after the failed operation settles.\n\nEach `timeoutMs` is one absolute budget starting when BrowserMesh accepts the operation. Time spent\nwaiting in the owning session queue and every later browser-adapter step consume that same budget;\nno adapter step receives a renewed full timeout.\n\nMCP request cancellation is propagated into BrowserMesh as an engine-independent operation signal.\nA same-session request cancelled while queued is skipped before it can touch browser state. If a\nPlaywright action is already running and cannot be aborted safely, BrowserMesh keeps its queue slot\nuntil the real action settles, so later work cannot overtake it. Passive waits detach their owned\nabort listeners and timers promptly, and the session queue remains usable after cancellation. MCP\nclients observe their SDK's cancellation error (typically an `AbortError`, or an MCP error carrying\nthat reason); a separate tool result after protocol cancellation is not guaranteed.\n\nDifferent sessions do **not** share a global operation lock:\n\n```text\nSession A ═════════════════════►\n\nSession B ═════════════════════►\n\nSession C ═════════════════════►\n```\n\nThis allows independent browser workflows to run concurrently.\n\n## Session closing\n\nWhen session close begins:\n\n1. the session enters `closing`;\n2. new operations targeting it are rejected;\n3. operations already accepted into its queue are drained;\n4. its pages and `BrowserContext` are closed;\n5. live engine handles are removed;\n6. the session becomes closed.\n\nRepeated close of a known closing/closed session is safe and returns an idempotent success result.\n\nA completely unknown session ID still returns `SESSION_NOT_FOUND`.\n\n## Supported MCP tools\n\n### Sessions and pages\n\n- `browser_runtime_info`\n- `browser_session_create`\n- `browser_session_list`\n- `browser_session_get`\n- `browser_session_close`\n- `browser_page_create`\n- `browser_page_list`\n- `browser_page_close`\n\n### Navigation\n\n- `browser_navigate`\n- `browser_back`\n- `browser_forward`\n- `browser_reload`\n\n### Inspection\n\n- `browser_get_url`\n- `browser_get_title`\n- `browser_snapshot`\n- `browser_visible_text`\n\n`browser_snapshot` is bounded by default and may be restricted with a semantic/CSS `scope`,\n`interactiveOnly`, per-node `maxChildren`, `maxDepth`, `includeBoundingBoxes`, `maxChars`, and\n`maxBytes`. Its structured result reports every applied bound, intentional tree omission, and\ncharacter/UTF-8 byte count. When either response cap is reached,\n`partial=true`, `truncation.truncated=true`, and `contentFormat=aria-yaml-fragment`; do not parse\nthat fragment as a complete ARIA YAML document. Password values remain redacted. Set\n`includeRefs=true` to receive at most `maxRefs` (default 50, maximum 100) opaque interactive-element\nrefs for immediate follow-up actions. Refs expire after 30 seconds, are scoped to the exact\nsession/page, and become stale after navigation, DOM replacement, page close, expiry, or a newer\nref snapshot. A non-null `nextCursor` continues the same immutable captured serialization without\nrereading a changed DOM. Cursors are scoped to the exact session/page, expire after 30 seconds, and\nbecome stale after navigation, page close, quota eviction, or shutdown. At most four paginated\nsnapshots and 1,000,000 Unicode code points per captured snapshot are retained per page.\nBefore native ARIA serialization, BrowserMesh also rejects a scope exceeding 20,000 DOM/text nodes\nor 2,000,000 source characters. This pre-capture budget limits browser-side work independently of\nthe smaller per-response and retained-cursor bounds.\n\n### Observability\n\n- `browser_observe`\n\nOne tool reads all four recorded sources, selected by `source`: `console`, `pageError`, `network`,\nor `requestFailed`. It requires an explicit `sessionId` and `pageId`, and echoes `source` so results\nread into one buffer stay distinguishable.\n\nConsole and page-error reads are metadata-only by default; set `includeText=true` for bounded,\nbest-effort-redacted evidence. The two network sources carry no text and reject that flag rather\nthan returning a metadata-only answer that looks complete. Use `nextCursor` as the next\nnon-destructive `sinceEventId` checkpoint. Always inspect `gap` and `droppedCount` before concluding\nthat an event was absent. Text may be truncated further to satisfy the total response-byte limit;\nthe event and its cursor are still returned so pagination cannot stall. BrowserMesh never captures\nconsole argument objects or raw error stacks.\n\nNetwork reads expose only correlated request/response/request-failed metadata: a bounded request\nID, method, sanitized URL, resource type, status, duration, and safe failure classification where\napplicable. Credentials and fragments are removed and sensitive query values are redacted before\nstorage. Headers, bodies, cookies, storage, service-worker traffic, WebSockets, `data:` URLs, and\n`blob:` URLs are excluded. Page-originated HTTP(S) EventSource requests are included as ordinary\nnetwork metadata. HTTP error responses such as 500 appear under `source: \"network\"`; only\ntransport-level failures appear under `source: \"requestFailed\"`.\n\n> Before 0.2 these were four tools — `browser_console_list`, `browser_page_errors_list`,\n> `browser_network_list`, and `browser_failed_requests_list` — publishing four copies of one\n> contract. Pass the matching `source` instead.\n\n### Interaction\n\n- `browser_click`\n- `browser_double_click`\n- `browser_hover`\n- `browser_focus`\n- `browser_check`\n- `browser_uncheck`\n- `browser_scroll_into_view`\n- `browser_scroll`\n- `browser_drag_and_drop`\n- `browser_fill`\n- `browser_press`\n- `browser_select_option`\n\nThese typed operations accept exactly one semantic/CSS locator or short-lived snapshot `ref`. They are explicitly\naddressed, bounded by `timeoutMs`, cancellation-aware, and serialized with all browser work in the\nowning session. `check` and `uncheck` ensure the requested state idempotently. `browser_scroll`\naccepts bounded integer pixel deltas (`deltaX` and `deltaY` from -1,000,000 through 1,000,000),\nwhile drag-and-drop resolves both source and target with the same strict locator semantics. None of\nthese tools exposes arbitrary page JavaScript.\n\n### Deterministic waits\n\n- `browser_wait`\n- `browser_action_and_wait`\n\n`browser_wait` observes one passive, typed condition through the owning session queue: an exact or\nsafe-glob URL, `domcontentloaded`/`load`, locator state, or bounded text presence/absence. Text\nmatching is a case-sensitive substring check against at most the first 1,000,000 characters of\nthe page body's rendered `innerText`; `absent` means that substring is not present in that bounded\nobservation. Caller regular expressions, JavaScript predicates, arbitrary sleeps, and\n`networkidle` are not supported.\n\nDo not queue a passive wait before the same-session action expected to satisfy it.\n`browser_action_and_wait` registers a typed navigation, HTTP response, popup, or dialog waiter first\nand then performs one click or key press under one shared deadline. Returned response URLs remove\ncredentials and fragments and redact common sensitive query values. A popup is assigned a new\nmanaged `pageId` in the same session with `isDefault=false`; overflow popups are closed before\n`LIMIT_EXCEEDED` is returned. Dialogs are handled atomically with an expected type and accept/dismiss\nchoice because a blocking dialog cannot be safely inspected later. Prompt input and returned dialog\nmetadata are bounded.\n\n`browser_action_and_wait` addresses its action with `target`, taking a semantic/CSS locator or a\nsnapshot `ref` exactly like the standalone interaction tools. Neither result restates the request:\n`browser_wait` returns `satisfied`, and `browser_action_and_wait` returns the observed `event`. The\ncaller already holds the condition and action it sent, and `operationId` correlates the result.\n\n### Capture\n\n- `browser_screenshot`\n\nScreenshots are returned as MCP image content instead of being written to a caller-controlled\nfilesystem path. The optional `capture` mode selects the viewport, the full scrollable page, or one\nstrictly resolved semantic/CSS element; the default remains the viewport. Structured output reports\nactual PNG width, height, and encoded bytes. BrowserMesh measures CSS-pixel dimensions before capture\nand validates actual PNG dimensions and bytes afterward. Full-page and element modes capture the\nfixed measured clip, so later page growth cannot expand native image allocation. Configured\noverflow returns `LIMIT_EXCEEDED` and does not poison the session queue.\n\n### Persistence\n\n- `browser_state_save`\n- `browser_state_list`\n- `browser_state_remove`\n\n`browser_session_create` accepts an optional `stateId`.\n\nWithout `stateId`, it creates a fresh isolated context.\n\nWith `stateId`, it initializes the new context using a previously saved BrowserMesh state.\n\nSaved-state count, individual bytes, and aggregate bytes are centrally bounded. Quota checks and\natomic replacement are serialized across all state IDs; a rejected replacement preserves the old\nstate. Existing files are size-checked and read with a hard bound before JSON parsing.\n\n`browser_session_create` also accepts an optional `contextSettings` object for an isolated viewport,\ndevice scale factor, locale, timezone, color scheme, reduced-motion preference, and user agent. The\nsession result returns the normalized effective settings. Use separate sessions for different\ndevice/accessibility/permission profiles. Geolocation is optional and the only supported browser\npermission is an explicit grant to one absolute HTTP(S) origin. Wildcards and arbitrary permission\nnames are rejected.\n\n```text\nbrowser_session_create({\n  name: \"mobile-fr\",\n  contextSettings: {\n    viewport: { width: 390, height: 844 },\n    deviceScaleFactor: 3,\n    locale: \"fr-FR\",\n    timezoneId: \"Europe/Paris\",\n    colorScheme: \"dark\",\n    reducedMotion: \"reduce\"\n  }\n})\n```\n\nGeolocation access must be scoped to the exact application origin:\n\n```text\nbrowser_session_create({\n  name: \"local-map-test\",\n  contextSettings: {\n    geolocation: { latitude: 41.3111, longitude: 69.2797, accuracy: 25 },\n    permissions: [\n      { permission: \"geolocation\", origin: \"https://maps.example.test\" }\n    ]\n  }\n})\n```\n\nThe permission is isolated to that session context and is removed when the session closes. Saved\nstorage state does not contain or restore BrowserMesh permission grants.\n\nExample conceptually:\n\n```text\nbrowser_session_create({\n  name: \"buyer\",\n  stateId: \"buyer-auth\"\n})\n```\n\n## Session labels\n\nA session may have:\n\n- an optional human-readable `name`;\n- optional string metadata.\n\nFor example an external AI client may label sessions:\n\n```text\nrole=buyer\nrole=seller\naccount=work\n```\n\nThese values are neutral workflow labels only.\n\nThey do **not** create:\n\n- internal Agent entities;\n- ownership principals;\n- permissions;\n- mailboxes;\n- message channels;\n- LLM identities.\n\n## MCP tool discovery\n\nBrowserMesh tool descriptions are part of the product contract.\n\nDescriptions must explain both what a tool does and when an AI client should use it.\n\nFor example, the description for `browser_session_create` must make it clear that separate sessions should be used for:\n\n- different users;\n- different accounts;\n- different roles;\n- different authentication states;\n- independent parallel browser workflows.\n\nThe goal is that a user can say:\n\n> Test this application as a buyer and an administrator.\n\nwithout having to manually instruct the AI to call `browser_session_create` twice.\n\nEvery discovered tool also publishes a human-readable title, an object-root `outputSchema`, and\nreviewed MCP risk hints (`readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint`).\nThese hints improve client UX only; BrowserMesh never treats them as authorization.\n\nSuccessful calls return schema-validated `structuredContent` with direct semantic fields. For\nexample, `browser_session_create` exposes `operationId`, `session`, and `initialPage` directly,\nwithout a nested `value.value` envelope. A concise JSON text block remains for text-only clients.\nScreenshots retain their in-memory MCP image block and add structured PNG/correlation metadata.\n\nApplication failures use `isError: true` and a bounded JSON error containing a stable code, safe\nmessage, optional sanitized details, and `operationId` correlation when the runtime accepted the\noperation. Raw causes, stacks, cycles, non-JSON values, and secret-bearing detail fields never cross\nthe MCP boundary. SDK input-schema failures remain distinguishable as MCP input-validation errors.\n\n## Locators\n\nBrowser actions prefer semantic locator strategies.\n\nSupported v0.1 strategies include:\n\n- role;\n- text;\n- label;\n- placeholder;\n- test ID;\n- CSS as an escape hatch.\n\nCommon interactive role values are supported by the v0.1 public contract.\n\nRole names use exact accessible-name matching by default. Set `exact: false` only when partial\nmatching is intentional. If a locator resolves to multiple elements, BrowserMesh returns\n`LOCATOR_AMBIGUOUS` and keeps the session usable.\n\nAny locator may optionally set `frame` to `{ \"kind\": \"main\" }` or to a bounded\n`{ \"kind\": \"iframe\", \"chain\": [...] }` of one through five outer-to-inner semantic/CSS iframe\nelement selectors. Each chain step must resolve exactly; numeric indexes and persistent frame\nhandles are not exposed. The same scope works for actions, locator waits, visible text, snapshot\nscope/ref capture, element screenshots, and drag/drop endpoints. Cross-origin iframe content is\nreturned only when the caller explicitly requests that scoped evidence.\n\nAccessibility snapshots redact non-empty values from `input[type=\"password\"]` elements before any\nsnapshot content crosses the MCP boundary.\n\nBrowserMesh does not expose Playwright `Locator` objects through its public API.\n\nElement refs are conveniences for immediate snapshot-to-action workflows, not durable identity.\nInvalid, expired, cross-page, or detached refs return `STALE_ELEMENT_REFERENCE`; semantic locators\nremain preferred for durable tests. BrowserMesh does not use undocumented Playwright AI/ref\nselectors or expose adapter-owned element handles.\n\n## Persistence and sensitive state\n\nBrowserMesh stores local persistence data beneath:\n\n```text\n.browsermesh/\n```\n\nby default.\n\nSaved browser state may contain authentication credentials or equivalent sensitive browser state.\n\nTherefore:\n\n- `.browsermesh/` is ignored by Git;\n- saved state must not be committed;\n- saved state must not be published;\n- logs must not contain storage-state contents;\n- callers provide logical state IDs, not arbitrary filesystem paths.\n\nPersistence represents serialized browser storage/auth state.\n\nBrowserMesh never attempts to serialize a live `BrowserContext`, open pages, pending operations, or live browser process state.\n\n## Configuration\n\n| Environment variable                       |          Default | Meaning                                        |\n| ------------------------------------------ | ---------------: | ---------------------------------------------- |\n| `BROWSERMESH_TIMEOUT_MS`                   |          `10000` | Default bounded operation timeout              |\n| `BROWSERMESH_DATA_DIR`                     | `~/.browsermesh` | Private local data directory                   |\n| `BROWSERMESH_LOG_LEVEL`                    |           `info` | `debug`, `info`, `warn`, `error`, or `silent`  |\n| `BROWSERMESH_MAX_SESSIONS`                 |             `50` | Active session limit                           |\n| `BROWSERMESH_MAX_PAGES`                    |             `20` | Managed pages per session                      |\n| `BROWSERMESH_PERSISTENCE`                  |           `true` | Enable saved browser state                     |\n| `BROWSERMESH_HEADLESS`                     |          `false` | Launch Chromium without a visible window       |\n| `BROWSERMESH_SCHEMA_REFS`                  |           `true` | Share repeated subschemas via `$defs`/`$ref`   |\n| `BROWSERMESH_AUTO_INSTALL`                 |           `true` | Download Chromium on first start if missing    |\n| `BROWSERMESH_TOOLS`                        |            (all) | Tool profiles to publish, comma-separated      |\n| `BROWSERMESH_OBSERVABILITY_EVENTS`         |            `200` | Retained mixed observability events per page   |\n| `BROWSERMESH_OBSERVABILITY_STRING_CHARS`   |           `2048` | Maximum exposed event string length            |\n| `BROWSERMESH_OBSERVABILITY_PAGE_SIZE`      |            `100` | Maximum events returned by one read            |\n| `BROWSERMESH_OBSERVABILITY_RESPONSE_BYTES` |          `65536` | Maximum serialized observability response size |\n| `BROWSERMESH_SCREENSHOT_MAX_DIMENSION`     |          `10000` | Maximum PNG width or height in CSS pixels      |\n| `BROWSERMESH_SCREENSHOT_MAX_PIXELS`        |       `40000000` | Maximum total PNG pixels                       |\n| `BROWSERMESH_SCREENSHOT_MAX_BYTES`         |       `16777216` | Maximum encoded PNG bytes                      |\n| `BROWSERMESH_VISIBLE_TEXT_MAX_CHARS`       |          `20000` | Maximum returned Unicode code points           |\n| `BROWSERMESH_VISIBLE_TEXT_MAX_BYTES`       |          `65536` | Maximum returned visible-text UTF-8 bytes      |\n| `BROWSERMESH_MAX_SAVED_STATES`             |            `100` | Maximum persisted logical states               |\n| `BROWSERMESH_MAX_STATE_BYTES`              |        `1048576` | Maximum bytes in one persisted state           |\n| `BROWSERMESH_MAX_STATE_TOTAL_BYTES`        |       `16777216` | Maximum aggregate persisted-state bytes        |\n\nThe options the command line accepts are `--headless`, `--headed`, `--timeout`, `--data-dir`,\n`--log-level`, `--max-sessions`, `--max-pages`, `--tools`, `--no-persistence`, `--no-schema-refs`,\nand `--no-auto-install`. Each sets the variable above that already configures it, and the command\nline wins. The remaining variables — the observability, screenshot, visible-text, and persistence\nbudgets — are set through the environment only. Run `browsermesh --help` for the current list. A\nrejected value names the variable it came from and exits with status 2 instead of printing a stack\ntrace.\n\nSaved state lives under the user's home directory rather than the working directory. An MCP client\nstarts BrowserMesh from whichever directory it happens to be in, so a relative default scattered\nsaved authentication across unrelated folders and made `browser_state_list` come back empty for no\nvisible reason. Pass `--data-dir .browsermesh` for the previous project-scoped behaviour.\n\nConfiguration is read and validated centrally.\n\n## Publishing fewer tools\n\nDiscovery costs context, once per session, in every client. `--tools` narrows what BrowserMesh\npublishes to the profiles a workflow actually needs:\n\n| Profile         | Tools | Contents                                                           |\n| --------------- | ----: | ------------------------------------------------------------------ |\n| `core`          |    31 | Sessions, pages, navigation, reading, interaction, waits, capture  |\n| `observability` |     1 | `browser_observe`                                                  |\n| `persistence`   |     3 | `browser_state_save`, `browser_state_list`, `browser_state_remove` |\n\nOmitting `--tools` publishes every profile, so an existing configuration keeps the tools it had.\n\n```sh\nnpx -y browsermesh --tools core,persistence\n```\n\nPublished schemas share their repeated subschemas through `$defs`/`$ref`, which every JSON Schema\n2020-12 validator resolves. Set `--no-schema-refs` for a client whose validator does not.\n\n## Prompts and resources\n\nBrowserMesh publishes two MCP prompts, so a client can offer the workflow rather than having to\ninfer it:\n\n- `parallel_roles` — carry one task out as several roles at once, one isolated session each.\n- `diagnose_page` — load a page and collect console, page-error, and network evidence about it.\n\nIt also publishes one read-only resource, `browsermesh://sessions`, listing the sessions the runtime\ncurrently holds with their status and labels.\n\nBoth are static templates. BrowserMesh renders text and returns it; it makes no LLM call and keeps\nno per-client state. The client still reasons and decides which tools to call.\n\nBrowserMesh launches Chromium in headed mode by default so the user can observe browser automation.\nSet `BROWSERMESH_HEADLESS=true` for CI, servers, and other environments without a display. Only the\nliteral values `true` and `false` are accepted; invalid values fail configuration instead of being\nsilently ignored. Browser startup remains lazy in either mode, so MCP discovery and actionable\nsetup errors stay available when Chromium has not been installed yet. The configured\n`BROWSERMESH_TIMEOUT_MS` also bounds browser launch. Set a larger per-tool `timeoutMs` only for\noperations that are expected to take longer than the safe default.\n\nSession labels are bounded even for direct runtime callers: names allow at most 128 Unicode code\npoints, metadata at most 32 entries, and keys/values have character, UTF-8 byte, and aggregate byte\nlimits. Control characters and dangerous object keys are rejected before BrowserMesh allocates a\nsession ID, context, or page. `browser_visible_text` retains its `text` field and adds explicit\ntruncation metadata so a client can distinguish complete evidence from a bounded prefix.\n\nDirect scattered `process.env` access throughout the codebase is not allowed.\n\n`browser_runtime_info` is safe to call before creating a session. It reports exact BrowserMesh,\nNode, and resolved Playwright versions; effective configuration and limits; browser launch state;\nnullable live Chromium version; and active/failed session counts. It does not launch Chromium and\ndoes not expose paths, environment values, browser state, or raw failures.\n\n## Logging\n\nMCP stdio reserves stdout for protocol traffic.\n\nBrowserMesh structured logs therefore go to stderr.\n\nLogs may contain safe correlation information such as:\n\n- `operationId`;\n- `sessionId`;\n- `pageId`;\n- tool/operation name;\n- duration;\n- safe error code.\n\nLogs must not contain:\n\n- cookies;\n- tokens;\n- saved state;\n- page contents;\n- screenshots;\n- form values;\n- passwords;\n- arbitrary message payloads.\n\n## Chromium disconnect behavior\n\nBrowserMesh does not silently reconstruct live sessions if Chromium unexpectedly disconnects.\n\nAffected sessions transition to a failed state and their live handles are invalidated.\n\nExisting sessions are never silently recreated because doing so would violate BrowserMesh state guarantees.\n\nA fresh Chromium process may be started for future newly created sessions if the runtime can safely recover, but old live sessions remain failed.\n\n## Development\n\n```sh\nnpm run typecheck\nnpm run lint\nnpm run format:check\nnpm test\nnpm run test:integration\nnpm run test:e2e\nnpm run test:stress\nnpm run test:coverage\nnpm run build\nnpm run verify\n```\n\nBrowser integration/e2e tests use real Chromium together with a deterministic loopback HTTP test server.\n\nTests do not depend on public websites.\n\nSee:\n\n- [Technical specification](docs/SPEC.md)\n- [Architecture](docs/architecture.md)\n- [Development](docs/development.md)\n- [Contributing](CONTRIBUTING.md)\n- [Release process](docs/releasing.md)\n- [Security policy](SECURITY.md)\n- [Architecture decisions](docs/decisions/)\n\nPull request titles follow Conventional Commits. Every PR is checked by the full test matrix,\npackage-install smoke tests, semantic-title validation, and CodeQL. Releases are prepared by\nRelease Please and published to npm through GitHub OIDC only after a maintainer merges the\ngenerated Release PR.\n\nBrowserMesh is distributed under the [Apache License 2.0](LICENSE).\n\n## Intentional v0.1 limitations\n\nBrowserMesh v0.1 intentionally does not include:\n\n- Firefox/WebKit parity;\n- remote Streamable HTTP;\n- BrowserMesh-hosted cloud infrastructure;\n- multi-tenant authentication;\n- distributed browser workers;\n- live-operation crash recovery;\n- internal Agent entities;\n- internal session ownership tied to LLM agents;\n- Agent registries;\n- mailboxes;\n- agent-to-agent messaging;\n- internal LLM calls;\n- prompt orchestration;\n- Claude/Codex/Qwen process spawning;\n- arbitrary shell execution;\n- arbitrary filesystem reads;\n- caller-controlled screenshot paths;\n- downloads;\n- web dashboard;\n- network allowlist;\n- full Playwright API.\n\nA future generic client/workflow lease may be introduced if real multi-client protection requires it.\n\nSuch a lease must remain independent of LLM/Agent abstractions.\n\n## Core v0.1 guarantee\n\nWithin one BrowserMesh runtime:\n\n- sessions are explicitly addressed;\n- pages are explicitly addressed;\n- each session has an isolated browser context;\n- different sessions may execute concurrently;\n- operations targeting one session execute deterministically through that session's queue;\n- failures do not poison future queued operations;\n- persisted state is handled through controlled logical identifiers;\n- shutdown cleans up live browser resources;\n- BrowserMesh performs browser execution while reasoning remains outside the runtime.\n",
  "bytes": 31207,
  "sha": "057fbc5030ae691371403841e0363cf6ddfb8717faa8ef87e132c7088f0b1b04",
  "repo_slug": "scrolldynasty/multi-agent-browser-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_scrolldynasty_browsermesh_9bd7cf73/readme"
}