Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
17 changes: 10 additions & 7 deletions src/engine/channel-cascade.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
8 changes: 7 additions & 1 deletion src/interfaces/engine-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
10 changes: 8 additions & 2 deletions src/server/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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(),
Expand Down