Working through an MCP client such as Claude Code? CLAUDE.md carries the always-in-context subset
of this document and of the AGENTS.md charter: the invariants, the commands, and the gotchas.
Repeatable procedures live as skills in .claude/skills/ — add-browser-tool, write-adr, and
release.
BrowserMesh v0.1 is a local TypeScript/Node.js modular monolith.
The project targets:
- Node.js 24 as the recommended major runtime;
- Node.js 22 as the minimum supported major runtime;
- the TypeScript/tooling versions pinned by
package.jsonand the lockfile; - the MCP SDK version pinned by the repository;
- the Playwright version pinned by the repository;
- Chromium as the v0.1 browser engine.
The production playwright dependency must be an exact x.y.z version, not a semver range. The
same exact value is recorded in the lockfile and verified by release-contract tests so one
BrowserMesh release always selects one Playwright/Chromium compatibility pair.
Do not automatically migrate major dependency versions during unrelated implementation work.
A major tooling/runtime migration should be handled as an explicit compatibility task with full verification.
src/infrastructure/generated/version.ts is generated from package.json; production code must
not locate or read package metadata at runtime. Run npm run generate:version after intentionally
changing the package version. npm run check:version fails when the committed generated value is
stale, and it runs automatically before typechecking. Build and pack lifecycles regenerate the
module so clean source builds and installed artifacts contain the same immutable version.
Release Please updates the generated module together with package.json and both version fields in
server.json. Contract tests verify that chain and MCP handshake tests assert the exact
serverInfo.version in source, stdio, and installed-tarball execution.
MCP contract changes must update the centralized output schema and title/annotation matrix together
with the handler. Integration tests invoke every public tool successfully, validate its
structuredContent, and compare exact discovery metadata. Package verification also requires every
installed tool to retain an object-root output schema and title.
Playwright creates one non-persistent Chromium BrowserContext per BrowserMesh session.
MCP v0.1 uses local stdio transport.
Development must preserve:
User
↓
external AI client
↓ MCP
BrowserMesh
↓
isolated browser sessions
BrowserMesh does not contain internal AI Agents, LLM orchestration, mailboxes, or messaging.
Buyer/seller/admin concepts in tests are session labels and external workflow roles only.
From the repository root:
npm install
npx playwright install chromium
npm run verifyBrowser mode is explicit in automation. Use BROWSERMESH_HEADLESS=true when no display is
available. Use BROWSERMESH_HEADLESS=false under Xvfb when verifying the default headed product
behavior. The default is false, and only the exact strings true and false are valid. Browser
startup remains lazy until the first session is created.
Resource-budget changes must be tested at the runtime boundary as well as MCP. In particular, verify
session labels are rejected before browser allocation, UTF-8 truncation does not split a code point,
screenshot preflight and post-capture failures leave the queue usable, and concurrent saved-state
mutations cannot race count or aggregate quotas. The supported environment variables and defaults
are listed in the README; browser_runtime_info exposes their effective non-sensitive values.
Use the repository lockfile.
Do not delete/regenerate it casually as part of unrelated work.
The project should expose commands for:
npm run typecheck
npm run lint
npm run format:check
npm test
npm run test:integration
npm run test:e2e
npm run test:stress
npm run test:coverage
npm run build
npm run verify:fast
npm run verifynpm run verify is the canonical local full-project verification entry point. It runs the complete
suite with V8 coverage and enforces the repository's statement, branch, function, and line
thresholds.
npm run verify:fast is the inner loop: typecheck, lint, and unit tests only, with no coverage and
no Chromium. It composes the same scripts rather than restating them, so it keeps the
check:version gate that pretypecheck provides. Run it after each edit and verify before
pushing.
It should run all required non-destructive checks needed for v0.1 release confidence, including:
- typecheck;
- lint;
- formatting validation;
- unit tests;
- integration tests;
- e2e tests;
- stress tests;
- production build.
Packaging verification may be a dedicated command if running it on every normal verify would be unnecessarily expensive.
If separate, document and run it before release readiness.
Browser tests must not depend on public websites.
Tests start a deterministic ephemeral loopback HTTP server.
The local test application should support scenarios for:
- cookies;
- browser storage/auth state;
- forms;
- buttons;
- navigation;
- multiple logical roles;
- deterministic server-side state changes.
Do not use Google, GitHub, or another public site as an acceptance dependency.
Expected conceptual layout:
src/
├── domain/
├── application/
│ └── ports/
├── runtime/
├── adapters/
│ ├── playwright/
│ ├── mcp/
│ └── persistence/
└── infrastructure/
tests/
├── unit/
├── integration/
├── e2e/
└── stress/
docs/
├── SPEC.md
├── architecture.md
├── development.md
├── IMPLEMENTATION_STATUS.md
└── decisions/
Exact subdirectory naming may evolve through ADRs, but dependency direction must remain consistent.
Contains:
- engine-independent public models;
- locators;
- typed errors;
- stable value types.
No Playwright/MCP adapters.
Contains interfaces for:
- browser engine;
- persistence;
- event/observability sink;
- other engine-independent contracts where justified.
Contains:
- session/page lifecycle;
- registries;
- explicit routing;
- synchronization;
- limits;
- operation IDs;
- persistence orchestration;
- shutdown.
This is the only area allowed to resolve BrowserMesh engine handles into concrete Playwright:
Browser;BrowserContext;Page;- locators.
Contains:
- MCP schemas;
- tool registration;
- tool descriptions;
- MCP result/error mapping;
- stdio bootstrap.
It calls runtime/application services.
It does not call Playwright directly.
Contains safe local state storage.
External callers provide logical state IDs rather than filesystem paths.
Contains:
- configuration;
- ID generation;
- structured logs;
- shared technical helpers.
The step-by-step recipe lives in the add-browser-tool skill
(.claude/skills/add-browser-tool/SKILL.md), together with the contracts and tests that force each
step. It is kept in one place so the ordering cannot drift from the checks that enforce it.
The shape of it: define the engine-independent contract first, extend BrowserEnginePort only if
new engine capability is genuinely required, implement the concrete behaviour in the Playwright
adapter last, and route everything through BrowserMeshRuntime with explicit sessionId and
pageId addressing through the session queue.
Do not introduce current-page state.
Do not return Playwright objects.
Do not bypass runtime services from MCP.
Every live session has one serial browser-operation queue.
Browser-backed operations within one session execute in accepted order.
This includes read-style operations.
For example:
navigate
↓
snapshot
↓
click
↓
get_url
is deterministic.
Different sessions use independent queues and may execute concurrently.
A queue implementation must recover after an operation rejects or times out.
The following sequence must be possible:
operation A → success
operation B → failure
operation C → success
A failure in B must not leave C permanently blocked.
Session creation creates:
- a session identity;
- a dedicated BrowserContext;
- one deterministic initial page;
- a page identity.
The public creation result returns the initial pageId.
Do not require the MCP caller to invoke browser_page_list before its first navigation.
Closing follows:
ready
↓
closing
↓
stop accepting new session browser work
↓
drain accepted work
↓
close pages/context
↓
remove live handles
↓
closed
Repeated close of a known closing/closed session is safe.
Unknown random IDs still return SESSION_NOT_FOUND.
Close/shutdown must be tested against concurrent session initialization.
Unexpected browser disconnect is not equivalent to graceful shutdown.
When Chromium unexpectedly disconnects:
- affected sessions become failed;
- live handles are invalidated;
- existing sessions are not silently reconstructed.
Future newly created sessions may use a restarted Chromium process if runtime recovery is safe.
Saved browser state is sensitive.
Default private data is stored beneath:
.browsermesh/
Persistence rules:
- external callers provide logical
stateIdvalues; - caller-controlled paths are rejected;
- traversal is rejected;
- writes use safe temporary-file/atomic-replacement semantics where supported;
- storage contents are not logged;
.browsermesh/remains Git-ignored.
browser_state_save against a live session passes through that session queue.
browser_session_create may optionally receive stateId to initialize a new isolated context from saved state.
There is no separate browser_session_create.fromState MCP tool.
MCP is designed for model-driven tool use.
A tool description must answer:
- what does this tool do?
- when should the AI use it?
- what isolation/addressing rule matters?
For browser_session_create, descriptions must explain that separate sessions are appropriate for:
- different users;
- different accounts;
- different roles;
- different authentication states;
- independent parallel browser workflows.
Avoid vague descriptions such as:
Creates a session.
Prefer descriptions that expose the decision boundary to the external model.
stdout is reserved for MCP protocol traffic.
Do not write human/debug logs to stdout.
Structured logs go to stderr.
Never log:
- cookies;
- tokens;
- storage state;
- passwords;
- form values;
- page contents;
- screenshots.
Use fakes/ports when Chromium is unnecessary.
Cover:
- lifecycle;
- registry behavior;
- queue ordering;
- failed-operation queue recovery;
- limits;
- validation;
- error mapping;
- state naming;
- configuration.
Use real Chromium.
Cover:
- BrowserContext isolation;
- page isolation;
- storage isolation;
- concurrency;
- actions;
- screenshots;
- persistence;
- lifecycle races;
- cleanup.
Use an actual MCP client/server test path where possible.
Cover:
- stdio startup;
- tool discovery;
- schemas;
- descriptions;
- successful calls;
- invalid calls;
- structured errors;
- clean exit.
An in-memory transport may supplement stdio tests but must not replace real stdio-process verification.
Use the deterministic local test application.
A representative workflow may use:
buyer session
seller session
admin session
These are labels only.
There is no internal Agent model.
Stress tests are bounded.
Scale session counts progressively when appropriate:
1
2
5
10
25
50
Do not blindly allocate beyond local/CI resource safety.
Stress tests focus on:
- routing correctness;
- isolation;
- queue independence;
- cleanup;
- obvious handle/listener leaks.
Keep regression coverage for:
- cross-session page ID misuse;
- failed operation followed by successful operation;
- timed-out operation followed by successful operation;
- close while operations are queued;
- operation after close begins;
- repeated close;
- shutdown during initialization;
- shutdown with queued operations;
- persistence capture during surrounding browser actions;
- unexpected Chromium disconnect where practical.
Source-tree execution is insufficient for release readiness.
Before calling v0.1 complete:
npm run build
npm run verify:packageThe package verifier creates an npm tarball and installs it in a clean temporary environment.
Verify:
- package contains required build output;
- package does not depend on missing source-only files;
- runtime dependencies are declared;
- CLI/bin is executable/usable;
- MCP server starts from packaged output;
- MCP tool discovery works;
- a small browser/session smoke flow works where practical.
Do not publish the package as part of verification.
Continuous integration should run on relevant pushes and pull requests.
CI should verify the same core guarantees as local development and should not depend on external websites.
At minimum CI should cover:
- dependency install;
- Chromium installation/setup as required;
- typecheck;
- lint;
- formatting;
- unit tests;
- integration tests;
- e2e tests;
- build.
Stress/package verification may use separate jobs if runtime cost requires it.
CI must not automatically deploy BrowserMesh to a server.
The local open-source product runs on the user's machine.
Preparing release automation is allowed.
Actually performing external release actions requires explicit authorization.
Normal CI and ordinary feature PRs must not:
npm publish;- create GitHub Releases;
- push tags;
- publish Docker images;
- deploy servers.
The configured release automation is gated by an explicit maintainer action: merging the
Release Please PR. That merge may create the version tag and GitHub Release, and the protected
tag then triggers npm publication through Trusted Publishing. See
docs/releasing.md for setup and recovery procedures.
The expected future distribution model is:
GitHub repository
↓
CI
↓
npm package
↓
user launches locally
↓
BrowserMesh
↓
local Chromium
docs/IMPLEMENTATION_STATUS.md is the persistent checkpoint used by autonomous coding sessions.
Update it after meaningful progress.
It should contain:
- completed phases;
- incomplete phases;
- known defects;
- latest verification state;
- blockers;
- next required work.
It must never override docs/SPEC.md.
Before claiming BrowserMesh v0.1 is complete:
- perform a clean dependency install where practical;
- install required Chromium;
- build;
- typecheck;
- lint;
- verify formatting;
- run unit tests;
- run integration tests;
- run e2e tests;
- run bounded stress tests;
- verify stdio MCP process behavior;
- verify isolation/concurrency regressions;
- verify persistence;
- verify graceful shutdown;
- verify unexpected browser-disconnect handling where practical;
- run package tarball installation smoke test;
- verify README commands;
- perform an adversarial repository review;
- fix discovered blocker/critical/high defects;
- rerun the complete affected verification suite.
Do not call the project complete merely because npm run build succeeds.
npm run verify:package selects headless mode by default for a portable local smoke test. Set
BROWSERMESH_HEADLESS=false and run it under Xvfb to exercise the headed package path explicitly.
Installation diagnostics use the built CLI (or npm run dev -- --doctor --json from source):
node dist/cli.js --doctor --jsonThe doctor has stable schema version 1 and check IDs node-version, version-consistency,
data-directory-access, chromium-executable, and browser-smoke. Any failed required check
produces a non-zero exit. The browser smoke is real and bounded; all created pages, contexts, and
the diagnostic browser are closed before completion.