From 5af8870732770bdfa3ad221d7815bc19ad08c30b Mon Sep 17 00:00:00 2001 From: Bruno Azoulay Date: Mon, 8 Jun 2026 23:32:01 +0200 Subject: [PATCH 1/2] fix(engine): default channel cascade to managed chromium, not system chrome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 0.1.50 cascade preferred channel=chrome (system Google Chrome). On some Linux servers the system chrome binary launches fine but has no network route (ERR_INTERNET_DISCONNECTED) — proven on a real host where channel=chrome fails on every navigation while the managed Chrome-for-Testing (channel=chromium) and the bundled shell both return 200. The cascade cannot recover from this: it only falls through on a missing-binary launch error, not a runtime navigation failure, so it served a network-dead browser. Default cascade is now [chromium, undefined]: the Playwright-managed full Chromium build (new headless, no HeadlessChrome in sec-ch-ua, real WebGL — same high-signal stealth, only the brand reads Chromium instead of Google Chrome) then the bundled shell. System chrome/msedge are opt-in via explicit channel. Exposes channel:"chromium" in the type + Zod enum and documents the tradeoff in the channel description so agents pick correctly (omit = safe default). Verified: macOS default stays clean (no HeadlessChrome, webdriver false, sec-ch-ua Chromium), the broken host now reaches Google. tsc clean, 194 unit + 20 integration green. --- src/engine/channel-cascade.ts | 17 ++++++++++------- src/interfaces/engine-types.ts | 8 +++++++- src/server/schemas.ts | 10 ++++++++-- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/engine/channel-cascade.ts b/src/engine/channel-cascade.ts index 424e408..f41d9ba 100644 --- a/src/engine/channel-cascade.ts +++ b/src/engine/channel-cascade.ts @@ -1,20 +1,23 @@ /** - * Stealth channel cascade: prefer real Google Chrome, then the full Chromium - * build, falling back to the bundled headless-shell. The shell leaks - * `HeadlessChrome` in sec-ch-ua / navigator.userAgentData and exposes no WebGL; - * the full builds (new headless) do neither. Each launch retries down the - * cascade only when the chosen browser binary is not installed. + * Stealth channel cascade: prefer the Playwright-managed full Chromium build + * (Chrome-for-Testing — new headless, no `HeadlessChrome` in sec-ch-ua / + * userAgentData, real WebGL), falling back to the bundled headless-shell. The + * SYSTEM Google Chrome (`channel:chrome`) is NOT used by default: it is host- + * specific and on some servers launches fine but has no network route + * (`ERR_INTERNET_DISCONNECTED`), which the cascade cannot detect (a runtime nav + * failure, not a launch failure). Pin `channel:"chrome"` explicitly to opt in. + * Each launch retries down the cascade only when the chosen binary is missing. * @module engine/channel-cascade */ import type { ResolvedConfig } from "../agent/config.js"; import type { BrowserChannel } from "../interfaces/engine-types.js"; import { isChromiumEngine } from "./loader.js"; -/** Launch channel, most-authentic first; `undefined` = bundled headless-shell. */ +/** Launch channel; `undefined` = bundled headless-shell. */ export type LaunchChannel = BrowserChannel | "chromium" | undefined; /** Ordered cascade for a realistic Chromium profile with no pinned channel. */ -const STEALTH_CASCADE: LaunchChannel[] = ["chrome", "chromium", undefined]; +const STEALTH_CASCADE: LaunchChannel[] = ["chromium", undefined]; /** True when a launch error means the requested browser binary is not installed. */ function isMissingBinary(err: unknown): boolean { diff --git a/src/interfaces/engine-types.ts b/src/interfaces/engine-types.ts index 0992512..67344ae 100644 --- a/src/interfaces/engine-types.ts +++ b/src/interfaces/engine-types.ts @@ -13,8 +13,14 @@ export type EngineName = "playwright" | "patchright" | "firefox" | "webkit"; /** Stable engine label: {@link EngineName} plus `cdp` (attach engine, not launchable). */ export type EngineId = EngineName | "cdp"; -/** Installed-browser channel to drive the user's real Chrome/Edge (Chromium-only). */ +/** + * Browser channel (Chromium-only). `chromium` = the Playwright-managed + * Chrome-for-Testing build (default, reliable on servers); `chrome`/`msedge`* + * drive the user's installed system browser (host-specific — may have no network + * route in some server setups, see engine/channel-cascade). + */ export type BrowserChannel = + | "chromium" | "chrome" | "chrome-beta" | "chrome-dev" diff --git a/src/server/schemas.ts b/src/server/schemas.ts index 841ca67..12fdee5 100644 --- a/src/server/schemas.ts +++ b/src/server/schemas.ts @@ -5,8 +5,9 @@ import { z } from "zod"; import { captchaSchema, circuitBreakerSchema, probeQueueSchema, retrySchema } from "./schemas-resilience.js"; -/** Installed-browser channels (real Chrome/Edge). */ +/** Browser channels: `chromium` = managed build (default); others = system browsers. */ const CHANNELS = [ + "chromium", "chrome", "chrome-beta", "chrome-dev", @@ -20,7 +21,12 @@ const CHANNELS = [ /** Shared browser identity / profile options. */ export const agentOptionShape = { engine: z.enum(["playwright", "patchright", "firefox", "webkit"]).optional(), - channel: z.enum(CHANNELS).optional(), + channel: z + .enum(CHANNELS) + .optional() + .describe( + "Browser channel. Omit for the safe default (managed `chromium` build — reliable everywhere incl. servers). `chrome`/`msedge*` drive the installed SYSTEM browser (real brand, but host-specific: on some servers it launches yet has no network → ERR_INTERNET_DISCONNECTED). Prefer omitting unless you need the system browser.", + ), executablePath: z.string().optional(), cdpEndpoint: z.string().optional(), cdpHeaders: z.record(z.string(), z.string()).optional(), From 2791b5991766b58b99ba7d9fa2e66d45b004ae8a Mon Sep 17 00:00:00 2001 From: Bruno Azoulay Date: Mon, 8 Jun 2026 23:32:27 +0200 Subject: [PATCH 2/2] chore(release): 0.1.54 --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c088588..4213db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.1.54] - 08-06-2026 + +### Fixed + +- fix(engine): **default to the managed Chromium build, not system Chrome** — the 0.1.50 channel cascade preferred `channel:chrome` (the installed Google Chrome). On some Linux servers that system binary **launches fine but has no network route** → every navigation fails with `ERR_INTERNET_DISCONNECTED`, and the cascade can't recover (it only falls through on a *missing-binary* launch error, not a runtime nav failure — so it served a network-dead browser). Reproduced on a real host: `channel:chrome` fails on every request while the managed **Chrome-for-Testing** (`channel:chromium`) and the bundled shell both return 200. **Default cascade is now `[chromium, undefined]`**: the Playwright-managed full Chromium (new headless, **no `HeadlessChrome` in sec-ch-ua, real WebGL** — same high-signal stealth, the brand just reads `Chromium` instead of `Google Chrome`) then the bundled shell. System `chrome`/`msedge*` are **opt-in** via an explicit `channel`. `channel:"chromium"` is now selectable (type + MCP schema) and the `channel` param documents the tradeoff so agents pick correctly (omit = safe default). + ## [0.1.53] - 08-06-2026 ### Added diff --git a/package.json b/package.json index ef6a0d6..c964a3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fusengine/browser-mcp", - "version": "0.1.53", + "version": "0.1.54", "description": "MCP server + CLI giving AI agents a real, stealth browser (Patchright/Playwright) — per-country identity, self-healing actions, snapshots, multi-step plans, structured extraction, CDP attach.", "license": "MIT", "author": "Fusengine",